perf-skills 1.0.3 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,286 +1,286 @@
1
- # Gatling Reference
2
-
3
- > Targets: Gatling 3.10+, Java DSL 3.7+
4
-
5
- Gatling is a high-performance, Scala/Java-based load testing tool designed for HTTP-heavy applications. Its simulation DSL is expressive, and its async Netty-based engine handles very high concurrency with low resource overhead.
6
-
7
- ---
8
-
9
- ## Core Concepts
10
-
11
- | Concept | Description |
12
- |---|---|
13
- | **Simulation** | Top-level test class extending `Simulation` |
14
- | **Protocol** | HTTP, JMS, or gRPC configuration |
15
- | **Scenario** | Named sequence of actions (chain of steps) |
16
- | **Feeder** | Data source for parameterization (CSV, JSON, JDBC) |
17
- | **Check** | Assertion on a response (status, body, header) |
18
- | **Population** | VU injection strategy (open/closed model) |
19
- | **Session** | VU-level state store (like JMeter `vars`) |
20
-
21
- ---
22
-
23
- ## Simulation Structure
24
-
25
- ```scala
26
- import io.gatling.core.Predef._
27
- import io.gatling.http.Predef._
28
- import scala.concurrent.duration._
29
-
30
- class CheckoutSimulation extends Simulation {
31
-
32
- // 1. Protocol config
33
- val httpProtocol = http
34
- .baseUrl("https://api.example.com")
35
- .acceptHeader("application/json")
36
- .contentTypeHeader("application/json")
37
- .shareConnections // Reuse connections across VUs (more realistic)
38
-
39
- // 2. Feeders (data)
40
- val userFeeder = csv("data/users.csv").circular
41
-
42
- // 3. Scenario (chain of steps)
43
- val loginScenario = scenario("Login and Browse")
44
- .feed(userFeeder)
45
- .exec(
46
- http("POST Login")
47
- .post("/auth/login")
48
- .body(StringBody("""{"username":"#{username}","password":"#{password}"}""")).asJson
49
- .check(status.is(200))
50
- .check(jsonPath("$.token").saveAs("authToken"))
51
- )
52
- .pause(1, 3) // Think time: 1–3 seconds
53
- .exec(
54
- http("GET Products")
55
- .get("/products")
56
- .header("Authorization", "Bearer #{authToken}")
57
- .check(status.is(200))
58
- .check(jsonPath("$[0].id").saveAs("productId"))
59
- )
60
- .pause(2)
61
-
62
- // 4. Injection (load profile)
63
- setUp(
64
- loginScenario.inject(
65
- nothingFor(5.seconds), // Wait before starting
66
- atOnceUsers(5), // Smoke check
67
- rampUsers(100).during(2.minutes),
68
- constantUsersPerSec(20).during(5.minutes),
69
- rampUsersPerSec(20).to(0).during(1.minute)
70
- ).protocols(httpProtocol)
71
- ).assertions(
72
- global.responseTime.percentile(95).lt(500),
73
- global.failedRequests.percent.lt(1)
74
- )
75
- }
76
- ```
77
-
78
- ---
79
-
80
- ## Injection Profiles
81
-
82
- ### Closed model (VU-based)
83
- ```scala
84
- rampUsers(100).during(2.minutes) // Ramp to 100 VUs over 2 min
85
- constantConcurrentUsers(100).during(5.minutes) // Hold at 100 VUs
86
- ```
87
-
88
- ### Open model (arrival-rate)
89
- ```scala
90
- constantUsersPerSec(50).during(5.minutes) // 50 new users/sec
91
- rampUsersPerSec(10).to(100).during(3.minutes) // Ramp RPS from 10 to 100
92
- heavisideUsers(1000).during(20.seconds) // S-curve injection (realistic burst)
93
- ```
94
-
95
- > For closed vs open model guidance and when to use each, see `../topics/workload-design.md`.
96
-
97
- ---
98
-
99
- ## Feeders (Parameterization)
100
-
101
- ```scala
102
- // CSV
103
- val csvFeeder = csv("data/users.csv").circular // Loop forever
104
- val csvFeeder = csv("data/users.csv").random // Pick random row
105
- val csvFeeder = csv("data/users.csv").shuffle // Randomize order, no repeat
106
- val csvFeeder = csv("data/users.csv").queue // Each VU gets next row; fail if empty
107
-
108
- // JSON
109
- val jsonFeeder = jsonFile("data/products.json").random
110
-
111
- // Custom feeder
112
- val customFeeder = Iterator.continually(Map(
113
- "timestamp" -> System.currentTimeMillis(),
114
- "uuid" -> java.util.UUID.randomUUID().toString
115
- ))
116
-
117
- // Use in scenario
118
- scenario("test").feed(csvFeeder).exec(...)
119
- ```
120
-
121
- ---
122
-
123
- ## Checks (Assertions)
124
-
125
- ```scala
126
- // Status code
127
- .check(status.is(200))
128
- .check(status.in(200, 201))
129
-
130
- // JSON body
131
- .check(jsonPath("$.userId").exists)
132
- .check(jsonPath("$.token").saveAs("token"))
133
- .check(jsonPath("$.items.length()").is("5"))
134
-
135
- // Header
136
- .check(header("X-Request-Id").exists)
137
-
138
- // Response time
139
- .check(responseTimeInMillis.lte(2000))
140
-
141
- // Body string
142
- .check(bodyString.contains("success"))
143
- .check(substring("access_granted").count.is(1))
144
-
145
- // Multiple checks (all must pass)
146
- .check(status.is(200), jsonPath("$.status").is("ok"))
147
- ```
148
-
149
- ---
150
-
151
- ## Session Variables
152
-
153
- ```scala
154
- // Save during check
155
- .check(jsonPath("$.orderId").saveAs("orderId"))
156
-
157
- // Access in body
158
- .body(StringBody("""{"orderId":"#{orderId}"}"""))
159
-
160
- // Access in Exec block
161
- .exec(session => {
162
- val orderId = session("orderId").as[String]
163
- println(s"Processing order: $orderId")
164
- session
165
- })
166
-
167
- // Conditional logic
168
- .doIf(session => session("isAdmin").as[Boolean]) {
169
- exec(http("Admin Action").get("/admin"))
170
- }
171
- ```
172
-
173
- ---
174
-
175
- ## Assertions (Global SLA)
176
-
177
- ```scala
178
- setUp(...).assertions(
179
- global.responseTime.percentile(95).lt(500), // p95 < 500ms
180
- global.responseTime.max.lt(2000), // max < 2s
181
- global.failedRequests.percent.lt(1), // Error rate < 1%
182
- global.requestsPerSec.gte(100), // Throughput >= 100 RPS
183
- forAll.failedRequests.count.is(0), // No failures on any request
184
- details("POST Login").responseTime.percentile(99).lt(1000) // Named request SLA
185
- )
186
- ```
187
-
188
- ---
189
-
190
- ## Protocol Configuration
191
-
192
- ### HTTP
193
- ```scala
194
- val httpProtocol = http
195
- .baseUrl("https://api.example.com")
196
- .proxy(Proxy("proxy.corp.net", 8080))
197
- .acceptHeader("application/json")
198
- .acceptEncodingHeader("gzip, deflate")
199
- .userAgentHeader("Gatling/Perf-Test")
200
- .maxConnectionsPerHost(10)
201
- .warmUp("https://api.example.com/health")
202
- .disableFollowRedirect // Disable for accuracy
203
- .disableAutoReferer
204
- ```
205
-
206
- ### WebSocket
207
- ```scala
208
- import io.gatling.http.Predef._
209
-
210
- exec(
211
- ws("Connect WS").connect("/ws/chat")
212
- .await(5.seconds)(
213
- ws.checkTextMessage("message received")
214
- .matching(jsonPath("$.type").is("ack"))
215
- )
216
- )
217
- .exec(ws("Send Message").sendText("""{"msg":"hello"}"""))
218
- .exec(ws("Close").close())
219
- ```
220
-
221
- ---
222
-
223
- ## Running Gatling
224
-
225
- ```bash
226
- # Maven (Scala DSL)
227
- mvn gatling:test -Dgatling.simulationClass=CheckoutSimulation
228
-
229
- # Gradle (Java DSL)
230
- ./gradlew gatlingRun
231
-
232
- # Bundle (no build tool)
233
- ./bin/gatling.sh -s CheckoutSimulation -rd "Staging load test"
234
-
235
- # With overrides
236
- mvn gatling:test \
237
- -Dgatling.simulationClass=CheckoutSimulation \
238
- -DbaseUrl=https://staging.example.com \
239
- -Dusers=100 \
240
- -Dduration=300
241
- ```
242
-
243
- ---
244
-
245
- ## Java DSL (Gatling 3.7+)
246
-
247
- Gatling now supports Java and Kotlin natively — no Scala required:
248
-
249
- ```java
250
- import io.gatling.javaapi.core.*;
251
- import io.gatling.javaapi.http.*;
252
- import static io.gatling.javaapi.core.CoreDsl.*;
253
- import static io.gatling.javaapi.http.HttpDsl.*;
254
-
255
- public class LoginSimulation extends Simulation {
256
- HttpProtocolBuilder httpProtocol = http.baseUrl("https://api.example.com");
257
-
258
- ScenarioBuilder scenario = scenario("Login")
259
- .exec(
260
- http("POST Login")
261
- .post("/auth/login")
262
- .body(StringBody("{\"user\":\"test\"}")).asJson()
263
- .check(status().is(200))
264
- );
265
-
266
- { setUp(scenario.injectOpen(rampUsers(100).during(60))).protocols(httpProtocol); }
267
- }
268
- ```
269
-
270
- ---
271
-
272
- ## Results
273
-
274
- Gatling generates an HTML report in `target/gatling/<simulation-timestamp>/index.html` — commit the link to CI artifacts or use Gatling Enterprise for centralized dashboards.
275
-
276
- ---
277
-
278
- ## Gatling-Specific Tips
279
-
280
- - **Never use blocking/synchronous calls inside `exec`** — Gatling's engine is async; blocking calls degrade throughput significantly.
281
- - **Use `.warmUp()` or an initial ramp phase** — JVM JIT compilation distorts early metrics without warm-up.
282
- - **Use `global` or named `details()` assertions** — asserting on individual requests instead of aggregated transactions is noisy.
283
- - **Prefer `heavisideUsers` for burst injection** — S-curve injection is more realistic than `atOnceUsers` for spike tests.
284
-
285
- > For CI/CD integration (Maven, Gradle, GitHub Actions), see `../topics/test-execution.md`.
286
- > For anti-patterns, assertions, think time, and parameterization principles, see **Key Principles** in `SKILL.md`.
1
+ # Gatling Reference
2
+
3
+ > Targets: Gatling 3.10+, Java DSL 3.7+
4
+
5
+ Gatling is a high-performance, Scala/Java-based load testing tool designed for HTTP-heavy applications. Its simulation DSL is expressive, and its async Netty-based engine handles very high concurrency with low resource overhead.
6
+
7
+ ---
8
+
9
+ ## Core Concepts
10
+
11
+ | Concept | Description |
12
+ |---|---|
13
+ | **Simulation** | Top-level test class extending `Simulation` |
14
+ | **Protocol** | HTTP, JMS, or gRPC configuration |
15
+ | **Scenario** | Named sequence of actions (chain of steps) |
16
+ | **Feeder** | Data source for parameterization (CSV, JSON, JDBC) |
17
+ | **Check** | Assertion on a response (status, body, header) |
18
+ | **Population** | VU injection strategy (open/closed model) |
19
+ | **Session** | VU-level state store (like JMeter `vars`) |
20
+
21
+ ---
22
+
23
+ ## Simulation Structure
24
+
25
+ ```scala
26
+ import io.gatling.core.Predef._
27
+ import io.gatling.http.Predef._
28
+ import scala.concurrent.duration._
29
+
30
+ class CheckoutSimulation extends Simulation {
31
+
32
+ // 1. Protocol config
33
+ val httpProtocol = http
34
+ .baseUrl("https://api.example.com")
35
+ .acceptHeader("application/json")
36
+ .contentTypeHeader("application/json")
37
+ .shareConnections // Reuse connections across VUs (more realistic)
38
+
39
+ // 2. Feeders (data)
40
+ val userFeeder = csv("data/users.csv").circular
41
+
42
+ // 3. Scenario (chain of steps)
43
+ val loginScenario = scenario("Login and Browse")
44
+ .feed(userFeeder)
45
+ .exec(
46
+ http("POST Login")
47
+ .post("/auth/login")
48
+ .body(StringBody("""{"username":"#{username}","password":"#{password}"}""")).asJson
49
+ .check(status.is(200))
50
+ .check(jsonPath("$.token").saveAs("authToken"))
51
+ )
52
+ .pause(1, 3) // Think time: 1–3 seconds
53
+ .exec(
54
+ http("GET Products")
55
+ .get("/products")
56
+ .header("Authorization", "Bearer #{authToken}")
57
+ .check(status.is(200))
58
+ .check(jsonPath("$[0].id").saveAs("productId"))
59
+ )
60
+ .pause(2)
61
+
62
+ // 4. Injection (load profile)
63
+ setUp(
64
+ loginScenario.inject(
65
+ nothingFor(5.seconds), // Wait before starting
66
+ atOnceUsers(5), // Smoke check
67
+ rampUsers(100).during(2.minutes),
68
+ constantUsersPerSec(20).during(5.minutes),
69
+ rampUsersPerSec(20).to(0).during(1.minute)
70
+ ).protocols(httpProtocol)
71
+ ).assertions(
72
+ global.responseTime.percentile(95).lt(500),
73
+ global.failedRequests.percent.lt(1)
74
+ )
75
+ }
76
+ ```
77
+
78
+ ---
79
+
80
+ ## Injection Profiles
81
+
82
+ ### Closed model (VU-based)
83
+ ```scala
84
+ rampUsers(100).during(2.minutes) // Ramp to 100 VUs over 2 min
85
+ constantConcurrentUsers(100).during(5.minutes) // Hold at 100 VUs
86
+ ```
87
+
88
+ ### Open model (arrival-rate)
89
+ ```scala
90
+ constantUsersPerSec(50).during(5.minutes) // 50 new users/sec
91
+ rampUsersPerSec(10).to(100).during(3.minutes) // Ramp RPS from 10 to 100
92
+ heavisideUsers(1000).during(20.seconds) // S-curve injection (realistic burst)
93
+ ```
94
+
95
+ > For closed vs open model guidance and when to use each, see `../topics/workload-design.md`.
96
+
97
+ ---
98
+
99
+ ## Feeders (Parameterization)
100
+
101
+ ```scala
102
+ // CSV
103
+ val csvFeeder = csv("data/users.csv").circular // Loop forever
104
+ val csvFeeder = csv("data/users.csv").random // Pick random row
105
+ val csvFeeder = csv("data/users.csv").shuffle // Randomize order, no repeat
106
+ val csvFeeder = csv("data/users.csv").queue // Each VU gets next row; fail if empty
107
+
108
+ // JSON
109
+ val jsonFeeder = jsonFile("data/products.json").random
110
+
111
+ // Custom feeder
112
+ val customFeeder = Iterator.continually(Map(
113
+ "timestamp" -> System.currentTimeMillis(),
114
+ "uuid" -> java.util.UUID.randomUUID().toString
115
+ ))
116
+
117
+ // Use in scenario
118
+ scenario("test").feed(csvFeeder).exec(...)
119
+ ```
120
+
121
+ ---
122
+
123
+ ## Checks (Assertions)
124
+
125
+ ```scala
126
+ // Status code
127
+ .check(status.is(200))
128
+ .check(status.in(200, 201))
129
+
130
+ // JSON body
131
+ .check(jsonPath("$.userId").exists)
132
+ .check(jsonPath("$.token").saveAs("token"))
133
+ .check(jsonPath("$.items.length()").is("5"))
134
+
135
+ // Header
136
+ .check(header("X-Request-Id").exists)
137
+
138
+ // Response time
139
+ .check(responseTimeInMillis.lte(2000))
140
+
141
+ // Body string
142
+ .check(bodyString.contains("success"))
143
+ .check(substring("access_granted").count.is(1))
144
+
145
+ // Multiple checks (all must pass)
146
+ .check(status.is(200), jsonPath("$.status").is("ok"))
147
+ ```
148
+
149
+ ---
150
+
151
+ ## Session Variables
152
+
153
+ ```scala
154
+ // Save during check
155
+ .check(jsonPath("$.orderId").saveAs("orderId"))
156
+
157
+ // Access in body
158
+ .body(StringBody("""{"orderId":"#{orderId}"}"""))
159
+
160
+ // Access in Exec block
161
+ .exec(session => {
162
+ val orderId = session("orderId").as[String]
163
+ println(s"Processing order: $orderId")
164
+ session
165
+ })
166
+
167
+ // Conditional logic
168
+ .doIf(session => session("isAdmin").as[Boolean]) {
169
+ exec(http("Admin Action").get("/admin"))
170
+ }
171
+ ```
172
+
173
+ ---
174
+
175
+ ## Assertions (Global SLA)
176
+
177
+ ```scala
178
+ setUp(...).assertions(
179
+ global.responseTime.percentile(95).lt(500), // p95 < 500ms
180
+ global.responseTime.max.lt(2000), // max < 2s
181
+ global.failedRequests.percent.lt(1), // Error rate < 1%
182
+ global.requestsPerSec.gte(100), // Throughput >= 100 RPS
183
+ forAll.failedRequests.count.is(0), // No failures on any request
184
+ details("POST Login").responseTime.percentile(99).lt(1000) // Named request SLA
185
+ )
186
+ ```
187
+
188
+ ---
189
+
190
+ ## Protocol Configuration
191
+
192
+ ### HTTP
193
+ ```scala
194
+ val httpProtocol = http
195
+ .baseUrl("https://api.example.com")
196
+ .proxy(Proxy("proxy.corp.net", 8080))
197
+ .acceptHeader("application/json")
198
+ .acceptEncodingHeader("gzip, deflate")
199
+ .userAgentHeader("Gatling/Perf-Test")
200
+ .maxConnectionsPerHost(10)
201
+ .warmUp("https://api.example.com/health")
202
+ .disableFollowRedirect // Disable for accuracy
203
+ .disableAutoReferer
204
+ ```
205
+
206
+ ### WebSocket
207
+ ```scala
208
+ import io.gatling.http.Predef._
209
+
210
+ exec(
211
+ ws("Connect WS").connect("/ws/chat")
212
+ .await(5.seconds)(
213
+ ws.checkTextMessage("message received")
214
+ .matching(jsonPath("$.type").is("ack"))
215
+ )
216
+ )
217
+ .exec(ws("Send Message").sendText("""{"msg":"hello"}"""))
218
+ .exec(ws("Close").close())
219
+ ```
220
+
221
+ ---
222
+
223
+ ## Running Gatling
224
+
225
+ ```bash
226
+ # Maven (Scala DSL)
227
+ mvn gatling:test -Dgatling.simulationClass=CheckoutSimulation
228
+
229
+ # Gradle (Java DSL)
230
+ ./gradlew gatlingRun
231
+
232
+ # Bundle (no build tool)
233
+ ./bin/gatling.sh -s CheckoutSimulation -rd "Staging load test"
234
+
235
+ # With overrides
236
+ mvn gatling:test \
237
+ -Dgatling.simulationClass=CheckoutSimulation \
238
+ -DbaseUrl=https://staging.example.com \
239
+ -Dusers=100 \
240
+ -Dduration=300
241
+ ```
242
+
243
+ ---
244
+
245
+ ## Java DSL (Gatling 3.7+)
246
+
247
+ Gatling now supports Java and Kotlin natively - no Scala required:
248
+
249
+ ```java
250
+ import io.gatling.javaapi.core.*;
251
+ import io.gatling.javaapi.http.*;
252
+ import static io.gatling.javaapi.core.CoreDsl.*;
253
+ import static io.gatling.javaapi.http.HttpDsl.*;
254
+
255
+ public class LoginSimulation extends Simulation {
256
+ HttpProtocolBuilder httpProtocol = http.baseUrl("https://api.example.com");
257
+
258
+ ScenarioBuilder scenario = scenario("Login")
259
+ .exec(
260
+ http("POST Login")
261
+ .post("/auth/login")
262
+ .body(StringBody("{\"user\":\"test\"}")).asJson()
263
+ .check(status().is(200))
264
+ );
265
+
266
+ { setUp(scenario.injectOpen(rampUsers(100).during(60))).protocols(httpProtocol); }
267
+ }
268
+ ```
269
+
270
+ ---
271
+
272
+ ## Results
273
+
274
+ Gatling generates an HTML report in `target/gatling/<simulation-timestamp>/index.html` - commit the link to CI artifacts or use Gatling Enterprise for centralized dashboards.
275
+
276
+ ---
277
+
278
+ ## Gatling-Specific Tips
279
+
280
+ - **Never use blocking/synchronous calls inside `exec`** - Gatling's engine is async; blocking calls degrade throughput significantly.
281
+ - **Use `.warmUp()` or an initial ramp phase** - JVM JIT compilation distorts early metrics without warm-up.
282
+ - **Use `global` or named `details()` assertions** - asserting on individual requests instead of aggregated transactions is noisy.
283
+ - **Prefer `heavisideUsers` for burst injection** - S-curve injection is more realistic than `atOnceUsers` for spike tests.
284
+
285
+ > For CI/CD integration (Maven, Gradle, GitHub Actions), see `../topics/test-execution.md`.
286
+ > For anti-patterns, assertions, think time, and parameterization principles, see **Key Principles** in `SKILL.md`.