Index: build.properties =================================================================== --- build.properties (revision 1656228) +++ build.properties (working copy) @@ -171,6 +171,11 @@ excalibur-pool-instrumented.loc = ${maven2.repo}/excalibur-pool/excalibur-pool-instrumented/${excalibur-pool-instrumented.version} excalibur-pool-instrumented.md5 = 1b5425fe0fe63dc67da6fe995db6be31 +freemarker.version = 2.3.21 +freemarker.loc = ${maven2.repo}/org/freemarker/freemarker/${freemarker.version} +freemarker.jar = freemarker-${freemarker.version}.jar +freemarker.md5 = bdc6a9d3a41bc13e5965fc06e74c16c2 + # Common file containing both htmlparser and htmllexer jars htmlparser.version = 2.1 htmllexer.loc = ${maven2.repo}/org/htmlparser/htmllexer/${htmlparser.version} Index: build.xml =================================================================== --- build.xml (revision 1656250) +++ build.xml (working copy) @@ -371,6 +371,7 @@ + @@ -446,6 +447,7 @@ + @@ -2871,6 +2873,7 @@ + Index: report_templates/HTML_REPORT.ftl =================================================================== --- report_templates/HTML_REPORT.ftl (revision 0) +++ report_templates/HTML_REPORT.ftl (revision 0) @@ -0,0 +1,240 @@ + +<#setting number_format="##0"> + + + + + + + + + + + Dashboard + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+

Dashboard

+ +
+
+
+
+

APDEX

+
+
+ ${APDEX} +
+
+
+
+
+
+

Requests Summary

+
+
+
+ +
+
+
+
+ +

