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.
- package/.claude-plugin/marketplace.json +3 -3
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +2 -1
- package/package.json +2 -2
- package/skills/perf/SKILL.md +228 -72
- package/skills/perf/evals/evals.json +149 -0
- package/skills/perf/references/tools/gatling.md +286 -286
- package/skills/perf/references/tools/jmeter.md +251 -251
- package/skills/perf/references/tools/k6.md +269 -269
- package/skills/perf/references/tools/locust.md +133 -133
- package/skills/perf/references/topics/correlation.md +406 -0
- package/skills/perf/references/topics/database-testing.md +8 -8
- package/skills/perf/references/topics/modern-architectures.md +15 -15
- package/skills/perf/references/topics/observability.md +3 -3
- package/skills/perf/references/topics/production-testing.md +13 -13
- package/skills/perf/references/topics/protocol-testing.md +15 -15
- package/skills/perf/references/topics/results-analysis.md +13 -13
- package/skills/perf/references/topics/script-generation.md +9 -9
- package/skills/perf/references/topics/test-data.md +6 -6
- package/skills/perf/references/topics/test-execution.md +5 -5
- package/skills/perf/references/topics/workload-design.md +3 -3
|
@@ -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
|
|
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`
|
|
275
|
-
|
|
276
|
-
---
|
|
277
|
-
|
|
278
|
-
## Gatling-Specific Tips
|
|
279
|
-
|
|
280
|
-
- **Never use blocking/synchronous calls inside `exec`**
|
|
281
|
-
- **Use `.warmUp()` or an initial ramp phase**
|
|
282
|
-
- **Use `global` or named `details()` assertions**
|
|
283
|
-
- **Prefer `heavisideUsers` for burst injection**
|
|
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`.
|