Skip to content

Commit

Permalink
Add showTimeline in TestPlan to ease reviewing thread groups configur…
Browse files Browse the repository at this point in the history
…ations 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
  • Loading branch information
rabelenda-abstracta committed May 15, 2024
1 parent 6b2a0b3 commit 0ef288b
Show file tree
Hide file tree
Showing 6 changed files with 182 additions and 12 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package us.abstracta.jmeter.javadsl.core;

import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
Expand All @@ -10,7 +13,14 @@
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
import javax.swing.border.TitledBorder;
import org.apache.jmeter.config.Arguments;
import org.apache.jmeter.control.gui.TestPlanGui;
import org.apache.jmeter.exceptions.IllegalUserActionException;
Expand All @@ -29,7 +39,9 @@
import us.abstracta.jmeter.javadsl.core.engines.JmeterEnvironment;
import us.abstracta.jmeter.javadsl.core.engines.JmeterGui;
import us.abstracta.jmeter.javadsl.core.testelements.TestElementContainer;
import us.abstracta.jmeter.javadsl.core.threadgroups.BaseThreadGroup;
import us.abstracta.jmeter.javadsl.core.threadgroups.DslDefaultThreadGroup;
import us.abstracta.jmeter.javadsl.core.threadgroups.LoadTimeLine;

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

/**
* For each thread group shows a graph with a timeline of planned load (threads or rps) to be
* generated.
* <p>
* Graphs will be displayed in a popup window.
* <p>
* This method eases test plan design when working with complex thread group profiles (several
* stages with ramps and holds).
*
* @since 1.28
*/
public void showTimeline() {
List<LoadTimeLine> timeLines = buildThreadGroupTimeLines();
normalizeTimelines(timeLines);
JPanel panel = buildChartsContainerPanel();
int chartWidth = 800;
int chartHeight = timeLines.size() > 2 ? 200 : 300;
timeLines.forEach(tl -> panel.add(buildChart(tl, chartWidth, chartHeight)));
showAndWaitFrameWith(buildScrollPane(panel), "Load timeline", chartWidth + 20,
(chartHeight) * Math.min(3, timeLines.size()));
}

private List<LoadTimeLine> buildThreadGroupTimeLines() {
return children.stream()
.filter(c -> c instanceof BaseThreadGroup)
.map(c -> ((BaseThreadGroup<?>) c).buildLoadTimeline())
.collect(Collectors.toList());
}

private void normalizeTimelines(List<LoadTimeLine> timeLines) {
long maxTime = timeLines.stream()
.mapToLong(LoadTimeLine::getMaxTime)
.max()
.orElse(0L);
timeLines.forEach(tl -> {
tl.add(1, 0);
long chartMaxTime = tl.getMaxTime();
if (chartMaxTime < maxTime) {
tl.add(maxTime - chartMaxTime + 1, 0);
}
});
}

private JPanel buildChartsContainerPanel() {
JPanel ret = new JPanel();
ret.setLayout(new BoxLayout(ret, BoxLayout.Y_AXIS));
return ret;
}

private JComponent buildChart(LoadTimeLine timeLine, int width, int height) {
JComponent ret = timeLine.buildChart();
TitledBorder border = BorderFactory.createTitledBorder(timeLine.getName());
border.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
border.setTitleFont(new Font("Arial", Font.BOLD, 14));
border.setTitleJustification(TitledBorder.CENTER);
ret.setBorder(border);
ret.setPreferredSize(new Dimension(width, height));
return ret;
}

private JScrollPane buildScrollPane(Component component) {
JScrollPane ret = new JScrollPane(component);
ret.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
ret.getVerticalScrollBar().setUnitIncrement(8); // makes scroll faster
return ret;
}

/**
* Saves the given test plan as JMX, which allows it to be loaded in JMeter GUI.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package us.abstracta.jmeter.javadsl.core.testelements;

import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.beans.BeanInfo;
Expand Down Expand Up @@ -138,6 +139,7 @@ public void showTestElementGui(Component guiComponent, Runnable closeListener) {

protected void showFrameWith(Component content, String title, int width, int height,
Runnable closeListener) {
content.setPreferredSize(new Dimension(width, height));
JFrame frame = new JFrame(title);
frame.setDefaultCloseOperation(
closeListener != null ? WindowConstants.DISPOSE_ON_CLOSE : WindowConstants.EXIT_ON_CLOSE);
Expand All @@ -151,8 +153,8 @@ public void windowClosed(WindowEvent e) {
});
}
frame.setLocation(200, 200);
frame.setSize(width, height);
frame.add(content);
frame.pack();
frame.setVisible(true);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.apache.jmeter.threads.AbstractThreadGroup;
import us.abstracta.jmeter.javadsl.codegeneration.params.EnumParam.EnumPropertyValue;
import us.abstracta.jmeter.javadsl.core.DslTestElement;
import us.abstracta.jmeter.javadsl.core.DslTestPlan;
import us.abstracta.jmeter.javadsl.core.testelements.TestElementContainer;
import us.abstracta.jmeter.javadsl.core.threadgroups.BaseThreadGroup.ThreadGroupChild;

Expand Down Expand Up @@ -61,6 +62,17 @@ protected TestElement buildTestElement() {

protected abstract AbstractThreadGroup buildThreadGroup();

/**
* This method is used by {@link DslTestPlan#showTimeline()} to get the timeline chart for
* this thread group.
*
* @return the timeline chart for this thread group or null if it is not supported.
* @since 1.28
*/
public LoadTimeLine buildLoadTimeline() {
return null;
}

