diff --git a/README.md b/README.md
index 166f659..9a3cfbc 100644
--- a/README.md
+++ b/README.md
@@ -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.
diff --git a/gatling-spring-boot/README.md b/gatling-spring-boot/README.md
new file mode 100644
index 0000000..3ef1f4f
--- /dev/null
+++ b/gatling-spring-boot/README.md
@@ -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
diff --git a/gatling-spring-boot/k6/knee.js b/gatling-spring-boot/k6/knee.js
new file mode 100644
index 0000000..a35514f
--- /dev/null
+++ b/gatling-spring-boot/k6/knee.js
@@ -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 });
+}
diff --git a/gatling-spring-boot/k6/threshold-fail.js b/gatling-spring-boot/k6/threshold-fail.js
new file mode 100644
index 0000000..e09644f
--- /dev/null
+++ b/gatling-spring-boot/k6/threshold-fail.js
@@ -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)]}`);
+}
diff --git a/gatling-spring-boot/output/01-saturation-test.txt b/gatling-spring-boot/output/01-saturation-test.txt
new file mode 100644
index 0000000..dd22a65
--- /dev/null
+++ b/gatling-spring-boot/output/01-saturation-test.txt
@@ -0,0 +1,3 @@
+workers=2 queue=3 delay=400ms, 7 simultaneous callers
+served=5 rejected=2
+pool stats: completed=5 rejected=2
diff --git a/gatling-spring-boot/output/02-single-request.txt b/gatling-spring-boot/output/02-single-request.txt
new file mode 100644
index 0000000..d6abd26
--- /dev/null
+++ b/gatling-spring-boot/output/02-single-request.txt
@@ -0,0 +1 @@
+{"symbol":"ORCL","price":344.44}
diff --git a/gatling-spring-boot/output/03-basic-console.txt b/gatling-spring-boot/output/03-basic-console.txt
new file mode 100644
index 0000000..eca00a1
--- /dev/null
+++ b/gatling-spring-boot/output/03-basic-console.txt
@@ -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] ------------------------------------------------------------------------
diff --git a/gatling-spring-boot/output/04-knee-steps-console.txt b/gatling-spring-boot/output/04-knee-steps-console.txt
new file mode 100644
index 0000000..2d2a6be
--- /dev/null
+++ b/gatling-spring-boot/output/04-knee-steps-console.txt
@@ -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] ------------------------------------------------------------------------
diff --git a/gatling-spring-boot/output/05-failing-assertion-console.txt b/gatling-spring-boot/output/05-failing-assertion-console.txt
new file mode 100644
index 0000000..f1cf1e4
--- /dev/null
+++ b/gatling-spring-boot/output/05-failing-assertion-console.txt
@@ -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
diff --git a/gatling-spring-boot/output/05b-failing-assertion-exit.txt b/gatling-spring-boot/output/05b-failing-assertion-exit.txt
new file mode 100644
index 0000000..47c3598
--- /dev/null
+++ b/gatling-spring-boot/output/05b-failing-assertion-exit.txt
@@ -0,0 +1 @@
+maven exit code: 1
diff --git a/gatling-spring-boot/output/06-knee-ramp-console.txt b/gatling-spring-boot/output/06-knee-ramp-console.txt
new file mode 100644
index 0000000..dec5ef3
--- /dev/null
+++ b/gatling-spring-boot/output/06-knee-ramp-console.txt
@@ -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] ------------------------------------------------------------------------
diff --git a/gatling-spring-boot/output/07-closed-model-console.txt b/gatling-spring-boot/output/07-closed-model-console.txt
new file mode 100644
index 0000000..ca4cb12
--- /dev/null
+++ b/gatling-spring-boot/output/07-closed-model-console.txt
@@ -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] ------------------------------------------------------------------------
diff --git a/gatling-spring-boot/output/10-gatling-steps-table.txt b/gatling-spring-boot/output/10-gatling-steps-table.txt
new file mode 100644
index 0000000..3fe6fd2
--- /dev/null
+++ b/gatling-spring-boot/output/10-gatling-steps-table.txt
@@ -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
diff --git a/gatling-spring-boot/output/20-k6-version.txt b/gatling-spring-boot/output/20-k6-version.txt
new file mode 100644
index 0000000..bfb820f
--- /dev/null
+++ b/gatling-spring-boot/output/20-k6-version.txt
@@ -0,0 +1 @@
+k6 v2.3.0 (commit/e088784614, go1.26.8, linux/amd64)
diff --git a/gatling-spring-boot/output/21-k6-steps-table.txt b/gatling-spring-boot/output/21-k6-steps-table.txt
new file mode 100644
index 0000000..81b22b7
--- /dev/null
+++ b/gatling-spring-boot/output/21-k6-steps-table.txt
@@ -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
diff --git a/gatling-spring-boot/output/22-k6-knee-console.txt b/gatling-spring-boot/output/22-k6-knee-console.txt
new file mode 100644
index 0000000..f544539
--- /dev/null
+++ b/gatling-spring-boot/output/22-k6-knee-console.txt
@@ -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
+
+
+
diff --git a/gatling-spring-boot/output/23-k6-threshold-fail-console.txt b/gatling-spring-boot/output/23-k6-threshold-fail-console.txt
new file mode 100644
index 0000000..a34ec9f
--- /dev/null
+++ b/gatling-spring-boot/output/23-k6-threshold-fail-console.txt
@@ -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"
diff --git a/gatling-spring-boot/output/23b-k6-threshold-fail-exit.txt b/gatling-spring-boot/output/23b-k6-threshold-fail-exit.txt
new file mode 100644
index 0000000..7474890
--- /dev/null
+++ b/gatling-spring-boot/output/23b-k6-threshold-fail-exit.txt
@@ -0,0 +1 @@
+k6 exit code: 99
diff --git a/gatling-spring-boot/output/30-knee-chart.svg b/gatling-spring-boot/output/30-knee-chart.svg
new file mode 100644
index 0000000..29a6790
--- /dev/null
+++ b/gatling-spring-boot/output/30-knee-chart.svg
@@ -0,0 +1,55 @@
+
diff --git a/gatling-spring-boot/output/knee-gatling.json b/gatling-spring-boot/output/knee-gatling.json
new file mode 100644
index 0000000..ab0a0c2
--- /dev/null
+++ b/gatling-spring-boot/output/knee-gatling.json
@@ -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
+ }
+]
diff --git a/gatling-spring-boot/output/knee-k6.json b/gatling-spring-boot/output/knee-k6.json
new file mode 100644
index 0000000..a3bd2ca
--- /dev/null
+++ b/gatling-spring-boot/output/knee-k6.json
@@ -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
+ }
+]
diff --git a/gatling-spring-boot/pom.xml b/gatling-spring-boot/pom.xml
new file mode 100644
index 0000000..fad8a83
--- /dev/null
+++ b/gatling-spring-boot/pom.xml
@@ -0,0 +1,56 @@
+
+
+ 4.0.0
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 4.1.2
+
+
+
+ com.ankurm.testing
+ gatling-spring-boot-lab
+ 1.0.0
+
+
+ 25
+ 25
+ 3.16.0
+ 4.21.12
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-webmvc
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ io.gatling.highcharts
+ gatling-charts-highcharts
+ ${gatling.version}
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ io.gatling
+ gatling-maven-plugin
+ ${gatling-maven-plugin.version}
+
+
+
+
diff --git a/gatling-spring-boot/scripts/analyze.py b/gatling-spring-boot/scripts/analyze.py
new file mode 100644
index 0000000..a26b9da
--- /dev/null
+++ b/gatling-spring-boot/scripts/analyze.py
@@ -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 -> output/10-gatling-steps-table.txt, output/knee-gatling.json
+ analyze.py k6 -> 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'(.*?)
', 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'' for s in steps if s in series)
+ return f'{dots}'
+
+ o = ['')
+ (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__)
diff --git a/gatling-spring-boot/scripts/run-all.sh b/gatling-spring-boot/scripts/run-all.sh
new file mode 100644
index 0000000..148cd38
--- /dev/null
+++ b/gatling-spring-boot/scripts/run-all.sh
@@ -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() { #