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,201 @@
1
+ # Observability in Performance Testing
2
+
3
+ Observability is what bridges "the test shows slow responses" to "the database is saturating its connection pool at 300 VUs." Without correlated server-side metrics, performance test analysis is guesswork.
4
+
5
+ ---
6
+
7
+ ## The Three Pillars
8
+
9
+ | Pillar | What It Tells You | Tools |
10
+ |---|---|---|
11
+ | **Metrics** | Numeric measurements over time (CPU, RPS, latency histograms) | Prometheus, InfluxDB, Datadog, CloudWatch |
12
+ | **Logs** | Event records with context (error details, request IDs) | Elasticsearch, Loki, Splunk, CloudWatch Logs |
13
+ | **Traces** | End-to-end request path across services | Jaeger, Tempo, Datadog APM, Dynatrace |
14
+
15
+ A complete performance investigation uses all three, correlated by time and request ID.
16
+
17
+ ---
18
+
19
+ ## Minimal Observability Stack for Performance Testing
20
+
21
+ ### Open Source (self-hosted)
22
+ ```
23
+ Load Tool (k6/JMeter)
24
+ │ pushes metrics
25
+
26
+ InfluxDB ←──────────────────────────────────
27
+ │ │
28
+ ▼ Prometheus
29
+ Grafana Dashboard │
30
+ │ Node Exporter (host metrics)
31
+ ▼ cAdvisor (container metrics)
32
+ Alerting (Slack/PagerDuty) JMX Exporter (JVM metrics)
33
+ ```
34
+
35
+ ### Cloud-native
36
+ ```
37
+ Load Tool → Grafana Cloud (k6 native) → Grafana Dashboard
38
+ App → Datadog / Dynatrace / New Relic → correlated in single pane
39
+ ```
40
+
41
+ ---
42
+
43
+ ## Correlating Load Test with APM
44
+
45
+ The most powerful workflow: overlay load metrics with APM metrics on the same timeline.
46
+
47
+ **Step 1:** Add a test identifier to all load-tool requests:
48
+ ```javascript
49
+ // k6: tag all requests
50
+ export const options = {
51
+ tags: { testRun: __ENV.BUILD_ID || 'manual', env: 'staging' }
52
+ };
53
+ ```
54
+ ```bash
55
+ # JMeter: set custom header on all requests
56
+ # In HTTP Header Manager (at Test Plan level):
57
+ X-Load-Test-Run: ${__P(build.id,manual)}
58
+ X-Load-Test-Phase: ${__P(phase,load)}
59
+ ```
60
+
61
+ **Step 2:** In your APM, filter traces and metrics by the test tag — isolate test traffic from organic traffic.
62
+
63
+ **Step 3:** Align timelines — zoom in on the window when p95 degraded and look at:
64
+ - Service CPU and memory during that window
65
+ - DB query duration spike
66
+ - Thread pool queue depth
67
+ - Cache hit rate drop
68
+
69
+ ---
70
+
71
+ ## Prometheus + Grafana Setup
72
+
73
+ ### k6 → Prometheus (remote write)
74
+ ```bash
75
+ k6 run --out experimental-prometheus-rw \
76
+ -e K6_PROMETHEUS_RW_SERVER_URL=http://prometheus:9090/api/v1/write \
77
+ -e K6_PROMETHEUS_RW_TREND_STATS='p(50),p(95),p(99)' \
78
+ script.js
79
+ ```
80
+
81
+ ### JMeter → InfluxDB
82
+ 1. Install **Backend Listener** config element.
83
+ 2. Set implementation: `InfluxdbBackendListenerClient`
84
+ 3. Config: `influxdbUrl=http://influx:8086`, `measurement=jmeter`, `token=<token>`
85
+
86
+ ### Useful Grafana Dashboards
87
+ | Dashboard | Grafana ID | For |
88
+ |---|---|---|
89
+ | k6 Load Testing Results | 2587 | k6 + InfluxDB |
90
+ | JMeter Load Test | 1152 | JMeter + InfluxDB |
91
+ | Node Exporter Full | 1860 | Host system metrics |
92
+ | JVM Overview | 4701 | Java app JVM metrics |
93
+ | Kubernetes Cluster | 7249 | K8s pod metrics |
94
+
95
+ ---
96
+
97
+ ## JVM Metrics (Java Applications)
98
+
99
+ For Java services under test, always collect JVM metrics alongside load tool metrics.
100
+
101
+ ### Expose via JMX Exporter (Prometheus)
102
+ ```yaml
103
+ # jmx_config.yaml
104
+ rules:
105
+ - pattern: java.lang<type=Memory><HeapMemoryUsage>used
106
+ name: jvm_heap_used_bytes
107
+ - pattern: java.lang<type=GarbageCollector, name=(.+)><CollectionTime>
108
+ name: jvm_gc_collection_seconds_total
109
+ labels:
110
+ gc: $1
111
+ ```
112
+
113
+ ### Key JVM Metrics to Monitor
114
+ | Metric | Normal | Warning |
115
+ |---|---|---|
116
+ | Heap used % | < 70% | > 85% = GC pressure |
117
+ | GC pause duration (p99) | < 200ms | > 500ms = impacting response time |
118
+ | GC frequency | Occasional | Continuous = memory leak |
119
+ | Thread pool queue size | 0–10 | Growing = throughput ceiling reached |
120
+ | JDBC pool active connections | < 80% | > 90% = connection starvation |
121
+
122
+ ---
123
+
124
+ ## Log Correlation
125
+
126
+ During a performance test, log volume explodes. Use structured logging and filtering:
127
+
128
+ ### Mark test traffic in logs
129
+ ```java
130
+ // Spring Boot: use MDC to add test context
131
+ MDC.put("testRun", request.getHeader("X-Load-Test-Run"));
132
+ // Log4j/Logback will include this in every log line for this thread
133
+ ```
134
+
135
+ ### Useful log queries during test (Elasticsearch/Kibana)
136
+ ```
137
+ # Error spike investigation
138
+ testRun: "build-42" AND level: ERROR
139
+ | group by logger, message
140
+ | order by count desc
141
+
142
+ # Slow transactions
143
+ testRun: "build-42" AND duration_ms: >500
144
+ | group by endpoint
145
+ | percentile(duration_ms, 95)
146
+ ```
147
+
148
+ ---
149
+
150
+ ## Distributed Tracing
151
+
152
+ Traces show the end-to-end path of a single request across microservices — invaluable for pinpointing *which service* in a chain is slow.
153
+
154
+ ### Setup for load testing
155
+ - Confirm tracing is active in the target environment.
156
+ - After the test, sample traces from the slowest requests (p99 window).
157
+ - Use the trace waterfall to find which service/DB call accounts for the most latency.
158
+
159
+ ### Adding trace context to load tool requests
160
+ ```javascript
161
+ // k6: pass trace context (Jaeger B3 format)
162
+ const traceId = randomString(32, '0123456789abcdef');
163
+ http.get(url, {
164
+ headers: {
165
+ 'X-B3-TraceId': traceId,
166
+ 'X-B3-SpanId': randomString(16, '0123456789abcdef'),
167
+ 'X-B3-Sampled': '1',
168
+ 'X-Load-Test-Run': __ENV.BUILD_ID
169
+ }
170
+ });
171
+ ```
172
+
173
+ ---
174
+
175
+ ## Synthetic Monitoring (Post-Test)
176
+
177
+ After a load test validates a build, set up **synthetic monitors** to continuously test a minimal user journey in production:
178
+
179
+ | Tool | Description |
180
+ |---|---|
181
+ | Grafana Synthetic Monitoring | k6 scripts run on schedule from cloud probes |
182
+ | Datadog Synthetics | Browser + API tests from global locations |
183
+ | AWS CloudWatch Synthetics (Canaries) | Node.js scripts on schedule |
184
+ | Checkly | API + Browser checks with k6 integration |
185
+
186
+ Synthetic monitors catch regressions that slip through staging without requiring another full load test.
187
+
188
+ ---
189
+
190
+ ## Performance Testing Observability Checklist
191
+
192
+ - [ ] APM tool active and capturing traces in test environment
193
+ - [ ] Server metrics (CPU, memory, disk I/O) dashboards ready before test start
194
+ - [ ] JVM metrics exposed (for Java services)
195
+ - [ ] DB slow query logging enabled and queryable
196
+ - [ ] Load tool pushing real-time metrics to dashboard (InfluxDB, Grafana Cloud, etc.)
197
+ - [ ] Test run tagged with build ID / run ID for filtering
198
+ - [ ] Alert thresholds set (so team is notified if something breaks during test)
199
+ - [ ] Log aggregation active and searchable
200
+ - [ ] Baseline metrics screenshot taken before test starts (for comparison)
201
+ - [ ] Metrics retention configured to keep results for trend analysis
@@ -0,0 +1,214 @@
1
+ # Production & Staging Performance Testing
2
+
3
+ Running tests in production is not inherently dangerous — it's a discipline. Many organizations run production performance testing routinely. The key is controlled blast radius, observability, and rollback readiness.
4
+
5
+ ---
6
+
7
+ ## Staging Environment Testing
8
+
9
+ ### What Makes Staging Valid for Performance Testing
10
+
11
+ Staging is only a reliable proxy for production if:
12
+
13
+ | Factor | Requirement |
14
+ |---|---|
15
+ | **Hardware** | Same instance type, CPU, memory as production |
16
+ | **Architecture** | Same number of replicas, same DB tier, same cache config |
17
+ | **Data volume** | DB has similar row counts to production (schema-level) |
18
+ | **Network** | Similar topology (no shortcutting the load balancer) |
19
+ | **Dependencies** | Real downstream services (not mocks for performance tests) |
20
+
21
+ Using mocks for load tests is only appropriate for component-level isolation tests — full user journey tests must hit real (or realistic stub) dependencies.
22
+
23
+ ### Staging Limitations
24
+ - Cache hit rates will differ if data set is too small.
25
+ - Third-party integrations may behave differently (rate limits are usually lower in test).
26
+ - Infrastructure auto-scaling may not be configured identically.
27
+ - Cold-start performance distorts early-stage results — pre-warm before measuring.
28
+
29
+ ### Pre-Warm Strategy
30
+ ```bash
31
+ # Run a smoke test (1 VU) for 5 minutes before starting the load test
32
+ # This warms:
33
+ # - JVM JIT compilation
34
+ # - Connection pools
35
+ # - Application-level caches
36
+ # - CDN edge caches (if applicable)
37
+
38
+ k6 run --vus 1 --duration 5m --tag phase=warmup warmup.js
39
+ k6 run --vus 500 --duration 20m --tag phase=load main.js
40
+ ```
41
+
42
+ ---
43
+
44
+ ## Production Testing Strategies
45
+
46
+ ### Strategy 1: Canary Deployment + Load Test
47
+
48
+ Route a small % of production traffic to the new release and observe:
49
+
50
+ ```
51
+ Production Traffic
52
+
53
+ ├─ 95% → Stable instances (v1.0)
54
+ └─ 5% → Canary instances (v1.1) ← Monitor closely
55
+ ```
56
+
57
+ Gradually increase canary traffic percentage while monitoring:
58
+ - Error rate (should not increase)
59
+ - p95 response time (should not increase)
60
+ - Business metrics (conversion rate, etc.)
61
+
62
+ **Tools:** AWS ALB weighted target groups, Kubernetes Argo Rollouts, Istio traffic splitting, NGINX split_clients.
63
+
64
+ ### Strategy 2: Shadow Testing (Traffic Mirroring)
65
+
66
+ Mirror production traffic to a shadow cluster — it receives all production requests but its responses are discarded:
67
+
68
+ ```
69
+ Real User Request
70
+
71
+ ├─ Live → Production (v1) → Response to user
72
+ └─ Mirror → Shadow (v2) → Response discarded (no user impact)
73
+ ```
74
+
75
+ Shadow cluster processes real production workload — perfect for validating performance of a new version without any user impact.
76
+
77
+ **Tools:** AWS ALB request mirroring, Istio `mirror`, NGINX `mirror` directive.
78
+
79
+ ```yaml
80
+ # Istio traffic mirroring
81
+ apiVersion: networking.istio.io/v1alpha3
82
+ kind: VirtualService
83
+ spec:
84
+ http:
85
+ - route:
86
+ - destination:
87
+ host: my-service-v1
88
+ mirror:
89
+ host: my-service-v2
90
+ mirrorPercentage:
91
+ value: 100.0
92
+ ```
93
+
94
+ ### Strategy 3: Synthetic Load Injection in Production
95
+
96
+ Inject artificial load at low volumes (typically 5–10% of peak) during off-peak hours:
97
+
98
+ - Use a dedicated load generator with production-class credentials.
99
+ - Tag synthetic requests to exclude from business metrics dashboards.
100
+ - Use a synthetic user pool (not real users).
101
+ - Monitor production SLAs during injection.
102
+
103
+ **When to use:** Validate autoscaling behavior, warm new nodes before peak, run regression checks post-deployment.
104
+
105
+ ### Strategy 4: Chaos Engineering (Resilience Testing)
106
+
107
+ Intentionally inject failures to test how the system degrades:
108
+
109
+ | Experiment | Tool | Tests |
110
+ |---|---|---|
111
+ | Kill a pod/instance | Chaos Monkey, LitmusChaos | Failover speed, error rate |
112
+ | Throttle CPU/memory | Stress-ng, LitmusChaos | Degradation under resource pressure |
113
+ | Introduce network latency | tc netem, Chaos Mesh | Timeout handling, retry logic |
114
+ | Kill the database | LitmusChaos | Circuit breaker, fallback |
115
+ | Saturate disk I/O | fio, LitmusChaos | Log rotation, disk-full handling |
116
+
117
+ ```bash
118
+ # Inject 100ms network latency + 10% packet loss to a pod
119
+ kubectl exec -it chaos-pod -- \
120
+ tc qdisc add dev eth0 root netem delay 100ms loss 10%
121
+ ```
122
+
123
+ **Always combine chaos with a baseline load** — run at 50% of expected peak so the system has load to respond to when faults occur.
124
+
125
+ ---
126
+
127
+ ## Pre-Production Performance Gates
128
+
129
+ ### Gate Criteria (must pass before production deploy)
130
+
131
+ ```
132
+ 1. Smoke test (1 VU): Zero errors, correct responses
133
+ 2. Load test (target VUs): p95 < SLA, error rate < 1%
134
+ 3. Stress test (1.5× target): Graceful degradation, no data corruption
135
+ 4. Regression comparison: p95 within 10% of previous passing run
136
+ ```
137
+
138
+ ### Exemptions and Escalation
139
+ - If a test fails, categorize: **known regression** (documented, fix tracked) vs **unexpected regression** (block deploy).
140
+ - SLA breach of < 5% may be acceptable with product owner sign-off.
141
+ - Emergency hotfixes may skip stress test but must pass smoke + load.
142
+
143
+ ---
144
+
145
+ ## Safety Controls for Production Testing
146
+
147
+ ### Mandatory Safety Controls
148
+
149
+ | Control | Implementation |
150
+ |---|---|
151
+ | **Blast radius limit** | Never inject more than 10–20% of prod capacity without approval |
152
+ | **Kill switch** | A single command / button to stop all injectors immediately |
153
+ | **Auto-abort on error spike** | Test stops automatically if error rate exceeds threshold |
154
+ | **Rollback plan** | Documented and tested rollback procedure ready |
155
+ | **Communication** | Ops/SRE on standby; incident channel open |
156
+ | **Synthetic user tagging** | All test requests tagged to exclude from real user metrics |
157
+ | **No real user data** | Synthetic test data only — never process real PII in load tests |
158
+
159
+ ### Auto-abort configuration
160
+
161
+ ```javascript
162
+ // k6: auto-abort on error spike
163
+ export const options = {
164
+ thresholds: {
165
+ http_req_failed: [{
166
+ threshold: 'rate<0.05', // Abort if error rate > 5%
167
+ abortOnFail: true,
168
+ delayAbortEval: '30s', // Wait 30s before aborting (avoid false positives)
169
+ }],
170
+ },
171
+ };
172
+ ```
173
+
174
+ ```bash
175
+ # JMeter: stop test on error rate threshold via Backend Listener + custom alerting
176
+ # Or use Taurus wrapper:
177
+ bzt test.jmx \
178
+ -o modules.passfail.checks[0].subject=fail \
179
+ -o modules.passfail.checks[0].threshold=5% \
180
+ -o modules.passfail.checks[0].condition=over \
181
+ -o modules.passfail.checks[0].timeframe=60s \
182
+ -o modules.passfail.checks[0].stop=true
183
+ ```
184
+
185
+ ---
186
+
187
+ ## Environments Progression
188
+
189
+ ```
190
+ Developer Laptop (smoke, 1 VU)
191
+
192
+ CI Environment (smoke + light load, 10–25 VUs, fast feedback)
193
+
194
+ Staging / Performance Environment (full load + stress tests)
195
+
196
+ Pre-Production / Mirror (shadow tests, canary validation)
197
+
198
+ Production (synthetic monitoring, canary, controlled injection)
199
+ ```
200
+
201
+ Each environment adds fidelity; the production stage assumes all previous stages passed.
202
+
203
+ ---
204
+
205
+ ## Incident Response During a Performance Test
206
+
207
+ If something breaks during a test:
208
+
209
+ 1. **Immediately stop the test** (kill switch — stop all injectors).
210
+ 2. **Capture current state** — snapshot metrics, take thread dumps (Java), capture logs.
211
+ 3. **Assess impact** — are real users affected? Is the failure isolated to test traffic?
212
+ 4. **Roll back if needed** — restore previous version, restart services.
213
+ 5. **Preserve evidence** — don't restart services without capturing logs and heap dumps.
214
+ 6. **Post-mortem** — document what broke, at what load, what the root cause was.
@@ -0,0 +1,274 @@
1
+ # Protocol-Specific Performance Testing
2
+
3
+ Covers gRPC, GraphQL, WebSocket/SSE, and message queue (Kafka, RabbitMQ, SQS) load testing — protocols with unique challenges beyond standard HTTP/REST.
4
+
5
+ ---
6
+
7
+ ## gRPC Performance Testing
8
+
9
+ gRPC uses HTTP/2, Protocol Buffers, and persistent connections — fundamentally different from REST at the wire level.
10
+
11
+ ### Key Challenges
12
+
13
+ | Challenge | Why It Matters |
14
+ |---|---|
15
+ | **Protobuf compilation** | Tests require compiled `.proto` definitions; can't just send raw JSON |
16
+ | **Connection multiplexing** | HTTP/2 multiplexes streams over a single connection — fewer connections needed but different bottleneck profile |
17
+ | **Streaming** | Unary, server-streaming, client-streaming, and bidirectional-streaming each need different test strategies |
18
+ | **Metadata vs headers** | gRPC metadata is the equivalent of HTTP headers; auth tokens go here |
19
+ | **Error codes** | gRPC uses its own status codes (OK, UNAVAILABLE, DEADLINE_EXCEEDED) — not HTTP status codes |
20
+
21
+ ### Tool Support
22
+
23
+ | Tool | gRPC Support | Notes |
24
+ |---|---|---|
25
+ | **k6** | `k6/grpc` built-in module | Supports unary and streaming; load `.proto` or use reflection |
26
+ | **Gatling** | `gatling-grpc` plugin | Scala/Java DSL; supports unary and streaming |
27
+ | **JMeter** | `gRPC Sampler` plugin | GUI-based; requires proto descriptor file |
28
+ | **ghz** | Dedicated gRPC benchmarking tool | CLI-only; excellent for quick benchmarks |
29
+
30
+ ### k6 gRPC Example
31
+
32
+ ```javascript
33
+ import grpc from 'k6/grpc';
34
+ import { check, sleep } from 'k6';
35
+
36
+ const client = new grpc.Client();
37
+ client.load(['definitions'], 'hello.proto');
38
+
39
+ export default function () {
40
+ client.connect('grpc-server:50051', { plaintext: true });
41
+
42
+ const response = client.invoke('hello.HelloService/SayHello', {
43
+ greeting: 'perf-test',
44
+ });
45
+
46
+ check(response, {
47
+ 'status is OK': (r) => r && r.status === grpc.StatusOK,
48
+ 'has message': (r) => r && r.message.reply !== '',
49
+ });
50
+
51
+ client.close();
52
+ sleep(1);
53
+ }
54
+ ```
55
+
56
+ ### gRPC Testing Considerations
57
+
58
+ - **Connection reuse**: Keep connections open across iterations; don't connect/close per request.
59
+ - **Streaming throughput**: For server-streaming RPCs, measure messages-per-second, not just request latency.
60
+ - **Deadline propagation**: Set gRPC deadlines in tests to match production timeouts.
61
+ - **Load balancer awareness**: gRPC over HTTP/2 with persistent connections can cause uneven load across backends — test with client-side load balancing or L7 proxy.
62
+ - **Protobuf payload size**: Binary encoding is smaller than JSON — adjust throughput expectations accordingly.
63
+
64
+ ---
65
+
66
+ ## GraphQL Performance Testing
67
+
68
+ GraphQL introduces query complexity as a variable — the same endpoint can serve trivially cheap or devastatingly expensive requests.
69
+
70
+ ### Key Challenges
71
+
72
+ | Challenge | Why It Matters |
73
+ |---|---|
74
+ | **Variable query cost** | A single endpoint can serve queries with wildly different server costs |
75
+ | **N+1 resolver problem** | Nested queries can trigger cascading DB calls; load tests expose this at scale |
76
+ | **Query depth / complexity** | Deep nesting can cause exponential resolver execution |
77
+ | **Batched queries** | Clients may send multiple operations in one request |
78
+ | **Persisted queries** | Production may use query allowlists; ad-hoc queries get rejected |
79
+
80
+ ### Testing Strategy
81
+
82
+ 1. **Catalog production queries**: Extract real queries from logs or APM traces — don't invent synthetic queries.
83
+ 2. **Categorize by cost**: Light (single field), medium (nested 2 levels), heavy (deep joins, lists).
84
+ 3. **Build a realistic query mix**: Weight tests by actual production query distribution.
85
+ 4. **Test with and without caching**: GraphQL caching (DataLoader, CDN) dramatically changes performance profiles.
86
+
87
+ ### k6 GraphQL Example
88
+
89
+ ```javascript
90
+ import http from 'k6/http';
91
+ import { check, sleep } from 'k6';
92
+
93
+ const GRAPHQL_ENDPOINT = `${__ENV.BASE_URL}/graphql`;
94
+
95
+ const QUERIES = {
96
+ lightQuery: `query { user(id: "${__VU}") { name email } }`,
97
+ heavyQuery: `query { users(first: 100) { edges { node { name orders(last: 10) { totalAmount items { name } } } } } }`,
98
+ };
99
+
100
+ export default function () {
101
+ // 80% light queries, 20% heavy queries
102
+ const query = Math.random() < 0.8 ? QUERIES.lightQuery : QUERIES.heavyQuery;
103
+
104
+ const res = http.post(GRAPHQL_ENDPOINT, JSON.stringify({ query }), {
105
+ headers: {
106
+ 'Content-Type': 'application/json',
107
+ 'Authorization': `Bearer ${__ENV.TOKEN}`,
108
+ },
109
+ tags: { name: 'POST /graphql' },
110
+ });
111
+
112
+ check(res, {
113
+ 'status 200': (r) => r.status === 200,
114
+ 'no errors': (r) => !r.json('errors'),
115
+ 'has data': (r) => r.json('data') !== null,
116
+ });
117
+
118
+ sleep(1);
119
+ }
120
+ ```
121
+
122
+ ### GraphQL-Specific Metrics to Track
123
+
124
+ - **Query complexity score** (if server exposes it)
125
+ - **Resolver execution time** (via APM / tracing)
126
+ - **DataLoader batch efficiency** (cache hit rate)
127
+ - **Error rate by query type** (complexity-related timeouts vs auth errors)
128
+
129
+ ---
130
+
131
+ ## WebSocket / SSE Performance Testing
132
+
133
+ Persistent connections have fundamentally different scaling characteristics from request/response HTTP.
134
+
135
+ ### Key Challenges
136
+
137
+ | Challenge | Why It Matters |
138
+ |---|---|
139
+ | **Connection count** | Each VU holds an open connection — tests memory/fd limits, not just CPU |
140
+ | **Message throughput** | Measure messages/sec independently from connection count |
141
+ | **Backpressure** | What happens when the server can't send fast enough? |
142
+ | **Reconnection behavior** | Dropped connections should auto-reconnect; test the reconnect storm |
143
+ | **Fan-out cost** | Broadcasting to N connections has O(N) server cost |
144
+
145
+ ### Tool Support
146
+
147
+ | Tool | WebSocket | SSE |
148
+ |---|---|---|
149
+ | **k6** | `k6/ws` module | Not built-in; use HTTP streaming |
150
+ | **Gatling** | Full WebSocket DSL | Not built-in |
151
+ | **JMeter** | WebSocket Sampler plugin | Not built-in |
152
+ | **Artillery** | Built-in `ws` engine | Built-in SSE |
153
+
154
+ ### k6 WebSocket Example
155
+
156
+ ```javascript
157
+ import ws from 'k6/ws';
158
+ import { check, sleep } from 'k6';
159
+
160
+ export default function () {
161
+ const url = `${__ENV.WS_URL}/ws/chat`;
162
+ const params = { headers: { 'Authorization': `Bearer ${__ENV.TOKEN}` } };
163
+
164
+ const res = ws.connect(url, params, function (socket) {
165
+ socket.on('open', () => {
166
+ socket.send(JSON.stringify({ type: 'subscribe', channel: 'updates' }));
167
+ });
168
+
169
+ socket.on('message', (msg) => {
170
+ const data = JSON.parse(msg);
171
+ check(data, {
172
+ 'has type': (d) => d.type !== undefined,
173
+ });
174
+ });
175
+
176
+ socket.on('error', (e) => {
177
+ console.error('WS error:', e.error());
178
+ });
179
+
180
+ // Hold connection open for 30 seconds, sending periodic pings
181
+ socket.setInterval(() => {
182
+ socket.send(JSON.stringify({ type: 'ping' }));
183
+ }, 5000);
184
+
185
+ socket.setTimeout(() => {
186
+ socket.close();
187
+ }, 30000);
188
+ });
189
+
190
+ check(res, { 'WS status 101': (r) => r && r.status === 101 });
191
+ sleep(1);
192
+ }
193
+ ```
194
+
195
+ ### WebSocket Testing Strategy
196
+
197
+ 1. **Connection ramp**: Gradually open connections — don't blast 10k connections at once.
198
+ 2. **Separate connection test from message test**: First, find max stable connections; then test message throughput at a stable connection count.
199
+ 3. **Measure server-side**: File descriptor count, memory per connection, event loop lag.
200
+ 4. **Test reconnection storms**: Kill server, observe client reconnection behavior and server recovery.
201
+
202
+ ---
203
+
204
+ ## Message Queue / Event Streaming Testing
205
+
206
+ Testing Kafka, RabbitMQ, SQS, and similar systems requires measuring producer throughput, consumer lag, and end-to-end latency.
207
+
208
+ ### Key Challenges
209
+
210
+ | Challenge | Why It Matters |
211
+ |---|---|
212
+ | **Producer throughput** | Messages/sec the system can ingest |
213
+ | **Consumer lag** | How far behind consumers fall under load |
214
+ | **End-to-end latency** | Time from produce to consume — the real user-facing metric |
215
+ | **Partition scaling** | Kafka throughput scales with partitions; test with realistic partition counts |
216
+ | **Message ordering** | Under load, verify ordering guarantees still hold |
217
+ | **Dead letter queues** | Verify failed messages route correctly under pressure |
218
+
219
+ ### Tool Support
220
+
221
+ | Tool | Kafka | RabbitMQ | SQS |
222
+ |---|---|---|---|
223
+ | **k6 (xk6-kafka)** | Yes | No (use HTTP management API) | No |
224
+ | **JMeter** | Kafka plugin / JMS Sampler | JMS Sampler | AWS SDK Sampler |
225
+ | **kafka-producer-perf-test** | Built-in Kafka tool | N/A | N/A |
226
+ | **rabbitmq-perf-test** | N/A | Official RabbitMQ tool | N/A |
227
+
228
+ ### k6 Kafka Example (xk6-kafka)
229
+
230
+ ```javascript
231
+ import { Writer, Reader, Connection } from 'k6/x/kafka';
232
+
233
+ const writer = new Writer({ brokers: ['kafka:9092'], topic: 'perf-test' });
234
+ const reader = new Reader({ brokers: ['kafka:9092'], topic: 'perf-test', groupID: 'perf-group' });
235
+
236
+ export default function () {
237
+ // Produce
238
+ writer.produce({
239
+ messages: [{
240
+ key: `key-${__VU}-${__ITER}`,
241
+ value: JSON.stringify({ orderId: `${__VU}-${__ITER}`, timestamp: Date.now() }),
242
+ }],
243
+ });
244
+
245
+ // Consume (in a separate scenario ideally)
246
+ const messages = reader.consume({ limit: 1 });
247
+ // Validate message content
248
+ }
249
+
250
+ export function teardown() {
251
+ writer.close();
252
+ reader.close();
253
+ }
254
+ ```
255
+
256
+ ### Message Queue Testing Strategy
257
+
258
+ 1. **Test producers and consumers separately first**, then together.
259
+ 2. **Measure consumer lag over time** — a growing lag under steady load indicates a bottleneck.
260
+ 3. **Test with realistic message sizes** — a 100-byte message vs a 1MB payload have very different throughput ceilings.
261
+ 4. **Verify idempotency** — under load with retries, duplicate messages should not corrupt state.
262
+ 5. **Test partition rebalancing** — add/remove consumers during a test to verify rebalance behavior.
263
+
264
+ ---
265
+
266
+ ## Protocol Testing Checklist
267
+
268
+ - [ ] Proto definitions / schemas compiled and available to test tool
269
+ - [ ] Connection reuse strategy defined (persistent vs per-request)
270
+ - [ ] Streaming scenarios identified and scripted separately from unary/request-response
271
+ - [ ] Message throughput targets defined (messages/sec, not just RPS)
272
+ - [ ] Backpressure / flow control behavior validated
273
+ - [ ] Error codes and retry behavior tested (gRPC status codes, AMQP nacks, etc.)
274
+ - [ ] End-to-end latency measured (not just request latency)