Skip to content

Commit 0ef288b

Browse files
Add showTimeline in TestPlan to ease reviewing thread groups configurations in test plans
Now is not needed to move the thread group separate from the test plan to be able to invoke showTimeline, just invoke it in test plan and get timelines for all thread groups in the testplan
1 parent 6b2a0b3 commit 0ef288b

File tree

6 files changed

+182
-12
lines changed

6 files changed

+182
-12
lines changed

jmeter-java-dsl/src/main/java/us/abstracta/jmeter/javadsl/core/DslTestPlan.java

+79
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package us.abstracta.jmeter.javadsl.core;
22

3+
import java.awt.Component;
4+
import java.awt.Dimension;
5+
import java.awt.Font;
36
import java.io.File;
47
import java.io.FileOutputStream;
58
import java.io.IOException;
@@ -10,7 +13,14 @@
1013
import java.util.concurrent.CompletableFuture;
1114
import java.util.concurrent.ExecutionException;
1215
import java.util.concurrent.TimeoutException;
16+
import java.util.stream.Collectors;
17+
import javax.swing.BorderFactory;
18+
import javax.swing.BoxLayout;
19+
import javax.swing.JComponent;
20+
import javax.swing.JPanel;
21+
import javax.swing.JScrollPane;
1322
import javax.swing.SwingUtilities;
23+
import javax.swing.border.TitledBorder;
1424
import org.apache.jmeter.config.Arguments;
1525
import org.apache.jmeter.control.gui.TestPlanGui;
1626
import org.apache.jmeter.exceptions.IllegalUserActionException;
@@ -29,7 +39,9 @@
2939
import us.abstracta.jmeter.javadsl.core.engines.JmeterEnvironment;
3040
import us.abstracta.jmeter.javadsl.core.engines.JmeterGui;
3141
import us.abstracta.jmeter.javadsl.core.testelements.TestElementContainer;
42+
import us.abstracta.jmeter.javadsl.core.threadgroups.BaseThreadGroup;
3243
import us.abstracta.jmeter.javadsl.core.threadgroups.DslDefaultThreadGroup;
44+
import us.abstracta.jmeter.javadsl.core.threadgroups.LoadTimeLine;
3345

