Skip to content

Commit

Permalink
Version 1.0 date 2024-09-27, First version.
Browse files Browse the repository at this point in the history
  • Loading branch information
DABURON Vincent committed Sep 27, 2024
1 parent 85a8ba2 commit 2d3bf3d
Show file tree
Hide file tree
Showing 17 changed files with 969 additions and 1 deletion.
119 changes: 118 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,119 @@
<p align="center">
<img src="https://github.com/vdaburon/pacing-jmeter-plugin/blob/main/doc/pacing_logo.png" alt="pacing jmeter plugin logo"/>
<p align="center">Apache JMeter Plugin to compute a pacing since thread start iteration or a variable contains start time</p>
<p align="center"><a href="https://github.com/vdaburon/pacing-jmeter-plugin">Link to github project pacing-jmeter-plugin</a></p>
</p>


# pacing-jmeter-plugin
Add notion of Pacing for JMeter
Add notion of Pacing for Apache JMeter.

## What is the Pacing in load testing ?
The Pacing in load testing is the **minimum time** before iterate.<br>
The Pacing is fixe but the waiting time to complete the pacing time is dynamic.<br>

### Avantages of using Pacing
* The pacing is useful for performance testing when modeling for a user at what rate (cadence) he will perform business actions.
* With a pacing of 3 min, one user will perform 20 iterations per hour and 5 vusers 100 iterations per hour.<br>
* Pacing allows for fixed rates (cadence) to be maintained despite reasonable deteriorations in call response times.
* Pacing makes easier to model cadences for load increases with several steps (eg: 50%, 100%, 150%)


![Schema Pacing](doc/images/schema_pacing.png)

Examples:
* The sum of time durations for multi http requests is 6 sec and i set the pacing to 10 sec. The dynamic waiting time will be 4 sec to complete the pacing time (10 - 6 = 4).
* The sum of time durations for multi http requests is 8 sec and i set the pacing to 10 sec. The dynamic waiting time will be 2 sec to complete the pacing time (10 - 8 = 2).
* The sum of time durations for multi http requests is 12 sec and i set the pacing to 10 sec. The dynamic waiting time will be 0 sec because the minimum time has been exceeded.

## Pacing Sampler in JMeter GUI

The pacing plugin add 2 new Samplers
1. Pacing Start to save start time ms in a variable with System.currentTimeMillis()
2. Pacing Pause to get the start time from the previous variable and compute the dynamic waiting time to complete the Pacing Duration

![JMeter GUI add Pacing Samplers](doc/images/gui_add_sampler_pacing.png)

### 1) Pacing Start Sampler
Declare a variable to save the start time (Variable value = System.currentTimeMillis())

![Pacing Start Sampler](doc/images/sampler_pacing_start.png)


### 2) Pacing Pause Sampler
Use the variable that contains the start time and compute the dynamic pause (waiting time) to complete the Pacing Duration. Pause = Pacing Duration - (Current Time - Start Time)<br>
Pause the thread for "Pause Computed" ms

![Pacing Start Pause](doc/images/sampler_pacing_pause.png)

## Demo a script with multi pacing samplers
A JMeter script contains 2 threads groups (Test Plan set "Run Thread Groups consecutively" to better understand the result).
1. The first thread group declare a single Pacing Start and a single Pacing Pause (5 sec).
2. The second thread group declare a Pacing Start at the begin of thread iteration and a Pacing Pause at the end of the script (60 sec) and a Pacing Start at the begin of a Loop and a Pacing Pause at the end of the Loop (10 sec).

To simulate variable duration of Sampler we use "jp@gc - Dummy Sampler" with sleep Random.

![Dummy Sampler Simulate Random Response Time](doc/images/dummy_sampler_random_response_time.png)

Configuration for the first Pacing Pause. Use the variable V_BEGIN declare in the Pacing Start and the Pacing Pause Duration (5000 ms or 5 sec)
![Script First Pacing Pause](doc/images/script_with_pacing_pause.png)

The View Results in Table to see sample time

![Script and results in View Table Results](doc/images/view_pacing_in_view_results_table.png)

In the View Results in Table, you see the Pacing to 5 sec (5000 ms) for the first thread group. The Pacing to 10 sec (10000 ms) for the Pacing in the Loop and Pacing 60 sec (60000 ms) for the thread iteration in the second Thread Group.