Statistics

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +<#list resultsPerSample?keys as key> +<#assign prop = resultsPerSample[key]> + + + + + + + + + + + + + + + +
Label#SamplesKOError%90% Line95% Line99% LineThroughputKB/secMinMax
${TOTAL.label}${TOTAL.count}${TOTAL.errorCount}${TOTAL.errorPercentage?string.percent}${TOTAL.getPercentPoint(0.9)}${TOTAL.getPercentPoint(0.95)}${TOTAL.getPercentPoint(0.99)}${TOTAL.rate}${TOTAL.getKBPerSecond()}${TOTAL.min}${TOTAL.max}
${prop.label}${prop.count}${prop.errorCount}${prop.errorPercentage?string.percent}${prop.getPercentPoint(0.9)}${prop.getPercentPoint(0.95)}${prop.getPercentPoint(0.99)}${prop.rate}${prop.getKBPerSecond()}${prop.min}${prop.max}
+
+
+
+
+ + + + + + + + + + + + + + Index: src/components/org/apache/jmeter/visualizers/backend/BackendListener.java =================================================================== --- src/components/org/apache/jmeter/visualizers/backend/BackendListener.java (revision 1656228) +++ src/components/org/apache/jmeter/visualizers/backend/BackendListener.java (working copy) @@ -169,6 +169,9 @@ BackendListenerContext context = new BackendListenerContext(args); SampleResult sr = listenerClientData.client.createSampleResult(context, event.getResult()); + if(sr == null) { + return; + } try { if (!listenerClientData.queue.offer(sr)){ // we failed to add the element first time listenerClientData.queueWaits.incrementAndGet(); Index: src/components/org/apache/jmeter/visualizers/backend/apdex/ApdexBackendListenerClient.java =================================================================== --- src/components/org/apache/jmeter/visualizers/backend/apdex/ApdexBackendListenerClient.java (revision 0) +++ src/components/org/apache/jmeter/visualizers/backend/apdex/ApdexBackendListenerClient.java (revision 0) @@ -0,0 +1,221 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.visualizers.backend.apdex; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileOutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicLong; + +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.visualizers.SamplingStatCalculator; +import org.apache.jmeter.visualizers.backend.AbstractBackendListenerClient; +import org.apache.jmeter.visualizers.backend.BackendListenerContext; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.util.JOrphanUtils; +import org.apache.log.Logger; + +import freemarker.template.Configuration; +import freemarker.template.Template; +import freemarker.template.TemplateExceptionHandler; + +/** + * APDEX Computer + * @since 2.13 + */ +public class ApdexBackendListenerClient extends AbstractBackendListenerClient { + private static final Logger LOGGER = LoggingManager.getLoggerForClass(); + private static final String SEPARATOR = ";"; //$NON-NLS-1$ + + /** + * T : Threshold for satistied users. + * Response times <= satisfiedResponseTimeMS count towards satisfying + */ + private long satisfiedResponseTimeMS; + /** + * F : Threshold for tolerated users. + * Response times > satisfiedResponseTimeMS and <= toleratedResponseTimeMS count towards tolerated response times + * If user does not provide it, it is equal to 4xT + */ + private long toleratedResponseTimeMS; + /** + * Semicolon separated samplers to include in results + */ + private String samplersList = ""; //$NON-NLS-1$ + /** + * Set of samplers to filter, computed from samplersList + */ + private Set samplersToFilter; + + /** + * Number of Successful Responses for samplers in samplersToFilter with Response Time <= T + */ + private AtomicLong numberOfSatistiedResponses = new AtomicLong(0); + /** + * Number of Successful Responses for samplers in samplersToFilter with Response Time > T and <= F + */ + private AtomicLong numberOfToleratedResponses = new AtomicLong(0); + /** + * Number of Responses for samplers in samplersToFilter (OK + KO) + */ + private AtomicLong totalResponses = new AtomicLong(0); + private String resultsFile; + private String reportFile; + public ApdexBackendListenerClient() { + super(); + } + + + /** + * @return the samplersList + */ + public String getSamplersList() { + return samplersList; + } + + /** + * @param samplersList the samplersList to set + */ + public void setSamplersList(String samplersList) { + this.samplersList = samplersList; + } + + @Override + public void handleSampleResults(List sampleResults, + BackendListenerContext context) { + // NOOP + } + + @Override + public void setupTest(BackendListenerContext context) throws Exception { + String satisfiedResponseTimeAsString = context.getParameter("satisfiedResponseTimeMS", ""); + if(!StringUtils.isEmpty(satisfiedResponseTimeAsString)) { + try { + satisfiedResponseTimeMS = Long.parseLong(satisfiedResponseTimeAsString); + } catch (Exception e) { + throw new IllegalArgumentException("Error parsing satisfiedResponseTimeMS '" + +satisfiedResponseTimeAsString+"', message:"+e.getMessage()); + } + } else { + throw new IllegalArgumentException("satisfiedResponseTimeMS is null or empty"); + } + + String toleratedResponseTimeAsString = context.getParameter("toleratedResponseTimeMS", ""); + if(!StringUtils.isEmpty(toleratedResponseTimeAsString)) { + try { + toleratedResponseTimeMS = Long.parseLong(toleratedResponseTimeAsString); + } catch (Exception e) { + throw new IllegalArgumentException("Error parsing toleratedResponseTimeMS '" + +satisfiedResponseTimeAsString+"', message:"+e.getMessage()); + } + } else { + toleratedResponseTimeMS = 4*satisfiedResponseTimeMS; + } + + samplersList = context.getParameter("samplersList", ""); + String[] samplers = samplersList.split(SEPARATOR); + samplersToFilter = new HashSet(); + for (String samplerName : samplers) { + samplersToFilter.add(samplerName.trim()); + } + + resultsFile = context.getParameter("resultsFile"); + reportFile = context.getParameter("reportFile"); + + numberOfSatistiedResponses.set(0); + numberOfToleratedResponses.set(0); + totalResponses.set(0); + } + + @Override + public void teardownTest(BackendListenerContext context) throws Exception { + ReportVisualizer reportVisualizer = new ReportVisualizer(samplersToFilter, satisfiedResponseTimeMS, toleratedResponseTimeMS); + ReportResultCollector collector = new ReportResultCollector(); + collector.setFilename(resultsFile); + collector.setListener(reportVisualizer); + collector.loadExistingFile(); + Map results = reportVisualizer.getResultsPerSampleLabel(); + for (Map.Entry entry : results.entrySet()) { + System.out.println(entry.getKey()+":"+entry.getValue()); + } + generateReport(reportFile, reportVisualizer); + samplersToFilter.clear(); + super.teardownTest(context); + } + + private void generateReport(String reportFile, ReportVisualizer reportVisualizer) + throws Exception { + Configuration cfg = new Configuration(Configuration.VERSION_2_3_21); + cfg.setDirectoryForTemplateLoading(new File(JMeterUtils.getJMeterHome(),"report_templates")); + cfg.setDefaultEncoding("UTF-8"); + cfg.setTemplateExceptionHandler(TemplateExceptionHandler.HTML_DEBUG_HANDLER); + Writer writer = null; + try { + //Load template from source folder + Template template = cfg.getTemplate("HTML_REPORT.ftl"); + + // Build the data-model + Map data = new HashMap(); + data.put("APDEX", "APDEX:"+Float.toString(reportVisualizer.computeApdex()) + " for thresholds T:"+satisfiedResponseTimeMS+"ms, F:"+toleratedResponseTimeMS + + "ms on samplers:"+samplersToFilter); + Map map = reportVisualizer.getResultsPerSampleLabel(); + SamplingStatCalculator total = map.get(ReportVisualizer.TOTAL_ROW_LABEL); + data.put("TOTAL", total); + map.remove(ReportVisualizer.TOTAL_ROW_LABEL); + data.put("resultsPerSample", map); + writer = new BufferedWriter(new OutputStreamWriter( + new FileOutputStream(reportFile), "UTF-8")); + // File output + template.process(data, writer); + } finally { + JOrphanUtils.closeQuietly(writer); + } + } + + @Override + public Arguments getDefaultParameters() { + Arguments arguments = new Arguments(); + arguments.addArgument("satisfiedResponseTimeMS", ""); + arguments.addArgument("toleratedResponseTimeMS", ""); + arguments.addArgument("samplersList", ""); + arguments.addArgument("resultsFile", ""); + arguments.addArgument("reportFile", ""); + return arguments; + } + + + /* (non-Javadoc) + * @see org.apache.jmeter.visualizers.backend.AbstractBackendListenerClient#createSampleResult(org.apache.jmeter.visualizers.backend.BackendListenerContext, org.apache.jmeter.samplers.SampleResult) + */ + @Override + public SampleResult createSampleResult(BackendListenerContext context, + SampleResult result) { + return result; + } +} Index: src/components/org/apache/jmeter/visualizers/backend/apdex/ReportResultCollector.java =================================================================== --- src/components/org/apache/jmeter/visualizers/backend/apdex/ReportResultCollector.java (revision 0) +++ src/components/org/apache/jmeter/visualizers/backend/apdex/ReportResultCollector.java (revision 0) @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.visualizers.backend.apdex; + +import org.apache.jmeter.reporters.ResultCollector; +import org.apache.jmeter.reporters.Summariser; +import org.apache.jmeter.samplers.SampleEvent; + +/** + * + */ +public class ReportResultCollector extends ResultCollector { + + /** + * + */ + private static final long serialVersionUID = 4360593252987031579L; + + /** + * + */ + public ReportResultCollector() { + super(); + } + + /** + * @param summer + */ + public ReportResultCollector(Summariser summer) { + super(summer); + } + + /* (non-Javadoc) + * @see org.apache.jmeter.reporters.ResultCollector#sampleOccurred(org.apache.jmeter.samplers.SampleEvent) + */ + @Override + public void sampleOccurred(SampleEvent event) { + super.sampleOccurred(event); + } + +} Index: src/components/org/apache/jmeter/visualizers/backend/apdex/ReportVisualizer.java =================================================================== --- src/components/org/apache/jmeter/visualizers/backend/apdex/ReportVisualizer.java (revision 0) +++ src/components/org/apache/jmeter/visualizers/backend/apdex/ReportVisualizer.java (revision 0) @@ -0,0 +1,139 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.visualizers.backend.apdex; + +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicLong; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.visualizers.SamplingStatCalculator; +import org.apache.jmeter.visualizers.gui.AbstractVisualizer; + +/** + * @since 2.13 + */ +public class ReportVisualizer extends AbstractVisualizer { + /** + * + */ + private static final long serialVersionUID = -783284260364641662L; + private final Map tableRows = + new ConcurrentHashMap(); + public final static String TOTAL_ROW_LABEL = JMeterUtils + .getResString("aggregate_report_total_label"); //$NON-NLS-1$ + /** + * T : Threshold for satistied users. + * Response times <= satisfiedResponseTimeMS count towards satisfying + */ + private long satisfiedResponseTimeMS; + /** + * F : Threshold for tolerated users. + * Response times > satisfiedResponseTimeMS and <= toleratedResponseTimeMS count towards tolerated response times + * If user does not provide it, it is equal to 4xT + */ + private long toleratedResponseTimeMS; + + /** + * Set of samplers to filter, computed from samplersList + */ + private Set samplersToFilter; + + /** + * Number of Successful Responses for samplers in samplersToFilter with Response Time <= T + */ + private AtomicLong numberOfSatistiedResponses = new AtomicLong(0); + /** + * Number of Successful Responses for samplers in samplersToFilter with Response Time > T and <= F + */ + private AtomicLong numberOfToleratedResponses = new AtomicLong(0); + /** + * Number of Responses for samplers in samplersToFilter (OK + KO) + */ + private AtomicLong totalResponses = new AtomicLong(0); + + public ReportVisualizer(Set samplersToFilter, + long satisfiedResponseTimeMS, + long toleratedResponseTimeMS) { + this.samplersToFilter = samplersToFilter; + this.satisfiedResponseTimeMS = satisfiedResponseTimeMS; + this.toleratedResponseTimeMS = toleratedResponseTimeMS; + } + + @Override + public void add(SampleResult res) { + final String sampleLabel = res.getSampleLabel(); + if(!samplersToFilter.contains(sampleLabel)) { + return; + } + SamplingStatCalculator row = null; + row = tableRows.get(sampleLabel); + if (row == null) { + row = new SamplingStatCalculator(sampleLabel); + tableRows.put(row.getLabel(), row); + } + row.addSample(res); + + SamplingStatCalculator tot = tableRows.get(TOTAL_ROW_LABEL); + if (tot == null) { + tot = new SamplingStatCalculator(TOTAL_ROW_LABEL); + tableRows.put(tot.getLabel(), tot); + } + tot.addSample(res); + + totalResponses.incrementAndGet(); + + if(res.isSuccessful()) { + if(res.getTime()<=satisfiedResponseTimeMS) { + numberOfSatistiedResponses.incrementAndGet(); + } else if (res.getTime()<=toleratedResponseTimeMS) { + numberOfToleratedResponses.incrementAndGet(); + } + } + } + + public Map getResultsPerSampleLabel() { + return tableRows; + } + + @Override + public void clearData() { + tableRows.clear(); + tableRows.put(TOTAL_ROW_LABEL, new SamplingStatCalculator(TOTAL_ROW_LABEL)); + numberOfSatistiedResponses.set(0); + numberOfToleratedResponses.set(0); + totalResponses.set(0); + } + + @Override + public String getLabelResource() { + return "TEST"; + } + + /** + * @return float APDEX + * See http://www.apdex.org/overview.html + */ + public float computeApdex() { + double apdex = ((double)(numberOfSatistiedResponses.get()+numberOfToleratedResponses.get()/2))/totalResponses.get(); + return (float)apdex; + } +}