3446
/**
3547
* Represents a JMeter test plan, with associated thread groups and other children elements.
@@ -170,6 +182,73 @@ and avoid NPE while updating RSyntaxTextArea in test plan load (e.g.: when test
170182
}
171183
}
172184

185+
/**
186+
* For each thread group shows a graph with a timeline of planned load (threads or rps) to be
187+
* generated.
188+
* <p>
189+
* Graphs will be displayed in a popup window.
190+
* <p>
191+
* This method eases test plan design when working with complex thread group profiles (several
192+
* stages with ramps and holds).
193+
*
194+
* @since 1.28
195+
*/
196+
public void showTimeline() {
197+
List<LoadTimeLine> timeLines = buildThreadGroupTimeLines();
198+
normalizeTimelines(timeLines);
199+
JPanel panel = buildChartsContainerPanel();
200+
int chartWidth = 800;
201+
int chartHeight = timeLines.size() > 2 ? 200 : 300;
202+
timeLines.forEach(tl -> panel.add(buildChart(tl, chartWidth, chartHeight)));
203+
showAndWaitFrameWith(buildScrollPane(panel), "Load timeline", chartWidth + 20,
204+
(chartHeight) * Math.min(3, timeLines.size()));
205+
}
206+
207+
private List<LoadTimeLine> buildThreadGroupTimeLines() {
208+
return children.stream()
209+
.filter(c -> c instanceof BaseThreadGroup)
210+
.map(c -> ((BaseThreadGroup<?>) c).buildLoadTimeline())
211+
.collect(Collectors.toList());
212+
}
213+
214+
private void normalizeTimelines(List<LoadTimeLine> timeLines) {
215+
long maxTime = timeLines.stream()
216+
.mapToLong(LoadTimeLine::getMaxTime)
217+
.max()
218+
.orElse(0L);
219+
timeLines.forEach(tl -> {
220+
tl.add(1, 0);
221+
long chartMaxTime = tl.getMaxTime();
222+
if (chartMaxTime < maxTime) {
223+
tl.add(maxTime - chartMaxTime + 1, 0);
224+
}
225+
});
226+
}
227+
228+
private JPanel buildChartsContainerPanel() {
229+
JPanel ret = new JPanel();
230+
ret.setLayout(new BoxLayout(ret, BoxLayout.Y_AXIS));
231+
return ret;
232+
}
233+
234+
private JComponent buildChart(LoadTimeLine timeLine, int width, int height) {
235+
JComponent ret = timeLine.buildChart();
236+
TitledBorder border = BorderFactory.createTitledBorder(timeLine.getName());
237+
border.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
238+
border.setTitleFont(new Font("Arial", Font.BOLD, 14));
239+
border.setTitleJustification(TitledBorder.CENTER);
240+
ret.setBorder(border);
241+
ret.setPreferredSize(new Dimension(width, height));
242+
return ret;
243+
}
244+
245+
private JScrollPane buildScrollPane(Component component) {
246+
JScrollPane ret = new JScrollPane(component);
247+
ret.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
248+
ret.getVerticalScrollBar().setUnitIncrement(8); // makes scroll faster
249+
return ret;
250+
}
251+
173252
/**
174253
* Saves the given test plan as JMX, which allows it to be loaded in JMeter GUI.
175254
*

jmeter-java-dsl/src/main/java/us/abstracta/jmeter/javadsl/core/testelements/BaseTestElement.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package us.abstracta.jmeter.javadsl.core.testelements;
22

33
import java.awt.Component;
4+
import java.awt.Dimension;
45
import java.awt.event.WindowAdapter;
56
import java.awt.event.WindowEvent;
67
import java.beans.BeanInfo;
@@ -138,6 +139,7 @@ public void showTestElementGui(Component guiComponent, Runnable closeListener) {
138139

139140
protected void showFrameWith(Component content, String title, int width, int height,
140141
Runnable closeListener) {
142+
content.setPreferredSize(new Dimension(width, height));
141143
JFrame frame = new JFrame(title);
142144
frame.setDefaultCloseOperation(
143145
closeListener != null ? WindowConstants.DISPOSE_ON_CLOSE : WindowConstants.EXIT_ON_CLOSE);
@@ -151,8 +153,8 @@ public void windowClosed(WindowEvent e) {
151153
});
152154
}
153155
frame.setLocation(200, 200);
154-
frame.setSize(width, height);
155156
frame.add(content);
157+
frame.pack();
156158
frame.setVisible(true);
157159
}
158160

jmeter-java-dsl/src/main/java/us/abstracta/jmeter/javadsl/core/threadgroups/BaseThreadGroup.java

+12
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import org.apache.jmeter.threads.AbstractThreadGroup;
88
import us.abstracta.jmeter.javadsl.codegeneration.params.EnumParam.EnumPropertyValue;
99
import us.abstracta.jmeter.javadsl.core.DslTestElement;
10+
import us.abstracta.jmeter.javadsl.core.DslTestPlan;
1011
import us.abstracta.jmeter.javadsl.core.testelements.TestElementContainer;
1112
import us.abstracta.jmeter.javadsl.core.threadgroups.BaseThreadGroup.ThreadGroupChild;
1213

@@ -61,6 +62,17 @@ protected TestElement buildTestElement() {
6162

6263
protected abstract AbstractThreadGroup buildThreadGroup();
6364

65+
/**
66+
* This method is used by {@link DslTestPlan#showTimeline()} to get the timeline chart for
67+
* this thread group.
68+
*
69+
* @return the timeline chart for this thread group or null if it is not supported.
70+
* @since 1.28
71+
*/
72+
public LoadTimeLine buildLoadTimeline() {
73+
return null;
74+
}
75+
6476
/**
6577
* Test elements that can be added as direct children of a thread group in jmeter should implement
6678
* this interface.

jmeter-java-dsl/src/main/java/us/abstracta/jmeter/javadsl/core/threadgroups/DslDefaultThreadGroup.java

+14-5
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
import us.abstracta.jmeter.javadsl.core.threadgroups.defaultthreadgroup.SimpleThreadGroupHelper;
1919
import us.abstracta.jmeter.javadsl.core.threadgroups.defaultthreadgroup.Stage;
2020
import us.abstracta.jmeter.javadsl.core.threadgroups.defaultthreadgroup.UltimateThreadGroupHelper;
21-
import us.abstracta.jmeter.javadsl.core.util.SingleSeriesTimelinePanel;
2221

2322
/**
2423
* Represents the standard thread group test element included by JMeter.
@@ -398,14 +397,24 @@ public AbstractThreadGroup buildThreadGroup() {
398397
* @since 0.26
399398
*/
400399
public void showTimeline() {
400+
showAndWaitFrameWith(buildLoadTimeline().buildChart(), name + " threads timeline", 800, 300);
401+
}
402+
403+
@Override
404+
public LoadTimeLine buildLoadTimeline() {
401405
if (stages.stream().anyMatch(s -> !s.isFixedStage())) {
402406
throw new IllegalStateException(
403407
"Can't display timeline when some JMeter expression is used in any ramp or hold.");
408+
} else if (stages.size() == 1 && stages.get(0).iterations() != null
409+
|| stages.size() == 2 && stages.get(1).iterations() != null
410+
|| stages.size() == 3 && stages.get(2).iterations() != null) {
411+
throw new IllegalStateException(
412+
"Can't display timeline when thread group is configured with iterations.");
404413
}
405-
SingleSeriesTimelinePanel chart = new SingleSeriesTimelinePanel("Threads");
406-
chart.add(0, 0);
407-
stages.forEach(s -> chart.add(((Duration) s.duration()).toMillis(), (int) s.threadCount()));
408-
showAndWaitFrameWith(chart, name + " threads timeline", 800, 300);
414+
LoadTimeLine ret = new LoadTimeLine(name, "Threads");
415+
ret.add(0, 0);
416+
stages.forEach(s -> ret.add(((Duration) s.duration()).toMillis(), (int) s.threadCount()));
417+
return ret;
409418
}
410419

