Skip to content

Commit cd7b949

Browse files
authored
New chart: percentiles over time (#761)
* Add chart elements for percentiles over time * Realign log4j versions * Update POM for publishing * Make it thread-safe * Add more subclass-friendly method * Add new graph: response times percentiles over time * Fix build * Fix POM * Remove obsolete classes
1 parent c6bba69 commit cd7b949

File tree

6 files changed

+241
-3
lines changed

6 files changed

+241
-3
lines changed

graphs/graphs-dist/pom.xml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<groupId>kg.apc</groupId>
88
<artifactId>jmeter-plugins-graphs-dist</artifactId>
9-
<version>2.0</version>
9+
<version>2.1</version>
1010

1111
<name>Distribution Graphs Plugins</name>
1212
<description>Distribution Graphs Plugins</description>
@@ -40,7 +40,7 @@
4040
<dependency>
4141
<groupId>kg.apc</groupId>
4242
<artifactId>jmeter-plugins-cmn-jmeter</artifactId>
43-
<version>0.3</version>
43+
<version>0.9</version>
4444
</dependency>
4545
<dependency>
4646
<groupId>org.apache.commons</groupId>
@@ -50,7 +50,7 @@
5050
<dependency>
5151
<groupId>kg.apc</groupId>
5252
<artifactId>jmeter-plugins-emulators</artifactId>
53-
<version>0.2</version>
53+
<version>0.4</version>
5454
<scope>test</scope>
5555
</dependency>
5656
</dependencies>
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
package kg.apc.jmeter.vizualizers;
2+
3+
import kg.apc.charting.AbstractGraphRow;
4+
import kg.apc.charting.rows.GraphRowOverTimePercentile;
5+
import kg.apc.jmeter.JMeterPluginsUtils;
6+
import kg.apc.jmeter.graphs.AbstractOverTimeVisualizer;
7+
import org.apache.jmeter.samplers.SampleResult;
8+
import org.apache.jmeter.util.JMeterUtils;
9+
import org.slf4j.Logger;
10+
import org.slf4j.LoggerFactory;
11+
12+
import java.awt.*;
13+
import java.text.DecimalFormat;
14+
import java.util.*;
15+
import java.util.List;
16+
import java.util.stream.Collectors;
17+
18+
public class ResponseTimesPercentilesOverTimeGui extends AbstractOverTimeVisualizer {
19+
20+
private static final Logger log = LoggerFactory.getLogger(ResponseTimesPercentilesOverTimeGui.class);
21+
protected List<Double> percentiles;
22+
protected Map<Double, Double> shadingFactors; // render each percentile with a lighter alpha value
23+
protected Map<String, Color> baseColors;
24+
25+
public ResponseTimesPercentilesOverTimeGui() {
26+
super();
27+
setGranulation(30000);
28+
getGraphPanelChart().getChartSettings().setLineWidth(3);
29+
getGraphPanelChart().setYAxisLabel("Response times in ms");
30+
31+
String percentilesConfig = JMeterUtils.getPropDefault("jmeterPlugin.percentilesOverTime", "50,90,95,99");
32+
try {
33+
percentiles = Arrays.stream(percentilesConfig.split(","))
34+
.map(Double::valueOf)
35+
.collect(Collectors.toList());
36+
} catch (NumberFormatException e) {
37+
log.error("Invalid percentiles configuration", e);
38+
percentiles = Collections.singletonList(50d);
39+
}
40+
shadingFactors = new HashMap<>();
41+
for (int i = 0; i < percentiles.size(); i++) {
42+
// color shades should not get lighter than an alpha value of 32 out of 255
43+
shadingFactors.put(percentiles.get(i), percentiles.size()>1 ? Math.pow(32d/256d, (double)i/(percentiles.size()-1)) : 1);
44+
}
45+
baseColors = new HashMap<>();
46+
}
47+
48+
public String getLabelResource() {
49+
return this.getClass().getSimpleName();
50+
}
51+
52+
@Override
53+
public String getStaticLabel() {
54+
return JMeterPluginsUtils.prefixLabel("Response Times Percentiles Over Time");
55+
}
56+
57+
protected synchronized AbstractGraphRow getNewRow(String label, boolean isAggregate, double percentile) {
58+
final AbstractGraphRow newRow = new GraphRowOverTimePercentile(percentile);
59+
final String suffix = new DecimalFormat(" (p#.####)").format(percentile);
60+
newRow.setLabel(label+suffix);
61+
newRow.setMarkerSize(AbstractGraphRow.MARKER_SIZE_NONE);
62+
newRow.setDrawBar(false);
63+
newRow.setDrawLine(true);
64+
newRow.setDrawValueLabel(false);
65+
newRow.setDrawThickLines(false);
66+
newRow.setShowInLegend(true);
67+
68+
// Avoid cycling of colors for subsequent percentiles: Let base class assign color only once, then store it.
69+
Color color = baseColors.get(label);
70+
if (color != null) {
71+
color = new Color(color.getRed(), color.getGreen(), color.getBlue(),
72+
(int)(color.getAlpha() * shadingFactors.get(percentile)));
73+
}
74+
AbstractGraphRow addedRow = getNewRow(newRow, color, isAggregate, true);
75+
if (color == null) {
76+
baseColors.put(label, addedRow.getColor());
77+
}
78+
return addedRow;
79+
}
80+
81+
@Override
82+
public void add(SampleResult res) {
83+
if (!isSampleIncluded(res)) {
84+
return;
85+
}
86+
super.add(res);
87+
88+
final String labelAgg = "Overall Response Times";
89+
String label = res.getSampleLabel();
90+
long time = normalizeTime(res.getEndTime());
91+
long elapsed = res.getTime();
92+
93+
for (double p : percentiles) {
94+
getNewRow(label, false, p).add(time, elapsed);
95+
getNewRow(labelAgg, true, p).add(time, elapsed);
96+
}
97+
98+
updateGui(null);
99+
}
100+
101+
@Override
102+
protected JSettingsPanel createSettingsPanel() {
103+
return new JSettingsPanel(this,
104+
JSettingsPanel.TIMELINE_OPTION
105+
| JSettingsPanel.GRADIENT_OPTION
106+
| JSettingsPanel.LIMIT_POINT_OPTION
107+
| JSettingsPanel.AGGREGATE_OPTION
108+
| JSettingsPanel.MAXY_OPTION
109+
| JSettingsPanel.RELATIVE_TIME_OPTION
110+
| JSettingsPanel.MARKERS_OPTION);
111+
}
112+
113+
@Override
114+
public String getWikiPage() {
115+
return "ResponseTimesPercentilesOverTime";
116+
}
117+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package kg.apc.jmeter.vizualizers;
2+
3+
import kg.apc.charting.AbstractGraphRow;
4+
import kg.apc.emulators.TestJMeterUtils;
5+
import org.apache.jmeter.samplers.SampleResult;
6+
import org.apache.jmeter.util.JMeterUtils;
7+
import org.junit.Before;
8+
import org.junit.BeforeClass;
9+
import org.junit.Test;
10+
11+
import static org.junit.Assert.*;
12+
13+
public class ResponseTimesPercentilesOverTimeGuiTest {
14+
15+
private ResponseTimesPercentilesOverTimeGui instance;
16+
17+
/**
18+
*
19+
*/
20+
@BeforeClass
21+
public static void setUpClass() {
22+
TestJMeterUtils.createJmeterEnv();
23+
}
24+
25+
/**
26+
*
27+
*/
28+
@Before
29+
public void setUp() {
30+
instance = new ResponseTimesPercentilesOverTimeGui();
31+
}
32+
33+
/**
34+
*
35+
*/
36+
@Test
37+
public void testConfigProperties() {
38+
JMeterUtils.setProperty("jmeterPlugin.percentilesOverTime", "50,60,70,80,90");
39+
instance = new ResponseTimesPercentilesOverTimeGui();
40+
assertEquals(5, instance.percentiles.size());
41+
assertEquals(instance.percentiles.size(), instance.shadingFactors.size());
42+
assertEquals(1.0d, instance.shadingFactors.get(50d), 0.0d);
43+
assertEquals(32/256d, instance.shadingFactors.get(90d), 0.0d);
44+
}
45+
46+
/**
47+
*
48+
*/
49+
@Test
50+
public void testGetNewRow() {
51+
AbstractGraphRow row = instance.getNewRow("label", true, 50d);
52+
assertNotNull(row);
53+
assertNotNull(row.getColor());
54+
assertEquals("label (p50)", row.getLabel());
55+
row = instance.getNewRow("label", true, 99d);
56+
assertNotNull(row.getColor());
57+
assertEquals(31, row.getColor().getAlpha());
58+
}
59+
60+
/**
61+
*
62+
*/
63+
@Test
64+
public void testGetLabelResource() {
65+
String result = instance.getLabelResource();
66+
assertEquals("ResponseTimesPercentilesOverTimeGui", result);
67+
}
68+
69+
/**
70+
*
71+
*/
72+
@Test
73+
public void testGetStaticLabel() {
74+
String result = instance.getStaticLabel();
75+
assertTrue(result.length() > 0);
76+
}
77+
78+
/**
79+
*
80+
*/
81+
@Test
82+
public void testAdd() {
83+
SampleResult res = new SampleResult();
84+
res.setSampleLabel("label");
85+
instance.add(res);
86+
}
87+
88+
/**
89+
*
90+
*/
91+
@Test
92+
public void testGetWikiPage() {
93+
String result = instance.getWikiPage();
94+
assertTrue(result.length() > 0);
95+
}
96+
97+
/**
98+
* Test of createSettingsPanel method, of class PercentilesOverTimeGui.
99+
*/
100+
@Test
101+
public void testCreateSettingsPanel() {
102+
JSettingsPanel result = instance.createSettingsPanel();
103+
assertNotNull(result);
104+
}
105+
}

site/dat/wiki/JMeterPluginsCMD.wiki

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ Most of class names are self-explanatory:
8080
* ResponseTimesDistribution
8181
* ResponseTimesOverTime
8282
* ResponseTimesPercentiles
83+
* ResponseTimesPercentilesOverTime
8384
* ThroughputVsThreads
8485
* TimesVsThreads = Response Times VS Threads
8586
* TransactionsPerSecond
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
= Response Times Percentiles Over Time =
2+
3+
<span class=''>[/?search=jpgc-graphs-dist <i class='fa fa-download'></i> Download]</span>
4+
5+
Similar to the Response Times Over Time that displays the average response times in discrete time intervals,
6+
this graph shows response times percentiles in those time intervals.
7+
8+
By default, the 50th, 90th, 95th and 99th percentile are plotted.
9+
10+
This can be configured via a JMeter property like so: {{{jmeterPlugin.percentilesOverTime=50,90,99,99.9}}}
11+
12+
Caveat: The percentiles are not calculated exactly but estimated via the P-Square algorithm
13+
(reference: [https://www.cse.wustl.edu/~jain/papers/psqr.htm]).
14+
15+
[/img/wiki/response_times_percentiles_over_time.png]
77.7 KB
Loading

0 commit comments

Comments
 (0)