![JAR search and dependency download from the Maven repository](/logo.png)
com.lazerycode.jmeter.analyzer.writer.ChartWriter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jmeter-analysis-maven-plugin Show documentation
Show all versions of jmeter-analysis-maven-plugin Show documentation
Parses JMeter result files and computes performance indicators such as average request duration
package com.lazerycode.jmeter.analyzer.writer;
import static com.lazerycode.jmeter.analyzer.config.Environment.*;
import static com.lazerycode.jmeter.analyzer.util.FileUtil.urlEncode;
import static org.jfree.chart.ChartUtilities.writeChartAsPNG;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import org.jfree.chart.plot.CombinedDomainXYPlot;
import org.jfree.chart.plot.XYPlot;
import org.jfree.data.statistics.HistogramDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import com.google.common.annotations.VisibleForTesting;
import com.lazerycode.jmeter.analyzer.ConfigurationCharts;
import com.lazerycode.jmeter.analyzer.parser.AggregatedResponses;
import com.lazerycode.jmeter.analyzer.statistics.Quantile;
import com.lazerycode.jmeter.analyzer.statistics.Samples;
import freemarker.template.TemplateException;
/**
* Writes sizes and durations charts as PNG images
*/
public class ChartWriter extends WriterBase {
// QUANTILE use for percentiles chart
private static final int Q = 1000;
// Must be > 0
private final static long SECONDS_ROUND = 10L;
/**
* Needed to check if an Instance of ChartWriter is already in the {@link com.lazerycode.jmeter.analyzer.AnalyzeMojo#writers}
* Since this is more or less a simple PoJo, it is not necessary to make more than a simple instanceof check.
*
* @param obj the object to check
* @return true of obj is an instance of ChartWriter.
*/
@Override
public boolean equals(Object obj) {
return obj instanceof ChartWriter;
}
@Override
public void write(Map testResults) throws IOException, TemplateException {
// Process every AggregatedResponse
for (Map.Entry entry : testResults.entrySet()) {
String name = entry.getKey();
AggregatedResponses aggregatedResponses = entry.getValue();
writeCharts(aggregatedResponses, name);
}
}
//--------------------------------------------------------------------------------------------------------------------
@VisibleForTesting
protected OutputStream getOut(File file) throws FileNotFoundException {
return new BufferedOutputStream(new FileOutputStream(file));
}
//====================================================================================================================
/**
* Generate Charts
*
* @param name identifier used as part of the name
* @param aggregatedResponses results to generate CSV from
* @throws IOException
*/
private void writeCharts(AggregatedResponses aggregatedResponses, String name) throws IOException, TemplateException {
File requestChartFile;
String chartName;
// Create Plot
XYPlot durationPlot = createDurationPlot(aggregatedResponses);
XYPlot sizePlot = createSizePlot(aggregatedResponses);
XYPlot activeThreadsPlot = createActiveThreadsPlot(aggregatedResponses);
XYPlot throughputPlot = createThroughputPlot(aggregatedResponses);
XYPlot responseTimesPlot = createResponseTimesPlot(aggregatedResponses);
// Create Combined Plot
CombinedDomainXYPlot combinedThroughputActiveThreadsPlot = ChartUtil.createCombinedDomainDatePlot();
combinedThroughputActiveThreadsPlot.add( throughputPlot, 2 );
combinedThroughputActiveThreadsPlot.add( activeThreadsPlot, 1 );
//throughput chart
requestChartFile = getFile(chartFileName(name, THROUGHPUT));
chartName = "Throughput (" + name + ")";
renderChart(chartName, combinedThroughputActiveThreadsPlot, requestChartFile);
//response times chart
requestChartFile = getFile(chartFileName(name, RESPONSE_TIMES));
chartName = "Response times (" + name + ")";
renderChart(chartName, responseTimesPlot, requestChartFile);
//durations chart
requestChartFile = getFile(chartFileName(name, DURATIONS));
chartName = "Requests Duration (" + name + ")";
renderChart(chartName, durationPlot, requestChartFile);
//sizes chart
requestChartFile = getFile(chartFileName(name, SIZES));
chartName = "Requests Size (" + name + ")";
renderChart(chartName, sizePlot, requestChartFile);
}
private XYPlot createDurationPlot(AggregatedResponses aggregatedResponses) {
XYPlot plot = ChartUtil.createDatePlot("Duration (ms)");
Samples durations = aggregatedResponses.getDuration();
ChartUtil.addDatasetRender(plot, new XYSeriesCollection(
createAverageValuesSeries(AVERAGE, durations.getTimestamps(),
durations.getSamples(), durations.getMinTimestamp())), ChartUtil.createLineAndShapeRenderer());
return ChartUtil.addDatasetRender(plot, new XYSeriesCollection(
createValuesSeries("Duration", durations.getTimestamps(),
durations.getSamples(), durations.getMinTimestamp())), ChartUtil.createBarRenderer());
}
private XYPlot createSizePlot(AggregatedResponses aggregatedResponses) {
XYPlot plot = ChartUtil.createDatePlot("Size (bytes)");
Samples durations = aggregatedResponses.getSize();
ChartUtil.addDatasetRender(plot, new XYSeriesCollection(
createAverageValuesSeries(AVERAGE, durations.getTimestamps(),
durations.getSamples(), durations.getMinTimestamp())), ChartUtil.createLineAndShapeRenderer());
return ChartUtil.addDatasetRender(plot, new XYSeriesCollection(
createValuesSeries("Size", durations.getTimestamps(),
durations.getSamples(), durations.getMinTimestamp())), ChartUtil.createBarRenderer());
}
private XYPlot createActiveThreadsPlot(AggregatedResponses aggregatedResponses) {
XYPlot plot = ChartUtil.createDatePlot("Thread Count");
Samples activeThreads = aggregatedResponses.getActiveThreads();
return ChartUtil.addDatasetRender(plot, new XYSeriesCollection(
createValuesSeries("Threads", activeThreads.getTimestamps(),
activeThreads.getSamples(), activeThreads.getMinTimestamp())), ChartUtil.createSecondaryLineAndShapeRenderer());
}
private XYPlot createThroughputPlot(AggregatedResponses aggregatedResponses) {
SortedMap throughput = new TreeMap();
for (long timestamp : aggregatedResponses.getActiveThreads().getTimestamps()) {
long timestampRound = convert(timestamp);
Long throughputResult = throughput.get(timestampRound);
// Add SECONDS_ROUND to get request by second.
if (null == throughputResult) {
throughput.put(timestampRound, SECONDS_ROUND);
} else {
throughputResult += SECONDS_ROUND;
throughput.put(timestampRound, throughputResult);
}
}
List timestamps = new ArrayList(throughput.keySet());
List samples = new ArrayList(throughput.values());
long minTimestamp = convert(aggregatedResponses.getActiveThreads().getMinTimestamp());
XYPlot plot = ChartUtil.createDatePlot("Requests (req/s)");
ChartUtil.addDatasetRender(plot, new XYSeriesCollection(
createAverageValuesSeries(AVERAGE, timestamps, samples, minTimestamp)), ChartUtil.createLineAndShapeRenderer());
return ChartUtil.addDatasetRender(plot, new XYSeriesCollection(
createValuesSeries("Throughput", timestamps, samples, minTimestamp)), ChartUtil.createLineAndShapeRenderer());
}
private XYPlot createResponseTimesPlot(AggregatedResponses aggregatedResponses) {
Samples durations = aggregatedResponses.getDuration();
// Distribution
double[] values = new double[durations.getSamples().size()];
for (int i = 0; i < durations.getSamples().size(); i++) {
values[i] = durations.getSamples().get(i);
}
HistogramDataset histogramdataset = new HistogramDataset();
histogramdataset.addSeries("Request Count", values, 40);
// Percentiles
List y = new ArrayList(Q + 1);
List x = new ArrayList(Q + 1);
Quantile quantile = durations.getQuantiles(Q);
// Add zero value
y.add(0D);
x.add(0L);
for (int i = 1; i < Q; i++) {
y.add(((double) i) / 10);
x.add(quantile.getQuantile(i));
}
// Add max value
y.add(100D);
x.add(durations.getMax());
XYPlot plot = ChartUtil.createResponseTimesPlot("Response Times (ms)");
ChartUtil.addDatasetRender(plot, new XYSeriesCollection(
createValuesSeries("Percentiles", x, y, 0)), ChartUtil.createLineAndShapeRenderer());
ChartUtil.addDatasetRender(plot, histogramdataset, ChartUtil.createBarRenderer());
plot.mapDatasetToRangeAxis(1, 1);
return plot;
}
private XYSeries createValuesSeries(String seriesName, List extends Number> x, List extends Number> y, long minimumTimestamp) {
XYSeries series = new XYSeries(seriesName);
for (int i = 0; i < y.size(); i++) {
series.add((x.get(i).doubleValue() - minimumTimestamp), y.get(i));
}
return series;
}
private XYSeries createAverageValuesSeries(String seriesName, List x, List y, long minimumTimestamp) {
XYSeries series = new XYSeries(seriesName);
long total = 0;
for (int i = 0; i < y.size(); i++) {
long current = y.get(i);
long timestamp = x.get(i);
total += current;
series.add((timestamp - minimumTimestamp), (total / (i + 1.0)));
}
return series;
}
/**
* Renders a single result as a chart
*
* @param singleValueName
* @param domainAxisName
* @param rangeAxisName
* @param principalDataset
* @param target
*/
private void renderChart(String name, XYPlot plot, File target) throws IOException {
OutputStream out = getOut(target);
try {
ConfigurationCharts configurationCharts = ENVIRONMENT.getConfigurationCharts();
writeChartAsPNG(out, ChartUtil.createJFreeChart(name, plot, configurationCharts.getHeight()),
configurationCharts.getWidth(), configurationCharts.getHeight(), null);
} finally {
out.close();
}
}
private String chartFileName(String name, String type) throws UnsupportedEncodingException {
return new StringBuilder(urlEncode(name)).append(type).append(super.getFileName()).append(PNG_EXT).toString();
}
private long convert(long timestamp) {
long ratio = 1000 / SECONDS_ROUND;
return (timestamp / ratio ) * ratio;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy