perf-skills 1.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.
@@ -0,0 +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 `references/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 `references/topics/test-execution.md`.
286
+ > For anti-patterns, assertions, think time, and parameterization principles, see **Key Principles** in `SKILL.md`.
@@ -0,0 +1,251 @@
1
+ # JMeter Reference
2
+
3
+ > Targets: JMeter 5.6+, Plugins Manager 1.10+
4
+
5
+ Apache JMeter is the most widely used open-source performance testing tool. It supports a broad range of protocols and is extended through a rich plugin ecosystem.
6
+
7
+ ---
8
+
9
+ ## Core Concepts
10
+
11
+ | Concept | Description |
12
+ |---|---|
13
+ | **Test Plan** | Root container — the `.jmx` file |
14
+ | **Thread Group** | Defines VU count, ramp-up, and loop count |
15
+ | **Sampler** | Makes a request (HTTP, JDBC, JMS, TCP, etc.) |
16
+ | **Controller** | Logic: Loop, If, While, Throughput |
17
+ | **Config Element** | HTTP Defaults, CSV Data Set, Cache Manager |
18
+ | **Pre/Post Processor** | Run before/after a sampler (BeanShell, JSR223) |
19
+ | **Assertion** | Validates the response (Response Code, Duration, JSON) |
20
+ | **Listener** | Collects and displays results (View Results Tree, Summary) |
21
+ | **Timer** | Adds think time (Constant, Gaussian, Synchronizing) |
22
+
23
+ ---
24
+
25
+ ## Thread Group Configuration Patterns
26
+
27
+ ### Standard load ramp
28
+ ```
29
+ Thread Group:
30
+ Number of Threads (users): 100
31
+ Ramp-Up Period (seconds): 120 ← 1 VU every 1.2s, avoids thundering herd
32
+ Loop Count: -1 ← run forever until duration
33
+ Duration: 600 ← 10 minutes
34
+ Startup Delay: 0
35
+ ```
36
+
37
+ ### Stepping Thread Group (requires JMeter Plugins)
38
+ Use `jp@gc - Stepping Thread Group` for staged load:
39
+ - Start 10 VUs, hold 60s → add 10 every 30s → max 100 VUs → hold 300s → ramp down
40
+
41
+ ### Concurrency Thread Group (preferred for modern tests)
42
+ Use `jp@gc - Concurrency Thread Group` for target-concurrency model:
43
+ - Maintains a target concurrency level dynamically, compensates for slow VUs
44
+
45
+ ---
46
+
47
+ ## HTTP Sampler Best Practices
48
+
49
+ - Always use **HTTP Request Defaults** config element for base URL/port/protocol — never hardcode in individual samplers.
50
+ - Set **Content-Type** header in an HTTP Header Manager at Thread Group level, not per sampler.
51
+ - Use **KeepAlive** (default on) for realistic connection reuse.
52
+ - Use **Follow Redirects** only when your app requires it; disable otherwise for accuracy.
53
+ - Avoid using **View Results Tree** listener in load tests — it's memory-intensive; use it only during script development.
54
+
55
+ ---
56
+
57
+ ## Correlation: Extracting Dynamic Values
58
+
59
+ Correlation is the #1 cause of script failures in session-heavy apps.
60
+
61
+ ### Regular Expression Extractor
62
+ ```
63
+ Reference Name: csrf_token
64
+ Regular Expression: name="_token" value="(.+?)"
65
+ Template: $1$
66
+ Match No.: 1
67
+ Default Value: NOT_FOUND
68
+ ```
69
+
70
+ ### JSON Extractor (REST APIs)
71
+ ```
72
+ Reference Name: access_token
73
+ JSON Path: $.data.token
74
+ Match No.: 1
75
+ Default Value: NOT_FOUND
76
+ ```
77
+
78
+ ### Boundary Extractor (fastest, no regex overhead)
79
+ ```
80
+ Reference Name: session_id
81
+ Left Boundary: "sessionId":"
82
+ Right Boundary: "
83
+ ```
84
+
85
+ ### Using Extracted Values
86
+ Reference with `${variable_name}` in subsequent requests.
87
+
88
+ Always add an assertion on the extracted value:
89
+ ```
90
+ Response Assertion → Variable: ${csrf_token} → Pattern: NOT_FOUND → Negate
91
+ ```
92
+
93
+ ---
94
+
95
+ ## CSV Data Set Config
96
+
97
+ ```
98
+ Filename: ${__P(data.dir,./data)}/users.csv
99
+ Variable Names: username,password,account_id
100
+ Delimiter: ,
101
+ Allow Quoted Data: true
102
+ Recycle on EOF: true
103
+ Stop Thread on EOF: false
104
+ Sharing Mode: All Threads ← or "Current Thread Group" if isolated data needed
105
+ ```
106
+
107
+ **Tips:**
108
+ - Use `${__P(data.dir,...)}` property for portable paths across environments.
109
+ - For high VU counts, ensure the CSV has at least as many rows as peak VU count to avoid data collisions.
110
+ - In distributed tests, the CSV file must exist on **each injector node**, not just the controller.
111
+
112
+ ---
113
+
114
+ ## JSR223 Scripting (Groovy)
115
+
116
+ Always use **JSR223** over BeanShell — Groovy is compiled and cached, BeanShell is not.
117
+
118
+ ### Pre-processor: Generate a dynamic timestamp
119
+ ```groovy
120
+ import java.time.Instant
121
+ vars.put("timestamp", Instant.now().toEpochMilli().toString())
122
+ ```
123
+
124
+ ### Post-processor: Parse JSON response
125
+ ```groovy
126
+ import groovy.json.JsonSlurper
127
+ def json = new JsonSlurper().parseText(prev.getResponseDataAsString())
128
+ vars.put("userId", json.data.id.toString())
129
+ ```
130
+
131
+ ### Pre-processor: Compute HMAC signature
132
+ ```groovy
133
+ import javax.crypto.Mac
134
+ import javax.crypto.spec.SecretKeySpec
135
+ def key = vars.get("api_secret")
136
+ def data = vars.get("request_body")
137
+ def mac = Mac.getInstance("HmacSHA256")
138
+ mac.init(new SecretKeySpec(key.bytes, "HmacSHA256"))
139
+ vars.put("signature", mac.doFinal(data.bytes).encodeHex().toString())
140
+ ```
141
+
142
+ ---
143
+
144
+ ## Assertions
145
+
146
+ ### Response Code Assertion
147
+ ```
148
+ Field to Test: Response Code
149
+ Pattern: 200
150
+ ```
151
+
152
+ ### JSON Assertion
153
+ ```
154
+ JSON Path: $.status
155
+ Expected Value: success
156
+ ```
157
+
158
+ ### Duration Assertion
159
+ ```
160
+ Duration to assert: 2000 ← flag any response > 2000ms
161
+ ```
162
+
163
+ ### Response Size Assertion
164
+ Use to detect incomplete responses — flag if body < 100 bytes unexpectedly.
165
+
166
+ **Important:** Add assertions to the **transaction controller level** where possible, not individual samplers, to get meaningful business-transaction-level validation.
167
+
168
+ ---
169
+
170
+ ## Transaction Controllers
171
+
172
+ Wrap related HTTP samplers in Transaction Controllers to measure end-to-end business transaction time:
173
+
174
+ ```
175
+ Transaction Controller: Login Flow
176
+ ├── HTTP Sampler: GET /login
177
+ ├── HTTP Sampler: POST /authenticate
178
+ └── HTTP Sampler: GET /dashboard
179
+ ```
180
+
181
+ Set **Generate Parent Sample = true** to log only the aggregate transaction (not individual child requests) in results.
182
+
183
+ ---
184
+
185
+ ## Timers (Think Time)
186
+
187
+ | Timer | Use Case |
188
+ |---|---|
189
+ | Constant Timer | Simple fixed think time |
190
+ | Uniform Random Timer | Range between min–max |
191
+ | Gaussian Random Timer | Bell-curve distribution — most realistic |
192
+ | Throughput Shaping Timer | Target a specific RPS regardless of VU count |
193
+
194
+ Gaussian formula: `delay = Constant + Gaussian(deviation)`
195
+ Realistic web-app think time: Gaussian with constant=3000ms, deviation=1500ms.
196
+
197
+ ---
198
+
199
+ ## Running JMeter Non-GUI (Command Line)
200
+
201
+ > For distributed testing architecture and setup, see `references/topics/test-execution.md`.
202
+
203
+ Always run load tests in non-GUI mode:
204
+ ```bash
205
+ jmeter -n \
206
+ -t test.jmx \
207
+ -l results/results.jtl \
208
+ -e -o results/dashboard \
209
+ -Jenv=staging \
210
+ -Jthreads=100 \
211
+ -Jduration=600
212
+ ```
213
+
214
+ | Flag | Purpose |
215
+ |---|---|
216
+ | `-n` | Non-GUI mode |
217
+ | `-t` | Test plan path |
218
+ | `-l` | JTL results file |
219
+ | `-e -o` | Generate HTML dashboard after run |
220
+ | `-J` | Set JMeter property (use `${__P(env,dev)}` in plan) |
221
+ | `-G` | Set global property (available to remote engines) |
222
+
223
+ ---
224
+
225
+ ## Key Plugins (JMeter Plugins Manager)
226
+
227
+ | Plugin | Purpose |
228
+ |---|---|
229
+ | `Concurrency Thread Group` | Maintain target concurrency |
230
+ | `Stepping Thread Group` | Step-ramp load profile |
231
+ | `Throughput Shaping Timer` | Control RPS exactly |
232
+ | `PerfMon` | Collect server-side CPU/memory via agent |
233
+ | `3 Basic Graphs` | Lightweight real-time charting |
234
+ | `JDBC Connection Configuration` | Database load testing |
235
+ | `WebSocket Sampler` | WS protocol support |
236
+ | `gRPC Sampler` | gRPC protocol support |
237
+
238
+ Install plugins via: **Options → Plugins Manager → Available Plugins**
239
+
240
+ ---
241
+
242
+ ## JMeter-Specific Tips
243
+
244
+ - **Always run in non-GUI mode** for load tests — GUI mode consumes JMeter's own resources and distorts results.
245
+ - **Use HTTP Request Defaults** — hardcoded hosts make environment switching painful.
246
+ - **Never put listeners inside loops** — View Results Tree in a loop will OOM the JVM.
247
+ - **Use JSR223 (Groovy) over BeanShell** — Groovy is compiled and cached; BeanShell is interpreted per invocation.
248
+ - **Set JVM heap** for large tests: `JVM_ARGS="-Xms2g -Xmx4g" jmeter -n -t test.jmx`
249
+
250
+ > For CI/CD integration (Maven, GitHub Actions, GitLab, Jenkins), see `references/topics/test-execution.md`.
251
+ > For anti-patterns, assertions, think time, and parameterization principles, see **Key Principles** in `SKILL.md`.