Skip to content

Commit

Permalink
[JENKINS-47808] Fix OutOfMemoryException when parsing huge JMeter XML…
Browse files Browse the repository at this point in the history
… result file (#148)

* add test to show bug (JENKINS-47808)

* use StAX to detect XML file type
  • Loading branch information
jcoste-orange authored and undera committed Nov 8, 2017
1 parent 8f0b83e commit 703fec4
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 32 deletions.
@@ -1,24 +1,21 @@
package hudson.plugins.performance.parsers;

import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.google.common.annotations.VisibleForTesting;

/**
* Auto-detect parser for file
*/
Expand Down Expand Up @@ -131,29 +128,37 @@ private static boolean isLoadRunnerFileType(String line) {
* <FinalStatus> - TAURUS.
*/
private static String detectXMLFileType(String reportPath) throws IOException {
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(reportPath);
XPathFactory xPathfactory = XPathFactory.newInstance();
XPath xpath = xPathfactory.newXPath();
if (docContainsTag(doc, xpath, "testResults")) {
return JMeterParser.class.getSimpleName();
} else if (docContainsTag(doc, xpath, "testsuite")) {
return JUnitParser.class.getSimpleName();
} else if (docContainsTag(doc, xpath, "FinalStatus")) {
return TaurusParser.class.getSimpleName();
} else {
throw new IllegalArgumentException("Unknown xml file format");
}
} catch (ParserConfigurationException | SAXException | XPathExpressionException ex) {
try (InputStream in = new FileInputStream(reportPath)) {
return detectXMLFileType(in);
} catch (Exception ex) {
throw new RuntimeException("XML parsing error: ", ex);
}
}

private static boolean docContainsTag(Document doc, XPath xpath, String tagName) throws XPathExpressionException {
XPathExpression expr = xpath.compile("//" + tagName);
NodeList list = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);
return (list.getLength() > 0);
@VisibleForTesting
protected static String detectXMLFileType(final InputStream in) throws XMLStreamException {
XMLInputFactory inputFactory = XMLInputFactory.newInstance();
XMLEventReader eventReader = inputFactory.createXMLEventReader(in);

while (eventReader.hasNext()) {
XMLEvent event = eventReader.nextEvent();
if (event.isStartElement()) {
StartElement startElement = event.asStartElement();
String name = startElement.getName().getLocalPart();

switch (name) {
case "testResults":
return JMeterParser.class.getSimpleName();
case "testsuite":
case "testsuites":
return JUnitParser.class.getSimpleName();
case "FinalStatus":
return TaurusParser.class.getSimpleName();
default:
throw new IllegalArgumentException("Unknown xml file format");
}
}
}
throw new RuntimeException("XML parsing error: no start element");
}
}
@@ -1,5 +1,11 @@
package hudson.plugins.performance.parsers;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.SequenceInputStream;
import java.nio.charset.StandardCharsets;

import org.junit.Test;
import org.jvnet.hudson.test.Issue;

Expand Down Expand Up @@ -51,4 +57,36 @@ public void testIssue45723() throws Exception {
String filePath = getClass().getResource("/TEST-JUnitResults-success-failure-error.xml").toURI().getPath();
assertEquals(JUnitParser.class.getSimpleName(), ParserDetector.detect(filePath));
}

@Issue("JENKINS-47808")
@Test
public void testIssue() throws Exception {
assertEquals(JMeterParser.class.getSimpleName(), ParserDetector.detectXMLFileType(getHugeJMeterInputStream()));
}


public static InputStream getHugeJMeterInputStream() {
return new SequenceInputStream(getPrefixInputStream(), getInfiniteSampleInputStream());
}

private static ByteArrayInputStream getPrefixInputStream() {
return new ByteArrayInputStream("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<testResults version=\"1.2\">".getBytes(
StandardCharsets.UTF_8));
}

private static InputStream getInfiniteSampleInputStream() {
return repeat("<httpSample t=\"289\" lt=\"289\" ts=\"1509447454646\" s=\"true\" lb=\"test\" rc=\"200\" rm=\"OK\" tn=\"test\" dt=\"text\" by=\"1410\"/>".getBytes(
StandardCharsets.UTF_8), Integer.MAX_VALUE);
}

public static InputStream repeat(final byte[] sample, final int times) {
return new InputStream() {
private long pos = 0;
private final long total = (long)sample.length * times;

public int read() throws IOException {
return pos < total ? sample[(int)(pos++ % sample.length)] : -1;
}
};
}
}

0 comments on commit 703fec4

Please sign in to comment.