diff --git a/examples/AutoStopExample_ErrCount.jmx b/examples/AutoStopExample_ErrCount.jmx
new file mode 100644
index 000000000..ed19cb5c0
--- /dev/null
+++ b/examples/AutoStopExample_ErrCount.jmx
@@ -0,0 +1,145 @@
+
+
+
+
+ This Test Plan demonstrates how AutoStop is used to terminate a test
+when the absolute error count in a window exceeds a threshold.
+ false
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 15
+ 5
+ Stops the test if more than 15 errors occur within a 5-second window.
+
+
+
+
+ false
+
+ saveConfig
+
+
+ true
+ true
+ true
+
+ true
+ true
+ true
+ true
+ false
+ true
+ true
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ 0
+ true
+ true
+
+
+
+ 1000
+ false
+
+
+
+
+ false
+ false
+ After 10 seconds of successful requests, errors start occurring.
+When error count exceeds 15 in the 5-second window, AutoStop terminates the test.
+
+
+
+
+
+ 2
+ 0
+ 0
+ 60
+ 0
+
+
+
+ -1
+ false
+
+ continue
+ Successful traffic: 2 threads with 200 OK responses
+
+
+
+ true
+ true
+ 200
+ OK
+ Successful response
+ 100
+ 20
+ 5
+ http://dummy
+ org.apache.jmeter.samplers.SampleResult
+
+
+
+
+
+
+
+ 5
+ 10
+ 0
+ 50
+ 0
+
+
+
+ -1
+ false
+
+ continue
+ Simulates errors after 10s: 5 threads generating 500 errors
+
+
+
+ true
+ false
+ 500
+ Internal Server Error
+ Error response
+ 100
+ 20
+ 5
+ http://dummy
+ org.apache.jmeter.samplers.SampleResult
+
+
+
+
+
+
+
diff --git a/examples/AutoStopExample_Percentile.jmx b/examples/AutoStopExample_Percentile.jmx
new file mode 100644
index 000000000..b861ec08a
--- /dev/null
+++ b/examples/AutoStopExample_Percentile.jmx
@@ -0,0 +1,145 @@
+
+
+
+
+ This Test Plan demonstrates how AutoStop is used to terminate a test
+when response time percentile degrades significantly compared to the previous window.
+ false
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 90
+ 5
+ 50
+
+
+ Stops the test if P90 response time degrades by more than 50%
+compared to the previous 5-second window.
+
+
+
+
+ false
+
+ saveConfig
+
+
+ true
+ true
+ true
+
+ true
+ true
+ true
+ true
+ false
+ true
+ true
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ 0
+ true
+ true
+
+
+
+ 500
+ false
+
+
+
+
+ false
+ false
+ Shows response times jumping when high latency threads start at 10s
+
+
+
+
+
+ 5
+ 0
+ 0
+ 60
+ 0
+
+
+
+ -1
+ false
+
+ continue
+ Baseline traffic: 5 threads with 50 ms response time
+
+
+
+ true
+ true
+ 200
+ OK
+ Baseline fast response
+ 50
+ 20
+ 5
+ http://dummy
+ org.apache.jmeter.samplers.SampleResult
+
+
+
+
+
+
+
+ 10
+ 10
+ 0
+ 50
+ 0
+
+
+
+ -1
+ false
+
+ continue
+ Simulates latency degradation after 10s: 10 threads with 500 ms response time
+
+
+
+ true
+ true
+ 200
+ OK
+ Degraded slow response
+ 500
+ 200
+ 10
+ http://dummy
+ org.apache.jmeter.samplers.SampleResult
+
+
+
+
+
+
+
diff --git a/plugins/autostop/src/main/java/kg/apc/jmeter/reporters/AutoStop.java b/plugins/autostop/src/main/java/kg/apc/jmeter/reporters/AutoStop.java
index 002bea890..6e1c9b895 100644
--- a/plugins/autostop/src/main/java/kg/apc/jmeter/reporters/AutoStop.java
+++ b/plugins/autostop/src/main/java/kg/apc/jmeter/reporters/AutoStop.java
@@ -32,6 +32,7 @@
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
+import java.util.concurrent.atomic.AtomicInteger;
public class AutoStop
extends AbstractListenerElement
@@ -49,6 +50,13 @@ public class AutoStop
private final static String PERCENTILE_RESPONSE_TIME_SECS ="percentile_response_time_secs";
private final static String PERCENTILE_VALUE = "percentile_value";
private final static String CUSTOM_VALIDATION_DURATION = "custom_validation_duration";
+ // New: relative window-to-window percentile degradation
+ private final static String REL_PERCENTILE_VALUE = "rel_percentile_value";
+ private final static String REL_PERCENTILE_WINDOW_SECS = "rel_percentile_window_secs";
+ private final static String REL_PERCENTILE_THRESHOLD_PCT = "rel_percentile_threshold_pct";
+ // New: per-window error count ceiling
+ private final static String ERROR_COUNT = "error_count";
+ private final static String ERROR_COUNT_SECS = "error_count_length";
private long curSec = 0L;
private GraphPanelChartAverageElement avgRespTime = new GraphPanelChartAverageElement();
private GraphPanelChartAverageElement avgRespLatency = new GraphPanelChartAverageElement();
@@ -76,6 +84,18 @@ public class AutoStop
private int testValueCustomSec = 180;
private boolean skipIter = false;
+ private final Object relWindowLock = new Object();
+ private SamplingStatCalculator relWindowStatCalc = new SamplingStatCalculator();
+ private long relWindowStart = 0;
+ private double previousWindowPercentile = -1;
+ private float testValueRelPercentileValue = 0;
+ private int testValueRelWindowSecs = 0;
+ private float testValueRelThresholdPct = 0;
+ private final AtomicInteger errorCountInWindow = new AtomicInteger(0);
+ private long errorCountWindowStart = 0;
+ private int testValueErrorCount = 0;
+ private int testValueErrorCountSec = 0;
+
public AutoStop() {
super();
@@ -85,6 +105,16 @@ public AutoStop() {
public void sampleOccurred(SampleEvent se) {
long sec = System.currentTimeMillis() / 1000;
+ if (testValueRelPercentileValue > 0 && testValueRelWindowSecs > 0 && testValueRelThresholdPct > 0) {
+ synchronized (relWindowLock) {
+ relWindowStatCalc.addSample(se.getResult());
+ }
+ }
+
+ if (testValueErrorCount > 0 && testValueErrorCountSec > 0 && !se.getResult().isSuccessful()) {
+ errorCountInWindow.incrementAndGet();
+ }
+
if (curSec != sec) {
if (testValueRespTime > 0) {
//log.debug("Avg resp time: "+avgRespTime.getValue());
@@ -144,6 +174,40 @@ public void sampleOccurred(SampleEvent se) {
}
}
+ // Relative window-to-window percentile degradation check
+ if (testValueRelPercentileValue > 0 && testValueRelWindowSecs > 0 && testValueRelThresholdPct > 0) {
+ if (sec - relWindowStart >= testValueRelWindowSecs) {
+ double currentPct;
+ synchronized (relWindowLock) {
+ currentPct = relWindowStatCalc.getPercentPoint(testValueRelPercentileValue).doubleValue();
+ relWindowStatCalc = new SamplingStatCalculator();
+ }
+ if (previousWindowPercentile > 0) {
+ double growthPct = (currentPct - previousWindowPercentile) * 100.0 / previousWindowPercentile;
+ if (growthPct >= testValueRelThresholdPct) {
+ log.info("P" + (int)(testValueRelPercentileValue * 100) + " grew " + String.format("%.1f", growthPct) + "% (" + (int)previousWindowPercentile + " ms -> " + (int)currentPct + " ms) over " + testValueRelWindowSecs + "s window, threshold " + testValueRelThresholdPct + "%. Auto-shutdown test...");
+ System.out.println("AutoStop - P" + (int)(testValueRelPercentileValue * 100) + " grew " + String.format("%.1f", growthPct) + "% (" + (int)previousWindowPercentile + " ms -> " + (int)currentPct + " ms) over " + testValueRelWindowSecs + "s window, threshold " + testValueRelThresholdPct + "%. Auto-shutdown test...");
+ stopTest();
+ }
+ }
+ previousWindowPercentile = currentPct;
+ relWindowStart = sec;
+ }
+ }
+
+ // Per-window error count ceiling check
+ if (testValueErrorCount > 0 && testValueErrorCountSec > 0) {
+ if (errorCountInWindow.get() > testValueErrorCount) {
+ log.info("Error count more than " + getErrorCount() + " within " + getErrorCountSecs() + "s. Auto-shutdown test...");
+ System.out.println("AutoStop - Error count more than " + getErrorCount() + " within " + getErrorCountSecs() + "s. Auto-shutdown test...");
+ stopTest();
+ }
+ if (sec - errorCountWindowStart >= testValueErrorCountSec) {
+ errorCountInWindow.set(0);
+ errorCountWindowStart = sec;
+ }
+ }
+
curSec = sec;
avgRespTime = new GraphPanelChartAverageElement();
avgRespLatency = new GraphPanelChartAverageElement();
@@ -191,6 +255,18 @@ public void testStarted() {
testPercentileValue = getPercentileValueAsFloat();
testValueCustomSec = getCustomValidationDurationAsInt();
+ testValueRelPercentileValue = getRelPercentileValueAsFloat();
+ testValueRelWindowSecs = getRelWindowSecsAsInt();
+ testValueRelThresholdPct = getRelThresholdPctAsFloat();
+ synchronized (relWindowLock) {
+ relWindowStatCalc = new SamplingStatCalculator();
+ }
+ relWindowStart = System.currentTimeMillis() / 1000;
+ previousWindowPercentile = -1;
+ testValueErrorCount = getErrorCountAsInt();
+ testValueErrorCountSec = getErrorCountSecsAsInt();
+ errorCountInWindow.set(0);
+ errorCountWindowStart = System.currentTimeMillis() / 1000;
}
@Override
@@ -238,6 +314,18 @@ void setErrorRateSecs(String text) {
setProperty(ERROR_RATE_SECS, text);
}
+ void setRelPercentileValue(String text) { setProperty(REL_PERCENTILE_VALUE, text); }
+ void setRelWindowSecs(String text) { setProperty(REL_PERCENTILE_WINDOW_SECS, text); }
+ void setRelThresholdPct(String text) { setProperty(REL_PERCENTILE_THRESHOLD_PCT, text); }
+ String getRelPercentileValue() { return getPropertyAsString(REL_PERCENTILE_VALUE); }
+ String getRelWindowSecs() { return getPropertyAsString(REL_PERCENTILE_WINDOW_SECS); }
+ String getRelThresholdPct() { return getPropertyAsString(REL_PERCENTILE_THRESHOLD_PCT); }
+
+ void setErrorCount(String text) { setProperty(ERROR_COUNT, text); }
+ void setErrorCountSecs(String text) { setProperty(ERROR_COUNT_SECS, text); }
+ String getErrorCount() { return getPropertyAsString(ERROR_COUNT); }
+ String getErrorCountSecs() { return getPropertyAsString(ERROR_COUNT_SECS); }
+
String getPercentileResponseTime() { return getPropertyAsString(PERCENTILE_RESPONSE_TIME); }
String getPercentileResponseTimeSecs() { return getPropertyAsString(PERCENTILE_RESPONSE_TIME_SECS); }
@@ -271,115 +359,226 @@ String getErrorRateSecs() {
}
private int getPercentileResponseTimeAsInt() {
+ String val = getPercentileResponseTime();
+ if (val == null || val.trim().isEmpty()) {
+ return 0;
+ }
int res = 0;
try {
- res = Integer.parseInt(getPercentileResponseTime());
+ res = Integer.parseInt(val.trim());
} catch (NumberFormatException e) {
- log.error("Wrong response time: " + getPercentileResponseTime(), e);
+ log.error("Wrong response time: " + val, e);
setPercentileResponseTime("0");
}
return res;
}
private int getPercentileResponseTimeSecsAsInt() {
+ String val = getPercentileResponseTimeSecs();
+ if (val == null || val.trim().isEmpty()) {
+ return 1;
+ }
int res = 0;
try {
- res = Integer.parseInt(getPercentileResponseTimeSecs());
+ res = Integer.parseInt(val.trim());
} catch (NumberFormatException e) {
- log.error("Wrong response time period: " + getPercentileResponseTimeSecs(), e);
+ log.error("Wrong response time period: " + val, e);
setPercentileResponseTimeSecs("1");
}
return res > 0 ? res : 1;
}
private int getCustomValidationDurationAsInt() {
+ String val = getCustomValidationDuration();
+ if (val == null || val.trim().isEmpty()) {
+ return 1;
+ }
int res = 0;
try {
- res = Integer.parseInt(getCustomValidationDuration());
+ res = Integer.parseInt(val.trim());
} catch (NumberFormatException e) {
- log.error("Wrong time period: " + getCustomValidationDuration(), e);
+ log.error("Wrong time period: " + val, e);
setCustomValidationDuration("1");
}
return res > 0 ? res : 1;
}
private float getPercentileValueAsFloat() {
+ String val = getPercentileValue();
+ if (val == null || val.trim().isEmpty()) {
+ return 1;
+ }
float res = 0;
try {
- res = Float.parseFloat(getPercentileValue()) / 100;
+ res = Float.parseFloat(val.trim()) / 100;
} catch (NumberFormatException e) {
- log.error("Wrong Percentile Value: " + getPercentileValue(), e);
+ log.error("Wrong Percentile Value: " + val, e);
setPercentileValue("1");
}
return res > 0 ? res : 1;
}
private int getResponseTimeAsInt() {
+ String val = getResponseTime();
+ if (val == null || val.trim().isEmpty()) {
+ return 0;
+ }
int res = 0;
try {
- res = Integer.parseInt(getResponseTime());
+ res = Integer.parseInt(val.trim());
} catch (NumberFormatException e) {
- log.error("Wrong response time: " + getResponseTime(), e);
+ log.error("Wrong response time: " + val, e);
setResponseTime("0");
}
return res;
}
private int getResponseTimeSecsAsInt() {
+ String val = getResponseTimeSecs();
+ if (val == null || val.trim().isEmpty()) {
+ return 1;
+ }
int res = 0;
try {
- res = Integer.parseInt(getResponseTimeSecs());
+ res = Integer.parseInt(val.trim());
} catch (NumberFormatException e) {
- log.error("Wrong response time period: " + getResponseTime(), e);
+ log.error("Wrong response time period: " + val, e);
setResponseTimeSecs("1");
}
return res > 0 ? res : 1;
}
private int getResponseLatencyAsInt() {
+ String val = getResponseLatency();
+ if (val == null || val.trim().isEmpty()) {
+ return 0;
+ }
int res = 0;
try {
- res = Integer.parseInt(getResponseLatency());
+ res = Integer.parseInt(val.trim());
} catch (NumberFormatException e) {
- log.error("Wrong response time: " + getResponseLatency(), e);
+ log.error("Wrong response latency: " + val, e);
setResponseLatency("0");
}
return res;
}
private int getResponseLatencySecsAsInt() {
+ String val = getResponseLatencySecs();
+ if (val == null || val.trim().isEmpty()) {
+ return 1;
+ }
int res = 0;
try {
- res = Integer.parseInt(getResponseLatencySecs());
+ res = Integer.parseInt(val.trim());
} catch (NumberFormatException e) {
- log.error("Wrong response time period: " + getResponseLatencySecs(), e);
+ log.error("Wrong response latency period: " + val, e);
setResponseLatencySecs("1");
}
return res > 0 ? res : 1;
}
private float getErrorRateAsFloat() {
+ String val = getErrorRate();
+ if (val == null || val.trim().isEmpty()) {
+ return 0;
+ }
float res = 0;
try {
- res = Float.parseFloat(getErrorRate()) / 100;
+ res = Float.parseFloat(val.trim()) / 100;
} catch (NumberFormatException e) {
- log.error("Wrong error rate: " + getErrorRate(), e);
+ log.error("Wrong error rate: " + val, e);
setErrorRate("0");
}
return res;
}
private int getErrorRateSecsAsInt() {
+ String val = getErrorRateSecs();
+ if (val == null || val.trim().isEmpty()) {
+ return 1;
+ }
int res = 0;
try {
- res = Integer.parseInt(getErrorRateSecs());
+ res = Integer.parseInt(val.trim());
} catch (NumberFormatException e) {
- log.error("Wrong error rate period: " + getResponseTime(), e);
+ log.error("Wrong error rate period: " + val, e);
setErrorRateSecs("1");
}
return res > 0 ? res : 1;
}
+ // New: parsers for relative window properties
+ private float getRelPercentileValueAsFloat() {
+ String val = getRelPercentileValue();
+ if (val == null || val.trim().isEmpty()) {
+ return 0;
+ }
+ float res = 0;
+ try {
+ res = Float.parseFloat(val.trim()) / 100f;
+ } catch (NumberFormatException e) {
+ log.error("Wrong relative percentile value: " + val, e);
+ }
+ return (res > 0 && res <= 1) ? res : 0;
+ }
+
+ private int getRelWindowSecsAsInt() {
+ String val = getRelWindowSecs();
+ if (val == null || val.trim().isEmpty()) {
+ return 0;
+ }
+ int res = 0;
+ try {
+ res = Integer.parseInt(val.trim());
+ } catch (NumberFormatException e) {
+ log.error("Wrong relative window secs: " + val, e);
+ }
+ return res > 0 ? res : 0;
+ }
+
+ private float getRelThresholdPctAsFloat() {
+ String val = getRelThresholdPct();
+ if (val == null || val.trim().isEmpty()) {
+ return 0;
+ }
+ float res = 0;
+ try {
+ res = Float.parseFloat(val.trim());
+ } catch (NumberFormatException e) {
+ log.error("Wrong relative threshold pct: " + val, e);
+ }
+ return res > 0 ? res : 0;
+ }
+
+ private int getErrorCountAsInt() {
+ String val = getErrorCount();
+ if (val == null || val.trim().isEmpty()) {
+ return 0;
+ }
+ int res = 0;
+ try {
+ res = Integer.parseInt(val.trim());
+ } catch (NumberFormatException e) {
+ log.error("Wrong error count: " + val, e);
+ }
+ return res > 0 ? res : 0;
+ }
+
+ private int getErrorCountSecsAsInt() {
+ String val = getErrorCountSecs();
+ if (val == null || val.trim().isEmpty()) {
+ return 0;
+ }
+ int res = 0;
+ try {
+ res = Integer.parseInt(val.trim());
+ } catch (NumberFormatException e) {
+ log.error("Wrong error count secs: " + val, e);
+ }
+ return res > 0 ? res : 0;
+ }
+
private void stopTest() {
stopTries++;
System.setProperty("auto_stopped", "true");
diff --git a/plugins/autostop/src/main/java/kg/apc/jmeter/reporters/JAutoStopPanel.java b/plugins/autostop/src/main/java/kg/apc/jmeter/reporters/JAutoStopPanel.java
index 49097dbc0..34eb52786 100644
--- a/plugins/autostop/src/main/java/kg/apc/jmeter/reporters/JAutoStopPanel.java
+++ b/plugins/autostop/src/main/java/kg/apc/jmeter/reporters/JAutoStopPanel.java
@@ -1,6 +1,16 @@
package kg.apc.jmeter.reporters;
import java.awt.Color;
+import java.awt.Component;
+import java.awt.FlowLayout;
+import java.awt.Font;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.event.ItemListener;
+import javax.swing.JCheckBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
@@ -20,45 +30,329 @@ public JAutoStopPanel() {
registerJTextfieldForValidation(jTextFieldPercentileRespTimeSec, false);
registerJTextfieldForValidation(jTextFieldPercentileValue, false);
registerJTextfieldForValidation(jTextFieldCustomDuration, false);
+ initExtraComponents();
initFields();
}
+ private boolean isConfigured(String value) {
+ return value != null && !value.trim().isEmpty() && !value.equals("0");
+ }
+
public void configure(AutoStop testElement) {
- jTextFieldRespTime.setText(testElement.getResponseTime());
- jTextFieldRespTimeSec.setText(testElement.getResponseTimeSecs());
- jTextFieldError.setText(testElement.getErrorRate());
- jTextFieldErrorSec.setText(testElement.getErrorRateSecs());
- jTextFieldRespLatency.setText(testElement.getResponseLatency());
- jTextFieldRespLatencySec.setText(testElement.getResponseLatencySecs());
- jTextFieldPercentileRespTime.setText(testElement.getPercentileResponseTime());
- jTextFieldPercentileRespTimeSec.setText(testElement.getPercentileResponseTimeSecs());
- jTextFieldPercentileValue.setText(testElement.getPercentileValue());
- jTextFieldCustomDuration.setText(testElement.getCustomValidationDuration());
+ String respTime = testElement.getResponseTime();
+ String respTimeSec = testElement.getResponseTimeSecs();
+ jCheckBoxRespTime.setSelected(isConfigured(respTime));
+ jTextFieldRespTime.setText(respTime != null ? respTime : "");
+ jTextFieldRespTimeSec.setText(respTimeSec != null ? respTimeSec : "");
+
+ String latency = testElement.getResponseLatency();
+ String latencySec = testElement.getResponseLatencySecs();
+ jCheckBoxLatency.setSelected(isConfigured(latency));
+ jTextFieldRespLatency.setText(latency != null ? latency : "");
+ jTextFieldRespLatencySec.setText(latencySec != null ? latencySec : "");
+
+ String errorRate = testElement.getErrorRate();
+ String errorSec = testElement.getErrorRateSecs();
+ jCheckBoxError.setSelected(isConfigured(errorRate));
+ jTextFieldError.setText(errorRate != null ? errorRate : "");
+ jTextFieldErrorSec.setText(errorSec != null ? errorSec : "");
+
+ String percRespTime = testElement.getPercentileResponseTime();
+ String percRespTimeSec = testElement.getPercentileResponseTimeSecs();
+ String percValue = testElement.getPercentileValue();
+ jCheckBoxPercentile.setSelected(isConfigured(percRespTime));
+ jTextFieldPercentileRespTime.setText(percRespTime != null ? percRespTime : "");
+ jTextFieldPercentileRespTimeSec.setText(percRespTimeSec != null ? percRespTimeSec : "");
+ jTextFieldPercentileValue.setText(percValue != null ? percValue : "");
+
+ String relPerc = testElement.getRelPercentileValue();
+ String relWindow = testElement.getRelWindowSecs();
+ String relThresh = testElement.getRelThresholdPct();
+ jCheckBoxRelPercentile.setSelected(isConfigured(relPerc));
+ jTextFieldRelPercentileValue.setText(relPerc != null ? relPerc : "");
+ jTextFieldRelWindowSecs.setText(relWindow != null ? relWindow : "");
+ jTextFieldRelThresholdPct.setText(relThresh != null ? relThresh : "");
+
+ String errCount = testElement.getErrorCount();
+ String errCountSec = testElement.getErrorCountSecs();
+ jCheckBoxErrorCount.setSelected(isConfigured(errCount));
+ jTextFieldErrorCount.setText(errCount != null ? errCount : "");
+ jTextFieldErrorCountSec.setText(errCountSec != null ? errCountSec : "");
+
+ updateAllRowStates();
+ processBullets();
}
public void modifyTestElement(AutoStop testElement) {
- testElement.setResponseTime(jTextFieldRespTime.getText());
- testElement.setResponseTimeSecs(jTextFieldRespTimeSec.getText());
- testElement.setErrorRate(jTextFieldError.getText());
- testElement.setErrorRateSecs(jTextFieldErrorSec.getText());
- testElement.setResponseLatency(jTextFieldRespLatency.getText());
- testElement.setResponseLatencySecs(jTextFieldRespLatencySec.getText());
- testElement.setPercentileResponseTime(jTextFieldPercentileRespTime.getText());
- testElement.setPercentileResponseTimeSecs(jTextFieldPercentileRespTimeSec.getText());
- testElement.setPercentileValue(jTextFieldPercentileValue.getText());
- testElement.setCustomValidationDuration(jTextFieldCustomDuration.getText());
+ if (jCheckBoxRespTime.isSelected()) {
+ testElement.setResponseTime(jTextFieldRespTime.getText());
+ testElement.setResponseTimeSecs(jTextFieldRespTimeSec.getText());
+ } else {
+ testElement.setResponseTime("");
+ testElement.setResponseTimeSecs("");
+ }
+
+ if (jCheckBoxLatency.isSelected()) {
+ testElement.setResponseLatency(jTextFieldRespLatency.getText());
+ testElement.setResponseLatencySecs(jTextFieldRespLatencySec.getText());
+ } else {
+ testElement.setResponseLatency("");
+ testElement.setResponseLatencySecs("");
+ }
+
+ if (jCheckBoxError.isSelected()) {
+ testElement.setErrorRate(jTextFieldError.getText());
+ testElement.setErrorRateSecs(jTextFieldErrorSec.getText());
+ } else {
+ testElement.setErrorRate("");
+ testElement.setErrorRateSecs("");
+ }
+
+ if (jCheckBoxPercentile.isSelected()) {
+ testElement.setPercentileResponseTime(jTextFieldPercentileRespTime.getText());
+ testElement.setPercentileResponseTimeSecs(jTextFieldPercentileRespTimeSec.getText());
+ testElement.setPercentileValue(jTextFieldPercentileValue.getText());
+ } else {
+ testElement.setPercentileResponseTime("");
+ testElement.setPercentileResponseTimeSecs("");
+ testElement.setPercentileValue("");
+ }
+
+ if (jCheckBoxRelPercentile.isSelected()) {
+ testElement.setRelPercentileValue(jTextFieldRelPercentileValue.getText());
+ testElement.setRelWindowSecs(jTextFieldRelWindowSecs.getText());
+ testElement.setRelThresholdPct(jTextFieldRelThresholdPct.getText());
+ } else {
+ testElement.setRelPercentileValue("");
+ testElement.setRelWindowSecs("");
+ testElement.setRelThresholdPct("");
+ }
+
+ if (jCheckBoxErrorCount.isSelected()) {
+ testElement.setErrorCount(jTextFieldErrorCount.getText());
+ testElement.setErrorCountSecs(jTextFieldErrorCountSec.getText());
+ } else {
+ testElement.setErrorCount("");
+ testElement.setErrorCountSecs("");
+ }
}
public final void initFields() {
+ jCheckBoxRespTime.setSelected(true);
jTextFieldRespTime.setText("10000");
jTextFieldRespTimeSec.setText("10");
- jTextFieldError.setText("50");
- jTextFieldErrorSec.setText("10");
+
+ jCheckBoxLatency.setSelected(true);
jTextFieldRespLatency.setText("5000");
jTextFieldRespLatencySec.setText("10");
+
+ jCheckBoxError.setSelected(true);
+ jTextFieldError.setText("50");
+ jTextFieldErrorSec.setText("10");
+
+ jCheckBoxPercentile.setSelected(true);
jTextFieldPercentileRespTime.setText("15000");
jTextFieldPercentileRespTimeSec.setText("10");
jTextFieldPercentileValue.setText("90");
+
+ jCheckBoxRelPercentile.setSelected(false);
+ jTextFieldRelPercentileValue.setText("95");
+ jTextFieldRelWindowSecs.setText("30");
+ jTextFieldRelThresholdPct.setText("20");
+
+ jCheckBoxErrorCount.setSelected(false);
+ jTextFieldErrorCount.setText("10");
+ jTextFieldErrorCountSec.setText("10");
+
+ updateAllRowStates();
+ processBullets();
+ }
+
+ private void updateAllRowStates() {
+ updateRowState(jPanel1, jCheckBoxRespTime);
+ updateRowState(jPanel2, jCheckBoxLatency);
+ updateRowState(jPanel4, jCheckBoxError);
+ updateRowState(jPanel5, jCheckBoxPercentile);
+ if (jPanelRelPercentile != null && jCheckBoxRelPercentile != null) {
+ updateRowState(jPanelRelPercentile, jCheckBoxRelPercentile);
+ }
+ if (jPanelErrorCount != null && jCheckBoxErrorCount != null) {
+ updateRowState(jPanelErrorCount, jCheckBoxErrorCount);
+ }
+ }
+
+ private void updateRowState(JPanel panel, JCheckBox cb) {
+ boolean selected = cb.isSelected();
+ for (Component c : panel.getComponents()) {
+ if (c != cb) {
+ c.setEnabled(selected);
+ }
+ }
+ }
+
+ /** Adds checkboxes and new condition rows to the layout. */
+ private void initExtraComponents() {
+ GridBagLayout layout = (GridBagLayout) getLayout();
+
+ // Adjust spacing for existing panels
+ jPanel1.setLayout(new FlowLayout(FlowLayout.LEFT, 5, 2));
+ jPanel2.setLayout(new FlowLayout(FlowLayout.LEFT, 5, 2));
+ jPanel4.setLayout(new FlowLayout(FlowLayout.LEFT, 5, 2));
+ jPanel5.setLayout(new FlowLayout(FlowLayout.LEFT, 5, 2));
+
+ // Add checkboxes to existing rows
+ jCheckBoxRespTime = new JCheckBox();
+ jCheckBoxRespTime.setToolTipText("Enable Average Response Time condition");
+ jPanel1.add(jCheckBoxRespTime, 0);
+
+ jCheckBoxLatency = new JCheckBox();
+ jCheckBoxLatency.setToolTipText("Enable Average Latency condition");
+ jPanel2.add(jCheckBoxLatency, 0);
+
+ jCheckBoxError = new JCheckBox();
+ jCheckBoxError.setToolTipText("Enable Error Rate condition");
+ jPanel4.add(jCheckBoxError, 0);
+
+ jCheckBoxPercentile = new JCheckBox();
+ jCheckBoxPercentile.setToolTipText("Enable Percentile Response Time condition");
+ jPanel5.add(jCheckBoxPercentile, 0);
+
+ // Adjust OR label indentation
+ adjustOrLabelInsets(jLabel8);
+ adjustOrLabelInsets(jLabel9);
+ adjustOrLabelInsets(jLabel13);
+
+ // Remove unused placeholder components to eliminate extra blank rows between criteria
+ remove(jLabel20);
+ remove(jPanel6);
+ remove(jPanel3);
+
+ GridBagConstraints gbc;
+
+ // "OR" separator before relative percentile
+ JLabel orLabelRel = new JLabel("OR");
+ orLabelRel.setFont(new Font("Tahoma", 0, 10));
+ gbc = new GridBagConstraints();
+ gbc.gridx = 0; gbc.gridy = 8;
+ gbc.anchor = GridBagConstraints.WEST;
+ gbc.insets = new Insets(0, 32, 0, 0);
+ add(orLabelRel, gbc);
+
+ // Row: [✓] [•] P [95] th percentile grew more than [20] % in a window of [30] seconds
+ jPanelRelPercentile = new JPanel(new FlowLayout(FlowLayout.LEFT, 5, 2));
+
+ jCheckBoxRelPercentile = new JCheckBox();
+ jCheckBoxRelPercentile.setToolTipText("Enable Relative Percentile Degradation condition");
+ jPanelRelPercentile.add(jCheckBoxRelPercentile);
+
+ jLabelBulletRelPercentile = new JLabel();
+ jLabelBulletRelPercentile.setIcon(new javax.swing.ImageIcon(
+ getClass().getResource("/kg/apc/jmeter/reporters/bulletGreen.png")));
+ jPanelRelPercentile.add(jLabelBulletRelPercentile);
+
+ jPanelRelPercentile.add(new JLabel("P"));
+
+ jTextFieldRelPercentileValue = new JTextField(3);
+ jTextFieldRelPercentileValue.setHorizontalAlignment(JTextField.RIGHT);
+ jTextFieldRelPercentileValue.setToolTipText("Percentile rank, e.g. 95 for P95.");
+ jPanelRelPercentile.add(jTextFieldRelPercentileValue);
+
+ jPanelRelPercentile.add(new JLabel("th percentile grew more than"));
+
+ jTextFieldRelThresholdPct = new JTextField(4);
+ jTextFieldRelThresholdPct.setHorizontalAlignment(JTextField.RIGHT);
+ jTextFieldRelThresholdPct.setToolTipText("Max allowed growth % before stop, e.g. 20.");
+ jPanelRelPercentile.add(jTextFieldRelThresholdPct);
+
+ jPanelRelPercentile.add(new JLabel("% in a window of"));
+
+ jTextFieldRelWindowSecs = new JTextField(4);
+ jTextFieldRelWindowSecs.setHorizontalAlignment(JTextField.RIGHT);
+ jTextFieldRelWindowSecs.setToolTipText("Observation window in seconds. Pn is computed per window and compared to the previous one.");
+ jPanelRelPercentile.add(jTextFieldRelWindowSecs);
+
+ jPanelRelPercentile.add(new JLabel("seconds"));
+
+ gbc = new GridBagConstraints();
+ gbc.gridx = 0; gbc.gridy = 9;
+ gbc.anchor = GridBagConstraints.WEST;
+ add(jPanelRelPercentile, gbc);
+
+ // "OR" separator before error count
+ JLabel orLabelErrCount = new JLabel("OR");
+ orLabelErrCount.setFont(new Font("Tahoma", 0, 10));
+ gbc = new GridBagConstraints();
+ gbc.gridx = 0; gbc.gridy = 10;
+ gbc.anchor = GridBagConstraints.WEST;
+ gbc.insets = new Insets(0, 32, 0, 0);
+ add(orLabelErrCount, gbc);
+
+ // Row: [✓] [•] Error Count is greater than [10] for [10] seconds
+ jPanelErrorCount = new JPanel(new FlowLayout(FlowLayout.LEFT, 5, 2));
+
+ jCheckBoxErrorCount = new JCheckBox();
+ jCheckBoxErrorCount.setToolTipText("Enable Error Count condition");
+ jPanelErrorCount.add(jCheckBoxErrorCount);
+
+ jLabelBulletErrorCount = new JLabel();
+ jLabelBulletErrorCount.setIcon(new javax.swing.ImageIcon(
+ getClass().getResource("/kg/apc/jmeter/reporters/bulletGreen.png")));
+ jPanelErrorCount.add(jLabelBulletErrorCount);
+
+ jPanelErrorCount.add(new JLabel("Error Count is greater than"));
+
+ jTextFieldErrorCount = new JTextField(5);
+ jTextFieldErrorCount.setHorizontalAlignment(JTextField.RIGHT);
+ jTextFieldErrorCount.setToolTipText("Maximum allowed error count within the duration.");
+ jPanelErrorCount.add(jTextFieldErrorCount);
+
+ jPanelErrorCount.add(new JLabel("within"));
+
+ jTextFieldErrorCountSec = new JTextField(5);
+ jTextFieldErrorCountSec.setHorizontalAlignment(JTextField.RIGHT);
+ jTextFieldErrorCountSec.setToolTipText("Duration window in seconds.");
+ jPanelErrorCount.add(jTextFieldErrorCountSec);
+
+ jPanelErrorCount.add(new JLabel("seconds"));
+
+ gbc = new GridBagConstraints();
+ gbc.gridx = 0; gbc.gridy = 11;
+ gbc.anchor = GridBagConstraints.WEST;
+ add(jPanelErrorCount, gbc);
+
+ // Bottom filler to restore resize room
+ JPanel newFiller = new JPanel();
+ gbc = new GridBagConstraints();
+ gbc.gridx = 0; gbc.gridy = 12;
+ gbc.fill = GridBagConstraints.BOTH;
+ gbc.weightx = 1.0; gbc.weighty = 1.0;
+ add(newFiller, gbc);
+
+ // Attach item listeners to all checkboxes
+ ItemListener itemListener = e -> {
+ updateAllRowStates();
+ processBullets();
+ };
+ jCheckBoxRespTime.addItemListener(itemListener);
+ jCheckBoxLatency.addItemListener(itemListener);
+ jCheckBoxError.addItemListener(itemListener);
+ jCheckBoxPercentile.addItemListener(itemListener);
+ jCheckBoxRelPercentile.addItemListener(itemListener);
+ jCheckBoxErrorCount.addItemListener(itemListener);
+
+ // Register new fields for live validation colouring
+ registerJTextfieldForValidation(jTextFieldRelPercentileValue, false);
+ registerJTextfieldForValidation(jTextFieldRelWindowSecs, false);
+ registerJTextfieldForValidation(jTextFieldRelThresholdPct, false);
+ registerJTextfieldForValidation(jTextFieldErrorCount, false);
+ registerJTextfieldForValidation(jTextFieldErrorCountSec, false);
+ }
+
+ private void adjustOrLabelInsets(JLabel label) {
+ GridBagLayout layout = (GridBagLayout) getLayout();
+ GridBagConstraints gbc = layout.getConstraints(label);
+ gbc.insets = new Insets(0, 32, 0, 0);
+ layout.setConstraints(label, gbc);
}
private int getIntValue(JTextField tf) {
@@ -83,7 +377,7 @@ private float getFloatValue(JTextField tf) {
private boolean isVariableValue(JTextField tf) {
String value = tf.getText();
- if(value != null) {
+ if (value != null) {
return value.startsWith("${") && value.endsWith("}");
} else {
return false;
@@ -91,16 +385,24 @@ private boolean isVariableValue(JTextField tf) {
}
private void processBullets() {
- jLabelBulletError.setEnabled(getFloatValue(jTextFieldError) > 0 || isVariableValue(jTextFieldError));
- jLabelBulletRespTime.setEnabled(getIntValue(jTextFieldRespTime) > 0 || isVariableValue(jTextFieldRespTime));
- jLabelBulletLatency.setEnabled(getIntValue(jTextFieldRespLatency) > 0 || isVariableValue(jTextFieldRespLatency));
- jLabelBulletPercentile.setEnabled(getIntValue(jTextFieldPercentileRespTime) > 0 || isVariableValue(jTextFieldPercentileRespTime));
+ jLabelBulletRespTime.setEnabled(jCheckBoxRespTime != null && jCheckBoxRespTime.isSelected()
+ && (getIntValue(jTextFieldRespTime) > 0 || isVariableValue(jTextFieldRespTime)));
+ jLabelBulletLatency.setEnabled(jCheckBoxLatency != null && jCheckBoxLatency.isSelected()
+ && (getIntValue(jTextFieldRespLatency) > 0 || isVariableValue(jTextFieldRespLatency)));
+ jLabelBulletError.setEnabled(jCheckBoxError != null && jCheckBoxError.isSelected()
+ && (getFloatValue(jTextFieldError) > 0 || isVariableValue(jTextFieldError)));
+ jLabelBulletPercentile.setEnabled(jCheckBoxPercentile != null && jCheckBoxPercentile.isSelected()
+ && (getIntValue(jTextFieldPercentileRespTime) > 0 || isVariableValue(jTextFieldPercentileRespTime)));
+ jLabelBulletRelPercentile.setEnabled(jCheckBoxRelPercentile != null && jCheckBoxRelPercentile.isSelected()
+ && (getIntValue(jTextFieldRelPercentileValue) > 0 || isVariableValue(jTextFieldRelPercentileValue)));
+ jLabelBulletErrorCount.setEnabled(jCheckBoxErrorCount != null && jCheckBoxErrorCount.isSelected()
+ && (getIntValue(jTextFieldErrorCount) > 0 || isVariableValue(jTextFieldErrorCount)));
}
private void setJTextFieldColor(final JTextField tf, boolean isFloat) {
- if(!isFloat && (getIntValue(tf) > -1 || isVariableValue(tf))) {
+ if (!isFloat && (getIntValue(tf) > -1 || isVariableValue(tf))) {
tf.setForeground(Color.black);
- } else if(isFloat && (getFloatValue(tf) > -1 || isVariableValue(tf))) {
+ } else if (isFloat && (getFloatValue(tf) > -1 || isVariableValue(tf))) {
tf.setForeground(Color.black);
} else {
tf.setForeground(Color.red);
@@ -409,4 +711,24 @@ private void initComponents() {
private javax.swing.JTextField jTextFieldRespTimeSec;
// End of variables declaration//GEN-END:variables
+ // Checkboxes for enabling/disabling each condition
+ private javax.swing.JCheckBox jCheckBoxRespTime;
+ private javax.swing.JCheckBox jCheckBoxLatency;
+ private javax.swing.JCheckBox jCheckBoxError;
+ private javax.swing.JCheckBox jCheckBoxPercentile;
+ private javax.swing.JCheckBox jCheckBoxRelPercentile;
+ private javax.swing.JCheckBox jCheckBoxErrorCount;
+
+ // Extra fields for relative window-to-window percentile row
+ private javax.swing.JPanel jPanelRelPercentile;
+ private javax.swing.JLabel jLabelBulletRelPercentile;
+ private javax.swing.JTextField jTextFieldRelPercentileValue;
+ private javax.swing.JTextField jTextFieldRelWindowSecs;
+ private javax.swing.JTextField jTextFieldRelThresholdPct;
+
+ // Extra fields for per-window error count ceiling row
+ private javax.swing.JPanel jPanelErrorCount;
+ private javax.swing.JLabel jLabelBulletErrorCount;
+ private javax.swing.JTextField jTextFieldErrorCount;
+ private javax.swing.JTextField jTextFieldErrorCountSec;
}
diff --git a/plugins/autostop/src/test/java/kg/apc/jmeter/reporters/AutoStopTest.java b/plugins/autostop/src/test/java/kg/apc/jmeter/reporters/AutoStopTest.java
index d1c26b064..5b37706a3 100644
--- a/plugins/autostop/src/test/java/kg/apc/jmeter/reporters/AutoStopTest.java
+++ b/plugins/autostop/src/test/java/kg/apc/jmeter/reporters/AutoStopTest.java
@@ -7,6 +7,9 @@
import org.junit.Test;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
public class AutoStopTest {
@BeforeClass
@@ -245,4 +248,243 @@ public void testGetPercentileValue() {
String result = instance.getPercentileValue();
assertEquals(expResult, result);
}
+
+ // --- New: relative window-to-window percentile tests ---
+
+ /** Creates a SampleResult with the given elapsed time in ms. */
+ private static SampleResult resultWithTime(long elapsedMs) {
+ SampleResult r = new SampleResult();
+ r.sampleStart();
+ r.setEndTime(r.getStartTime() + elapsedMs);
+ r.setSuccessful(true);
+ return r;
+ }
+
+ @Test
+ public void testSetGetRelPercentileValue() {
+ System.out.println("setGetRelPercentileValue");
+ AutoStop instance = new AutoStop();
+ instance.setRelPercentileValue("95");
+ assertEquals("95", instance.getRelPercentileValue());
+ }
+
+ @Test
+ public void testSetGetRelWindowSecs() {
+ System.out.println("setGetRelWindowSecs");
+ AutoStop instance = new AutoStop();
+ instance.setRelWindowSecs("30");
+ assertEquals("30", instance.getRelWindowSecs());
+ }
+
+ @Test
+ public void testSetGetRelThresholdPct() {
+ System.out.println("setGetRelThresholdPct");
+ AutoStop instance = new AutoStop();
+ instance.setRelThresholdPct("20");
+ assertEquals("20", instance.getRelThresholdPct());
+ }
+
+ /** Feature is a no-op when percentile rank is not configured (P0 must not be compared). */
+ @Test
+ public void testRelativeWindowDisabledWhenNotConfigured() throws InterruptedException {
+ System.out.println("relativeWindowDisabledWhenNotConfigured");
+ System.clearProperty("auto_stopped");
+ AutoStop instance = new AutoStop();
+ // Rank blank, window and threshold set (Andrei's reproduction case)
+ instance.setRelWindowSecs("1");
+ instance.setRelThresholdPct("10");
+ instance.testStarted();
+ for (int i = 0; i < 5; i++) instance.sampleOccurred(new SampleEvent(resultWithTime(100), ""));
+ Thread.sleep(1100);
+ for (int i = 0; i < 5; i++) instance.sampleOccurred(new SampleEvent(resultWithTime(100), ""));
+ Thread.sleep(1100);
+ for (int i = 0; i < 5; i++) instance.sampleOccurred(new SampleEvent(resultWithTime(900), ""));
+ Thread.sleep(1100);
+ instance.sampleOccurred(new SampleEvent(resultWithTime(900), ""));
+
+ assertNull("Blank percentile rank must not trigger shutdown", System.getProperty("auto_stopped"));
+ }
+
+ /** Percentile breach across windows stops the test. */
+ @Test
+ public void testRelativeWindowBreachStopsTest() throws InterruptedException {
+ System.out.println("relativeWindowBreachStopsTest");
+ System.clearProperty("auto_stopped");
+ AutoStop instance = new AutoStop();
+ instance.setRelPercentileValue("90");
+ instance.setRelWindowSecs("1");
+ instance.setRelThresholdPct("50");
+ instance.testStarted();
+
+ SampleEvent fastEvent = new SampleEvent(resultWithTime(100), "");
+ for (int i = 0; i < 10; i++) { instance.sampleOccurred(fastEvent); }
+
+ Thread.sleep(1100);
+
+ SampleEvent slowEvent = new SampleEvent(resultWithTime(500), "");
+ for (int i = 0; i < 10; i++) { instance.sampleOccurred(slowEvent); }
+
+ Thread.sleep(1100);
+ instance.sampleOccurred(slowEvent);
+
+ assertEquals("true", System.getProperty("auto_stopped"));
+ }
+
+ /** Stable latency across windows must not trigger stop. */
+ @Test
+ public void testRelativeWindowNoTriggerWhenStable() throws InterruptedException {
+ System.out.println("relativeWindowNoTriggerWhenStable");
+ System.clearProperty("auto_stopped");
+ AutoStop instance = new AutoStop();
+ instance.setRelPercentileValue("90");
+ instance.setRelWindowSecs("1");
+ instance.setRelThresholdPct("50");
+ instance.testStarted();
+
+ SampleEvent event = new SampleEvent(resultWithTime(100), "");
+ for (int i = 0; i < 10; i++) { instance.sampleOccurred(event); }
+ Thread.sleep(1100);
+ for (int i = 0; i < 10; i++) { instance.sampleOccurred(event); }
+ Thread.sleep(1100);
+ instance.sampleOccurred(event);
+
+ assertNull("Stable latency should not stop test", System.getProperty("auto_stopped"));
+ }
+
+ /** Verifies that concurrent sampleOccurred calls do not throw exceptions or corrupt state. */
+ @Test
+ public void testConcurrentSampleOccurredThreadSafety() throws Exception {
+ System.out.println("concurrentSampleOccurredThreadSafety");
+ System.clearProperty("auto_stopped");
+ final AutoStop instance = new AutoStop();
+ instance.setRelPercentileValue("90");
+ instance.setRelWindowSecs("10");
+ instance.setRelThresholdPct("50");
+ instance.setErrorCount("100000");
+ instance.setErrorCountSecs("10");
+ instance.testStarted();
+
+ int numThreads = 8;
+ final int samplesPerThread = 500;
+ Thread[] threads = new Thread[numThreads];
+ final java.util.concurrent.atomic.AtomicBoolean hasError = new java.util.concurrent.atomic.AtomicBoolean(false);
+
+ for (int t = 0; t < numThreads; t++) {
+ final int threadId = t;
+ threads[t] = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ for (int i = 0; i < samplesPerThread; i++) {
+ boolean fail = (i % 5 == 0);
+ SampleResult r = resultWithTime(50 + (i % 100));
+ r.setSuccessful(!fail);
+ instance.sampleOccurred(new SampleEvent(r, "Thread " + threadId));
+ }
+ } catch (Throwable ex) {
+ ex.printStackTrace();
+ hasError.set(true);
+ }
+ }
+ });
+ threads[t].start();
+ }
+
+ for (Thread t : threads) {
+ t.join();
+ }
+
+ assertFalse("Concurrent samples must not throw exceptions or corrupt state", hasError.get());
+ }
+
+ // --- New: per-window error count ceiling tests ---
+
+ /** Creates a failed SampleResult. */
+ private static SampleResult resultFailed() {
+ SampleResult r = new SampleResult();
+ r.sampleStart();
+ r.setEndTime(r.getStartTime() + 50);
+ r.setSuccessful(false);
+ return r;
+ }
+
+ @Test
+ public void testSetGetErrorCount() {
+ System.out.println("setGetErrorCount");
+ AutoStop instance = new AutoStop();
+ instance.setErrorCount("15");
+ assertEquals("15", instance.getErrorCount());
+ }
+
+ @Test
+ public void testSetGetErrorCountSecs() {
+ System.out.println("setGetErrorCountSecs");
+ AutoStop instance = new AutoStop();
+ instance.setErrorCountSecs("10");
+ assertEquals("10", instance.getErrorCountSecs());
+ }
+
+ /** Feature is disabled when error count is not configured. */
+ @Test
+ public void testErrorCountDisabledWhenNotConfigured() throws InterruptedException {
+ System.out.println("errorCountDisabledWhenNotConfigured");
+ System.clearProperty("auto_stopped");
+ AutoStop instance = new AutoStop();
+ instance.setErrorCountSecs("5");
+ instance.testStarted();
+ SampleEvent event = new SampleEvent(resultFailed(), "");
+ for (int i = 0; i < 10; i++) {
+ instance.sampleOccurred(event);
+ }
+ Thread.sleep(1100);
+ instance.sampleOccurred(event);
+
+ assertNull("Unconfigured error count must not stop test", System.getProperty("auto_stopped"));
+ }
+
+ /** Error count exceeding threshold stops test. */
+ @Test
+ public void testErrorCountExceededStopsTest() throws InterruptedException {
+ System.out.println("errorCountExceededStopsTest");
+ System.clearProperty("auto_stopped");
+ AutoStop instance = new AutoStop();
+ instance.setErrorCount("3");
+ instance.setErrorCountSecs("10");
+ instance.testStarted();
+
+ SampleEvent event = new SampleEvent(resultFailed(), "");
+ for (int i = 0; i < 5; i++) {
+ instance.sampleOccurred(event);
+ }
+ Thread.sleep(1100);
+ instance.sampleOccurred(event);
+
+ assertEquals("true", System.getProperty("auto_stopped"));
+ }
+
+ /** Error count within limit does not stop test across window boundary. */
+ @Test
+ public void testErrorCountBelowThresholdDoesNotStop() throws InterruptedException {
+ System.out.println("errorCountBelowThresholdDoesNotStop");
+ System.clearProperty("auto_stopped");
+ AutoStop instance = new AutoStop();
+ instance.setErrorCount("5");
+ instance.setErrorCountSecs("1");
+ instance.testStarted();
+
+ SampleEvent event = new SampleEvent(resultFailed(), "");
+ // 2 errors in window 1 (< 5)
+ for (int i = 0; i < 2; i++) {
+ instance.sampleOccurred(event);
+ }
+ Thread.sleep(1100);
+ // 2 errors in window 2 (< 5)
+ for (int i = 0; i < 2; i++) {
+ instance.sampleOccurred(event);
+ }
+ Thread.sleep(1100);
+ instance.sampleOccurred(new SampleEvent(resultWithTime(10), ""));
+
+ assertNull("Errors below limit should not stop test", System.getProperty("auto_stopped"));
+ }
}
diff --git a/plugins/autostop/src/test/java/kg/apc/jmeter/reporters/JAutoStopPanelTest.java b/plugins/autostop/src/test/java/kg/apc/jmeter/reporters/JAutoStopPanelTest.java
index c64a8c7cd..7de5a30a8 100644
--- a/plugins/autostop/src/test/java/kg/apc/jmeter/reporters/JAutoStopPanelTest.java
+++ b/plugins/autostop/src/test/java/kg/apc/jmeter/reporters/JAutoStopPanelTest.java
@@ -64,4 +64,20 @@ public void testInitFields()
instance.initFields();
}
+ @Test
+ public void testConfigureUnsetFieldsRoundTrip() {
+ System.out.println("configureUnsetFieldsRoundTrip");
+ AutoStop testElement = new AutoStop();
+ JAutoStopPanel instance = new JAutoStopPanel();
+ instance.configure(testElement);
+ AutoStop outputElement = new AutoStop();
+ instance.modifyTestElement(outputElement);
+
+ org.junit.Assert.assertEquals("", outputElement.getResponseTime());
+ org.junit.Assert.assertEquals("", outputElement.getResponseLatency());
+ org.junit.Assert.assertEquals("", outputElement.getErrorRate());
+ org.junit.Assert.assertEquals("", outputElement.getPercentileResponseTime());
+ org.junit.Assert.assertEquals("", outputElement.getRelPercentileValue());
+ org.junit.Assert.assertEquals("", outputElement.getErrorCount());
+ }
}
\ No newline at end of file
diff --git a/site/dat/wiki/AutoStop.md b/site/dat/wiki/AutoStop.md
index d3c57db16..7b3d8e8fc 100644
--- a/site/dat/wiki/AutoStop.md
+++ b/site/dat/wiki/AutoStop.md
@@ -3,7 +3,7 @@
[ Download](/?search=jpgc-autostop)
AutoStop used when you want to stop test on some runtime criteria.
-Currently 3 criteria available: average response time, average latency and error rate.
+Currently 6 criteria available: average response time, average latency, error rate, response time percentile, relative window percentile degradation and error count.
This criteria are used in OR logic, the component will ask JMeter to stop test
if one of the criteria has been met.
@@ -27,7 +27,27 @@ Error rate specified in percent. Rate can be float number.
Test will be stopped only if specified error rate exceeded for *sequentially* N seconds.
To disable auto-stop on rate criteria, just set error rate to zero.
+## AutoStop on Percentile Response Time
+
+Percentile rank and threshold specified in milliseconds.
+Test will be stopped only if specified percentile response time exceeded for *sequentially* N seconds.
+
+## AutoStop on Relative Window Percentile Degradation
+
+Compares a response time percentile across two adjacent tumbling windows.
+Test will be stopped if the current window's percentile grew by more than the specified percentage compared to the previous window.
+
+## AutoStop on Error Count
+
+Error count limit within a fixed time window (tumbling window, not sustained).
+Test will be stopped if the specified error count is exceeded *within* N seconds.
+
## Examples
[Example AutoStop on Response Time JMX](/editor/?utm_source=jpgc&utm_medium=openurl&utm_campaign=examples#/img/examples/AutoStopExample_Time.jmx)
[Example AutoStop on Error Rate JMX](/editor/?utm_source=jpgc&utm_medium=openurl&utm_campaign=examples#/img/examples/AutoStopExample_ErrRate.jmx)
+
+[Example AutoStop on Percentile JMX](/editor/?utm_source=jpgc&utm_medium=openurl&utm_campaign=examples#/img/examples/AutoStopExample_Percentile.jmx)
+
+[Example AutoStop on Error Count JMX](/editor/?utm_source=jpgc&utm_medium=openurl&utm_campaign=examples#/img/examples/AutoStopExample_ErrCount.jmx)
+