New posts
This commit is contained in:
@@ -8,6 +8,7 @@ Runnable companion code for the testing posts on [ankurm.com](https://ankurm.com
|
||||
| [`junit-6-migration/`](junit-6-migration) | JUnit 5 to 6 Migration: What Was Removed, What Changed and How to Upgrade | JUnit 5.14.4 and 6.1.3, JDK 25 |
|
||||
| [`assertj/`](assertj) | AssertJ Complete Guide: Soft Assertions, Recursive Comparison and Custom Assertions | AssertJ 3.27.7, JUnit 6.1.3, JDK 25 |
|
||||
| [`instancio/`](instancio) | Instancio: Generate Realistic Test Data Automatically in Java | Instancio 6.1.0, JUnit 6.1.3, JDK 25 |
|
||||
| [`gatling-spring-boot/`](gatling-spring-boot) | Load Testing Spring Boot with Gatling's Java DSL (and How It Compares to k6) | Gatling 3.16.0, Spring Boot 4.1.2, k6 v2.3.0, JDK 25 |
|
||||
|
||||
Every module keeps the real output of its tests in `output/`. Each test writes the transcript and asserts the same facts, so the numbers in the posts fail the build if they stop being true.
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
# gatling-spring-boot
|
||||
|
||||
Companion code for the ankurm.com post *Load Testing Spring Boot with Gatling's Java DSL (and How It Compares to k6)*.
|
||||
|
||||
A tiny Spring Boot app whose `/api/quote/{symbol}` endpoint calls a simulated slow dependency with limited capacity
|
||||
(8 workers x 50 ms = about 160 requests/s, queue of 100, then HTTP 503), plus Gatling Java DSL simulations that
|
||||
find the knee, and the same test written for k6.
|
||||
|
||||
| Tool | Version | Source |
|
||||
|---|---|---|
|
||||
| Spring Boot | 4.1.2 | `maven-metadata.xml` (latest GA; 4.2.0-RC2 is not GA) |
|
||||
| Gatling (`gatling-charts-highcharts`) | 3.16.0 | `maven-metadata.xml`; the report itself says released 2026-09-29 |
|
||||
| `gatling-maven-plugin` | 4.21.12 | `maven-metadata.xml` |
|
||||
| k6 | v2.3.0 | `dl.k6.io` deb package, unpacked without installing |
|
||||
| JDK | 25 | |
|
||||
|
||||
## Run
|
||||
|
||||
```bash
|
||||
./scripts/run-all.sh # about 6 minutes; regenerates everything in output/
|
||||
K6=/path/to/k6 ./scripts/run-all.sh # include the k6 steps (skipped when k6 is not found)
|
||||
```
|
||||
|
||||
Load numbers depend on the machine: treat them as indicative. Single runs: `mvn test-compile gatling:test -Dgatling.simulationClass=lab.gatling.KneeStepsSimulation`
|
||||
(start the app first: `mvn package -DskipTests && java -jar target/gatling-spring-boot-lab-1.0.0.jar`).
|
||||
|
||||
## Files
|
||||
|
||||
- `src/main/java/lab/gatling/` the app: `DownstreamPool` (the bounded pool), `QuoteController`
|
||||
- `src/test/java/lab/gatling/` simulations `BasicSimulation`, `KneeStepsSimulation`, `KneeRampSimulation`, `ClosedModelSimulation`, `FailingAssertionSimulation`, and `SaturationTest` (JUnit)
|
||||
- `k6/knee.js`, `k6/threshold-fail.js` the k6 equivalents
|
||||
- `scripts/run-all.sh`, `scripts/analyze.py` run everything, turn reports into tables and the chart
|
||||
- `output/` captured real output of the last run
|
||||
@@ -0,0 +1,49 @@
|
||||
// k6 equivalent of KneeStepsSimulation: open model, 30 -> 240 requests/s in 8 steps,
|
||||
// each held 6 s and separated by 2 s ramps. Every request is tagged with the step it was sent in.
|
||||
import http from 'k6/http';
|
||||
import exec from 'k6/execution';
|
||||
import { check } from 'k6';
|
||||
import { SharedArray } from 'k6/data';
|
||||
|
||||
const BASE = __ENV.APP_URL || 'http://localhost:18300';
|
||||
const STEPS = 8, RATE_STEP = 30, HOLD_S = 6, RAMP_S = 2;
|
||||
|
||||
// the feeder: same CSV Gatling reads
|
||||
const symbols = new SharedArray('symbols', () =>
|
||||
open('../src/test/resources/data/symbols.csv').trim().split('\n').slice(1));
|
||||
|
||||
// k6 treats only these statuses as success, so a 503 counts in http_req_failed
|
||||
http.setResponseCallback(http.expectedStatuses(200));
|
||||
|
||||
const stages = [];
|
||||
for (let i = 0; i < STEPS; i++) {
|
||||
if (i > 0) stages.push({ duration: `${RAMP_S}s`, target: RATE_STEP * (i + 1) });
|
||||
stages.push({ duration: `${HOLD_S}s`, target: RATE_STEP * (i + 1) });
|
||||
}
|
||||
|
||||
export const options = {
|
||||
scenarios: {
|
||||
knee: {
|
||||
executor: 'ramping-arrival-rate', // open model: iterations start on a schedule
|
||||
startRate: RATE_STEP,
|
||||
timeUnit: '1s',
|
||||
preAllocatedVUs: 200,
|
||||
maxVUs: 1000,
|
||||
stages,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
function stepLabel() {
|
||||
const elapsed = Date.now() - exec.scenario.startTime;
|
||||
const slot = Math.floor(elapsed / ((HOLD_S + RAMP_S) * 1000));
|
||||
const into = elapsed % ((HOLD_S + RAMP_S) * 1000);
|
||||
if (into >= HOLD_S * 1000 || slot >= STEPS) return 'ramp';
|
||||
return `rate ${String(RATE_STEP * (slot + 1)).padStart(3, '0')}/s`;
|
||||
}
|
||||
|
||||
export default function () {
|
||||
const symbol = symbols[Math.floor(Math.random() * symbols.length)];
|
||||
const res = http.get(`${BASE}/api/quote/${symbol}`, { tags: { step: stepLabel() } });
|
||||
check(res, { 'status is 200': (r) => r.status === 200 });
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// k6 equivalent of FailingAssertionSimulation: 220 requests/s against ~160/s of capacity,
|
||||
// with thresholds that cannot hold. k6 exits with code 99 when a threshold fails.
|
||||
import http from 'k6/http';
|
||||
import { SharedArray } from 'k6/data';
|
||||
|
||||
const BASE = __ENV.APP_URL || 'http://localhost:18300';
|
||||
const symbols = new SharedArray('symbols', () =>
|
||||
open('../src/test/resources/data/symbols.csv').trim().split('\n').slice(1));
|
||||
|
||||
http.setResponseCallback(http.expectedStatuses(200));
|
||||
|
||||
export const options = {
|
||||
scenarios: {
|
||||
overload: {
|
||||
executor: 'constant-arrival-rate',
|
||||
rate: 220,
|
||||
timeUnit: '1s',
|
||||
duration: '15s',
|
||||
preAllocatedVUs: 200,
|
||||
maxVUs: 1000,
|
||||
},
|
||||
},
|
||||
thresholds: {
|
||||
http_req_duration: ['p(95)<200'],
|
||||
http_req_failed: ['rate<0.01'],
|
||||
},
|
||||
};
|
||||
|
||||
export default function () {
|
||||
http.get(`${BASE}/api/quote/${symbols[Math.floor(Math.random() * symbols.length)]}`);
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
workers=2 queue=3 delay=400ms, 7 simultaneous callers
|
||||
served=5 rejected=2
|
||||
pool stats: completed=5 rejected=2
|
||||
@@ -0,0 +1 @@
|
||||
{"symbol":"ORCL","price":344.44}
|
||||
@@ -0,0 +1,124 @@
|
||||
[INFO] Scanning for projects...
|
||||
[INFO]
|
||||
[INFO] -------------< com.ankurm.testing:gatling-spring-boot-lab >-------------
|
||||
[INFO] Building gatling-spring-boot-lab 1.0.0
|
||||
[INFO] from pom.xml
|
||||
[INFO] --------------------------------[ jar ]---------------------------------
|
||||
[INFO]
|
||||
[INFO] --- resources:3.5.0:resources (default-resources) @ gatling-spring-boot-lab ---
|
||||
[INFO] Copying 1 resource from src/main/resources to target/classes
|
||||
[INFO] Copying 0 resource from src/main/resources to target/classes
|
||||
[INFO]
|
||||
[INFO] --- compiler:3.15.0:compile (default-compile) @ gatling-spring-boot-lab ---
|
||||
[INFO] Nothing to compile - all classes are up to date.
|
||||
[INFO]
|
||||
[INFO] --- resources:3.5.0:testResources (default-testResources) @ gatling-spring-boot-lab ---
|
||||
[INFO] Copying 2 resources from src/test/resources to target/test-classes
|
||||
[INFO]
|
||||
[INFO] --- compiler:3.15.0:testCompile (default-testCompile) @ gatling-spring-boot-lab ---
|
||||
[INFO] Nothing to compile - all classes are up to date.
|
||||
[INFO]
|
||||
[INFO] >>> gatling:4.21.12:test (default-cli) > test-compile @ gatling-spring-boot-lab >>>
|
||||
[INFO]
|
||||
[INFO] --- resources:3.5.0:resources (default-resources) @ gatling-spring-boot-lab ---
|
||||
[INFO] Copying 1 resource from src/main/resources to target/classes
|
||||
[INFO] Copying 0 resource from src/main/resources to target/classes
|
||||
[INFO]
|
||||
[INFO] --- compiler:3.15.0:compile (default-compile) @ gatling-spring-boot-lab ---
|
||||
[INFO] Nothing to compile - all classes are up to date.
|
||||
[INFO]
|
||||
[INFO] --- resources:3.5.0:testResources (default-testResources) @ gatling-spring-boot-lab ---
|
||||
[INFO] Copying 2 resources from src/test/resources to target/test-classes
|
||||
[INFO]
|
||||
[INFO] --- compiler:3.15.0:testCompile (default-testCompile) @ gatling-spring-boot-lab ---
|
||||
[INFO] Nothing to compile - all classes are up to date.
|
||||
[INFO]
|
||||
[INFO] <<< gatling:4.21.12:test (default-cli) < test-compile @ gatling-spring-boot-lab <<<
|
||||
[INFO]
|
||||
[INFO]
|
||||
[INFO] --- gatling:4.21.12:test (default-cli) @ gatling-spring-boot-lab ---
|
||||
[INFO] Running simulation lab.gatling.BasicSimulation.
|
||||
|
||||
========================================================================================================================
|
||||
2026-11-06 13:22:03 GMT 5s elapsed
|
||||
---- Requests -----------------------------------------------------------------------|---Total---|-----OK----|----KO----
|
||||
> Global | 32 | 32 | 0
|
||||
> get quote | 17 | 17 | 0
|
||||
> get another quote | 15 | 15 | 0
|
||||
|
||||
---- Browse quotes -----------------------------------------------------------------------------------------------------
|
||||
[#############|| ] 12%
|
||||
waiting: 108 / active: 2 / done: 15
|
||||
========================================================================================================================
|
||||
|
||||
|
||||
========================================================================================================================
|
||||
2026-11-06 13:22:08 GMT 10s elapsed
|
||||
---- Requests -----------------------------------------------------------------------|---Total---|-----OK----|----KO----
|
||||
> Global | 107 | 107 | 0
|
||||
> get quote | 55 | 55 | 0
|
||||
> get another quote | 52 | 52 | 0
|
||||
|
||||
---- Browse quotes -----------------------------------------------------------------------------------------------------
|
||||
[##############################################||| ] 41.6%
|
||||
waiting: 70 / active: 3 / done: 52
|
||||
========================================================================================================================
|
||||
|
||||
|
||||
========================================================================================================================
|
||||
2026-11-06 13:22:13 GMT 15s elapsed
|
||||
---- Requests -----------------------------------------------------------------------|---Total---|-----OK----|----KO----
|
||||
> Global | 206 | 206 | 0
|
||||
> get quote | 105 | 105 | 0
|
||||
> get another quote | 101 | 101 | 0
|
||||
|
||||
---- Browse quotes -----------------------------------------------------------------------------------------------------
|
||||
[#########################################################################################|||| ] 80.8%
|
||||
waiting: 20 / active: 4 / done: 101
|
||||
========================================================================================================================
|
||||
|
||||
|
||||
========================================================================================================================
|
||||
2026-11-06 13:22:15 GMT 17s elapsed
|
||||
---- Requests -----------------------------------------------------------------------|---Total---|-----OK----|----KO----
|
||||
> Global | 250 | 250 | 0
|
||||
> get quote | 125 | 125 | 0
|
||||
> get another quote | 125 | 125 | 0
|
||||
|
||||
---- Browse quotes -----------------------------------------------------------------------------------------------------
|
||||
[###############################################################################################################] 100%
|
||||
waiting: 0 / active: 0 / done: 125
|
||||
========================================================================================================================
|
||||
|
||||
Parsing log file(s)...
|
||||
Parsing log file(s) done in 0s.
|
||||
Generating reports...
|
||||
|
||||
========================================================================================================================
|
||||
---- Global Information -------------------------------------------------------------|---Total---|-----OK----|----KO----
|
||||
> request count | 250 | 250 | -
|
||||
> min response time (ms) | 48 | 48 | -
|
||||
> max response time (ms) | 161 | 161 | -
|
||||
> mean response time (ms) | 57 | 57 | -
|
||||
> response time std deviation (ms) | 8 | 8 | -
|
||||
> response time 50th percentile (ms) | 56 | 56 | -
|
||||
> response time 75th percentile (ms) | 58 | 58 | -
|
||||
> response time 95th percentile (ms) | 62 | 62 | -
|
||||
> response time 99th percentile (ms) | 87 | 87 | -
|
||||
> mean throughput (rps) | 13.89 | 13.89 | -
|
||||
---- Response Time Distribution ----------------------------------------------------------------------------------------
|
||||
> OK: t < 800 ms 250 (100%)
|
||||
> OK: 800 ms <= t < 1200 ms 0 (0%)
|
||||
> OK: t >= 1200 ms 0 (0%)
|
||||
> KO 0 (0%)
|
||||
========================================================================================================================
|
||||
|
||||
Reports generated, please open the following file: file:///tmp/work/3/repos/asmhatre/java-testing-lab/gatling-spring-boot/target/gatling/basicsimulation-20261106132157606/index.html
|
||||
Global: 95th percentile of response time is less than 200.0 : true (actual : 62.0)
|
||||
Global: percentage of successful events is greater than 99.0 : true (actual : 100.0)
|
||||
[INFO] ------------------------------------------------------------------------
|
||||
[INFO] BUILD SUCCESS
|
||||
[INFO] ------------------------------------------------------------------------
|
||||
[INFO] Total time: 22.242 s
|
||||
[INFO] Finished at: 2026-11-06T18:52:15+05:30
|
||||
[INFO] ------------------------------------------------------------------------
|
||||
@@ -0,0 +1,299 @@
|
||||
[INFO] Scanning for projects...
|
||||
[INFO]
|
||||
[INFO] -------------< com.ankurm.testing:gatling-spring-boot-lab >-------------
|
||||
[INFO] Building gatling-spring-boot-lab 1.0.0
|
||||
[INFO] from pom.xml
|
||||
[INFO] --------------------------------[ jar ]---------------------------------
|
||||
[INFO]
|
||||
[INFO] --- resources:3.5.0:resources (default-resources) @ gatling-spring-boot-lab ---
|
||||
[INFO] Copying 1 resource from src/main/resources to target/classes
|
||||
[INFO] Copying 0 resource from src/main/resources to target/classes
|
||||
[INFO]
|
||||
[INFO] --- compiler:3.15.0:compile (default-compile) @ gatling-spring-boot-lab ---
|
||||
[INFO] Nothing to compile - all classes are up to date.
|
||||
[INFO]
|
||||
[INFO] --- resources:3.5.0:testResources (default-testResources) @ gatling-spring-boot-lab ---
|
||||
[INFO] Copying 2 resources from src/test/resources to target/test-classes
|
||||
[INFO]
|
||||
[INFO] --- compiler:3.15.0:testCompile (default-testCompile) @ gatling-spring-boot-lab ---
|
||||
[INFO] Nothing to compile - all classes are up to date.
|
||||
[INFO]
|
||||
[INFO] >>> gatling:4.21.12:test (default-cli) > test-compile @ gatling-spring-boot-lab >>>
|
||||
[INFO]
|
||||
[INFO] --- resources:3.5.0:resources (default-resources) @ gatling-spring-boot-lab ---
|
||||
[INFO] Copying 1 resource from src/main/resources to target/classes
|
||||
[INFO] Copying 0 resource from src/main/resources to target/classes
|
||||
[INFO]
|
||||
[INFO] --- compiler:3.15.0:compile (default-compile) @ gatling-spring-boot-lab ---
|
||||
[INFO] Nothing to compile - all classes are up to date.
|
||||
[INFO]
|
||||
[INFO] --- resources:3.5.0:testResources (default-testResources) @ gatling-spring-boot-lab ---
|
||||
[INFO] Copying 2 resources from src/test/resources to target/test-classes
|
||||
[INFO]
|
||||
[INFO] --- compiler:3.15.0:testCompile (default-testCompile) @ gatling-spring-boot-lab ---
|
||||
[INFO] Nothing to compile - all classes are up to date.
|
||||
[INFO]
|
||||
[INFO] <<< gatling:4.21.12:test (default-cli) < test-compile @ gatling-spring-boot-lab <<<
|
||||
[INFO]
|
||||
[INFO]
|
||||
[INFO] --- gatling:4.21.12:test (default-cli) @ gatling-spring-boot-lab ---
|
||||
[INFO] Running simulation lab.gatling.KneeStepsSimulation.
|
||||
|
||||
========================================================================================================================
|
||||
2026-11-06 13:22:26 GMT 5s elapsed
|
||||
---- Requests -----------------------------------------------------------------------|---Total---|-----OK----|----KO----
|
||||
> Global | 149 | 149 | 0
|
||||
> quote rate 030/s | 149 | 149 | 0
|
||||
|
||||
---- One quote per user ------------------------------------------------------------------------------------------------
|
||||
[#| ] 1.78%
|
||||
waiting: 8,220 / active: 1 / done: 149
|
||||
========================================================================================================================
|
||||
|
||||
|
||||
========================================================================================================================
|
||||
2026-11-06 13:22:31 GMT 10s elapsed
|
||||
---- Requests -----------------------------------------------------------------------|---Total---|-----OK----|----KO----
|
||||
> Global | 387 | 387 | 0
|
||||
> quote rate 030/s | 182 | 182 | 0
|
||||
> quote ramp | 91 | 91 | 0
|
||||
> quote rate 060/s | 114 | 114 | 0
|
||||
|
||||
---- One quote per user ------------------------------------------------------------------------------------------------
|
||||
[#####| ] 4.62%
|
||||
waiting: 7,980 / active: 3 / done: 387
|
||||
========================================================================================================================
|
||||
|
||||
|
||||
========================================================================================================================
|
||||
2026-11-06 13:22:36 GMT 15s elapsed
|
||||
---- Requests -----------------------------------------------------------------------|---Total---|-----OK----|----KO----
|
||||
> Global | 694 | 694 | 0
|
||||
> quote rate 030/s | 182 | 182 | 0
|
||||
> quote ramp | 152 | 152 | 0
|
||||
> quote rate 060/s | 360 | 360 | 0
|
||||
|
||||
---- One quote per user ------------------------------------------------------------------------------------------------
|
||||
[#########| ] 8.29%
|
||||
waiting: 7,673 / active: 3 / done: 694
|
||||
========================================================================================================================
|
||||
|
||||
|
||||
========================================================================================================================
|
||||
2026-11-06 13:22:41 GMT 20s elapsed
|
||||
---- Requests -----------------------------------------------------------------------|---Total---|-----OK----|----KO----
|
||||
> Global | 1,135 | 1,135 | 0
|
||||
> quote rate 030/s | 182 | 182 | 0
|
||||
> quote ramp | 243 | 243 | 0
|
||||
> quote rate 060/s | 360 | 360 | 0
|
||||
> quote rate 090/s | 350 | 350 | 0
|
||||
|
||||
---- One quote per user ------------------------------------------------------------------------------------------------
|
||||
[###############| ] 13.56%
|
||||
waiting: 7,230 / active: 5 / done: 1,135
|
||||
========================================================================================================================
|
||||
|
||||
|
||||
========================================================================================================================
|
||||
2026-11-06 13:22:46 GMT 25s elapsed
|
||||
---- Requests -----------------------------------------------------------------------|---Total---|-----OK----|----KO----
|
||||
> Global | 1,643 | 1,643 | 0
|
||||
> quote rate 030/s | 182 | 182 | 0
|
||||
> quote ramp | 454 | 454 | 0
|
||||
> quote rate 060/s | 360 | 360 | 0
|
||||
> quote rate 090/s | 540 | 540 | 0
|
||||
> quote rate 120/s | 107 | 107 | 0
|
||||
|
||||
---- One quote per user ------------------------------------------------------------------------------------------------
|
||||
[#####################| ] 19.63%
|
||||
waiting: 6,721 / active: 6 / done: 1,643
|
||||
========================================================================================================================
|
||||
|
||||
|
||||
========================================================================================================================
|
||||
2026-11-06 13:22:51 GMT 30s elapsed
|
||||
---- Requests -----------------------------------------------------------------------|---Total---|-----OK----|----KO----
|
||||
> Global | 2,244 | 2,244 | 0
|
||||
> quote rate 030/s | 182 | 182 | 0
|
||||
> quote ramp | 454 | 454 | 0
|
||||
> quote rate 060/s | 360 | 360 | 0
|
||||
> quote rate 090/s | 540 | 540 | 0
|
||||
> quote rate 120/s | 708 | 708 | 0
|
||||
|
||||
---- One quote per user ------------------------------------------------------------------------------------------------
|
||||
[#############################| ] 26.81%
|
||||
waiting: 6,121 / active: 5 / done: 2,244
|
||||
========================================================================================================================
|
||||
|
||||
|
||||
========================================================================================================================
|
||||
2026-11-06 13:22:56 GMT 35s elapsed
|
||||
---- Requests -----------------------------------------------------------------------|---Total---|-----OK----|----KO----
|
||||
> Global | 2,961 | 2,961 | 0
|
||||
> quote rate 030/s | 182 | 182 | 0
|
||||
> quote ramp | 725 | 725 | 0
|
||||
> quote rate 060/s | 360 | 360 | 0
|
||||
> quote rate 090/s | 540 | 540 | 0
|
||||
> quote rate 120/s | 720 | 720 | 0
|
||||
> quote rate 150/s | 434 | 434 | 0
|
||||
|
||||
---- One quote per user ------------------------------------------------------------------------------------------------
|
||||
[#######################################| ] 35.38%
|
||||
waiting: 5,401 / active: 8 / done: 2,961
|
||||
========================================================================================================================
|
||||
|
||||
|
||||
========================================================================================================================
|
||||
2026-11-06 13:23:01 GMT 40s elapsed
|
||||
---- Requests -----------------------------------------------------------------------|---Total---|-----OK----|----KO----
|
||||
> Global | 3,723 | 3,723 | 0
|
||||
> quote rate 030/s | 182 | 182 | 0
|
||||
> quote ramp | 1,021 | 1,021 | 0
|
||||
> quote rate 060/s | 360 | 360 | 0
|
||||
> quote rate 090/s | 540 | 540 | 0
|
||||
> quote rate 120/s | 720 | 720 | 0
|
||||
> quote rate 150/s | 900 | 900 | 0
|
||||
|
||||
---- One quote per user ------------------------------------------------------------------------------------------------
|
||||
[#################################################| ] 44.48%
|
||||
waiting: 4,621 / active: 26 / done: 3,723
|
||||
========================================================================================================================
|
||||
|
||||
|
||||
========================================================================================================================
|
||||
2026-11-06 13:23:06 GMT 45s elapsed
|
||||
---- Requests -----------------------------------------------------------------------|---Total---|-----OK----|----KO----
|
||||
> Global | 4,542 | 4,515 | 27
|
||||
> quote rate 030/s | 182 | 182 | 0
|
||||
> quote ramp | 1,057 | 1,057 | 0
|
||||
> quote rate 060/s | 360 | 360 | 0
|
||||
> quote rate 090/s | 540 | 540 | 0
|
||||
> quote rate 120/s | 720 | 720 | 0
|
||||
> quote rate 150/s | 900 | 900 | 0
|
||||
> quote rate 180/s | 783 | 756 | 27
|
||||
---- Errors ------------------------------------------------------------------------------------------------------------
|
||||
> status.find.is(200), found 503 27 (100%)
|
||||
|
||||
---- One quote per user ------------------------------------------------------------------------------------------------
|
||||
[############################################################|| ] 54.27%
|
||||
waiting: 3,721 / active: 107 / done: 4,542
|
||||
========================================================================================================================
|
||||
|
||||
|
||||
========================================================================================================================
|
||||
2026-11-06 13:23:11 GMT 50s elapsed
|
||||
---- Requests -----------------------------------------------------------------------|---Total---|-----OK----|----KO----
|
||||
> Global | 5,531 | 5,303 | 228
|
||||
> quote rate 030/s | 182 | 182 | 0
|
||||
> quote ramp | 1,448 | 1,374 | 74
|
||||
> quote rate 060/s | 360 | 360 | 0
|
||||
> quote rate 090/s | 540 | 540 | 0
|
||||
> quote rate 120/s | 720 | 720 | 0
|
||||
> quote rate 150/s | 900 | 900 | 0
|
||||
> quote rate 180/s | 1,080 | 1,029 | 51
|
||||
> quote rate 210/s | 301 | 198 | 103
|
||||
---- Errors ------------------------------------------------------------------------------------------------------------
|
||||
> status.find.is(200), found 503 228 (100%)
|
||||
|
||||
---- One quote per user ------------------------------------------------------------------------------------------------
|
||||
[#########################################################################|| ] 66.08%
|
||||
waiting: 2,731 / active: 108 / done: 5,531
|
||||
========================================================================================================================
|
||||
|
||||
|
||||
========================================================================================================================
|
||||
2026-11-06 13:23:16 GMT 55s elapsed
|
||||
---- Requests -----------------------------------------------------------------------|---Total---|-----OK----|----KO----
|
||||
> Global | 6,588 | 6,091 | 497
|
||||
> quote rate 030/s | 182 | 182 | 0
|
||||
> quote ramp | 1,546 | 1,414 | 132
|
||||
> quote rate 060/s | 360 | 360 | 0
|
||||
> quote rate 090/s | 540 | 540 | 0
|
||||
> quote rate 120/s | 720 | 720 | 0
|
||||
> quote rate 150/s | 900 | 900 | 0
|
||||
> quote rate 180/s | 1,080 | 1,029 | 51
|
||||
> quote rate 210/s | 1,260 | 946 | 314
|
||||
---- Errors ------------------------------------------------------------------------------------------------------------
|
||||
> status.find.is(200), found 503 497 (100%)
|
||||
|
||||
---- One quote per user ------------------------------------------------------------------------------------------------
|
||||
[#######################################################################################|| ] 78.71%
|
||||
waiting: 1,675 / active: 107 / done: 6,588
|
||||
========================================================================================================================
|
||||
|
||||
|
||||
========================================================================================================================
|
||||
2026-11-06 13:23:21 GMT 60s elapsed
|
||||
---- Requests -----------------------------------------------------------------------|---Total---|-----OK----|----KO----
|
||||
> Global | 7,780 | 6,882 | 898
|
||||
> quote rate 030/s | 182 | 182 | 0
|
||||
> quote ramp | 1,899 | 1,688 | 211
|
||||
> quote rate 060/s | 360 | 360 | 0
|
||||
> quote rate 090/s | 540 | 540 | 0
|
||||
> quote rate 120/s | 720 | 720 | 0
|
||||
> quote rate 150/s | 900 | 900 | 0
|
||||
> quote rate 180/s | 1,080 | 1,029 | 51
|
||||
> quote rate 210/s | 1,260 | 946 | 314
|
||||
> quote rate 240/s | 839 | 517 | 322
|
||||
---- Errors ------------------------------------------------------------------------------------------------------------
|
||||
> status.find.is(200), found 503 898 (100%)
|
||||
|
||||
---- One quote per user ------------------------------------------------------------------------------------------------
|
||||
[#######################################################################################################|| ] 92.95%
|
||||
waiting: 482 / active: 108 / done: 7,780
|
||||
========================================================================================================================
|
||||
|
||||
|
||||
========================================================================================================================
|
||||
2026-11-06 13:23:24 GMT 62s elapsed
|
||||
---- Requests -----------------------------------------------------------------------|---Total---|-----OK----|----KO----
|
||||
> Global | 8,370 | 7,308 | 1,062
|
||||
> quote rate 030/s | 182 | 182 | 0
|
||||
> quote ramp | 1,899 | 1,688 | 211
|
||||
> quote rate 060/s | 360 | 360 | 0
|
||||
> quote rate 090/s | 540 | 540 | 0
|
||||
> quote rate 120/s | 720 | 720 | 0
|
||||
> quote rate 150/s | 900 | 900 | 0
|
||||
> quote rate 180/s | 1,080 | 1,029 | 51
|
||||
> quote rate 210/s | 1,260 | 946 | 314
|
||||
> quote rate 240/s | 1,429 | 943 | 486
|
||||
---- Errors ------------------------------------------------------------------------------------------------------------
|
||||
> status.find.is(200), found 503 1,062 (100%)
|
||||
|
||||
---- One quote per user ------------------------------------------------------------------------------------------------
|
||||
[###############################################################################################################] 100%
|
||||
waiting: 0 / active: 0 / done: 8,370
|
||||
========================================================================================================================
|
||||
|
||||
Parsing log file(s)...
|
||||
Parsing log file(s) done in 0s.
|
||||
Generating reports...
|
||||
|
||||
========================================================================================================================
|
||||
---- Global Information -------------------------------------------------------------|---Total---|-----OK----|----KO----
|
||||
> request count | 8,370 | 7,308 | 1,062
|
||||
> min response time (ms) | 0 | 46 | 0
|
||||
> max response time (ms) | 711 | 711 | 27
|
||||
> mean response time (ms) | 293 | 335 | 1
|
||||
> response time std deviation (ms) | 301 | 300 | 1
|
||||
> response time 50th percentile (ms) | 57 | 105 | 1
|
||||
> response time 75th percentile (ms) | 674 | 676 | 1
|
||||
> response time 95th percentile (ms) | 688 | 689 | 3
|
||||
> response time 99th percentile (ms) | 695 | 696 | 5
|
||||
> mean throughput (rps) | 132.86 | 116 | 16.86
|
||||
---- Response Time Distribution ----------------------------------------------------------------------------------------
|
||||
> OK: t < 800 ms 7,308 (87.31%)
|
||||
> OK: 800 ms <= t < 1200 ms 0 (0%)
|
||||
> OK: t >= 1200 ms 0 (0%)
|
||||
> KO 1,062 (12.69%)
|
||||
---- Errors ------------------------------------------------------------------------------------------------------------
|
||||
> status.find.is(200), found 503 1,062 (100%)
|
||||
========================================================================================================================
|
||||
|
||||
Reports generated, please open the following file: file:///tmp/work/3/repos/asmhatre/java-testing-lab/gatling-spring-boot/target/gatling/kneestepssimulation-20261106132221044/index.html
|
||||
[INFO] ------------------------------------------------------------------------
|
||||
[INFO] BUILD SUCCESS
|
||||
[INFO] ------------------------------------------------------------------------
|
||||
[INFO] Total time: 01:07 min
|
||||
[INFO] Finished at: 2026-11-06T18:53:24+05:30
|
||||
[INFO] ------------------------------------------------------------------------
|
||||
@@ -0,0 +1,138 @@
|
||||
[INFO] Scanning for projects...
|
||||
[INFO]
|
||||
[INFO] -------------< com.ankurm.testing:gatling-spring-boot-lab >-------------
|
||||
[INFO] Building gatling-spring-boot-lab 1.0.0
|
||||
[INFO] from pom.xml
|
||||
[INFO] --------------------------------[ jar ]---------------------------------
|
||||
[INFO]
|
||||
[INFO] --- resources:3.5.0:resources (default-resources) @ gatling-spring-boot-lab ---
|
||||
[INFO] Copying 1 resource from src/main/resources to target/classes
|
||||
[INFO] Copying 0 resource from src/main/resources to target/classes
|
||||
[INFO]
|
||||
[INFO] --- compiler:3.15.0:compile (default-compile) @ gatling-spring-boot-lab ---
|
||||
[INFO] Nothing to compile - all classes are up to date.
|
||||
[INFO]
|
||||
[INFO] --- resources:3.5.0:testResources (default-testResources) @ gatling-spring-boot-lab ---
|
||||
[INFO] Copying 2 resources from src/test/resources to target/test-classes
|
||||
[INFO]
|
||||
[INFO] --- compiler:3.15.0:testCompile (default-testCompile) @ gatling-spring-boot-lab ---
|
||||
[INFO] Nothing to compile - all classes are up to date.
|
||||
[INFO]
|
||||
[INFO] >>> gatling:4.21.12:test (default-cli) > test-compile @ gatling-spring-boot-lab >>>
|
||||
[INFO]
|
||||
[INFO] --- resources:3.5.0:resources (default-resources) @ gatling-spring-boot-lab ---
|
||||
[INFO] Copying 1 resource from src/main/resources to target/classes
|
||||
[INFO] Copying 0 resource from src/main/resources to target/classes
|
||||
[INFO]
|
||||
[INFO] --- compiler:3.15.0:compile (default-compile) @ gatling-spring-boot-lab ---
|
||||
[INFO] Nothing to compile - all classes are up to date.
|
||||
[INFO]
|
||||
[INFO] --- resources:3.5.0:testResources (default-testResources) @ gatling-spring-boot-lab ---
|
||||
[INFO] Copying 2 resources from src/test/resources to target/test-classes
|
||||
[INFO]
|
||||
[INFO] --- compiler:3.15.0:testCompile (default-testCompile) @ gatling-spring-boot-lab ---
|
||||
[INFO] Nothing to compile - all classes are up to date.
|
||||
[INFO]
|
||||
[INFO] <<< gatling:4.21.12:test (default-cli) < test-compile @ gatling-spring-boot-lab <<<
|
||||
[INFO]
|
||||
[INFO]
|
||||
[INFO] --- gatling:4.21.12:test (default-cli) @ gatling-spring-boot-lab ---
|
||||
[INFO] Running simulation lab.gatling.FailingAssertionSimulation.
|
||||
|
||||
========================================================================================================================
|
||||
2026-11-06 13:24:47 GMT 5s elapsed
|
||||
---- Requests -----------------------------------------------------------------------|---Total---|-----OK----|----KO----
|
||||
> Global | 993 | 769 | 224
|
||||
> get quote | 993 | 769 | 224
|
||||
---- Errors ------------------------------------------------------------------------------------------------------------
|
||||
> status.find.is(200), found 503 224 (100%)
|
||||
|
||||
---- One quote per user ------------------------------------------------------------------------------------------------
|
||||
[#################################|||| ] 30.06%
|
||||
waiting: 2,200 / active: 108 / done: 992
|
||||
========================================================================================================================
|
||||
|
||||
|
||||
========================================================================================================================
|
||||
2026-11-06 13:24:52 GMT 10s elapsed
|
||||
---- Requests -----------------------------------------------------------------------|---Total---|-----OK----|----KO----
|
||||
> Global | 2,093 | 1,563 | 530
|
||||
> get quote | 2,093 | 1,563 | 530
|
||||
---- Errors ------------------------------------------------------------------------------------------------------------
|
||||
> status.find.is(200), found 503 530 (100%)
|
||||
|
||||
---- One quote per user ------------------------------------------------------------------------------------------------
|
||||
[######################################################################|||| ] 63.42%
|
||||
waiting: 1,100 / active: 107 / done: 2,093
|
||||
========================================================================================================================
|
||||
|
||||
|
||||
========================================================================================================================
|
||||
2026-11-06 13:24:57 GMT 15s elapsed
|
||||
---- Requests -----------------------------------------------------------------------|---Total---|-----OK----|----KO----
|
||||
> Global | 3,191 | 2,356 | 835
|
||||
> get quote | 3,191 | 2,356 | 835
|
||||
---- Errors ------------------------------------------------------------------------------------------------------------
|
||||
> status.find.is(200), found 503 835 (100%)
|
||||
|
||||
---- One quote per user ------------------------------------------------------------------------------------------------
|
||||
[###########################################################################################################||||] 96.7%
|
||||
waiting: 1 / active: 108 / done: 3,191
|
||||
========================================================================================================================
|
||||
|
||||
|
||||
========================================================================================================================
|
||||
2026-11-06 13:24:58 GMT 15s elapsed
|
||||
---- Requests -----------------------------------------------------------------------|---Total---|-----OK----|----KO----
|
||||
> Global | 3,300 | 2,464 | 836
|
||||
> get quote | 3,300 | 2,464 | 836
|
||||
---- Errors ------------------------------------------------------------------------------------------------------------
|
||||
> status.find.is(200), found 503 836 (100%)
|
||||
|
||||
---- One quote per user ------------------------------------------------------------------------------------------------
|
||||
[###############################################################################################################] 100%
|
||||
waiting: 0 / active: 0 / done: 3,300
|
||||
========================================================================================================================
|
||||
|
||||
Parsing log file(s)...
|
||||
Parsing log file(s) done in 0s.
|
||||
Generating reports...
|
||||
|
||||
========================================================================================================================
|
||||
---- Global Information -------------------------------------------------------------|---Total---|-----OK----|----KO----
|
||||
> request count | 3,300 | 2,464 | 836
|
||||
> min response time (ms) | 0 | 64 | 0
|
||||
> max response time (ms) | 706 | 706 | 9
|
||||
> mean response time (ms) | 479 | 641 | 1
|
||||
> response time std deviation (ms) | 295 | 112 | 1
|
||||
> response time 50th percentile (ms) | 671 | 676 | 1
|
||||
> response time 75th percentile (ms) | 680 | 682 | 1
|
||||
> response time 95th percentile (ms) | 690 | 691 | 2
|
||||
> response time 99th percentile (ms) | 696 | 696 | 4
|
||||
> mean throughput (rps) | 206.25 | 154 | 52.25
|
||||
---- Response Time Distribution ----------------------------------------------------------------------------------------
|
||||
> OK: t < 800 ms 2,464 (74.67%)
|
||||
> OK: 800 ms <= t < 1200 ms 0 (0%)
|
||||
> OK: t >= 1200 ms 0 (0%)
|
||||
> KO 836 (25.33%)
|
||||
---- Errors ------------------------------------------------------------------------------------------------------------
|
||||
> status.find.is(200), found 503 836 (100%)
|
||||
========================================================================================================================
|
||||
|
||||
Reports generated, please open the following file: file:///tmp/work/3/repos/asmhatre/java-testing-lab/gatling-spring-boot/target/gatling/failingassertionsimulation-20261106132442427/index.html
|
||||
Global: 95th percentile of response time is less than 200.0 : false (actual : 690.0)
|
||||
Global: percentage of successful events is greater than 99.0 : false (actual : 74.66666666666667)
|
||||
get quote: max of response time is less than 1000.0 : true (actual : 706.0)
|
||||
[INFO] ------------------------------------------------------------------------
|
||||
[INFO] BUILD FAILURE
|
||||
[INFO] ------------------------------------------------------------------------
|
||||
[INFO] Total time: 20.175 s
|
||||
[INFO] Finished at: 2026-11-06T18:54:58+05:30
|
||||
[INFO] ------------------------------------------------------------------------
|
||||
[ERROR] Failed to execute goal io.gatling:gatling-maven-plugin:4.21.12:test (default-cli) on project gatling-spring-boot-lab: Gatling simulation assertions failed ! ForkException -> [Help 1]
|
||||
[ERROR]
|
||||
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
|
||||
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
|
||||
[ERROR]
|
||||
[ERROR] For more information about the errors and possible solutions, please read the following articles:
|
||||
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException
|
||||
@@ -0,0 +1 @@
|
||||
maven exit code: 1
|
||||
@@ -0,0 +1,186 @@
|
||||
[INFO] Scanning for projects...
|
||||
[INFO]
|
||||
[INFO] -------------< com.ankurm.testing:gatling-spring-boot-lab >-------------
|
||||
[INFO] Building gatling-spring-boot-lab 1.0.0
|
||||
[INFO] from pom.xml
|
||||
[INFO] --------------------------------[ jar ]---------------------------------
|
||||
[INFO]
|
||||
[INFO] --- resources:3.5.0:resources (default-resources) @ gatling-spring-boot-lab ---
|
||||
[INFO] Copying 1 resource from src/main/resources to target/classes
|
||||
[INFO] Copying 0 resource from src/main/resources to target/classes
|
||||
[INFO]
|
||||
[INFO] --- compiler:3.15.0:compile (default-compile) @ gatling-spring-boot-lab ---
|
||||
[INFO] Nothing to compile - all classes are up to date.
|
||||
[INFO]
|
||||
[INFO] --- resources:3.5.0:testResources (default-testResources) @ gatling-spring-boot-lab ---
|
||||
[INFO] Copying 2 resources from src/test/resources to target/test-classes
|
||||
[INFO]
|
||||
[INFO] --- compiler:3.15.0:testCompile (default-testCompile) @ gatling-spring-boot-lab ---
|
||||
[INFO] Nothing to compile - all classes are up to date.
|
||||
[INFO]
|
||||
[INFO] >>> gatling:4.21.12:test (default-cli) > test-compile @ gatling-spring-boot-lab >>>
|
||||
[INFO]
|
||||
[INFO] --- resources:3.5.0:resources (default-resources) @ gatling-spring-boot-lab ---
|
||||
[INFO] Copying 1 resource from src/main/resources to target/classes
|
||||
[INFO] Copying 0 resource from src/main/resources to target/classes
|
||||
[INFO]
|
||||
[INFO] --- compiler:3.15.0:compile (default-compile) @ gatling-spring-boot-lab ---
|
||||
[INFO] Nothing to compile - all classes are up to date.
|
||||
[INFO]
|
||||
[INFO] --- resources:3.5.0:testResources (default-testResources) @ gatling-spring-boot-lab ---
|
||||
[INFO] Copying 2 resources from src/test/resources to target/test-classes
|
||||
[INFO]
|
||||
[INFO] --- compiler:3.15.0:testCompile (default-testCompile) @ gatling-spring-boot-lab ---
|
||||
[INFO] Nothing to compile - all classes are up to date.
|
||||
[INFO]
|
||||
[INFO] <<< gatling:4.21.12:test (default-cli) < test-compile @ gatling-spring-boot-lab <<<
|
||||
[INFO]
|
||||
[INFO]
|
||||
[INFO] --- gatling:4.21.12:test (default-cli) @ gatling-spring-boot-lab ---
|
||||
[INFO] Running simulation lab.gatling.KneeRampSimulation.
|
||||
|
||||
========================================================================================================================
|
||||
2026-11-06 13:23:36 GMT 5s elapsed
|
||||
---- Requests -----------------------------------------------------------------------|---Total---|-----OK----|----KO----
|
||||
> Global | 123 | 123 | 0
|
||||
> get quote | 123 | 123 | 0
|
||||
|
||||
---- One quote per user ------------------------------------------------------------------------------------------------
|
||||
[##| ] 2.37%
|
||||
waiting: 5,075 / active: 2 / done: 123
|
||||
========================================================================================================================
|
||||
|
||||
|
||||
========================================================================================================================
|
||||
2026-11-06 13:23:41 GMT 10s elapsed
|
||||
---- Requests -----------------------------------------------------------------------|---Total---|-----OK----|----KO----
|
||||
> Global | 396 | 396 | 0
|
||||
> get quote | 396 | 396 | 0
|
||||
|
||||
---- One quote per user ------------------------------------------------------------------------------------------------
|
||||
[########| ] 7.62%
|
||||
waiting: 4,800 / active: 4 / done: 396
|
||||
========================================================================================================================
|
||||
|
||||
|
||||
========================================================================================================================
|
||||
2026-11-06 13:23:46 GMT 15s elapsed
|
||||
---- Requests -----------------------------------------------------------------------|---Total---|-----OK----|----KO----
|
||||
> Global | 819 | 819 | 0
|
||||
> get quote | 819 | 819 | 0
|
||||
|
||||
---- One quote per user ------------------------------------------------------------------------------------------------
|
||||
[#################| ] 15.75%
|
||||
waiting: 4,376 / active: 5 / done: 819
|
||||
========================================================================================================================
|
||||
|
||||
|
||||
========================================================================================================================
|
||||
2026-11-06 13:23:51 GMT 20s elapsed
|
||||
---- Requests -----------------------------------------------------------------------|---Total---|-----OK----|----KO----
|
||||
> Global | 1,391 | 1,391 | 0
|
||||
> get quote | 1,391 | 1,391 | 0
|
||||
|
||||
---- One quote per user ------------------------------------------------------------------------------------------------
|
||||
[#############################| ] 26.75%
|
||||
waiting: 3,801 / active: 8 / done: 1,391
|
||||
========================================================================================================================
|
||||
|
||||
|
||||
========================================================================================================================
|
||||
2026-11-06 13:23:56 GMT 25s elapsed
|
||||
---- Requests -----------------------------------------------------------------------|---Total---|-----OK----|----KO----
|
||||
> Global | 2,115 | 2,115 | 0
|
||||
> get quote | 2,115 | 2,115 | 0
|
||||
|
||||
---- One quote per user ------------------------------------------------------------------------------------------------
|
||||
[#############################################| ] 40.67%
|
||||
waiting: 3,076 / active: 9 / done: 2,115
|
||||
========================================================================================================================
|
||||
|
||||
|
||||
========================================================================================================================
|
||||
2026-11-06 13:24:01 GMT 30s elapsed
|
||||
---- Requests -----------------------------------------------------------------------|---Total---|-----OK----|----KO----
|
||||
> Global | 2,904 | 2,904 | 0
|
||||
> get quote | 2,904 | 2,904 | 0
|
||||
|
||||
---- One quote per user ------------------------------------------------------------------------------------------------
|
||||
[#############################################################||| ] 55.85%
|
||||
waiting: 2,202 / active: 94 / done: 2,904
|
||||
========================================================================================================================
|
||||
|
||||
|
||||
========================================================================================================================
|
||||
2026-11-06 13:24:06 GMT 35s elapsed
|
||||
---- Requests -----------------------------------------------------------------------|---Total---|-----OK----|----KO----
|
||||
> Global | 3,915 | 3,695 | 220
|
||||
> get quote | 3,915 | 3,695 | 220
|
||||
---- Errors ------------------------------------------------------------------------------------------------------------
|
||||
> status.find.is(200), found 503 220 (100%)
|
||||
|
||||
---- One quote per user ------------------------------------------------------------------------------------------------
|
||||
[###################################################################################||| ] 75.29%
|
||||
waiting: 1,178 / active: 107 / done: 3,915
|
||||
========================================================================================================================
|
||||
|
||||
|
||||
========================================================================================================================
|
||||
2026-11-06 13:24:11 GMT 40s elapsed
|
||||
---- Requests -----------------------------------------------------------------------|---Total---|-----OK----|----KO----
|
||||
> Global | 5,091 | 4,488 | 603
|
||||
> get quote | 5,091 | 4,488 | 603
|
||||
---- Errors ------------------------------------------------------------------------------------------------------------
|
||||
> status.find.is(200), found 503 603 (100%)
|
||||
|
||||
---- One quote per user ------------------------------------------------------------------------------------------------
|
||||
[############################################################################################################|||] 97.9%
|
||||
waiting: 2 / active: 107 / done: 5,091
|
||||
========================================================================================================================
|
||||
|
||||
|
||||
========================================================================================================================
|
||||
2026-11-06 13:24:12 GMT 40s elapsed
|
||||
---- Requests -----------------------------------------------------------------------|---Total---|-----OK----|----KO----
|
||||
> Global | 5,200 | 4,596 | 604
|
||||
> get quote | 5,200 | 4,596 | 604
|
||||
---- Errors ------------------------------------------------------------------------------------------------------------
|
||||
> status.find.is(200), found 503 604 (100%)
|
||||
|
||||
---- One quote per user ------------------------------------------------------------------------------------------------
|
||||
[###############################################################################################################] 100%
|
||||
waiting: 0 / active: 0 / done: 5,200
|
||||
========================================================================================================================
|
||||
|
||||
Parsing log file(s)...
|
||||
Parsing log file(s) done in 0s.
|
||||
Generating reports...
|
||||
|
||||
========================================================================================================================
|
||||
---- Global Information -------------------------------------------------------------|---Total---|-----OK----|----KO----
|
||||
> request count | 5,200 | 4,596 | 604
|
||||
> min response time (ms) | 0 | 46 | 0
|
||||
> max response time (ms) | 706 | 706 | 22
|
||||
> mean response time (ms) | 272 | 308 | 1
|
||||
> response time std deviation (ms) | 288 | 288 | 1
|
||||
> response time 50th percentile (ms) | 58 | 96 | 1
|
||||
> response time 75th percentile (ms) | 670 | 673 | 1
|
||||
> response time 95th percentile (ms) | 686 | 686 | 2
|
||||
> response time 99th percentile (ms) | 693 | 693 | 5
|
||||
> mean throughput (rps) | 126.83 | 112.1 | 14.73
|
||||
---- Response Time Distribution ----------------------------------------------------------------------------------------
|
||||
> OK: t < 800 ms 4,596 (88.38%)
|
||||
> OK: 800 ms <= t < 1200 ms 0 (0%)
|
||||
> OK: t >= 1200 ms 0 (0%)
|
||||
> KO 604 (11.62%)
|
||||
---- Errors ------------------------------------------------------------------------------------------------------------
|
||||
> status.find.is(200), found 503 604 (100%)
|
||||
========================================================================================================================
|
||||
|
||||
Reports generated, please open the following file: file:///tmp/work/3/repos/asmhatre/java-testing-lab/gatling-spring-boot/target/gatling/kneerampsimulation-20261106132330421/index.html
|
||||
[INFO] ------------------------------------------------------------------------
|
||||
[INFO] BUILD SUCCESS
|
||||
[INFO] ------------------------------------------------------------------------
|
||||
[INFO] Total time: 47.002 s
|
||||
[INFO] Finished at: 2026-11-06T18:54:13+05:30
|
||||
[INFO] ------------------------------------------------------------------------
|
||||
@@ -0,0 +1,114 @@
|
||||
[INFO] Scanning for projects...
|
||||
[INFO]
|
||||
[INFO] -------------< com.ankurm.testing:gatling-spring-boot-lab >-------------
|
||||
[INFO] Building gatling-spring-boot-lab 1.0.0
|
||||
[INFO] from pom.xml
|
||||
[INFO] --------------------------------[ jar ]---------------------------------
|
||||
[INFO]
|
||||
[INFO] --- resources:3.5.0:resources (default-resources) @ gatling-spring-boot-lab ---
|
||||
[INFO] Copying 1 resource from src/main/resources to target/classes
|
||||
[INFO] Copying 0 resource from src/main/resources to target/classes
|
||||
[INFO]
|
||||
[INFO] --- compiler:3.15.0:compile (default-compile) @ gatling-spring-boot-lab ---
|
||||
[INFO] Nothing to compile - all classes are up to date.
|
||||
[INFO]
|
||||
[INFO] --- resources:3.5.0:testResources (default-testResources) @ gatling-spring-boot-lab ---
|
||||
[INFO] Copying 2 resources from src/test/resources to target/test-classes
|
||||
[INFO]
|
||||
[INFO] --- compiler:3.15.0:testCompile (default-testCompile) @ gatling-spring-boot-lab ---
|
||||
[INFO] Nothing to compile - all classes are up to date.
|
||||
[INFO]
|
||||
[INFO] >>> gatling:4.21.12:test (default-cli) > test-compile @ gatling-spring-boot-lab >>>
|
||||
[INFO]
|
||||
[INFO] --- resources:3.5.0:resources (default-resources) @ gatling-spring-boot-lab ---
|
||||
[INFO] Copying 1 resource from src/main/resources to target/classes
|
||||
[INFO] Copying 0 resource from src/main/resources to target/classes
|
||||
[INFO]
|
||||
[INFO] --- compiler:3.15.0:compile (default-compile) @ gatling-spring-boot-lab ---
|
||||
[INFO] Nothing to compile - all classes are up to date.
|
||||
[INFO]
|
||||
[INFO] --- resources:3.5.0:testResources (default-testResources) @ gatling-spring-boot-lab ---
|
||||
[INFO] Copying 2 resources from src/test/resources to target/test-classes
|
||||
[INFO]
|
||||
[INFO] --- compiler:3.15.0:testCompile (default-testCompile) @ gatling-spring-boot-lab ---
|
||||
[INFO] Nothing to compile - all classes are up to date.
|
||||
[INFO]
|
||||
[INFO] <<< gatling:4.21.12:test (default-cli) < test-compile @ gatling-spring-boot-lab <<<
|
||||
[INFO]
|
||||
[INFO]
|
||||
[INFO] --- gatling:4.21.12:test (default-cli) @ gatling-spring-boot-lab ---
|
||||
[INFO] Running simulation lab.gatling.ClosedModelSimulation.
|
||||
|
||||
========================================================================================================================
|
||||
2026-11-06 13:24:26 GMT 5s elapsed
|
||||
---- Requests -----------------------------------------------------------------------|---Total---|-----OK----|----KO----
|
||||
> Global | 767 | 767 | 0
|
||||
> get quote | 767 | 767 | 0
|
||||
|
||||
---- Closed loop -------------------------------------------------------------------------------------------------------
|
||||
active: 50 / done: 0
|
||||
========================================================================================================================
|
||||
|
||||
|
||||
========================================================================================================================
|
||||
2026-11-06 13:24:31 GMT 10s elapsed
|
||||
---- Requests -----------------------------------------------------------------------|---Total---|-----OK----|----KO----
|
||||
> Global | 1,556 | 1,556 | 0
|
||||
> get quote | 1,556 | 1,556 | 0
|
||||
|
||||
---- Closed loop -------------------------------------------------------------------------------------------------------
|
||||
active: 50 / done: 0
|
||||
========================================================================================================================
|
||||
|
||||
|
||||
========================================================================================================================
|
||||
2026-11-06 13:24:36 GMT 15s elapsed
|
||||
---- Requests -----------------------------------------------------------------------|---Total---|-----OK----|----KO----
|
||||
> Global | 2,347 | 2,347 | 0
|
||||
> get quote | 2,347 | 2,347 | 0
|
||||
|
||||
---- Closed loop -------------------------------------------------------------------------------------------------------
|
||||
active: 50 / done: 0
|
||||
========================================================================================================================
|
||||
|
||||
|
||||
========================================================================================================================
|
||||
2026-11-06 13:24:37 GMT 16s elapsed
|
||||
---- Requests -----------------------------------------------------------------------|---Total---|-----OK----|----KO----
|
||||
> Global | 2,506 | 2,506 | 0
|
||||
> get quote | 2,506 | 2,506 | 0
|
||||
|
||||
---- Closed loop -------------------------------------------------------------------------------------------------------
|
||||
active: 0 / done: 50
|
||||
========================================================================================================================
|
||||
|
||||
Parsing log file(s)...
|
||||
Parsing log file(s) done in 0s.
|
||||
Generating reports...
|
||||
|
||||
========================================================================================================================
|
||||
---- Global Information -------------------------------------------------------------|---Total---|-----OK----|----KO----
|
||||
> request count | 2,506 | 2,506 | -
|
||||
> min response time (ms) | 47 | 47 | -
|
||||
> max response time (ms) | 343 | 343 | -
|
||||
> mean response time (ms) | 301 | 301 | -
|
||||
> response time std deviation (ms) | 51 | 51 | -
|
||||
> response time 50th percentile (ms) | 314 | 314 | -
|
||||
> response time 75th percentile (ms) | 320 | 320 | -
|
||||
> response time 95th percentile (ms) | 329 | 329 | -
|
||||
> response time 99th percentile (ms) | 334 | 334 | -
|
||||
> mean throughput (rps) | 147.41 | 147.41 | -
|
||||
---- Response Time Distribution ----------------------------------------------------------------------------------------
|
||||
> OK: t < 800 ms 2,506 (100%)
|
||||
> OK: 800 ms <= t < 1200 ms 0 (0%)
|
||||
> OK: t >= 1200 ms 0 (0%)
|
||||
> KO 0 (0%)
|
||||
========================================================================================================================
|
||||
|
||||
Reports generated, please open the following file: file:///tmp/work/3/repos/asmhatre/java-testing-lab/gatling-spring-boot/target/gatling/closedmodelsimulation-20261106132420420/index.html
|
||||
[INFO] ------------------------------------------------------------------------
|
||||
[INFO] BUILD SUCCESS
|
||||
[INFO] ------------------------------------------------------------------------
|
||||
[INFO] Total time: 22.031 s
|
||||
[INFO] Finished at: 2026-11-06T18:54:37+05:30
|
||||
[INFO] ------------------------------------------------------------------------
|
||||
@@ -0,0 +1,10 @@
|
||||
step requests ok ko ko % p50 p95 p99 max (times in ms)
|
||||
rate 030/s 182 182 0 0.0 55 60 69 82
|
||||
rate 060/s 360 360 0 0.0 53 58 62 66
|
||||
rate 090/s 540 540 0 0.0 52 57 59 61
|
||||
rate 120/s 720 720 0 0.0 51 57 58 61
|
||||
rate 150/s 900 900 0 0.0 52 57 60 64
|
||||
rate 180/s 1080 1029 51 4.7 543 685 689 692
|
||||
rate 210/s 1260 946 314 24.9 676 693 699 711
|
||||
rate 240/s 1429 943 486 34.0 672 689 695 702
|
||||
ramp 1899 1688 211 11.1 56 690 699 708
|
||||
@@ -0,0 +1 @@
|
||||
k6 v2.3.0 (commit/e088784614, go1.26.8, linux/amd64)
|
||||
@@ -0,0 +1,10 @@
|
||||
step requests ok ko ko % p50 p95 p99 max (times in ms)
|
||||
rate 030/s 179 179 0 0.0 51 56 57 59
|
||||
rate 060/s 360 360 0 0.0 51 56 56 57
|
||||
rate 090/s 540 540 0 0.0 51 56 56 64
|
||||
rate 120/s 720 720 0 0.0 51 56 56 58
|
||||
rate 150/s 900 900 0 0.0 52 56 58 60
|
||||
rate 180/s 1080 1037 43 4.0 506 682 688 693
|
||||
rate 210/s 1260 945 315 25.0 675 691 698 702
|
||||
rate 240/s 1440 948 492 34.2 674 693 698 706
|
||||
ramp 1885 1678 207 11.0 55 687 694 701
|
||||
@@ -0,0 +1,29 @@
|
||||
|
||||
|
||||
█ TOTAL RESULTS
|
||||
|
||||
checks_total.......: 8364 133.443081/s
|
||||
checks_succeeded...: 87.36% 7307 out of 8364
|
||||
checks_failed......: 12.63% 1057 out of 8364
|
||||
|
||||
✗ status is 200
|
||||
↳ 87% — ✓ 7307 / ✗ 1057
|
||||
|
||||
HTTP
|
||||
http_req_duration..............: avg=289.82ms min=334.47µs med=55.3ms max=705.87ms p(90)=684.17ms p(95)=688.07ms
|
||||
{ expected_response:true }...: avg=331.64ms min=46.44ms med=92.48ms max=705.87ms p(90)=685ms p(95)=688.76ms
|
||||
http_req_failed................: 12.63% 1057 out of 8364
|
||||
http_reqs......................: 8364 133.443081/s
|
||||
|
||||
EXECUTION
|
||||
iteration_duration.............: avg=290.06ms min=566.23µs med=55.68ms max=706.04ms p(90)=684.43ms p(95)=688.3ms
|
||||
iterations.....................: 8364 133.443081/s
|
||||
vus............................: 109 min=2 max=109
|
||||
vus_max........................: 200 min=200 max=200
|
||||
|
||||
NETWORK
|
||||
data_received..................: 1.2 MB 19 kB/s
|
||||
data_sent......................: 709 kB 11 kB/s
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
|
||||
|
||||
█ THRESHOLDS
|
||||
|
||||
http_req_duration
|
||||
✗ 'p(95)<200' p(95)=687.37ms
|
||||
|
||||
http_req_failed
|
||||
✗ 'rate<0.01' rate=24.81%
|
||||
|
||||
|
||||
█ TOTAL RESULTS
|
||||
|
||||
HTTP
|
||||
http_req_duration..............: avg=474.35ms min=391.27µs med=669.21ms max=702.82ms p(90)=683.72ms p(95)=687.37ms
|
||||
{ expected_response:true }...: avg=630.67ms min=49.56ms med=673.86ms max=702.82ms p(90)=685.57ms p(95)=689.1ms
|
||||
http_req_failed................: 24.81% 819 out of 3301
|
||||
http_reqs......................: 3301 210.547036/s
|
||||
|
||||
EXECUTION
|
||||
iteration_duration.............: avg=474.54ms min=477.48µs med=669.34ms max=703.17ms p(90)=683.89ms p(95)=687.51ms
|
||||
iterations.....................: 3301 210.547036/s
|
||||
vus............................: 107 min=68 max=108
|
||||
vus_max........................: 200 min=200 max=200
|
||||
|
||||
NETWORK
|
||||
data_received..................: 478 kB 31 kB/s
|
||||
data_sent......................: 280 kB 18 kB/s
|
||||
|
||||
|
||||
|
||||
time="2026-11-06T13:26:18Z" level=error msg="thresholds on metrics 'http_req_duration, http_req_failed' have been crossed"
|
||||
@@ -0,0 +1 @@
|
||||
k6 exit code: 99
|
||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 14 KiB |
@@ -0,0 +1,101 @@
|
||||
[
|
||||
{
|
||||
"step": "rate 030/s",
|
||||
"requests": 182,
|
||||
"ok": 182,
|
||||
"ko": 0,
|
||||
"ko_pct": 0.0,
|
||||
"p50": 55,
|
||||
"p95": 60,
|
||||
"p99": 69,
|
||||
"max": 82
|
||||
},
|
||||
{
|
||||
"step": "ramp",
|
||||
"requests": 1899,
|
||||
"ok": 1688,
|
||||
"ko": 211,
|
||||
"ko_pct": 11.11,
|
||||
"p50": 56,
|
||||
"p95": 690,
|
||||
"p99": 699,
|
||||
"max": 708
|
||||
},
|
||||
{
|
||||
"step": "rate 060/s",
|
||||
"requests": 360,
|
||||
"ok": 360,
|
||||
"ko": 0,
|
||||
"ko_pct": 0.0,
|
||||
"p50": 53,
|
||||
"p95": 58,
|
||||
"p99": 62,
|
||||
"max": 66
|
||||
},
|
||||
{
|
||||
"step": "rate 090/s",
|
||||
"requests": 540,
|
||||
"ok": 540,
|
||||
"ko": 0,
|
||||
"ko_pct": 0.0,
|
||||
"p50": 52,
|
||||
"p95": 57,
|
||||
"p99": 59,
|
||||
"max": 61
|
||||
},
|
||||
{
|
||||
"step": "rate 120/s",
|
||||
"requests": 720,
|
||||
"ok": 720,
|
||||
"ko": 0,
|
||||
"ko_pct": 0.0,
|
||||
"p50": 51,
|
||||
"p95": 57,
|
||||
"p99": 58,
|
||||
"max": 61
|
||||
},
|
||||
{
|
||||
"step": "rate 150/s",
|
||||
"requests": 900,
|
||||
"ok": 900,
|
||||
"ko": 0,
|
||||
"ko_pct": 0.0,
|
||||
"p50": 52,
|
||||
"p95": 57,
|
||||
"p99": 60,
|
||||
"max": 64
|
||||
},
|
||||
{
|
||||
"step": "rate 180/s",
|
||||
"requests": 1080,
|
||||
"ok": 1029,
|
||||
"ko": 51,
|
||||
"ko_pct": 4.72,
|
||||
"p50": 543,
|
||||
"p95": 685,
|
||||
"p99": 689,
|
||||
"max": 692
|
||||
},
|
||||
{
|
||||
"step": "rate 210/s",
|
||||
"requests": 1260,
|
||||
"ok": 946,
|
||||
"ko": 314,
|
||||
"ko_pct": 24.92,
|
||||
"p50": 676,
|
||||
"p95": 693,
|
||||
"p99": 699,
|
||||
"max": 711
|
||||
},
|
||||
{
|
||||
"step": "rate 240/s",
|
||||
"requests": 1429,
|
||||
"ok": 943,
|
||||
"ko": 486,
|
||||
"ko_pct": 34.01,
|
||||
"p50": 672,
|
||||
"p95": 689,
|
||||
"p99": 695,
|
||||
"max": 702
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,101 @@
|
||||
[
|
||||
{
|
||||
"step": "rate 030/s",
|
||||
"requests": 179,
|
||||
"ok": 179,
|
||||
"ko": 0,
|
||||
"ko_pct": 0.0,
|
||||
"p50": 51,
|
||||
"p95": 56,
|
||||
"p99": 57,
|
||||
"max": 59
|
||||
},
|
||||
{
|
||||
"step": "rate 060/s",
|
||||
"requests": 360,
|
||||
"ok": 360,
|
||||
"ko": 0,
|
||||
"ko_pct": 0.0,
|
||||
"p50": 51,
|
||||
"p95": 56,
|
||||
"p99": 56,
|
||||
"max": 57
|
||||
},
|
||||
{
|
||||
"step": "rate 090/s",
|
||||
"requests": 540,
|
||||
"ok": 540,
|
||||
"ko": 0,
|
||||
"ko_pct": 0.0,
|
||||
"p50": 51,
|
||||
"p95": 56,
|
||||
"p99": 56,
|
||||
"max": 64
|
||||
},
|
||||
{
|
||||
"step": "rate 120/s",
|
||||
"requests": 720,
|
||||
"ok": 720,
|
||||
"ko": 0,
|
||||
"ko_pct": 0.0,
|
||||
"p50": 51,
|
||||
"p95": 56,
|
||||
"p99": 56,
|
||||
"max": 58
|
||||
},
|
||||
{
|
||||
"step": "rate 150/s",
|
||||
"requests": 900,
|
||||
"ok": 900,
|
||||
"ko": 0,
|
||||
"ko_pct": 0.0,
|
||||
"p50": 52,
|
||||
"p95": 56,
|
||||
"p99": 58,
|
||||
"max": 60
|
||||
},
|
||||
{
|
||||
"step": "rate 180/s",
|
||||
"requests": 1080,
|
||||
"ok": 1037,
|
||||
"ko": 43,
|
||||
"ko_pct": 4.0,
|
||||
"p50": 506,
|
||||
"p95": 682,
|
||||
"p99": 688,
|
||||
"max": 693
|
||||
},
|
||||
{
|
||||
"step": "rate 210/s",
|
||||
"requests": 1260,
|
||||
"ok": 945,
|
||||
"ko": 315,
|
||||
"ko_pct": 25.0,
|
||||
"p50": 675,
|
||||
"p95": 691,
|
||||
"p99": 698,
|
||||
"max": 702
|
||||
},
|
||||
{
|
||||
"step": "rate 240/s",
|
||||
"requests": 1440,
|
||||
"ok": 948,
|
||||
"ko": 492,
|
||||
"ko_pct": 34.2,
|
||||
"p50": 674,
|
||||
"p95": 693,
|
||||
"p99": 698,
|
||||
"max": 706
|
||||
},
|
||||
{
|
||||
"step": "ramp",
|
||||
"requests": 1885,
|
||||
"ok": 1678,
|
||||
"ko": 207,
|
||||
"ko_pct": 11.0,
|
||||
"p50": 55,
|
||||
"p95": 687,
|
||||
"p99": 694,
|
||||
"max": 701
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>4.1.2</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<groupId>com.ankurm.testing</groupId>
|
||||
<artifactId>gatling-spring-boot-lab</artifactId>
|
||||
<version>1.0.0</version>
|
||||
|
||||
<properties>
|
||||
<java.version>25</java.version>
|
||||
<maven.compiler.release>25</maven.compiler.release>
|
||||
<gatling.version>3.16.0</gatling.version>
|
||||
<gatling-maven-plugin.version>4.21.12</gatling-maven-plugin.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webmvc</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.gatling.highcharts</groupId>
|
||||
<artifactId>gatling-charts-highcharts</artifactId>
|
||||
<version>${gatling.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
<!-- Gatling simulations live in src/test/java. Pick one with -Dgatling.simulationClass=... -->
|
||||
<plugin>
|
||||
<groupId>io.gatling</groupId>
|
||||
<artifactId>gatling-maven-plugin</artifactId>
|
||||
<version>${gatling-maven-plugin.version}</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,145 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Turns the raw load-test artefacts into the tables and chart quoted in the post.
|
||||
|
||||
analyze.py gatling <report-dir> -> output/10-gatling-steps-table.txt, output/knee-gatling.json
|
||||
analyze.py k6 <k6-json> -> output/21-k6-steps-table.txt, output/knee-k6.json
|
||||
analyze.py chart -> output/30-knee-chart.svg
|
||||
|
||||
Gatling's open-source report has no stats.json, so the per-step rows are read from the
|
||||
statistics table in index.html (columns: total, ok, ko, ko%, req/s, min, p50, p75, p95, p99, max, mean, stddev).
|
||||
Standard library only.
|
||||
"""
|
||||
import html, json, math, re, sys
|
||||
from pathlib import Path
|
||||
|
||||
OUT = Path(__file__).resolve().parent.parent / "output"
|
||||
KEYS = ["step", "requests", "ok", "ko", "ko_pct", "p50", "p95", "p99", "max"]
|
||||
|
||||
|
||||
def gatling(report_dir):
|
||||
page = (Path(report_dir) / "index.html").read_text(encoding="utf-8")
|
||||
rows = []
|
||||
for tr in re.findall(r'<tr id="req_[^"]+" data-parent="ROOT">(.*?)</tr>', page, re.S):
|
||||
name = html.unescape(re.search(r'class="ellipsed-name">([^<]+)<', tr).group(1)).replace("quote ", "")
|
||||
c = {int(n): v for n, v in re.findall(r'class="value [a-z ]*col-(\d+)">([^<]*)<', tr)}
|
||||
rows.append(dict(step=name, requests=int(c[2]), ok=int(c[3]), ko=int(c[4]), ko_pct=float(c[5]),
|
||||
p50=int(c[8]), p95=int(c[10]), p99=int(c[11]), max=int(c[12])))
|
||||
return rows
|
||||
|
||||
|
||||
def pct(values, p):
|
||||
"""k6's own method: linear interpolation between closest ranks."""
|
||||
if not values:
|
||||
return 0
|
||||
s = sorted(values)
|
||||
idx = p / 100 * (len(s) - 1)
|
||||
lo, hi = math.floor(idx), math.ceil(idx)
|
||||
return s[lo] + (s[hi] - s[lo]) * (idx - lo)
|
||||
|
||||
|
||||
def k6(json_file):
|
||||
dur, failed = {}, {}
|
||||
with open(json_file, encoding="utf-8") as f:
|
||||
for line in f:
|
||||
if '"http_req_duration"' not in line and '"http_req_failed"' not in line:
|
||||
continue
|
||||
o = json.loads(line)
|
||||
if o.get("type") != "Point":
|
||||
continue
|
||||
tags = o["data"]["tags"]
|
||||
if "step" not in tags:
|
||||
continue
|
||||
if o["metric"] == "http_req_duration":
|
||||
dur.setdefault(tags["step"], []).append(o["data"]["value"])
|
||||
elif o["metric"] == "http_req_failed":
|
||||
failed.setdefault(tags["step"], []).append(o["data"]["value"])
|
||||
rows = []
|
||||
for step in sorted(dur, key=lambda s: (s == "ramp", s)):
|
||||
n = len(dur[step]); ko = int(sum(failed.get(step, [])))
|
||||
rows.append(dict(step=step, requests=n, ok=n - ko, ko=ko, ko_pct=round(100 * ko / n, 1),
|
||||
p50=round(pct(dur[step], 50)), p95=round(pct(dur[step], 95)),
|
||||
p99=round(pct(dur[step], 99)), max=round(max(dur[step]))))
|
||||
return rows
|
||||
|
||||
|
||||
def table(rows):
|
||||
# Gatling lists the ramp row where it was first seen; show steps in rate order, ramp last.
|
||||
rows = sorted(rows, key=lambda r: (r["step"] == "ramp", r["step"]))
|
||||
lines = [f'{"step":<13}{"requests":>9}{"ok":>7}{"ko":>6}{"ko %":>7}{"p50":>6}{"p95":>6}{"p99":>6}{"max":>6} (times in ms)']
|
||||
for r in rows:
|
||||
lines.append(f'{r["step"]:<13}{r["requests"]:>9}{r["ok"]:>7}{r["ko"]:>6}{r["ko_pct"]:>7.1f}'
|
||||
f'{r["p50"]:>6}{r["p95"]:>6}{r["p99"]:>6}{r["max"]:>6}')
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
def chart():
|
||||
g = {r["step"]: r for r in json.loads((OUT / "knee-gatling.json").read_text())}
|
||||
k = {}
|
||||
if (OUT / "knee-k6.json").exists():
|
||||
k = {r["step"]: r for r in json.loads((OUT / "knee-k6.json").read_text())}
|
||||
steps = [f"rate {r:03d}/s" for r in range(30, 241, 30)]
|
||||
X0, X1, Y0, Y1 = 70, 700, 210, 40 # plot box (y grows downwards)
|
||||
xs = {s: X0 + (X1 - X0) * i / (len(steps) - 1) for i, s in enumerate(steps)}
|
||||
ymax = 900
|
||||
y = lambda ms: Y0 - (Y0 - Y1) * min(ms, ymax) / ymax
|
||||
|
||||
def line(series, key, cls):
|
||||
pts = " ".join(f"{xs[s]:.0f},{y(series[s][key]):.0f}" for s in steps if s in series)
|
||||
dots = "".join(f'<circle class="{cls}d" cx="{xs[s]:.0f}" cy="{y(series[s][key]):.0f}" r="3"/>' for s in steps if s in series)
|
||||
return f'<polyline class="{cls}" points="{pts}"/>{dots}'
|
||||
|
||||
o = ['<svg viewBox="0 0 740 490" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Two panels over eight injected request rates from 30 to 240 per second. Top: Gatling p50 and p95 response time, flat near 50 to 60 milliseconds at low rates and climbing steeply from 150 per second, with k6 p95 shown for comparison. Bottom: percentage of requests rejected with HTTP 503, zero up to 150 per second and rising from 180 per second.">',
|
||||
'<style>.t{font:600 14px sans-serif;fill:#222}.c{font:12px sans-serif;fill:#444}.m{font:11px monospace;fill:#555}.ax{stroke:#b7bec9;stroke-width:1}.gr{stroke:#e2e5ea;stroke-width:1}'
|
||||
'.p95{fill:none;stroke:#5b7fc7;stroke-width:2.5}.p95d{fill:#5b7fc7}.p50{fill:none;stroke:#4a9d63;stroke-width:2.5}.p50d{fill:#4a9d63}'
|
||||
'.k95{fill:none;stroke:#c9973f;stroke-width:2;stroke-dasharray:5 3}.k95d{fill:#c9973f}.kneeline{stroke:#c56a54;stroke-width:1.5;stroke-dasharray:3 3}'
|
||||
'.gb{fill:#5b7fc7}.kb{fill:#c9973f}</style>',
|
||||
'<text class="t" x="70" y="22">Response time (ms) vs injected rate (users/s), Gatling 3.16.0 and k6 v2.3.0</text>']
|
||||
for ms in range(0, ymax + 1, 300):
|
||||
o.append(f'<line class="gr" x1="{X0}" y1="{y(ms):.0f}" x2="{X1}" y2="{y(ms):.0f}"/><text class="m" x="{X0-8}" y="{y(ms)+4:.0f}" text-anchor="end">{ms}</text>')
|
||||
o.append(f'<line class="ax" x1="{X0}" y1="{Y0}" x2="{X1}" y2="{Y0}"/><line class="ax" x1="{X0}" y1="{Y1}" x2="{X0}" y2="{Y0}"/>')
|
||||
o.append(line(g, "p50", "p50")); o.append(line(g, "p95", "p95"))
|
||||
if k: o.append(line(k, "p95", "k95"))
|
||||
for s in steps:
|
||||
o.append(f'<text class="m" x="{xs[s]:.0f}" y="{Y0+16}" text-anchor="middle">{int(s[5:8])}</text>')
|
||||
# the capacity worked out from the app's settings: 8 workers / 0.050 s = 160 per second
|
||||
kx = xs["rate 150/s"] + (xs["rate 180/s"] - xs["rate 150/s"]) / 3
|
||||
o.append(f'<line class="kneeline" x1="{kx:.0f}" y1="{Y1}" x2="{kx:.0f}" y2="{Y0}"/><text class="m" x="{kx-6:.0f}" y="{Y1+110}" text-anchor="end">calculated capacity: 8 workers / 0.050 s = 160 per second</text>')
|
||||
# legend
|
||||
o.append('<line class="p50" x1="90" y1="58" x2="120" y2="58"/><text class="c" x="126" y="62">Gatling p50</text>'
|
||||
'<line class="p95" x1="90" y1="76" x2="120" y2="76"/><text class="c" x="126" y="80">Gatling p95</text>')
|
||||
if k: o.append('<line class="k95" x1="90" y1="94" x2="120" y2="94"/><text class="c" x="126" y="98">k6 p95</text>')
|
||||
# bottom panel: KO %
|
||||
B0, B1 = 440, 300
|
||||
kmax = max([r["ko_pct"] for r in g.values()] + [r["ko_pct"] for r in k.values()] + [1])
|
||||
top = math.ceil(kmax / 10) * 10
|
||||
by = lambda v: B0 - (B0 - B1) * v / top
|
||||
o.append(f'<text class="t" x="70" y="276">Requests rejected with HTTP 503 (%)</text>')
|
||||
for v in range(0, top + 1, 10):
|
||||
o.append(f'<line class="gr" x1="{X0}" y1="{by(v):.0f}" x2="{X1}" y2="{by(v):.0f}"/><text class="m" x="{X0-8}" y="{by(v)+4:.0f}" text-anchor="end">{v}</text>')
|
||||
for s in steps:
|
||||
for off, series, cls in ((-9, g, "gb"), (3, k, "kb")):
|
||||
if s in series:
|
||||
h = B0 - by(series[s]["ko_pct"])
|
||||
o.append(f'<rect class="{cls}" x="{xs[s]+off:.0f}" y="{by(series[s]["ko_pct"]):.1f}" width="8" height="{h:.1f}"/>')
|
||||
o.append(f'<text class="m" x="{xs[s]:.0f}" y="{B0+16}" text-anchor="middle">{int(s[5:8])}</text>')
|
||||
o.append(f'<line class="ax" x1="{X0}" y1="{B0}" x2="{X1}" y2="{B0}"/>')
|
||||
o.append(f'<text class="c" x="{(X0+X1)//2}" y="{B0+34}" text-anchor="middle">injected rate, users per second (blue = Gatling, amber = k6)</text>')
|
||||
o.append('</svg>')
|
||||
(OUT / "30-knee-chart.svg").write_text("\n".join(o) + "\n", encoding="utf-8")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
OUT.mkdir(exist_ok=True)
|
||||
cmd = sys.argv[1]
|
||||
if cmd == "gatling":
|
||||
rows = gatling(sys.argv[2])
|
||||
(OUT / "knee-gatling.json").write_text(json.dumps(rows, indent=1) + "\n")
|
||||
(OUT / "10-gatling-steps-table.txt").write_text(table(rows))
|
||||
elif cmd == "k6":
|
||||
rows = k6(sys.argv[2])
|
||||
(OUT / "knee-k6.json").write_text(json.dumps(rows, indent=1) + "\n")
|
||||
(OUT / "21-k6-steps-table.txt").write_text(table(rows))
|
||||
elif cmd == "chart":
|
||||
chart()
|
||||
else:
|
||||
sys.exit(__doc__)
|
||||
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env bash
|
||||
# Regenerates every file in gatling-spring-boot/output/.
|
||||
# Needs JDK 25 and Maven on the PATH and python3 (standard library only).
|
||||
# k6 is optional: set K6=/path/to/k6 or have it on the PATH; without it the k6 steps are skipped.
|
||||
# Takes about 6 minutes: no single load run is longer than ~70 seconds.
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
PORT="${PORT:-18300}"
|
||||
export APP_URL="http://localhost:${PORT}"
|
||||
K6="${K6:-$(command -v k6 || true)}"
|
||||
OUT=output
|
||||
JAR=target/gatling-spring-boot-lab-1.0.0.jar
|
||||
|
||||
# keep transcripts free of JVM launcher noise that varies per machine
|
||||
filter() { grep -vE '^(Picked up JAVA_TOOL_OPTIONS|WARNING: )' || true; }
|
||||
|
||||
rm -f "$OUT"/*.txt "$OUT"/*.json "$OUT"/*.svg
|
||||
mkdir -p "$OUT"
|
||||
|
||||
# 1. unit test: the capacity limit the whole post relies on (writes output/01-saturation-test.txt)
|
||||
mvn -B -q clean test 2>&1 | filter
|
||||
|
||||
# 2. build and start the app under test
|
||||
mvn -B -q package -DskipTests 2>&1 | filter
|
||||
mkdir -p target/k6
|
||||
PORT="$PORT" java -jar "$JAR" > target/app.log 2>&1 &
|
||||
APP_PID=$!
|
||||
trap 'kill $APP_PID 2>/dev/null || true' EXIT
|
||||
for _ in $(seq 1 60); do
|
||||
curl -fs "$APP_URL/api/stats" > /dev/null 2>&1 && break
|
||||
sleep 1
|
||||
done
|
||||
curl -fs "$APP_URL/api/quote/ORCL" > "$OUT/02-single-request.txt"; echo >> "$OUT/02-single-request.txt"
|
||||
|
||||
# 3. Gatling runs, one simulation per mvn invocation
|
||||
gatling() { # <output file> <simulation class>
|
||||
mvn -B test-compile gatling:test -Dgatling.simulationClass="lab.gatling.$2" 2>&1 | filter > "$OUT/$1"
|
||||
}
|
||||
gatling 03-basic-console.txt BasicSimulation
|
||||
gatling 04-knee-steps-console.txt KneeStepsSimulation
|
||||
python3 scripts/analyze.py gatling "$(ls -dt target/gatling/kneestepssimulation-* | head -1)"
|
||||
gatling 06-knee-ramp-console.txt KneeRampSimulation
|
||||
gatling 07-closed-model-console.txt ClosedModelSimulation
|
||||
|
||||
# 4. the run that must fail the build; keep going, record Maven's exit code
|
||||
set +e
|
||||
gatling 05-failing-assertion-console.txt FailingAssertionSimulation
|
||||
echo "maven exit code: ${PIPESTATUS[0]}" > "$OUT/05b-failing-assertion-exit.txt"
|
||||
set -e
|
||||
|
||||
# 5. k6, same app, same profile
|
||||
if [ -n "$K6" ]; then
|
||||
"$K6" version > "$OUT/20-k6-version.txt"
|
||||
"$K6" run --quiet --out json=target/k6/knee.json k6/knee.js > "$OUT/22-k6-knee-console.txt" 2>&1
|
||||
python3 scripts/analyze.py k6 target/k6/knee.json
|
||||
set +e
|
||||
"$K6" run --quiet k6/threshold-fail.js > "$OUT/23-k6-threshold-fail-console.txt" 2>&1
|
||||
echo "k6 exit code: $?" > "$OUT/23b-k6-threshold-fail-exit.txt"
|
||||
set -e
|
||||
else
|
||||
echo "k6 not found: skipping k6 steps" >&2
|
||||
fi
|
||||
|
||||
python3 scripts/analyze.py chart
|
||||
echo "done: $(ls "$OUT" | wc -l) files in $OUT/"
|
||||
@@ -0,0 +1,64 @@
|
||||
package lab.gatling;
|
||||
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* A stand-in for a slow dependency with limited capacity (a legacy pricing service, a
|
||||
* connection-limited database). {@code workers} requests are served at once, each taking
|
||||
* about {@code delayMs}; up to {@code queueSize} more wait; everything beyond is rejected.
|
||||
*
|
||||
* Capacity is therefore workers / delay: 8 / 0.050 s = 160 requests per second.
|
||||
* Below that the service answers in ~50 ms. Above it the queue fills, latency climbs, and
|
||||
* once the queue is full requests are rejected. That is the knee the load test looks for.
|
||||
*/
|
||||
@Component
|
||||
public class DownstreamPool {
|
||||
|
||||
private final ThreadPoolExecutor executor;
|
||||
private final int delayMs;
|
||||
private final AtomicLong rejected = new AtomicLong();
|
||||
|
||||
public DownstreamPool(@Value("${lab.downstream.workers:8}") int workers,
|
||||
@Value("${lab.downstream.queue:100}") int queueSize,
|
||||
@Value("${lab.downstream.delay-ms:50}") int delayMs) {
|
||||
this.delayMs = delayMs;
|
||||
this.executor = new ThreadPoolExecutor(workers, workers, 0, TimeUnit.SECONDS,
|
||||
new ArrayBlockingQueue<>(queueSize), new ThreadPoolExecutor.AbortPolicy());
|
||||
}
|
||||
|
||||
/** Runs the lookup on the bounded pool and waits for it. Throws {@link Saturated} when full. */
|
||||
public double price(String symbol) throws InterruptedException {
|
||||
Callable<Double> lookup = () -> {
|
||||
// 0.9x - 1.1x of the nominal delay, derived from the symbol so runs are repeatable
|
||||
long ms = delayMs - delayMs / 10 + Math.floorMod(symbol.hashCode(), Math.max(1, delayMs / 5 + 1));
|
||||
Thread.sleep(ms);
|
||||
return 10 + Math.floorMod(symbol.hashCode(), 49000) / 100.0;
|
||||
};
|
||||
try {
|
||||
return executor.submit(lookup).get();
|
||||
} catch (RejectedExecutionException e) {
|
||||
rejected.incrementAndGet();
|
||||
throw new Saturated();
|
||||
} catch (ExecutionException e) {
|
||||
throw new IllegalStateException(e.getCause());
|
||||
}
|
||||
}
|
||||
|
||||
public Stats stats() {
|
||||
return new Stats(executor.getActiveCount(), executor.getQueue().size(),
|
||||
executor.getCompletedTaskCount(), rejected.get());
|
||||
}
|
||||
|
||||
public record Stats(int active, int queued, long completed, long rejected) { }
|
||||
|
||||
public static class Saturated extends RuntimeException { }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package lab.gatling;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class LabApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(LabApplication.class, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package lab.gatling;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
public class QuoteController {
|
||||
|
||||
private final DownstreamPool pool;
|
||||
|
||||
public QuoteController(DownstreamPool pool) {
|
||||
this.pool = pool;
|
||||
}
|
||||
|
||||
public record Quote(String symbol, double price) { }
|
||||
|
||||
@GetMapping("/api/quote/{symbol}")
|
||||
public Quote quote(@PathVariable String symbol) throws InterruptedException {
|
||||
return new Quote(symbol, pool.price(symbol));
|
||||
}
|
||||
|
||||
/** Diagnostic endpoint: delete before shipping. */
|
||||
@GetMapping("/api/stats")
|
||||
public DownstreamPool.Stats stats() {
|
||||
return pool.stats();
|
||||
}
|
||||
|
||||
@ExceptionHandler(DownstreamPool.Saturated.class)
|
||||
ResponseEntity<String> saturated() {
|
||||
return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).header("Retry-After", "1").body("downstream saturated");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
server.port=${PORT:18300}
|
||||
spring.application.name=gatling-spring-boot-lab
|
||||
@@ -0,0 +1,44 @@
|
||||
package lab.gatling;
|
||||
|
||||
import static io.gatling.javaapi.core.CoreDsl.*;
|
||||
import static io.gatling.javaapi.http.HttpDsl.*;
|
||||
|
||||
import io.gatling.javaapi.core.FeederBuilder;
|
||||
import io.gatling.javaapi.core.ScenarioBuilder;
|
||||
import io.gatling.javaapi.core.Simulation;
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* A scenario with a feeder, checks and a mix of injection steps.
|
||||
* Every user looks up two random symbols with a short think time in between.
|
||||
*/
|
||||
public class BasicSimulation extends Simulation {
|
||||
|
||||
FeederBuilder<String> symbols = csv("data/symbols.csv").random();
|
||||
|
||||
ScenarioBuilder browse = scenario("Browse quotes")
|
||||
.feed(symbols)
|
||||
.exec(http("get quote")
|
||||
.get("/api/quote/#{symbol}")
|
||||
.check(
|
||||
status().is(200),
|
||||
jmesPath("symbol").isEL("#{symbol}"),
|
||||
jmesPath("price").ofDouble().gt(0.0)))
|
||||
.pause(Duration.ofMillis(200), Duration.ofMillis(500))
|
||||
.feed(symbols)
|
||||
.exec(http("get another quote")
|
||||
.get("/api/quote/#{symbol}")
|
||||
.check(status().is(200)));
|
||||
|
||||
{
|
||||
setUp(browse.injectOpen(
|
||||
atOnceUsers(5),
|
||||
nothingFor(Duration.ofSeconds(2)),
|
||||
rampUsers(20).during(Duration.ofSeconds(5)),
|
||||
constantUsersPerSec(10).during(Duration.ofSeconds(10))))
|
||||
.protocols(Config.protocol())
|
||||
.assertions(
|
||||
global().responseTime().percentile(95).lt(200),
|
||||
global().successfulRequests().percent().gt(99.0));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package lab.gatling;
|
||||
|
||||
import static io.gatling.javaapi.core.CoreDsl.*;
|
||||
import static io.gatling.javaapi.http.HttpDsl.*;
|
||||
|
||||
import io.gatling.javaapi.core.ScenarioBuilder;
|
||||
import io.gatling.javaapi.core.Simulation;
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* CLOSED model: 50 virtual users, each sends its next request only after the previous one returned.
|
||||
* The server can never be asked for more than 50 requests at once, so it is never overloaded
|
||||
* the way an open arrival rate can overload it.
|
||||
*/
|
||||
public class ClosedModelSimulation extends Simulation {
|
||||
|
||||
ScenarioBuilder loop = scenario("Closed loop")
|
||||
.feed(csv("data/symbols.csv").random())
|
||||
.during(Duration.ofSeconds(15)).on(
|
||||
exec(http("get quote").get("/api/quote/#{symbol}").check(status().is(200))));
|
||||
|
||||
{
|
||||
setUp(loop.injectClosed(constantConcurrentUsers(50).during(Duration.ofSeconds(15))))
|
||||
.protocols(Config.protocol());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package lab.gatling;
|
||||
|
||||
import static io.gatling.javaapi.http.HttpDsl.http;
|
||||
|
||||
import io.gatling.javaapi.http.HttpProtocolBuilder;
|
||||
|
||||
/** Shared protocol so every simulation targets the same app. */
|
||||
final class Config {
|
||||
private Config() { }
|
||||
|
||||
static String baseUrl() {
|
||||
String url = System.getenv("APP_URL");
|
||||
return url != null ? url : "http://localhost:18300";
|
||||
}
|
||||
|
||||
static HttpProtocolBuilder protocol() {
|
||||
return http.baseUrl(baseUrl())
|
||||
.acceptHeader("application/json")
|
||||
.userAgentHeader("gatling-lab");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package lab.gatling;
|
||||
|
||||
import static io.gatling.javaapi.core.CoreDsl.*;
|
||||
import static io.gatling.javaapi.http.HttpDsl.*;
|
||||
|
||||
import io.gatling.javaapi.core.ScenarioBuilder;
|
||||
import io.gatling.javaapi.core.Simulation;
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* Deliberately fails: 220 users/s is above the ~160/s capacity, but the assertions below
|
||||
* demand a p95 under 200 ms and at least 99% success. Gatling exits non-zero, so Maven fails the build.
|
||||
*/
|
||||
public class FailingAssertionSimulation extends Simulation {
|
||||
|
||||
ScenarioBuilder oneRequest = scenario("One quote per user")
|
||||
.feed(csv("data/symbols.csv").random())
|
||||
.exec(http("get quote").get("/api/quote/#{symbol}").check(status().is(200)));
|
||||
|
||||
{
|
||||
setUp(oneRequest.injectOpen(constantUsersPerSec(220).during(Duration.ofSeconds(15))))
|
||||
.protocols(Config.protocol())
|
||||
.assertions(
|
||||
global().responseTime().percentile(95).lt(200),
|
||||
global().successfulRequests().percent().gt(99.0),
|
||||
details("get quote").responseTime().max().lt(1000));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package lab.gatling;
|
||||
|
||||
import static io.gatling.javaapi.core.CoreDsl.*;
|
||||
import static io.gatling.javaapi.http.HttpDsl.*;
|
||||
|
||||
import io.gatling.javaapi.core.ScenarioBuilder;
|
||||
import io.gatling.javaapi.core.Simulation;
|
||||
import java.time.Duration;
|
||||
|
||||
/** The stress profile: one continuous ramp from 10 to 250 users/s over 40 seconds. */
|
||||
public class KneeRampSimulation extends Simulation {
|
||||
|
||||
ScenarioBuilder oneRequest = scenario("One quote per user")
|
||||
.feed(csv("data/symbols.csv").random())
|
||||
.exec(http("get quote").get("/api/quote/#{symbol}").check(status().is(200)));
|
||||
|
||||
{
|
||||
setUp(oneRequest.injectOpen(
|
||||
rampUsersPerSec(10).to(250).during(Duration.ofSeconds(40))))
|
||||
.protocols(Config.protocol());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package lab.gatling;
|
||||
|
||||
import static io.gatling.javaapi.core.CoreDsl.*;
|
||||
import static io.gatling.javaapi.http.HttpDsl.*;
|
||||
|
||||
import io.gatling.javaapi.core.ScenarioBuilder;
|
||||
import io.gatling.javaapi.core.Simulation;
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
* Finds the knee. OPEN model: one request per arriving user, so the arrival rate IS the request rate.
|
||||
* Rates 30, 60, ... 240 users/s, each held 6 s, separated by 2 s ramps.
|
||||
*
|
||||
* Gatling's open-source report aggregates everything into one table, which would hide the knee.
|
||||
* So each request is NAMED after the step it was sent in ("rate 150/s"); the report then has one
|
||||
* row per step. Requests sent during a ramp between two steps are named "ramp".
|
||||
*/
|
||||
public class KneeStepsSimulation extends Simulation {
|
||||
|
||||
static final int STEPS = 8, RATE_STEP = 30, HOLD_S = 6, RAMP_S = 2;
|
||||
|
||||
// Set by the first user to arrive; every later user derives its step from the time elapsed since then.
|
||||
static final AtomicLong startedAt = new AtomicLong();
|
||||
|
||||
static String stepLabel() {
|
||||
long now = System.currentTimeMillis();
|
||||
startedAt.compareAndSet(0, now);
|
||||
long elapsedMs = now - startedAt.get();
|
||||
long slot = Math.max(0, elapsedMs) / ((HOLD_S + RAMP_S) * 1000L);
|
||||
long into = Math.max(0, elapsedMs) % ((HOLD_S + RAMP_S) * 1000L);
|
||||
if (into >= HOLD_S * 1000L || slot >= STEPS) {
|
||||
return "ramp";
|
||||
}
|
||||
return String.format("rate %03d/s", RATE_STEP * (slot + 1));
|
||||
}
|
||||
|
||||
ScenarioBuilder oneRequest = scenario("One quote per user")
|
||||
.feed(csv("data/symbols.csv").random())
|
||||
.exec(session -> session.set("step", stepLabel()))
|
||||
.exec(http("quote #{step}").get("/api/quote/#{symbol}").check(status().is(200)));
|
||||
|
||||
{
|
||||
setUp(oneRequest.injectOpen(
|
||||
incrementUsersPerSec(RATE_STEP)
|
||||
.times(STEPS)
|
||||
.eachLevelLasting(Duration.ofSeconds(HOLD_S))
|
||||
.separatedByRampsLasting(Duration.ofSeconds(RAMP_S))
|
||||
.startingFrom(RATE_STEP)))
|
||||
.protocols(Config.protocol());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package lab.gatling;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Proves the lab app has the capacity limit the post depends on, without any load tool:
|
||||
* 2 workers + a queue of 3 can hold 5 requests; the 6th and 7th are rejected.
|
||||
*/
|
||||
class SaturationTest {
|
||||
|
||||
@Test
|
||||
void poolRejectsOnceWorkersAndQueueAreFull() throws Exception {
|
||||
DownstreamPool pool = new DownstreamPool(2, 3, 400);
|
||||
List<Future<String>> results = new ArrayList<>();
|
||||
CountDownLatch go = new CountDownLatch(1);
|
||||
try (ExecutorService callers = Executors.newFixedThreadPool(7)) {
|
||||
for (int i = 0; i < 7; i++) {
|
||||
final int n = i;
|
||||
Callable<String> call = () -> {
|
||||
go.await();
|
||||
Thread.sleep(n * 15L); // stagger so the first five are admitted in order
|
||||
try {
|
||||
pool.price("ORCL");
|
||||
return "ok";
|
||||
} catch (DownstreamPool.Saturated e) {
|
||||
return "rejected";
|
||||
}
|
||||
};
|
||||
results.add(callers.submit(call));
|
||||
}
|
||||
go.countDown();
|
||||
long ok = 0;
|
||||
long rejected = 0;
|
||||
for (Future<String> f : results) {
|
||||
if (f.get().equals("ok")) ok++; else rejected++;
|
||||
}
|
||||
DownstreamPool.Stats stats = pool.stats();
|
||||
new Transcript("01-saturation-test.txt")
|
||||
.line("workers=2 queue=3 delay=400ms, 7 simultaneous callers")
|
||||
.line("served=%d rejected=%d", ok, rejected)
|
||||
.line("pool stats: completed=%d rejected=%d", stats.completed(), stats.rejected())
|
||||
.write();
|
||||
assertThat(ok).isEqualTo(5);
|
||||
assertThat(rejected).isEqualTo(2);
|
||||
assertThat(stats.rejected()).isEqualTo(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package lab.gatling;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/** Collects lines during a test and writes them to output/NN-name.txt. */
|
||||
final class Transcript {
|
||||
private final String file;
|
||||
private final List<String> lines = new ArrayList<>();
|
||||
|
||||
Transcript(String file) {
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
Transcript line(String format, Object... args) {
|
||||
lines.add(args.length == 0 ? format : String.format(format, args));
|
||||
return this;
|
||||
}
|
||||
|
||||
void write() {
|
||||
try {
|
||||
Path dir = Path.of("output");
|
||||
Files.createDirectories(dir);
|
||||
Files.writeString(dir.resolve(file), String.join("\n", lines) + "\n");
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
symbol
|
||||
ORCL
|
||||
MSFT
|
||||
GOOG
|
||||
AMZN
|
||||
TCS
|
||||
INFY
|
||||
HDFC
|
||||
RELI
|
||||
SBIN
|
||||
WIPRO
|
||||
LT
|
||||
ITC
|
||||
|
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Gatling logs through logback, and Spring Boot's logback support is also on the test classpath.
|
||||
Without this file logback prints a screenful of its own status lines before every simulation. -->
|
||||
<configuration>
|
||||
<statusListener class="ch.qos.logback.core.status.NopStatusListener"/>
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder><pattern>%d{HH:mm:ss.SSS} [%-5level] %logger{15} - %msg%n</pattern></encoder>
|
||||
</appender>
|
||||
<root level="WARN"><appender-ref ref="CONSOLE"/></root>
|
||||
</configuration>
|
||||
Reference in New Issue
Block a user