package kg.apc.jmeter.threads; import kg.apc.charting.AbstractGraphRow; import kg.apc.charting.DateTimeRenderer; import kg.apc.charting.GraphPanelChart; import kg.apc.charting.rows.GraphRowSumValues; import kg.apc.jmeter.JMeterPluginsUtils; import kg.apc.jmeter.gui.GuiBuilderHelper; import org.apache.jmeter.control.LoopController; import org.apache.jmeter.control.gui.LoopControlPanel; import org.apache.jmeter.engine.util.CompoundVariable; import org.apache.jmeter.testelement.TestElement; import org.apache.jmeter.threads.AbstractThreadGroup; import org.apache.jmeter.threads.JMeterThread; import org.apache.jmeter.threads.gui.AbstractThreadGroupGui; import org.apache.jorphan.collections.HashTree; import javax.swing.*; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import java.awt.*; import java.util.concurrent.ConcurrentHashMap; public class SteppingThreadGroupGui extends AbstractThreadGroupGui { private class FieldChangesListener implements DocumentListener { private final JTextField tf; public FieldChangesListener(JTextField field) { tf = field; } private void update() { refreshPreview(); } @Override public void insertUpdate(DocumentEvent e) { if (tf.hasFocus()) { update(); } } @Override public void removeUpdate(DocumentEvent e) { if (tf.hasFocus()) { update(); } } @Override public void changedUpdate(DocumentEvent e) { if (tf.hasFocus()) { update(); } } } public static final String WIKIPAGE = "SteppingThreadGroup"; protected ConcurrentHashMap<String, AbstractGraphRow> model; private GraphPanelChart chart; private JTextField initialDelay; private JTextField incUserCount; private JTextField incUserCountBurst; private JTextField incUserPeriod; private JTextField flightTime; private JTextField decUserCount; private JTextField decUserPeriod; private JTextField totalThreads; private LoopControlPanel loopPanel; private JTextField rampUp; public SteppingThreadGroupGui() { super(); init(); initGui(); } protected final void init() { JMeterPluginsUtils.addHelpLinkToPanel(this, WIKIPAGE); JPanel containerPanel = new JPanel(new BorderLayout()); containerPanel.add(createParamsPanel(), BorderLayout.NORTH); chart = new GraphPanelChart(false, true); model = new ConcurrentHashMap<>(); chart.setRows(model); chart.getChartSettings().setDrawFinalZeroingLines(true); chart.setxAxisLabel("Elapsed time"); chart.setYAxisLabel("Number of active threads"); chart.setBorder(javax.swing.BorderFactory.createBevelBorder(javax.swing.border.BevelBorder.LOWERED)); containerPanel.add(GuiBuilderHelper.getComponentWithMargin(chart, 2, 2, 0, 2), BorderLayout.CENTER); add(containerPanel, BorderLayout.CENTER); // this magic LoopPanel provides functionality for thread loops createControllerPanel(); } @Override public void clearGui() { super.clearGui(); initGui(); } // Initialise the gui field values private void initGui() { totalThreads.setText("100"); initialDelay.setText("0"); incUserCount.setText("10"); incUserCountBurst.setText("0"); incUserPeriod.setText("30"); flightTime.setText("60"); decUserCount.setText("5"); decUserPeriod.setText("1"); rampUp.setText("5"); } private JPanel createParamsPanel() { JPanel panel = new JPanel(new GridLayout(0, 5, 5, 5)); panel.setBorder(BorderFactory.createTitledBorder("Threads Scheduling Parameters")); panel.add(new JLabel("This group will start", JLabel.RIGHT)); totalThreads = new JTextField(5); panel.add(totalThreads); panel.add(new JLabel("threads:", JLabel.LEFT)); panel.add(new JLabel()); panel.add(new JLabel()); panel.add(new JLabel("First, wait for", JLabel.RIGHT)); initialDelay = new JTextField(5); panel.add(initialDelay); panel.add(new JLabel("seconds;", JLabel.LEFT)); panel.add(new JLabel()); panel.add(new JLabel()); panel.add(new JLabel("Then start", JLabel.RIGHT)); incUserCountBurst = new JTextField(5); panel.add(incUserCountBurst); panel.add(new JLabel("threads; ", JLabel.LEFT)); panel.add(new JLabel("")); panel.add(new JLabel()); panel.add(new JLabel("Next, add", JLabel.RIGHT)); incUserCount = new JTextField(5); panel.add(incUserCount); panel.add(new JLabel("threads every", JLabel.CENTER)); incUserPeriod = new JTextField(5); panel.add(incUserPeriod); panel.add(new JLabel("seconds, ", JLabel.LEFT)); panel.add(new JLabel()); panel.add(new JLabel()); panel.add(new JLabel("using ramp-up", JLabel.RIGHT)); rampUp = new JTextField(5); panel.add(rampUp); panel.add(new JLabel("seconds.", JLabel.LEFT)); panel.add(new JLabel("Then hold load for", JLabel.RIGHT)); flightTime = new JTextField(5); panel.add(flightTime); panel.add(new JLabel("seconds.", JLabel.LEFT)); panel.add(new JLabel()); panel.add(new JLabel()); panel.add(new JLabel("Finally, stop", JLabel.RIGHT)); decUserCount = new JTextField(5); panel.add(decUserCount); panel.add(new JLabel("threads every", JLabel.CENTER)); decUserPeriod = new JTextField(5); panel.add(decUserPeriod); panel.add(new JLabel("seconds.", JLabel.LEFT)); registerJTextfieldForGraphRefresh(totalThreads); registerJTextfieldForGraphRefresh(initialDelay); registerJTextfieldForGraphRefresh(incUserCount); registerJTextfieldForGraphRefresh(incUserCountBurst); registerJTextfieldForGraphRefresh(incUserPeriod); registerJTextfieldForGraphRefresh(flightTime); registerJTextfieldForGraphRefresh(decUserCount); registerJTextfieldForGraphRefresh(decUserPeriod); registerJTextfieldForGraphRefresh(rampUp); return panel; } @Override public String getLabelResource() { return this.getClass().getSimpleName(); } @Override public String getStaticLabel() { return JMeterPluginsUtils.prefixLabel("Stepping Thread Group"); } @Override public TestElement createTestElement() { SteppingThreadGroup tg = new SteppingThreadGroup(); modifyTestElement(tg); tg.setComment(JMeterPluginsUtils.getWikiLinkText(WIKIPAGE)); return tg; } private void refreshPreview() { SteppingThreadGroup tgForPreview = new SteppingThreadGroup(); tgForPreview.setNumThreads(new CompoundVariable(totalThreads.getText()).execute()); tgForPreview.setThreadGroupDelay(new CompoundVariable(initialDelay.getText()).execute()); tgForPreview.setInUserCount(new CompoundVariable(incUserCount.getText()).execute()); tgForPreview.setInUserCountBurst(new CompoundVariable(incUserCountBurst.getText()).execute()); tgForPreview.setInUserPeriod(new CompoundVariable(incUserPeriod.getText()).execute()); tgForPreview.setOutUserCount(new CompoundVariable(decUserCount.getText()).execute()); tgForPreview.setOutUserPeriod(new CompoundVariable(decUserPeriod.getText()).execute()); tgForPreview.setFlightTime(new CompoundVariable(flightTime.getText()).execute()); tgForPreview.setRampUp(new CompoundVariable(rampUp.getText()).execute()); if (tgForPreview.getInUserCountAsInt() == 0) { tgForPreview.setInUserCount(new CompoundVariable(totalThreads.getText()).execute()); } if (tgForPreview.getOutUserCountAsInt() == 0) { tgForPreview.setOutUserCount(new CompoundVariable(totalThreads.getText()).execute()); } updateChart(tgForPreview); } @Override public void modifyTestElement(TestElement te) { super.configureTestElement(te); if (te instanceof SteppingThreadGroup) { SteppingThreadGroup tg = (SteppingThreadGroup) te; tg.setProperty(SteppingThreadGroup.NUM_THREADS, totalThreads.getText()); tg.setThreadGroupDelay(initialDelay.getText()); tg.setInUserCount(incUserCount.getText()); tg.setInUserCountBurst(incUserCountBurst.getText()); tg.setInUserPeriod(incUserPeriod.getText()); tg.setOutUserCount(decUserCount.getText()); tg.setOutUserPeriod(decUserPeriod.getText()); tg.setFlightTime(flightTime.getText()); tg.setRampUp(rampUp.getText()); tg.setSamplerController((LoopController) loopPanel.createTestElement()); refreshPreview(); } } @Override public void configure(TestElement te) { super.configure(te); SteppingThreadGroup tg = (SteppingThreadGroup) te; totalThreads.setText(tg.getNumThreadsAsString()); initialDelay.setText(tg.getThreadGroupDelay()); incUserCount.setText(tg.getInUserCount()); incUserCountBurst.setText(tg.getInUserCountBurst()); incUserPeriod.setText(tg.getInUserPeriod()); decUserCount.setText(tg.getOutUserCount()); decUserPeriod.setText(tg.getOutUserPeriod()); flightTime.setText(tg.getFlightTime()); rampUp.setText(tg.getRampUp()); TestElement controller = (TestElement) tg.getProperty(AbstractThreadGroup.MAIN_CONTROLLER).getObjectValue(); if (controller != null) { loopPanel.configure(controller); } } private void updateChart(SteppingThreadGroup tg) { model.clear(); GraphRowSumValues row = new GraphRowSumValues(); row.setColor(Color.RED); row.setDrawLine(true); row.setMarkerSize(AbstractGraphRow.MARKER_SIZE_NONE); row.setDrawThickLines(true); final HashTree hashTree = new HashTree(); hashTree.add(new LoopController()); JMeterThread thread = new JMeterThread(hashTree, null, null); long now = System.currentTimeMillis(); // test start chart.setxAxisLabelRenderer(new DateTimeRenderer(DateTimeRenderer.HHMMSS, now - 1)); //-1 because row.add(thread.getStartTime() - 1, 0) row.add(now, 0); row.add(now + tg.getThreadGroupDelayAsInt(), 0); int numThreads = tg.getNumThreads(); // users in for (int n = 0; n < numThreads; n++) { thread.setThreadNum(n); tg.scheduleThread(thread, now); row.add(thread.getStartTime() - 1, 0); row.add(thread.getStartTime(), 1); } // users out for (int n = 0; n < numThreads; n++) { thread.setThreadNum(n); tg.scheduleThread(thread, now); row.add(thread.getEndTime() - 1, 0); row.add(thread.getEndTime(), -1); } model.put("Expected Active Users Count", row); chart.invalidateCache(); chart.repaint(); } private JPanel createControllerPanel() { loopPanel = new LoopControlPanel(false); LoopController looper = (LoopController) loopPanel.createTestElement(); looper.setLoops(-1); looper.setContinueForever(true); loopPanel.configure(looper); return loopPanel; } private void registerJTextfieldForGraphRefresh(final JTextField tf) { tf.addActionListener(loopPanel); tf.getDocument().addDocumentListener(new FieldChangesListener(tf)); } }