## RIP - Random Intervals Pacing
If you want to add some randomize (1sec to 3sec) to the Pacing Duration (5sec). You could add somme Random ms to the Duration like:
<pre>
${__groovy(5000+org.apache.commons.lang3.RandomUtils.nextInt(1000\,3000))}
</pre>

![Pacing Pause RIP](doc/images/sampler_pacing_pause_rip.png)

If the Pacing duration is declared in a variable (e.g:V_PACING_ITER), you could call this variable and add random ms like:
<pre>
${__groovy(Long.parseLong(vars.get("V_PACING_ITER"))+org.apache.commons.lang3.RandomUtils.nextInt(1000\,3000))}
</pre>

## Plugin installed with jmeter-plugins-manager
This plugin could be installed with the jmeter-plugins-manager from jmeter.plugins.org.<br>
The plugin name is : "vdn@github - pacing-jmeter-plugin"

The default variable name could be set with property <code>pacing.default_variable_name</code> usually in user.properties or jmeter.properties.

E.g:
<pre>
pacing.default_variable_name=V_START_ITERATION
</pre>

The default pacing duration could be set with property <code>pacing.default_duration_ms</code> in user.properties or jmeter.properties.

E.g:
<pre>
pacing.default_duration_ms=60000
</pre>

## Usage Maven
The maven groupId, artifactId and version, this plugin is in the **Maven Central Repository** [![Maven Central pacing-jmeter-plugin](https://maven-badges.herokuapp.com/maven-central/io.github.vdaburon/pacing-jmeter-plugin/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.github.vdaburon/pacing-jmeter-plugin)

```xml
<groupId>io.github.vdaburon</groupId>
<artifactId>pacing-jmeter-plugin</artifactId>
<version>1.0</version>
```


## Limitation
The main limitation of this Pacing Plugin is the Sampler Pause could be not call because an error occurred and the Thread Group is configured with "Start Next Thread Loop" on error.

There is no simple solution to guarantee that Pacing Pause will be called in all error cases.

This notion of Pacing would have to be declared at the Thread Group level or there would have to be a way to intercept an error to direct it to a dedicated piece of code.

## License
Licensed under the Apache License, Version 2.0

## versions
Version 1.0 date 2024-09-27, First version.
Binary file added doc/images/dummy_sampler_random_response_time.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/images/gui_add_sampler_pacing.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/images/sampler_pacing_pause.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/images/sampler_pacing_pause_rip.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/images/sampler_pacing_start.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/images/schema_pacing.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/images/script_with_pacing_pause.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/images/view_pacing_in_view_results_table.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/pacing_logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
165 changes: 165 additions & 0 deletions jmeter_script_demo/pacing_demo_2_thread_groups.jmx
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
<?xml version="1.0" encoding="UTF-8"?>
<jmeterTestPlan version="1.2" properties="5.0" jmeter="5.5">
<hashTree>
<TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Test Plan" enabled="true">
<stringProp name="TestPlan.comments"></stringProp>
<boolProp name="TestPlan.functional_mode">false</boolProp>
<boolProp name="TestPlan.tearDown_on_shutdown">true</boolProp>
<boolProp name="TestPlan.serialize_threadgroups">true</boolProp>
<elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<stringProp name="TestPlan.user_define_classpath"></stringProp>
</TestPlan>
<hashTree>
<ResultCollector guiclass="TableVisualizer" testclass="ResultCollector" testname="View Results in Table" enabled="true">
<boolProp name="ResultCollector.error_logging">false</boolProp>
<objProp>
<name>saveConfig</name>
<value class="SampleSaveConfiguration">
<time>true</time>
<latency>true</latency>
<timestamp>true</timestamp>
<success>true</success>
<label>true</label>
<code>true</code>
<message>true</message>
<threadName>true</threadName>
<dataType>true</dataType>
<encoding>false</encoding>
<assertions>true</assertions>
<subresults>true</subresults>
<responseData>false</responseData>
<samplerData>false</samplerData>
<xml>false</xml>
<fieldNames>true</fieldNames>
<responseHeaders>false</responseHeaders>
<requestHeaders>false</requestHeaders>
<responseDataOnError>false</responseDataOnError>
<saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>
<assertionsResultsToSave>0</assertionsResultsToSave>
<bytes>true</bytes>
<sentBytes>true</sentBytes>
<url>true</url>
<hostname>true</hostname>
<threadCounts>true</threadCounts>
<idleTime>true</idleTime>
<connectTime>true</connectTime>
</value>
</objProp>
<stringProp name="filename"></stringProp>
</ResultCollector>
<hashTree/>
<ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Thread Group simple pacing" enabled="true">
<stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
<elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true">
<boolProp name="LoopController.continue_forever">false</boolProp>
<stringProp name="LoopController.loops">3</stringProp>
</elementProp>
<stringProp name="ThreadGroup.num_threads">2</stringProp>
<stringProp name="ThreadGroup.ramp_time">1</stringProp>
<boolProp name="ThreadGroup.scheduler">false</boolProp>
<stringProp name="ThreadGroup.duration"></stringProp>
<stringProp name="ThreadGroup.delay"></stringProp>
<boolProp name="ThreadGroup.same_user_on_next_iteration">true</boolProp>
</ThreadGroup>
<hashTree>
<TransactionController guiclass="TransactionControllerGui" testclass="TransactionController" testname="Transaction Controller 1 Include duration and Pacing" enabled="true">
<boolProp name="TransactionController.parent">false</boolProp>
</TransactionController>
<hashTree>
<io.github.vdaburon.jmeterplugins.pacing.PacingStart guiclass="io.github.vdaburon.jmeterplugins.pacing.gui.PacingStartGui" testclass="io.github.vdaburon.jmeterplugins.pacing.PacingStart" testname="Pacing Start out V_BEGIN" enabled="true">
<stringProp name="PacingStart.variableName">V_BEGIN</stringProp>
</io.github.vdaburon.jmeterplugins.pacing.PacingStart>
<hashTree/>
<kg.apc.jmeter.samplers.DummySampler guiclass="kg.apc.jmeter.samplers.DummySamplerGui" testclass="kg.apc.jmeter.samplers.DummySampler" testname="jp@gc - Dummy Sampler wait Random(1000,4000) ms" enabled="true">
<boolProp name="WAITING">true</boolProp>
<boolProp name="SUCCESFULL">true</boolProp>
<stringProp name="RESPONSE_CODE">200</stringProp>
<stringProp name="RESPONSE_MESSAGE">OK</stringProp>
<stringProp name="REQUEST_DATA">Dummy Sampler used to simulate requests and responses
without actual network activity. This helps debugging tests.</stringProp>
<stringProp name="RESPONSE_DATA">Dummy Sampler used to simulate requests and responses
without actual network activity. This helps debugging tests.</stringProp>
<stringProp name="RESPONSE_TIME">${__Random(1000,4000)}</stringProp>
<stringProp name="LATENCY">${__Random(1,50)}</stringProp>
<stringProp name="CONNECT">${__Random(1,5)}</stringProp>
<stringProp name="URL"></stringProp>
<stringProp name="RESULT_CLASS">org.apache.jmeter.samplers.SampleResult</stringProp>
</kg.apc.jmeter.samplers.DummySampler>
<hashTree/>
<io.github.vdaburon.jmeterplugins.pacing.PacingPause guiclass="io.github.vdaburon.jmeterplugins.pacing.gui.PacingPauseGui" testclass="io.github.vdaburon.jmeterplugins.pacing.PacingPause" testname="Pacing Pause in V_BEGIN pacing 5000 ms" enabled="true">
<stringProp name="PacingPause.variableName">V_BEGIN</stringProp>
<stringProp name="PacingPause.duration">5000</stringProp>
</io.github.vdaburon.jmeterplugins.pacing.PacingPause>
<hashTree/>
</hashTree>
</hashTree>
<ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Thread Group pacing in Loop" enabled="true">
<stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
<elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true">
<boolProp name="LoopController.continue_forever">false</boolProp>
<stringProp name="LoopController.loops">2</stringProp>
</elementProp>
<stringProp name="ThreadGroup.num_threads">1</stringProp>
<stringProp name="ThreadGroup.ramp_time">1</stringProp>
<boolProp name="ThreadGroup.scheduler">false</boolProp>
<stringProp name="ThreadGroup.duration"></stringProp>
<stringProp name="ThreadGroup.delay"></stringProp>
<boolProp name="ThreadGroup.same_user_on_next_iteration">false</boolProp>
</ThreadGroup>
<hashTree>
<TransactionController guiclass="TransactionControllerGui" testclass="TransactionController" testname="Transaction Controller 2 Include duration and Pacing" enabled="true">
<boolProp name="TransactionController.parent">false</boolProp>
</TransactionController>
<hashTree>
<io.github.vdaburon.jmeterplugins.pacing.PacingStart guiclass="io.github.vdaburon.jmeterplugins.pacing.gui.PacingStartGui" testclass="io.github.vdaburon.jmeterplugins.pacing.PacingStart" testname="Pacing Start out V_START_ITER" enabled="true">
<stringProp name="PacingStart.variableName">V_START_ITER</stringProp>
</io.github.vdaburon.jmeterplugins.pacing.PacingStart>
<hashTree/>
<LoopController guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller x 3" enabled="true">
<boolProp name="LoopController.continue_forever">true</boolProp>
<stringProp name="LoopController.loops">3</stringProp>
</LoopController>
<hashTree>
<TransactionController guiclass="TransactionControllerGui" testclass="TransactionController" testname="Transaction Controller 3 Loop include duration and pacing" enabled="true">
<boolProp name="TransactionController.parent">false</boolProp>
</TransactionController>
<hashTree>
<io.github.vdaburon.jmeterplugins.pacing.PacingStart guiclass="io.github.vdaburon.jmeterplugins.pacing.gui.PacingStartGui" testclass="io.github.vdaburon.jmeterplugins.pacing.PacingStart" testname="Pacing Start out V_START_WHILE" enabled="true">
<stringProp name="PacingStart.variableName">V_START_WHILE</stringProp>
</io.github.vdaburon.jmeterplugins.pacing.PacingStart>
<hashTree/>
<kg.apc.jmeter.samplers.DummySampler guiclass="kg.apc.jmeter.samplers.DummySamplerGui" testclass="kg.apc.jmeter.samplers.DummySampler" testname="jp@gc - Dummy Sampler wait Random(2000,6000) ms" enabled="true">
<boolProp name="WAITING">true</boolProp>
<boolProp name="SUCCESFULL">true</boolProp>
<stringProp name="RESPONSE_CODE">200</stringProp>
<stringProp name="RESPONSE_MESSAGE">OK</stringProp>
<stringProp name="REQUEST_DATA">Dummy Sampler used to simulate requests and responses
without actual network activity. This helps debugging tests.</stringProp>
<stringProp name="RESPONSE_DATA">Dummy Sampler used to simulate requests and responses
without actual network activity. This helps debugging tests.</stringProp>
<stringProp name="RESPONSE_TIME">${__Random(2000,6000)}</stringProp>
<stringProp name="LATENCY">${__Random(1,50)}</stringProp>
<stringProp name="CONNECT">${__Random(1,5)}</stringProp>
<stringProp name="URL"></stringProp>
<stringProp name="RESULT_CLASS">org.apache.jmeter.samplers.SampleResult</stringProp>
</kg.apc.jmeter.samplers.DummySampler>
<hashTree/>
<io.github.vdaburon.jmeterplugins.pacing.PacingPause guiclass="io.github.vdaburon.jmeterplugins.pacing.gui.PacingPauseGui" testclass="io.github.vdaburon.jmeterplugins.pacing.PacingPause" testname="Pacing Pause in V_START_WHILE pacing 10000 (10 sec)" enabled="true">
<stringProp name="PacingPause.variableName">V_START_WHILE</stringProp>
<stringProp name="PacingPause.duration">10000</stringProp>
</io.github.vdaburon.jmeterplugins.pacing.PacingPause>
<hashTree/>
</hashTree>
</hashTree>
<io.github.vdaburon.jmeterplugins.pacing.PacingPause guiclass="io.github.vdaburon.jmeterplugins.pacing.gui.PacingPauseGui" testclass="io.github.vdaburon.jmeterplugins.pacing.PacingPause" testname="Pacing Pause in V_START_ITER pacing 60000 ms (60 sec)" enabled="true">
<stringProp name="PacingPause.variableName">V_START_ITER</stringProp>
<stringProp name="PacingPause.duration">60000</stringProp>
</io.github.vdaburon.jmeterplugins.pacing.PacingPause>
<hashTree/>
</hashTree>
</hashTree>
</hashTree>
</hashTree>
</jmeterTestPlan>
Loading

0 comments on commit 2d3bf3d

Please sign in to comment.