/**
* Test elements that can be added as direct children of a thread group in jmeter should implement
* this interface.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
import us.abstracta.jmeter.javadsl.core.threadgroups.defaultthreadgroup.SimpleThreadGroupHelper;
import us.abstracta.jmeter.javadsl.core.threadgroups.defaultthreadgroup.Stage;
import us.abstracta.jmeter.javadsl.core.threadgroups.defaultthreadgroup.UltimateThreadGroupHelper;
import us.abstracta.jmeter.javadsl.core.util.SingleSeriesTimelinePanel;

/**
* Represents the standard thread group test element included by JMeter.
Expand Down Expand Up @@ -398,14 +397,24 @@ public AbstractThreadGroup buildThreadGroup() {
* @since 0.26
*/
public void showTimeline() {
showAndWaitFrameWith(buildLoadTimeline().buildChart(), name + " threads timeline", 800, 300);
}

@Override
public LoadTimeLine buildLoadTimeline() {
if (stages.stream().anyMatch(s -> !s.isFixedStage())) {
throw new IllegalStateException(
"Can't display timeline when some JMeter expression is used in any ramp or hold.");
} else if (stages.size() == 1 && stages.get(0).iterations() != null
|| stages.size() == 2 && stages.get(1).iterations() != null
|| stages.size() == 3 && stages.get(2).iterations() != null) {
throw new IllegalStateException(
"Can't display timeline when thread group is configured with iterations.");
}
SingleSeriesTimelinePanel chart = new SingleSeriesTimelinePanel("Threads");
chart.add(0, 0);
stages.forEach(s -> chart.add(((Duration) s.duration()).toMillis(), (int) s.threadCount()));
showAndWaitFrameWith(chart, name + " threads timeline", 800, 300);
LoadTimeLine ret = new LoadTimeLine(name, "Threads");
ret.add(0, 0);
stages.forEach(s -> ret.add(((Duration) s.duration()).toMillis(), (int) s.threadCount()));
return ret;
}

public static class CodeBuilder extends MethodCallBuilder {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package us.abstracta.jmeter.javadsl.core.threadgroups;

import java.util.ArrayList;
import java.util.List;
import javax.swing.JComponent;
import us.abstracta.jmeter.javadsl.core.util.SingleSeriesTimelinePanel;

public class LoadTimeLine {

private final String name;
private final String loadUnit;
private final List<TimePoint> timePoints = new ArrayList<>();

public LoadTimeLine(String name, String loadUnit) {
this.name = name;
this.loadUnit = loadUnit;
}

public void add(long timeMillis, double value) {
timePoints.add(new TimePoint(timeMillis, value));
}

public String getName() {
return name;
}

public JComponent buildChart() {
SingleSeriesTimelinePanel ret = new SingleSeriesTimelinePanel(loadUnit);
for (TimePoint tp : timePoints) {
ret.add(tp.timeMillis, tp.value);
}
return ret;
}

public long getMaxTime() {
return timePoints.stream()
.mapToLong(tp -> tp.timeMillis)
.max()
.orElse(0L);
}

private static class TimePoint {

private final long timeMillis;
private final double value;

private TimePoint(long timeIncrMillis, double value) {
this.timeMillis = timeIncrMillis;
this.value = value;
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import org.apache.jorphan.collections.HashTree;
import us.abstracta.jmeter.javadsl.core.BuildTreeContext;
import us.abstracta.jmeter.javadsl.core.util.JmeterFunction;
import us.abstracta.jmeter.javadsl.core.util.SingleSeriesTimelinePanel;

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

public void showTimeline() {
SingleSeriesTimelinePanel chart = new SingleSeriesTimelinePanel(counting.label + " per second");
@Override
public LoadTimeLine buildLoadTimeline() {
LoadTimeLine ret = new LoadTimeLine(name, counting.label + " per second");
if (!schedules.isEmpty()) {
chart.add(0, schedules.get(0).fromRps);
schedules.forEach(s -> chart.add(s.durationSecs * 1000, s.toRps));
ret.add(0, schedules.get(0).fromRps);
schedules.forEach(s -> ret.add(s.durationSecs * 1000, s.toRps));
}
showAndWaitFrameWith(chart, name + " timeline", 800, 300);
return ret;
}

/**
* Shows a graph with a timeline of planned rps count execution for this test plan.
* <p>
* The graph will be displayed in a popup window.
* <p>
* This method is provided mainly to ease test plan designing when working with complex thread
* group profiles (several stages with ramps and holds).
*
* @since 0.26
*/
public void showTimeline() {
showAndWaitFrameWith(buildLoadTimeline().buildChart(), name + " timeline", 800, 300);
}

}

0 comments on commit 0ef288b

Please sign in to comment.