411420
public static class CodeBuilder extends MethodCallBuilder {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package us.abstracta.jmeter.javadsl.core.threadgroups;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
import javax.swing.JComponent;
6+
import us.abstracta.jmeter.javadsl.core.util.SingleSeriesTimelinePanel;
7+
8+
public class LoadTimeLine {
9+
10+
private final String name;
11+
private final String loadUnit;
12+
private final List<TimePoint> timePoints = new ArrayList<>();
13+
14+
public LoadTimeLine(String name, String loadUnit) {
15+
this.name = name;
16+
this.loadUnit = loadUnit;
17+
}
18+
19+
public void add(long timeMillis, double value) {
20+
timePoints.add(new TimePoint(timeMillis, value));
21+
}
22+
23+
public String getName() {
24+
return name;
25+
}
26+
27+
public JComponent buildChart() {
28+
SingleSeriesTimelinePanel ret = new SingleSeriesTimelinePanel(loadUnit);
29+
for (TimePoint tp : timePoints) {
30+
ret.add(tp.timeMillis, tp.value);
31+
}
32+
return ret;
33+
}
34+
35+
public long getMaxTime() {
36+
return timePoints.stream()
37+
.mapToLong(tp -> tp.timeMillis)
38+
.max()
39+
.orElse(0L);
40+
}
41+
42+
private static class TimePoint {
43+
44+
private final long timeMillis;
45+
private final double value;
46+
47+
private TimePoint(long timeIncrMillis, double value) {
48+
this.timeMillis = timeIncrMillis;
49+
this.value = value;
50+
}
51+
52+
}
53+
54+
}

jmeter-java-dsl/src/main/java/us/abstracta/jmeter/javadsl/core/threadgroups/RpsThreadGroup.java

+20-6
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import org.apache.jorphan.collections.HashTree;
2020
import us.abstracta.jmeter.javadsl.core.BuildTreeContext;
2121
import us.abstracta.jmeter.javadsl.core.util.JmeterFunction;
22-
import us.abstracta.jmeter.javadsl.core.util.SingleSeriesTimelinePanel;
2322

2423
/**
2524
* Configures a thread group which dynamically adapts the number of threads and pauses to match a
@@ -286,13 +285,28 @@ protected AbstractThreadGroup buildThreadGroup() {
286285
return ret;
287286
}
288287

289-
public void showTimeline() {
290-
SingleSeriesTimelinePanel chart = new SingleSeriesTimelinePanel(counting.label + " per second");
288+
@Override
289+
public LoadTimeLine buildLoadTimeline() {
290+
LoadTimeLine ret = new LoadTimeLine(name, counting.label + " per second");
291291
if (!schedules.isEmpty()) {
292-
chart.add(0, schedules.get(0).fromRps);
293-
schedules.forEach(s -> chart.add(s.durationSecs * 1000, s.toRps));
292+
ret.add(0, schedules.get(0).fromRps);
293+
schedules.forEach(s -> ret.add(s.durationSecs * 1000, s.toRps));
294294
}
295-
showAndWaitFrameWith(chart, name + " timeline", 800, 300);
295+
return ret;
296+
}
297+
298+
/**
299+
* Shows a graph with a timeline of planned rps count execution for this test plan.
300+
* <p>
301+
* The graph will be displayed in a popup window.
302+
* <p>
303+
* This method is provided mainly to ease test plan designing when working with complex thread
304+
* group profiles (several stages with ramps and holds).
305+
*
306+
* @since 0.26
307+
*/
308+
public void showTimeline() {
309+
showAndWaitFrameWith(buildLoadTimeline().buildChart(), name + " timeline", 800, 300);
296310
}
297311

298312
}

0 commit comments

Comments
 (0)