package hudson.plugins.performance.parsers; import hudson.Extension; import hudson.plugins.performance.data.HttpSample; import hudson.plugins.performance.descriptors.PerformanceReportParserDescriptor; import hudson.plugins.performance.reports.PerformanceReport; import org.kohsuke.stapler.DataBoundConstructor; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; import javax.xml.parsers.SAXParserFactory; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.util.Date; /** * Parser for JMeter. * * @author Kohsuke Kawaguchi */ public class JMeterParser extends AbstractParser { @Extension public static class DescriptorImpl extends PerformanceReportParserDescriptor { @Override public String getDisplayName() { return "JMeter"; } } @DataBoundConstructor public JMeterParser(String glob) { super(glob); } @Override public String getDefaultGlobPattern() { return "**/*.jtl"; } PerformanceReport parse(File reportFile) throws Exception { // JMeter stores either CSV or XML in .JTL files. final boolean isXml = isXmlFile(reportFile); if (isXml) { return parseXml(reportFile); } else { return parseCsv(reportFile); } } /** * Utility method that checks if the provided file has XML content. * <p> * This implementation looks for the first non-empty file. If an XML prolog appears there, this method returns <code>true</code>, otherwise <code>false</code> is returned. * * @param file File from which the content is to e analyzed. Cannot be null. * @return <code>true</code> if the file content has been determined to be XML, otherwise <code>false</code>. */ public static boolean isXmlFile(File file) throws IOException { BufferedReader reader = null; try { reader = new BufferedReader(new FileReader(file)); String firstLine; while ((firstLine = reader.readLine()) != null) { if (firstLine.trim().length() == 0) continue; // skip empty lines. return firstLine != null && firstLine.toLowerCase().trim().startsWith("<?xml "); } return false; } finally { if (reader != null) { reader.close(); } } } /** * A delegate for {@link #parse(File)} that can process XML data. */ PerformanceReport parseXml(File reportFile) throws Exception { final SAXParserFactory factory = SAXParserFactory.newInstance(); factory.setValidating(false); factory.setNamespaceAware(false); final PerformanceReport report = new PerformanceReport(); report.setReportFileName(reportFile.getName()); factory.newSAXParser().parse(reportFile, new DefaultHandler() { HttpSample currentSample; int counter = 0; /** * Performance XML log format is in http://jakarta.apache.org/jmeter/usermanual/listeners.html * * There are two different tags which delimit jmeter samples: * - httpSample for http samples * - sample for non http samples * * There are also two different XML formats which we have to handle: * v2.0 = "label", "timeStamp", "time", "success" * v2.1 = "lb", "ts", "t", "s" */ @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { if (!"httpSample".equalsIgnoreCase(qName) && !"sample".equalsIgnoreCase(qName)) { return; } final HttpSample sample = new HttpSample(); final String dateValue; if (attributes.getValue("ts") != null) { dateValue = attributes.getValue("ts"); } else { dateValue = attributes.getValue("timeStamp"); } sample.setDate(new Date(Long.valueOf(dateValue))); final String durationValue; if (attributes.getValue("t") != null) { durationValue = attributes.getValue("t"); } else { durationValue = attributes.getValue("time"); } sample.setDuration(Long.valueOf(durationValue)); final String successfulValue; if (attributes.getValue("s") != null) { successfulValue = attributes.getValue("s"); } else { successfulValue = attributes.getValue("success"); } sample.setSuccessful(Boolean.parseBoolean(successfulValue)); final String uriValue; if (attributes.getValue("lb") != null) { uriValue = attributes.getValue("lb"); } else { uriValue = attributes.getValue("label"); } sample.setUri(uriValue); final String httpCodeValue; if (attributes.getValue("rc") != null && attributes.getValue("rc").length() <= 3) { httpCodeValue = attributes.getValue("rc"); } else { httpCodeValue = "0"; } sample.setHttpCode(httpCodeValue); final String sizeInKbValue; if (attributes.getValue("by") != null) { sizeInKbValue = attributes.getValue("by"); } else { sizeInKbValue = "0"; } sample.setSizeInKb(Double.valueOf(sizeInKbValue) / 1024d); if (counter == 0) { currentSample = sample; } counter++; } @Override public void endElement(String uri, String localName, String qName) { if ("httpSample".equalsIgnoreCase(qName) || "sample".equalsIgnoreCase(qName)) { if (counter == 1) { try { report.addSample(currentSample); } catch (SAXException e) { e.printStackTrace(); } } counter--; } } }); return report; } /** * A delegate for {@link #parse(File)} that can process CSV data. */ PerformanceReport parseCsv(File reportFile) throws Exception { final JMeterCsvParser delegate = new JMeterCsvParser(this.glob); return delegate.parse(reportFile); } }