perf-skills 2.0.0 → 3.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/README.md
CHANGED
|
@@ -1,165 +1,182 @@
|
|
|
1
|
-
# perf-skills
|
|
2
|
-
|
|
3
|
-
> *"The more you sweat in training, the less you bleed in battle."* - Richard Marcinko
|
|
4
|
-
>
|
|
5
|
-
> The more relevant skills your AI loads during development, the fewer fires you fight in production.
|
|
6
|
-
|
|
7
|
-
## What Is This?
|
|
8
|
-
|
|
9
|
-
`perf-skills` is a structured knowledge base that gives AI coding assistants deep, opinionated expertise in performance testing. It works with any AI tool that supports custom instructions, rules, or skill files - helping you plan, script, execute, and analyze load tests using any major tool.
|
|
10
|
-
|
|
11
|
-
### Supported Tools
|
|
12
|
-
|
|
13
|
-
| Open Source | Commercial |
|
|
14
|
-
|---|---|
|
|
15
|
-
| Apache JMeter | NeoLoad (Tricentis) |
|
|
16
|
-
| k6 (Grafana Labs) | LoadRunner (OpenText) |
|
|
17
|
-
| Gatling | OctoPerf (SaaS) |
|
|
18
|
-
| Locust | |
|
|
19
|
-
|
|
20
|
-
### Topics Covered
|
|
21
|
-
|
|
22
|
-
- **Workload design** - concurrency models, Little's Law, transaction mix, SLA targets
|
|
23
|
-
- **Test data** - parameterization, CSV feeds, synthetic data, data isolation
|
|
24
|
-
- **Script generation** - correlation, assertions, error handling, session management
|
|
25
|
-
- **Test execution** - local, distributed, CI/CD pipelines, cloud execution
|
|
26
|
-
- **Results analysis** - percentiles, bottleneck identification, trend comparison, reporting
|
|
27
|
-
- **Observability** - APM integration, Prometheus/Grafana, distributed tracing, log correlation
|
|
28
|
-
- **Production testing** - canary, shadow traffic, chaos engineering, safety controls
|
|
29
|
-
- **Protocol testing** - gRPC, GraphQL, WebSocket/SSE, Kafka/message queues
|
|
30
|
-
- **Database testing** - JDBC load testing, connection pools, query concurrency, replication lag
|
|
31
|
-
- **Modern architectures** - microservices, Kubernetes (HPA, service mesh), serverless (cold starts), frontend (Core Web Vitals)
|
|
32
|
-
|
|
33
|
-
## Compatible AI Coding Assistants
|
|
34
|
-
|
|
35
|
-
| Tool | Integration Method | Setup |
|
|
36
|
-
|---|---|---|
|
|
37
|
-
| **Windsurf (Cascade)** | Skills | Copy to skills directory - auto-triggers on perf questions |
|
|
38
|
-
| **Cursor** | Rules / Docs | Add as project rules or index via `@Docs` |
|
|
39
|
-
| **Claude Code** | CLAUDE.md / Custom instructions | Reference files in `CLAUDE.md` or feed as context |
|
|
40
|
-
| **Cline** | Custom instructions / `.clinerules` | Add to `.clinerules` or workspace instructions |
|
|
41
|
-
| **Roo Code** | Custom instructions / Rules | Add as workspace rules or custom instructions |
|
|
42
|
-
| **Aider** | Conventions / Chat context | Add to `.aider.conf.yml` conventions or `/read` files |
|
|
43
|
-
| **OpenCode** | Custom instructions | Add to project-level instructions |
|
|
44
|
-
| **Antigravity** | Context files | Add as context / knowledge files |
|
|
45
|
-
| **Pochi** | Custom instructions | Reference files in project instructions |
|
|
46
|
-
| **GitHub Copilot** | Custom instructions / `.github/copilot-instructions.md` | Reference in repo-level instructions |
|
|
47
|
-
|
|
48
|
-
## Installation
|
|
1
|
+
# ⚡ Performance Testing Skills (`perf-skills`)
|
|
49
2
|
|
|
50
|
-
|
|
3
|
+
> **Turn your AI assistant into a senior performance engineer.** An opinionated, token-optimized knowledge base for planning, scripting, executing, and analyzing load tests across any major tool.
|
|
51
4
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
/plugin marketplace add QAInsights/perf-skills
|
|
55
|
-
/plugin install perf@qainsights
|
|
56
|
-
/reload-plugins
|
|
57
|
-
```
|
|
5
|
+
> *"The more you sweat in training, the less you bleed in battle."* - Richard Marcinko
|
|
6
|
+
> *"The more relevant skills your AI loads during development, the fewer fires you fight in production."*
|
|
58
7
|
|
|
59
|
-
|
|
8
|
+
---
|
|
60
9
|
|
|
61
|
-
|
|
62
|
-
```bash
|
|
63
|
-
/plugin marketplace update
|
|
64
|
-
/plugin uninstall perf
|
|
65
|
-
/plugin install perf@qainsights
|
|
66
|
-
/reload-plugins
|
|
67
|
-
```
|
|
10
|
+
## 📖 Table of Contents
|
|
68
11
|
|
|
69
|
-
|
|
12
|
+
- [🔍 What is perf-skills?](#-what-is-perf-skills)
|
|
13
|
+
- [📦 Supported Tools & Protocols](#-supported-tools--protocols)
|
|
14
|
+
- [⛓️ Dynamic Value Correlation](#️-dynamic-value-correlation)
|
|
15
|
+
- [🛠️ Compatible AI Assistants](#️-compatible-ai-assistants)
|
|
16
|
+
- [🚀 Installation & Setup](#-installation--setup)
|
|
17
|
+
- [📂 File Structure & Routing](#-file-structure--routing)
|
|
18
|
+
- [💡 How the Skill Works](#-how-the-skill-works)
|
|
19
|
+
- [🤝 Contributing & License](#-contributing--license)
|
|
70
20
|
|
|
71
|
-
|
|
72
|
-
npx skills add QAInsights/perf-skills
|
|
73
|
-
```
|
|
21
|
+
---
|
|
74
22
|
|
|
75
|
-
|
|
23
|
+
## 🔍 What is perf-skills?
|
|
76
24
|
|
|
77
|
-
|
|
78
|
-
# Clone and copy to Claude skills directory
|
|
79
|
-
git clone https://github.com/QAInsights/perf-skills.git
|
|
80
|
-
cp -r perf-skills/skills/perf ~/.claude/skills/
|
|
81
|
-
```
|
|
25
|
+
`perf-skills` is a highly structured knowledge base designed specifically for AI coding assistants (like Claude, Cursor, Windsurf, etc.). It injects deep, expert-level performance testing context on-demand, allowing your AI to generate correct scripts, follow best practices, and debug bottlenecks without bloating its context window.
|
|
82
26
|
|
|
83
|
-
|
|
27
|
+
---
|
|
84
28
|
|
|
85
|
-
|
|
86
|
-
2. Copy the skill directory: `cp -r perf-skills/skills/perf ~/.windsurf/skills/`
|
|
87
|
-
3. The skill auto-triggers when you ask about performance testing, load testing, or any supported tool.
|
|
29
|
+
## 📦 Supported Tools & Protocols
|
|
88
30
|
|
|
89
|
-
|
|
31
|
+
| Category | Supported Technologies |
|
|
32
|
+
| :--- | :--- |
|
|
33
|
+
| **Open Source** | Apache JMeter, k6 (Grafana Labs), Gatling, Locust |
|
|
34
|
+
| **Commercial/SaaS** | NeoLoad (Tricentis), LoadRunner (OpenText), OctoPerf |
|
|
35
|
+
| **Protocols** | HTTP/REST, gRPC, GraphQL, WebSocket, SSE, JDBC, SOAP, Kafka/MQ, Citrix, SAP |
|
|
90
36
|
|
|
91
|
-
|
|
92
|
-
1. Clone the repository: `git clone https://github.com/QAInsights/perf-skills.git`
|
|
93
|
-
2. Create `.cursor/rules/perf.mdc` in your project root.
|
|
94
|
-
3. Copy content from `perf-skills/skills/perf/SKILL.md` into the rule file.
|
|
37
|
+
---
|
|
95
38
|
|
|
96
|
-
|
|
97
|
-
1. Clone the repository into your project or a known location.
|
|
98
|
-
2. Open Cursor Settings → Features → Docs.
|
|
99
|
-
3. Add the `perf-skills/skills/perf/` directory as a doc source.
|
|
100
|
-
4. Reference with `@Docs perf` in chat.
|
|
39
|
+
## ⛓️ Dynamic Value Correlation
|
|
101
40
|
|
|
102
|
-
|
|
41
|
+
A major highlight of this library is the **Dynamic Value Correlation** engine ([correlation.md](skills/perf/references/topics/correlation.md)). Correlation is often the most fragile part of performance scripting; `perf-skills` equips your AI with robust strategies to handle it flawlessly:
|
|
103
42
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
43
|
+
- **The Correlation Workflow**: Standardizes identifying, extracting, injecting, and verifying dynamic tokens.
|
|
44
|
+
- **Advanced Scanning**: Strategies for Next.js/React SSR hydration payloads (`__NEXT_DATA__`), SPA client-side token retrieval, runtime hooking, and Swagger contract mapping.
|
|
45
|
+
- **Robust vs. Brittle Regex**: Guides your AI to write expressions that don't break on minor HTML structure modifications.
|
|
46
|
+
- **Tech Stack Rules**: Pre-defined extraction rules for enterprise environments:
|
|
47
|
+
- **ASP.NET**: ViewState, RequestVerificationToken, EventValidation.
|
|
48
|
+
- **Java**: JSF ViewState, Spring Security `_csrf`, JSESSIONID.
|
|
49
|
+
- **SAP & Oracle**: NetWeaver `sap-contextid`, ADF `_afrLoop`.
|
|
50
|
+
- **OAuth 2.0 / SAML**: SAMLResponse, code, state tokens.
|
|
51
|
+
- **Modern SSR/SPA**: Hydration states, Laravel `_token`, Django `csrfmiddlewaretoken`, Rails `authenticity_token`.
|
|
52
|
+
- **Diagnostic Playbook**: Step-by-step troubleshooting for URL-encoding, base64 url-safe transforms, JSON-escaping, and thread safety.
|
|
111
53
|
|
|
112
|
-
|
|
54
|
+
---
|
|
113
55
|
|
|
114
|
-
|
|
115
|
-
2. Copy the skill directory into your project: `cp -r perf-skills/skills/perf ./perf`
|
|
116
|
-
3. Add to your custom instructions or `.clinerules`:
|
|
117
|
-
```
|
|
118
|
-
For performance testing guidance, consult the perf knowledge base.
|
|
119
|
-
Start with perf/SKILL.md for routing to the correct reference file.
|
|
120
|
-
```
|
|
56
|
+
## 🛠️ Compatible AI Assistants
|
|
121
57
|
|
|
122
|
-
|
|
58
|
+
`perf-skills` works seamlessly with any modern AI development tool:
|
|
123
59
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
60
|
+
| AI Assistant | Integration Method |
|
|
61
|
+
| :--- | :--- |
|
|
62
|
+
| **Claude Code** | Native Plugin or `CLAUDE.md` context referencing |
|
|
63
|
+
| **Windsurf (Cascade)** | Local Skill configuration |
|
|
64
|
+
| **Cursor** | Custom rules `.cursor/rules/` or index via `@Docs` |
|
|
65
|
+
| **Cline / Roo Code** | Workspace instructions / custom rules |
|
|
66
|
+
| **Aider** | Conventions file or `/read` context loading |
|
|
67
|
+
| **GitHub Copilot** | Repository-level custom instructions |
|
|
68
|
+
| **Antigravity / Pochi** | Context files / instruction references |
|
|
128
69
|
|
|
129
|
-
|
|
70
|
+
---
|
|
130
71
|
|
|
131
|
-
|
|
132
|
-
2. Copy the skill directory into your project: `cp -r perf-skills/skills/perf ./perf`
|
|
133
|
-
3. In `.github/copilot-instructions.md`, add:
|
|
134
|
-
```markdown
|
|
135
|
-
For performance testing questions, reference the perf knowledge base.
|
|
136
|
-
Start with perf/SKILL.md, then load relevant files from perf/references/.
|
|
137
|
-
```
|
|
72
|
+
## 🚀 Installation & Setup
|
|
138
73
|
|
|
139
|
-
###
|
|
74
|
+
### Claude Code Plugin (Recommended)
|
|
140
75
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
Start with perf/SKILL.md for routing to the correct reference file.
|
|
76
|
+
To install as a Claude Code plugin, run:
|
|
77
|
+
```bash
|
|
78
|
+
/plugin marketplace add QAInsights/perf-skills
|
|
79
|
+
/plugin install perf@qainsights
|
|
80
|
+
/reload-plugins
|
|
147
81
|
```
|
|
148
82
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
The skill is plain markdown files. Any AI tool that can read files or accept custom instructions can use it:
|
|
152
|
-
1. Point the tool to `SKILL.md` as the entry point.
|
|
153
|
-
2. Let the Reference Map in `SKILL.md` guide which file(s) to load.
|
|
83
|
+
Once installed, the `/perf` skill will be available and automatically load when you ask performance-related questions.
|
|
154
84
|
|
|
155
|
-
|
|
85
|
+
To update to the latest version:
|
|
86
|
+
```bash
|
|
87
|
+
/plugin marketplace update
|
|
88
|
+
/plugin uninstall perf
|
|
89
|
+
/plugin install perf@qainsights
|
|
90
|
+
/reload-plugins
|
|
91
|
+
```
|
|
156
92
|
|
|
157
|
-
|
|
93
|
+
---
|
|
158
94
|
|
|
159
|
-
|
|
95
|
+
### Install as local Skills (npx)
|
|
160
96
|
|
|
97
|
+
If you are using a tool that supports `npx skills`, add it directly:
|
|
98
|
+
```bash
|
|
99
|
+
npx skills add QAInsights/perf-skills
|
|
161
100
|
```
|
|
162
|
-
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
### Setup for Other Editors
|
|
105
|
+
|
|
106
|
+
<details>
|
|
107
|
+
<summary><b>🌊 Windsurf (Cascade)</b></summary>
|
|
108
|
+
<br>
|
|
109
|
+
|
|
110
|
+
1. Clone this repository:
|
|
111
|
+
```bash
|
|
112
|
+
git clone https://github.com/QAInsights/perf-skills.git
|
|
113
|
+
```
|
|
114
|
+
2. Copy the skill directory:
|
|
115
|
+
```bash
|
|
116
|
+
cp -r perf-skills/skills/perf ~/.windsurf/skills/
|
|
117
|
+
```
|
|
118
|
+
The skill will auto-trigger when you ask about performance testing, load testing, or any supported tool.
|
|
119
|
+
</details>
|
|
120
|
+
|
|
121
|
+
<details>
|
|
122
|
+
<summary><b>🎯 Cursor</b></summary>
|
|
123
|
+
<br>
|
|
124
|
+
|
|
125
|
+
**Option A (Project Rules):**
|
|
126
|
+
1. Copy [SKILL.md](skills/perf/SKILL.md) content into a rule file: `.cursor/rules/perf.mdc`.
|
|
127
|
+
2. Configure rules to trigger on file paths matching performance scripts or performance-related prompts.
|
|
128
|
+
|
|
129
|
+
**Option B (@Docs indexing):**
|
|
130
|
+
1. Open Cursor Settings → Features → Docs.
|
|
131
|
+
2. Add the path to `skills/perf/` directory as a doc source.
|
|
132
|
+
3. Reference with `@Docs perf` in your prompts.
|
|
133
|
+
</details>
|
|
134
|
+
|
|
135
|
+
<details>
|
|
136
|
+
<summary><b>🤖 Claude Code (CLAUDE.md)</b></summary>
|
|
137
|
+
<br>
|
|
138
|
+
|
|
139
|
+
1. Copy the `skills/perf` directory into your project root:
|
|
140
|
+
```bash
|
|
141
|
+
cp -r perf-skills/skills/perf ./perf
|
|
142
|
+
```
|
|
143
|
+
2. In your `CLAUDE.md`, add:
|
|
144
|
+
```markdown
|
|
145
|
+
For performance testing questions, read `perf/SKILL.md` for routing, then load the relevant reference files from `perf/references/`.
|
|
146
|
+
```
|
|
147
|
+
</details>
|
|
148
|
+
|
|
149
|
+
<details>
|
|
150
|
+
<summary><b> Cline / Roo Code</b></summary>
|
|
151
|
+
<br>
|
|
152
|
+
|
|
153
|
+
1. Copy the `skills/perf` directory into your project root.
|
|
154
|
+
2. In your `.clinerules` or custom instructions, append:
|
|
155
|
+
```text
|
|
156
|
+
For performance testing guidance, consult the perf knowledge base starting at perf/SKILL.md.
|
|
157
|
+
```
|
|
158
|
+
</details>
|
|
159
|
+
|
|
160
|
+
<details>
|
|
161
|
+
<summary><b>⚓ Aider</b></summary>
|
|
162
|
+
<br>
|
|
163
|
+
|
|
164
|
+
1. Copy the `skills/perf` directory into your project root.
|
|
165
|
+
2. Tell Aider to read the entrypoint:
|
|
166
|
+
```bash
|
|
167
|
+
/read perf/SKILL.md
|
|
168
|
+
```
|
|
169
|
+
3. Ask your question. Aider will load the corresponding tool/topic files from `perf/references/`.
|
|
170
|
+
</details>
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## 📂 File Structure & Routing
|
|
175
|
+
|
|
176
|
+
Here is how the knowledge base is organized. The entry point [SKILL.md](skills/perf/SKILL.md) routes queries to specific reference guides to keep prompt sizes minimal.
|
|
177
|
+
|
|
178
|
+
```text
|
|
179
|
+
perf-skills/
|
|
163
180
|
├── .claude-plugin/
|
|
164
181
|
│ ├── marketplace.json # Claude Code marketplace catalog
|
|
165
182
|
│ └── plugin.json # Claude Code plugin manifest
|
|
@@ -167,73 +184,65 @@ perf-skills/ # Repository root
|
|
|
167
184
|
├── LICENSE.md
|
|
168
185
|
└── skills/
|
|
169
186
|
└── perf/ # Skill module
|
|
170
|
-
├── SKILL.md # Entry
|
|
187
|
+
├── SKILL.md # [Entry Point] Routing, Key Principles
|
|
171
188
|
└── references/
|
|
172
|
-
├── tools/ # Tool-specific syntax
|
|
173
|
-
│ ├── jmeter.md # JMeter
|
|
174
|
-
│ ├── k6.md # k6
|
|
175
|
-
│ ├── gatling.md # Gatling
|
|
176
|
-
│ ├── locust.md # Locust
|
|
177
|
-
|
|
178
|
-
│ ├──
|
|
179
|
-
│
|
|
180
|
-
└──
|
|
181
|
-
|
|
182
|
-
├──
|
|
183
|
-
├──
|
|
184
|
-
├──
|
|
185
|
-
├──
|
|
186
|
-
├──
|
|
187
|
-
├──
|
|
188
|
-
├──
|
|
189
|
-
├──
|
|
190
|
-
├──
|
|
191
|
-
|
|
189
|
+
├── tools/ # Tool-specific syntax & configs
|
|
190
|
+
│ ├── jmeter.md # Apache JMeter (Groovy, Samplers, Extractors)
|
|
191
|
+
│ ├── k6.md # k6 JS/TS scripting, thresholds, VU options
|
|
192
|
+
│ ├── gatling.md # Gatling Scala/Java DSL & feeders
|
|
193
|
+
│ ├── locust.md # Locust Python scripting & FastHttpUser
|
|
194
|
+
│ ├── artillery.md # Artillery YAML/JS/TS scripting, phases, ensure SLOs, cloud
|
|
195
|
+
│ ├── neoload.md # NeoLoad GUI configs & API execution
|
|
196
|
+
│ ├── loadrunner.md # LoadRunner VuGen C scripting
|
|
197
|
+
│ └── octoperf.md # OctoPerf JMeter-based cloud execution
|
|
198
|
+
└── topics/ # Cross-cutting topics (Tool-agnostic)
|
|
199
|
+
├── workload-design.md # Little's Law, concurrency, pacing
|
|
200
|
+
├── test-data.md # Parameterization, CSV, data isolation
|
|
201
|
+
├── script-generation.md # Naming conventions, error handling
|
|
202
|
+
├── correlation.md # Dynamic values, Regex, JSONPath (Highlighted!)
|
|
203
|
+
├── test-execution.md # Distributed load, CI/CD pipelines
|
|
204
|
+
├── results-analysis.md # Latency percentiles, bottleneck triage
|
|
205
|
+
├── observability.md # Grafana, APM integration, VM profiling
|
|
206
|
+
├── production-testing.md # Chaos engineering, canary safety controls
|
|
207
|
+
├── protocol-testing.md # gRPC, GraphQL, WebSockets, Kafka
|
|
208
|
+
├── database-testing.md # JDBC, pools, deadlocks, slow queries
|
|
209
|
+
├── modern-architectures.md # K8s scaling, HPA, Frontend CWV
|
|
210
|
+
├── llm-inference.md # LLM serving: TTFT, TPOT/ITL, TPS, goodput, tools
|
|
211
|
+
└── slo-capacity.md # SLOs, error budgets, headroom, CI gating
|
|
192
212
|
```
|
|
193
213
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
### Routing Logic
|
|
197
|
-
|
|
198
|
-
`SKILL.md` acts as the entry point and router. It contains:
|
|
214
|
+
---
|
|
199
215
|
|
|
200
|
-
|
|
201
|
-
2. **Reference Map** - maps user intent to the right file.
|
|
202
|
-
3. **Protocol Routing Table** - maps protocols (gRPC, GraphQL, etc.) to recommended tools and reference files.
|
|
203
|
-
4. **Tool Selection Matrix** - helps recommend a tool when the user hasn't chosen one.
|
|
204
|
-
5. **Key Principles** - the single source of truth for cross-cutting best practices (assertions, think time, parameterization, correlation).
|
|
216
|
+
## 💡 How the Skill Works
|
|
205
217
|
|
|
206
|
-
|
|
218
|
+
To avoid hitting context window limits, the skill operates on a **selective loading** design:
|
|
207
219
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
- **Opinionated**: The skill prescribes best practices, not just documentation. It tells you what to do, not just what's possible.
|
|
220
|
+
1. **Routing**: [SKILL.md](skills/perf/SKILL.md) is always read first.
|
|
221
|
+
2. **Selective Context**: Based on the query, only the exact tool file (e.g., `k6.md`) and the relevant topic file (e.g., `correlation.md`) are loaded.
|
|
222
|
+
3. **Consistency**: Global rules (like realistic think times and assertions) are enforced uniformly across all tool files by anchoring them in [SKILL.md](skills/perf/SKILL.md).
|
|
212
223
|
|
|
213
|
-
|
|
224
|
+
### ❓ Example Queries and Loaded Files
|
|
214
225
|
|
|
215
|
-
|
|
|
216
|
-
|
|
217
|
-
| "Help me write a k6 load test for our REST API" | `k6.md` |
|
|
218
|
-
| "How should I
|
|
219
|
-
| "Set up
|
|
220
|
-
| "Our p95 latency
|
|
221
|
-
| "
|
|
222
|
-
| "
|
|
223
|
-
| "Test our Kafka consumer throughput" | `protocol-testing.md` |
|
|
224
|
-
| "Validate our K8s HPA scales correctly under load" | `modern-architectures.md` |
|
|
225
|
-
| "Load test our PostgreSQL connection pool" | `database-testing.md` + `jmeter.md` |
|
|
226
|
+
| Query | Files Loaded |
|
|
227
|
+
| :--- | :--- |
|
|
228
|
+
| *"Help me write a k6 load test for our REST API"* | `k6.md` |
|
|
229
|
+
| *"How should I correlate a dynamic JSESSIONID in JMeter?"* | `jmeter.md` + `correlation.md` |
|
|
230
|
+
| *"Set up a distributed Locust execution in GitLab CI"* | `locust.md` + `test-execution.md` |
|
|
231
|
+
| *"Our p95 latency spikes during database writes, how do we fix?"* | `database-testing.md` + `results-analysis.md` |
|
|
232
|
+
| *"What TTFT/throughput can our vLLM endpoint sustain at 200 concurrent users?"* | `llm-inference.md` |
|
|
233
|
+
| *"How many replicas do we need, and what SLO headroom should we keep?"* | `slo-capacity.md` |
|
|
226
234
|
|
|
227
|
-
|
|
235
|
+
---
|
|
228
236
|
|
|
229
|
-
|
|
237
|
+
## 🤝 Contributing
|
|
230
238
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
239
|
+
We welcome contributions to expand the knowledge base:
|
|
240
|
+
1. **Tool-specific syntax** belongs in `references/tools/`.
|
|
241
|
+
2. **Generic concepts** belong in `references/topics/`.
|
|
242
|
+
3. **Keep it DRY**: Reference topic files from tool files instead of duplicating.
|
|
243
|
+
4. **Update the Index**: Remember to update the Reference Map in [SKILL.md](skills/perf/SKILL.md) when adding files.
|
|
236
244
|
|
|
237
|
-
## License
|
|
245
|
+
## 📄 License
|
|
238
246
|
|
|
239
|
-
See
|
|
247
|
+
This project is licensed under the MIT License. See [LICENSE.md](LICENSE.md) for details.
|
|
248
|
+
tails.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "perf-skills",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "Expert AI skill for performance testing - covers JMeter, k6, Gatling, Locust, NeoLoad, LoadRunner, OctoPerf across the full lifecycle",
|
|
5
5
|
"main": "skills/perf/SKILL.md",
|
|
6
6
|
"files": [
|
package/skills/perf/SKILL.md
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: perf
|
|
3
3
|
description: Performance testing expert covering the full lifecycle for
|
|
4
|
-
JMeter, k6, Gatling, Locust, NeoLoad,
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
4
|
+
JMeter, k6, Gatling, Locust, NeoLoad, LoadRunner, and Artillery, plus
|
|
5
|
+
LLM inference (vLLM, TRT-LLM, SGLang, OpenAI-compatible endpoints).
|
|
6
|
+
Use this skill whenever writing or reviewing load test scripts,
|
|
7
|
+
setting thresholds, choosing executors, configuring CI/CD pipelines,
|
|
8
|
+
diagnosing latency issues, designing workloads, analyzing results,
|
|
9
|
+
or recommending tools - even if the tool is not named explicitly.
|
|
10
|
+
Always consult before suggesting thresholds, executor types, or
|
|
11
|
+
output configuration. Prefer this skill over general knowledge for
|
|
12
|
+
any performance testing decision, debugging session, tool comparison,
|
|
13
|
+
LLM/TGPT streaming-performance question, or SLO/capacity-planning
|
|
14
|
+
decision.
|
|
12
15
|
---
|
|
13
16
|
|
|
14
17
|
# Performance Testing Skill
|
|
@@ -46,6 +49,7 @@ Multiple files may apply.
|
|
|
46
49
|
| k6 scripting, extensions, cloud | `references/tools/k6.md` |
|
|
47
50
|
| Gatling simulations, Scala/Java DSL | `references/tools/gatling.md` |
|
|
48
51
|
| Locust Python tests, distributed | `references/tools/locust.md` |
|
|
52
|
+
| Artillery YAML/JS/TS scripts, cloud | `references/tools/artillery.md` |
|
|
49
53
|
| NeoLoad projects, GUI, APIs | `references/tools/neoload.md` |
|
|
50
54
|
| LoadRunner scripts, protocols, VuGen | `references/tools/loadrunner.md` |
|
|
51
55
|
| OctoPerf cloud test management | `references/tools/octoperf.md` |
|
|
@@ -60,6 +64,8 @@ Multiple files may apply.
|
|
|
60
64
|
| gRPC, GraphQL, WebSocket, messaging protocols | `references/topics/protocol-testing.md` |
|
|
61
65
|
| Database load testing (JDBC, connection pools) | `references/topics/database-testing.md` |
|
|
62
66
|
| Microservices, K8s, serverless performance | `references/topics/modern-architectures.md` |
|
|
67
|
+
| LLM inference: TTFT, TPOT/ITL, TPS, goodput | `references/topics/llm-inference.md` |
|
|
68
|
+
| SLOs, error budgets, capacity & headroom | `references/topics/slo-capacity.md` |
|
|
63
69
|
|
|
64
70
|
### Protocol Routing Table
|
|
65
71
|
|
|
@@ -76,6 +82,8 @@ right tool and reference:
|
|
|
76
82
|
| Kafka / Message Queues | k6 (xk6-kafka), JMeter | `references/topics/protocol-testing.md` |
|
|
77
83
|
| SOAP / WSDL | LoadRunner, JMeter | Tool file |
|
|
78
84
|
| SAP / Citrix | LoadRunner, NeoLoad | Tool file |
|
|
85
|
+
| LLM inference (streaming) | vLLM bench, GenAI-Perf, GuideLLM, llmperf | `references/topics/llm-inference.md` |
|
|
86
|
+
| SLO/capacity (error budgets, headroom) | any | `references/topics/slo-capacity.md` |
|
|
79
87
|
|
|
80
88
|
---
|
|
81
89
|
|
|
@@ -83,16 +91,16 @@ right tool and reference:
|
|
|
83
91
|
|
|
84
92
|
Use this to recommend the right tool when the user hasn't decided yet.
|
|
85
93
|
|
|
86
|
-
| Criteria | JMeter | k6 | Gatling | Locust | NeoLoad | LoadRunner | OctoPerf |
|
|
94
|
+
| Criteria | JMeter | k6 | Gatling | Locust | Artillery | NeoLoad | LoadRunner | OctoPerf |
|
|
87
95
|
|----------------------|---------------------|-----------------------|----------------------|----------------|------------------|-------------------------|-----------------------|
|
|
88
96
|
| **Language** | GUI/XML + Groovy | JavaScript/TypeScript | Scala/Java | Python | GUI + NeoLoad DSL| VuGen C-like | Web UI (JMeter-based) |
|
|
89
97
|
| **Open source** | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ (SaaS) |
|
|
90
98
|
| **Protocol support** | HTTP, JDBC, JMS, MQTT, FTP, gRPC | HTTP, gRPC, WS | HTTP, JMS, gRPC | HTTP, gRPC | HTTP, gRPC, WS, SAP | HTTP, Citrix, SAP, Flex | HTTP (JMeter-backed) |
|
|
91
99
|
| **Developer-friendly** | Medium | High | High | High | Low | Low | Medium |
|
|
92
|
-
| **Enterprise support** | Community + BlazeMeter | Grafana Cloud | Gatling Enterprise | Limited | ✅ | ✅ | ✅ |
|
|
100
|
+
| **Enterprise support** | Community + BlazeMeter | Grafana Cloud | Gatling Enterprise | Limited | Artillery Cloud | ✅ | ✅ | ✅ |
|
|
93
101
|
| **CI/CD integration** | Good (Maven/Gradle) | Excellent | Excellent | Good | Good | Moderate | Good |
|
|
94
|
-
| **Cloud execution** | BlazeMeter, OctoPerf | Grafana Cloud | Gatling Enterprise | Self-managed | NeoLoad Cloud | AWS/on-prem | OctoPerf Cloud |
|
|
95
|
-
| **Best for** | Legacy systems, JDBC, protocols | Modern APIs, TypeScript devs | High-throughput HTTP | Python teams, flexible | SAP/Citrix enterprise | Mainframe, legacy enterprise | JMeter teams needing cloud UI |
|
|
102
|
+
| **Cloud execution** | BlazeMeter, OctoPerf | Grafana Cloud | Gatling Enterprise | Self-managed | Artillery Cloud (Lambda/Fargate) | NeoLoad Cloud | AWS/on-prem | OctoPerf Cloud |
|
|
103
|
+
| **Best for** | Legacy systems, JDBC, protocols | Modern APIs, TypeScript devs | High-throughput HTTP | Python teams, flexible | Node teams, YAML tests, cloud scale | SAP/Citrix enterprise | Mainframe, legacy enterprise | JMeter teams needing cloud UI |
|
|
96
104
|
|
|
97
105
|
### Quick decision rules
|
|
98
106
|
|
|
@@ -107,6 +115,7 @@ Use this to recommend the right tool when the user hasn't decided yet.
|
|
|
107
115
|
Correlation Recorder) or LoadRunner
|
|
108
116
|
- **gRPC or GraphQL APIs** → k6 or Gatling
|
|
109
117
|
- **Message queues (Kafka, RabbitMQ)** → k6 (xk6-kafka) or JMeter
|
|
118
|
+
- **Node shop / prefer YAML over code** → Artillery (YAML, JS, TS; easy Lambda/Fargate cloud scale)
|
|
110
119
|
|
|
111
120
|
---
|
|
112
121
|
|
|
@@ -181,26 +190,44 @@ ask about them.
|
|
|
181
190
|
that skews both throughput and latency measurements. Always run
|
|
182
191
|
workers on separate machines or containers for distributed tests.
|
|
183
192
|
|
|
184
|
-
|
|
193
|
+
### Artillery
|
|
194
|
+
|
|
195
|
+
- **No `ensure` block** - without `ensure`, Artillery reports metrics
|
|
196
|
+
but always exits 0, so CI never fails on latency or error spikes.
|
|
197
|
+
Always add `ensure.thresholds` (or `conditions`) for SLA gates.
|
|
198
|
+
- **`arrivalRate` mistaken for concurrency** - it is new users per
|
|
199
|
+
second (open model). On a slow backend, pending VUs pile up unbounded.
|
|
200
|
+
Set `maxVusers` to cap real concurrency, or use `arrivalCount`.
|
|
201
|
+
- **Strict captures aborting VUs** - captures are strict by default;
|
|
202
|
+
a missed extractor stops the whole VU. Only set `strict: false`
|
|
203
|
+
when a downstream 404 is acceptable.
|
|
204
|
+
- **`payload.order: sequence` in distributed runs** - sequential CSV
|
|
205
|
+
consumption breaks under Lambda/Fargate workers (each has its own
|
|
206
|
+
copy). Use the default `random` ordering for distributed tests.
|
|
207
|
+
- **Zero `think` time** - 1 VU/sec with no think time fires the
|
|
208
|
+
maximum RPS for the journey; add `think` to model real pacing.
|
|
209
|
+
- **Forgetting `http.response_time` is TTFB** - latency metrics are
|
|
210
|
+
time-to-first-byte by default. Enable `config.http.extendedMetrics`
|
|
211
|
+
for full download timing (`http.total.*`) when that matters.
|
|
185
212
|
|
|
186
|
-
|
|
213
|
+
---
|
|
187
214
|
|
|
188
215
|
Use this when users are migrating between tools or asking how a
|
|
189
216
|
concept from one tool maps to another. Claude should always provide
|
|
190
217
|
the specific mapping rather than a generic explanation.
|
|
191
218
|
|
|
192
|
-
| Concept | JMeter | k6 | Gatling | Locust | LoadRunner |
|
|
193
|
-
|
|
194
|
-
| Virtual user | Thread | VU | User | User | Vuser |
|
|
195
|
-
| Test plan | .jmx file | .js / .ts script | Simulation class | .py file | VuGen script (.usr) |
|
|
196
|
-
| User entrypoint | Thread Group | `default()` function | `scenario()` | task methods | `Action()` |
|
|
197
|
-
| Concurrency ctrl | Thread Group settings | executor | `inject()` | `spawn_rate` | Vuser Group |
|
|
198
|
-
| Think time | Constant/Uniform Timer | `sleep()` | `pause()` | `time.sleep()` | `lr_think_time()` |
|
|
219
|
+
| Concept | JMeter | k6 | Gatling | Locust | Artillery | LoadRunner |
|
|
220
|
+
|------------------|-------------------------|-------------------------|--------------------------|--------------------------|--------------------------|---------------------|
|
|
221
|
+
| Virtual user | Thread | VU | User | User | VU (arrival per sec) | Vuser |
|
|
222
|
+
| Test plan | .jmx file | .js / .ts script | Simulation class | .py file | .yml / .js / .ts script | VuGen script (.usr) |
|
|
223
|
+
| User entrypoint | Thread Group | `default()` function | `scenario()` | task methods | `flow` in scenario | `Action()` |
|
|
224
|
+
| Concurrency ctrl | Thread Group settings | executor | `inject()` | `spawn_rate` | `maxVusers` / `arrivalRate` | Vuser Group |
|
|
225
|
+
| Think time | Constant/Uniform Timer | `sleep()` | `pause()` | `time.sleep()` | `think` | `lr_think_time()` |
|
|
199
226
|
| Inline assertion | Response Assertion | `check()` | `.check()` | `catch_response` | `lr_eval_string()` |
|
|
200
|
-
| SLA enforcement | Duration Assertion | `thresholds` | Assertions (Enterprise) | custom + exit code | SLA definition |
|
|
201
|
-
| Correlation | Regex / CSS Extractor | `res.json()` / regex | `.check()` + `saveAs()` | `response.text` + regex | `web_reg_save_param`|
|
|
202
|
-
| Data feed | CSV Data Set Config | `SharedArray` | `feeder` | CSV reader | `lr_paramarr()` |
|
|
203
|
-
| Grouping | Transaction Controller | `group()` | `group()` | task sets | Transaction |
|
|
227
|
+
| SLA enforcement | Duration Assertion | `thresholds` | Assertions (Enterprise) | custom + exit code | `ensure` plugin | SLA definition |
|
|
228
|
+
| Correlation | Regex / CSS Extractor | `res.json()` / regex | `.check()` + `saveAs()` | `response.text` + regex | `capture` (json/xpath/regexp/header) | `web_reg_save_param`|
|
|
229
|
+
| Data feed | CSV Data Set Config | `SharedArray` | `feeder` | CSV reader | `payload` / `variables` | `lr_paramarr()` |
|
|
230
|
+
| Grouping | Transaction Controller | `group()` | `group()` | task sets | `name` on scenario | Transaction |
|
|
204
231
|
| Distributed | Controller + Agents | k6 cloud / k6 operator | Gatling Enterprise | master + workers | Load Generator |
|
|
205
232
|
| Results output | .jtl (CSV/XML) | JSON / InfluxDB / cloud | simulation.log | CSV / Locust web UI | .lrr file |
|
|
206
233
|
|
|
@@ -317,4 +344,4 @@ prescribing a solution:
|
|
|
317
344
|
- Where will tests **run from** (local, CI, cloud)?
|
|
318
345
|
- What **environment** is being tested (dev, staging, prod)?
|
|
319
346
|
- Is there an **APM tool** in place (Datadog, Dynatrace, Grafana,
|
|
320
|
-
New Relic)
|
|
347
|
+
New Relic)??)??
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
# Artillery Reference
|
|
2
|
+
|
|
3
|
+
> Targets: Artillery v2.x (YAML, JS, and TS test definitions; `http`, `playwright`, `socketio`, `ws` engines)
|
|
4
|
+
|
|
5
|
+
Artillery is a developer-centric, open-source load testing tool that runs on Node.js. Tests can be written in YAML, JavaScript, or TypeScript, and it scales from a single laptop to distributed AWS Lambda / Fargate runs (Artillery Cloud). It is the closest JS/TS-native alternative to k6 and is a strong fit for teams already in the Node ecosystem.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Core Concepts
|
|
10
|
+
|
|
11
|
+
| Concept | Description |
|
|
12
|
+
|---|---|
|
|
13
|
+
| **Virtual User (VU)** | A single simulated user executing a `flow` |
|
|
14
|
+
| **Arrival (open model)** | New VUs generated per second - the default load model |
|
|
15
|
+
| **Phase** | A timed load segment (`arrivalRate`, `rampTo`, `arrivalCount`, `pause`) |
|
|
16
|
+
| **Scenario** | Named user journey; a `flow` of requests/actions |
|
|
17
|
+
| **Flow** | Ordered list of actions (request, `think`, `capture`, `loop`, `function`) |
|
|
18
|
+
| **Capture** | Extract a dynamic value from a response for later reuse (correlation) |
|
|
19
|
+
| **Processor** | Custom JS/TS module supplying hooks and metric logic |
|
|
20
|
+
| **`ensure`** | SLO/assertion plugin - FAILS the run (non-zero exit) if breached |
|
|
21
|
+
| **Environment** | Named config profile switched with `-e` |
|
|
22
|
+
|
|
23
|
+
> Artillery uses an **open (arrival-rate) load model by default**. `arrivalRate` is *new users per second*, NOT concurrent users. Use `maxVusers` to cap real concurrency. For closed/concurrency modeling, see `../topics/workload-design.md`.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Script Structure (YAML)
|
|
28
|
+
|
|
29
|
+
```yaml
|
|
30
|
+
config:
|
|
31
|
+
target: 'https://staging.example.com'
|
|
32
|
+
phases:
|
|
33
|
+
- duration: '2m'
|
|
34
|
+
arrivalRate: 10
|
|
35
|
+
rampTo: 50
|
|
36
|
+
name: ramp-up
|
|
37
|
+
- duration: '5m'
|
|
38
|
+
arrivalRate: 50
|
|
39
|
+
maxVusers: 200
|
|
40
|
+
name: sustain
|
|
41
|
+
ensure:
|
|
42
|
+
thresholds:
|
|
43
|
+
- 'http.response_time.p95': 500
|
|
44
|
+
- 'http.response_time.p99': 1000
|
|
45
|
+
conditions:
|
|
46
|
+
- expression: 'http.codes.5xx < http.codes.2xx * 0.01' # <1% 5xx
|
|
47
|
+
processor: './helpers.js'
|
|
48
|
+
|
|
49
|
+
scenarios:
|
|
50
|
+
- name: 'Browse + Checkout'
|
|
51
|
+
weight: 1
|
|
52
|
+
flow:
|
|
53
|
+
- post:
|
|
54
|
+
url: '/auth'
|
|
55
|
+
json:
|
|
56
|
+
username: '{{ username }}'
|
|
57
|
+
password: '{{ password }}'
|
|
58
|
+
capture:
|
|
59
|
+
- json: '$.id_token'
|
|
60
|
+
as: token
|
|
61
|
+
- think: 2
|
|
62
|
+
- get:
|
|
63
|
+
url: '/products'
|
|
64
|
+
headers:
|
|
65
|
+
authorization: 'Bearer {{ token }}'
|
|
66
|
+
- post:
|
|
67
|
+
url: '/checkout'
|
|
68
|
+
json:
|
|
69
|
+
itemId: '{{ $uuid }}'
|
|
70
|
+
capture:
|
|
71
|
+
- json: '$.orderId'
|
|
72
|
+
as: orderId
|
|
73
|
+
- get:
|
|
74
|
+
url: '/orders/{{ orderId }}'
|
|
75
|
+
headers:
|
|
76
|
+
authorization: 'Bearer {{ token }}'
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Script Structure (JS / TS)
|
|
82
|
+
|
|
83
|
+
```javascript
|
|
84
|
+
export const config = {
|
|
85
|
+
target: 'https://staging.example.com',
|
|
86
|
+
phases: [
|
|
87
|
+
{ duration: '2m', arrivalRate: 10, rampTo: 50, name: 'ramp-up' },
|
|
88
|
+
{ duration: '5m', arrivalRate: 50, maxVusers: 200, name: 'sustain' },
|
|
89
|
+
],
|
|
90
|
+
ensure: {
|
|
91
|
+
thresholds: [
|
|
92
|
+
'http.response_time.p95: 500',
|
|
93
|
+
'http.response_time.p99: 1000',
|
|
94
|
+
],
|
|
95
|
+
},
|
|
96
|
+
processor: './helpers.js',
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export const scenarios = [
|
|
100
|
+
{
|
|
101
|
+
name: 'Browse + Checkout',
|
|
102
|
+
flow: [
|
|
103
|
+
{
|
|
104
|
+
post: {
|
|
105
|
+
url: '/auth',
|
|
106
|
+
json: { username: '{{ username }}', password: '{{ password }}' },
|
|
107
|
+
capture: [{ json: '$.id_token', as: 'token' }],
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
{ think: 2 },
|
|
111
|
+
{
|
|
112
|
+
get: {
|
|
113
|
+
url: '/products',
|
|
114
|
+
headers: { authorization: 'Bearer {{ token }}' },
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
],
|
|
118
|
+
},
|
|
119
|
+
];
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## Load Phases
|
|
125
|
+
|
|
126
|
+
`config.phases` is an array executed sequentially. Four phase kinds:
|
|
127
|
+
|
|
128
|
+
| Phase kind | Key options | Use case |
|
|
129
|
+
|---|---|---|
|
|
130
|
+
| **Constant arrival** | `arrivalRate` | Steady RPS-style load (open model) |
|
|
131
|
+
| **Ramp** | `arrivalRate` + `rampTo` (both over `duration`) | Warm-up / ramp-up |
|
|
132
|
+
| **Fixed count** | `arrivalCount` | Exact N total users spread over `duration` |
|
|
133
|
+
| **Pause** | `pause` | Idle gap (soak cool-down, between spikes) |
|
|
134
|
+
|
|
135
|
+
```yaml
|
|
136
|
+
phases:
|
|
137
|
+
- duration: '30m'
|
|
138
|
+
arrivalRate: 1
|
|
139
|
+
rampTo: 100
|
|
140
|
+
name: ramp-up
|
|
141
|
+
- duration: '3h'
|
|
142
|
+
arrivalRate: 100
|
|
143
|
+
name: sustain # soak/endurance
|
|
144
|
+
- duration: '1m'
|
|
145
|
+
arrivalRate: 500
|
|
146
|
+
name: spike # spike test
|
|
147
|
+
- pause: 60
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
- `duration` / `pause` accept human-readable units (`'5m'`, `'3h'`) as well as seconds.
|
|
151
|
+
- `maxVusers` caps in-flight VUs for any phase - essential to bound concurrency on slow servers (open-model load otherwise queues unbounded pending VUs).
|
|
152
|
+
- `name` makes phases identifiable in CLI output and Artillery Cloud.
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Correlation (Capture / Dynamic Values)
|
|
157
|
+
|
|
158
|
+
Use `capture` on a request to extract a value for later steps. Requires `as` and one extractor:
|
|
159
|
+
|
|
160
|
+
| Extractor | Syntax | Example |
|
|
161
|
+
|---|---|---|
|
|
162
|
+
| JSONPath | `json: '$.path'` | `json: '$.id_token'` |
|
|
163
|
+
| XPath | `xpath: '//node/text()'` | SOAP / XML bodies |
|
|
164
|
+
| Regex | `regexp: 'pattern'`, optional `group`, `flags` | `regexp: 'sid=([^&]+)'` |
|
|
165
|
+
| Header | `header: 'X-Custom'` | `header: 'Set-Cookie'` |
|
|
166
|
+
| Selector | `selector: 'a.product'`, `attr`, `index` | HTML scraping |
|
|
167
|
+
|
|
168
|
+
```yaml
|
|
169
|
+
- get:
|
|
170
|
+
url: '/login'
|
|
171
|
+
capture:
|
|
172
|
+
- json: '$.csrf'
|
|
173
|
+
as: csrf
|
|
174
|
+
- header: 'set-cookie'
|
|
175
|
+
as: cookie
|
|
176
|
+
- post:
|
|
177
|
+
url: '/submit'
|
|
178
|
+
headers:
|
|
179
|
+
x-csrf-token: '{{ csrf }}'
|
|
180
|
+
cookie:
|
|
181
|
+
session: '{{ cookie }}'
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
- **Captures are strict by default**: a failed capture stops that VU. Set `strict: false` only when a later request can safely 404.
|
|
185
|
+
- For multi-step journeys, capture once near the top and reuse via `{{ var }}` in every later request.
|
|
186
|
+
- Capture multiple values from one response with an array of capture specs.
|
|
187
|
+
|
|
188
|
+
> For framework-specific extraction rules (ASP.NET ViewState, JSF, JWT, SAP `sap-contextid`), see `../topics/correlation.md`.
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## Parameterization
|
|
193
|
+
|
|
194
|
+
### CSV payload (`config.payload`)
|
|
195
|
+
```yaml
|
|
196
|
+
config:
|
|
197
|
+
payload:
|
|
198
|
+
path: 'users.csv'
|
|
199
|
+
fields: ['username', 'password']
|
|
200
|
+
skipHeader: true
|
|
201
|
+
order: sequence # 'random' (default) | 'sequence'
|
|
202
|
+
scenarios:
|
|
203
|
+
- flow:
|
|
204
|
+
- post:
|
|
205
|
+
url: '/auth'
|
|
206
|
+
json:
|
|
207
|
+
username: '{{ username }}'
|
|
208
|
+
password: '{{ password }}'
|
|
209
|
+
```
|
|
210
|
+
- `order: sequence` is deterministic but **breaks under distributed runs** (each worker has its own copy). Use `random` (default) for distributed tests.
|
|
211
|
+
- `loadAll: true` + `name` exposes the whole dataset to each VU for `loop`.
|
|
212
|
+
- `cast: false` keeps values as strings; `delimiter` overrides the comma.
|
|
213
|
+
|
|
214
|
+
### Inline variables (`config.variables`)
|
|
215
|
+
```yaml
|
|
216
|
+
config:
|
|
217
|
+
variables:
|
|
218
|
+
postcode: ['SE1', 'EC1', 'E8']
|
|
219
|
+
id: ['8731', '9965', '2806']
|
|
220
|
+
```
|
|
221
|
+
One value is picked at random per VU. Cannot template `config` values.
|
|
222
|
+
|
|
223
|
+
### Environment variables (`$env`)
|
|
224
|
+
```yaml
|
|
225
|
+
headers:
|
|
226
|
+
x-api-key: '{{ $env.API_KEY }}'
|
|
227
|
+
```
|
|
228
|
+
Run with `API_KEY=xxx artillery run script.yml` or `--env-file .env`. Keeps secrets out of source.
|
|
229
|
+
|
|
230
|
+
### Environments (`-e`)
|
|
231
|
+
Reuse one script across dev/staging/prod by defining `config.environments` with per-env `target` and `phases`:
|
|
232
|
+
```bash
|
|
233
|
+
artillery run -e production script.yml
|
|
234
|
+
```
|
|
235
|
+
Access the active name via `{{ $environment }}` (e.g. to pick a CSV: `path: '{{ $environment }}-logins.csv'`).
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## SLO Checks with `ensure` (Assertions)
|
|
240
|
+
|
|
241
|
+
`ensure` is Artillery's SLA gate - **without it, the run reports metrics but always exits 0**, so CI never fails on latency. Always add it for CI.
|
|
242
|
+
|
|
243
|
+
```yaml
|
|
244
|
+
config:
|
|
245
|
+
plugins:
|
|
246
|
+
ensure:
|
|
247
|
+
thresholds: # value must be LESS than this
|
|
248
|
+
- 'http.response_time.p95': 500
|
|
249
|
+
- 'http.response_time.p99': 1000
|
|
250
|
+
conditions: # advanced boolean/numeric expressions
|
|
251
|
+
- expression: 'http.response_time.p95 < 500 and http.request_rate > 1000'
|
|
252
|
+
- expression: 'http.codes.5xx <= http.codes.2xx * 0.01'
|
|
253
|
+
strict: false # optional check; failure won't fail the run
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
- `thresholds` check a metric's aggregate is **below** the integer.
|
|
257
|
+
- `conditions` combine metrics with `+ - * / % ^`, comparisons, `and`/`or`/`not`, and `ceil/floor/round`.
|
|
258
|
+
- `strict: true` (default) fails the run on breach; `strict: false` reports only.
|
|
259
|
+
- Using a non-existent metric name makes that check fail.
|
|
260
|
+
|
|
261
|
+
### Key metrics for `ensure`
|
|
262
|
+
| Metric | Meaning |
|
|
263
|
+
|---|---|
|
|
264
|
+
| `http.response_time.p95` / `.p99` | Latency percentile (ms) |
|
|
265
|
+
| `http.request_rate` | Requests/sec |
|
|
266
|
+
| `http.codes.2xx` / `.4xx` / `.5xx` | Status-code counters |
|
|
267
|
+
| `http.downloaded_bytes` | Total payload bytes |
|
|
268
|
+
| `vusers.completed` / `vusers.failed` | VU outcomes |
|
|
269
|
+
|
|
270
|
+
> `http.response_time.*` is **TTFB** by default. Enable `config.http.extendedMetrics: true` for full `http.total.*` (download-complete) timing. For SLA baseline tables, see SKILL.md "Threshold Starting Points" and `../topics/results-analysis.md`.
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
## Custom Logic (Processor Hooks)
|
|
275
|
+
|
|
276
|
+
Load JS/TS via `config.processor`:
|
|
277
|
+
|
|
278
|
+
```javascript
|
|
279
|
+
// helpers.js
|
|
280
|
+
module.exports = {
|
|
281
|
+
setApiKey(context, events, done) {
|
|
282
|
+
context.vars.apiKey = process.env.API_KEY;
|
|
283
|
+
return done();
|
|
284
|
+
},
|
|
285
|
+
assertOrder(context, events, done) {
|
|
286
|
+
const status = context.vars.orderStatus;
|
|
287
|
+
if (status !== 'confirmed') {
|
|
288
|
+
events.emit('counter', 'order_failures', 1);
|
|
289
|
+
}
|
|
290
|
+
return done();
|
|
291
|
+
},
|
|
292
|
+
};
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
Hook points:
|
|
296
|
+
- **`beforeRequest` / `afterResponse`** - set on a request; customize/inspect URL, headers, body.
|
|
297
|
+
- **`beforeScenario` / `afterScenario`** - set on a scenario.
|
|
298
|
+
- **`function`** step - run arbitrary code mid-flow.
|
|
299
|
+
|
|
300
|
+
Async hooks are supported (v2.0.7+). Use `events.emit('counter'|'histogram'|'rate', name, value)` for custom metrics.
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
304
|
+
## Output and Observability
|
|
305
|
+
|
|
306
|
+
| Output | How |
|
|
307
|
+
|---|---|
|
|
308
|
+
| Terminal summary | Default (`artillery run script.yml`) |
|
|
309
|
+
| JSON report | `artillery run -o json=report.json script.yml` |
|
|
310
|
+
| Artillery Cloud | `artillery run --record script.yml` (dashboards, historical trends) |
|
|
311
|
+
| CSV | `artillery run -o csv=results.csv script.yml` |
|
|
312
|
+
| Distributed | AWS Lambda / Fargate workers via `artillery run-fargate` |
|
|
313
|
+
|
|
314
|
+
Enable `config.http.distributedTracing: true` to attach a W3C `traceparent` header to every request - correlates load with backend spans in your APM.
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## Artillery-Specific Tips
|
|
319
|
+
|
|
320
|
+
- **`arrivalRate` is new-users-per-second, not concurrency.** A slow backend makes pending VUs pile up. Always set `maxVusers` to bound real concurrency, or switch to `arrivalCount`/closed-model thinking via `workload-design.md`.
|
|
321
|
+
- **Always add `ensure` for CI.** A test with no `ensure` exits 0 regardless of latency or error spikes - it generates traffic but enforces nothing.
|
|
322
|
+
- **Captures are strict by default.** A missed extractor aborts the whole VU. Only set `strict: false` when a downstream 404 is acceptable.
|
|
323
|
+
- **Add `think` between steps.** Zero think time maximizes RPS unrealistically; use `think` (seconds or `ms` units) to model real pacing.
|
|
324
|
+
- **Default `payload.order` is `random`** - deterministic `sequence` ordering does not work correctly in distributed runs.
|
|
325
|
+
- **`http.response_time` is TTFB.** Turn on `extendedMetrics` if you need full download time (`http.total.*`).
|
|
326
|
+
- **Secrets via `$env` / `--env-file`**, never inline. Use `config.environments` + `-e` to promote the same script dev → staging → prod.
|
|
327
|
+
- **Avoid heavy `log` actions under load** - they add overhead; prefer `ensure`/custom counters for visibility.
|
|
328
|
+
- **Browser load?** Use the `playwright` engine (`engines: { playwright: {} }`) to drive real pages; note it is far heavier per VU than HTTP.
|
|
329
|
+
|
|
330
|
+
> For CI/CD integration (GitHub Actions, GitLab CI, distributed execution), see `../topics/test-execution.md`.
|
|
331
|
+
> For anti-patterns, assertions, think time, and parameterization principles, see **Key Principles** in `SKILL.md`.
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# LLM Inference Performance
|
|
2
|
+
|
|
3
|
+
> Scope: load and capacity testing of LLM inference servers (vLLM, TRT-LLM, SGLang, OpenAI-compatible endpoints, Ray Serve LLM, KServe). Covers metric definitions, workload design, tools, and SLO methodology for generative AI.
|
|
4
|
+
|
|
5
|
+
LLM inference is **not conventional HTTP load testing**. Responses stream token-by-token over an open connection, output length is unbounded, and the dominant cost is GPU compute, not network. A generic `k6`/`JMeter` run that only measures request latency will badly misreport performance - it ignores the streaming shape entirely. This topic defines the metrics that actually matter, how to design realistic LLM workloads, and which tools to use.
|
|
6
|
+
|
|
7
|
+
> For general workload theory (Little's Law, open vs closed models), see `workload-design.md`. For analyzing the percentiles and SLO failures this produces, see `results-analysis.md`. For running these tests in CI/CD, see `test-execution.md`.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Why LLM load testing is different
|
|
12
|
+
|
|
13
|
+
| Aspect | Traditional API | LLM inference |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| Response | Single body, fixed size | Streamed tokens, variable length |
|
|
16
|
+
| Latency signal | TTFB + total | TTFT + inter-token gaps + E2E |
|
|
17
|
+
| Cost driver | CPU/network | GPU memory (KV cache), compute |
|
|
18
|
+
| Throughput unit | Requests/sec | Tokens/sec (not requests/sec) |
|
|
19
|
+
| Concurrency limit | Threads/sockets | KV cache capacity (`max_num_seqs`) |
|
|
20
|
+
| Key risk | Timeouts | Queueing delay, GPU saturation, OOM |
|
|
21
|
+
|
|
22
|
+
Two phases dominate every request:
|
|
23
|
+
- **Prefill** - the model processes the full prompt to build the KV cache. Compute-heavy, determines **TTFT**. Scales with prompt length.
|
|
24
|
+
- **Decode** - tokens generated one at a time using the KV cache. Memory-bandwidth-bound, determines **ITL/TPOT**. Scales with output length.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Core Metrics
|
|
29
|
+
|
|
30
|
+
### TTFT - Time to First Token
|
|
31
|
+
Time from request send to first streamed token. Driven by prompt length, queue/self.time, and prefill speed. The primary *perceived responsiveness* metric for chat/coding assistants. High TTFT under load usually means the scheduler is queuing requests (KV cache exhausted) rather than a slow model.
|
|
32
|
+
|
|
33
|
+
### TPOT / ITL - Generation smoothness
|
|
34
|
+
- **TPOT (Time Per Output Token)** - average gap between tokens for a single request: `(E2E - TTFT) / (output_tokens - 1)`. Report the mean of per-request TPOTs.
|
|
35
|
+
- **ITL (Inter-Token Latency)** - same gaps, but token-weighted across all requests (mean of every gap). This is the system's steady streaming speed.
|
|
36
|
+
- **Which to use**: TPOT compares per-request behavior (each request equal); ITL estimates system-wide streaming feel across mixed traffic. A bursty ITL (high p99 vs mean) makes output appear in clumps - the classic "GPU contention" signature.
|
|
37
|
+
|
|
38
|
+
### E2E Latency
|
|
39
|
+
Total time from prompt to final token. `E2E = TTFT + (output_tokens - 1) * TPOT`. Matters for batch/codegen/summarization where the full response is needed before the next step.
|
|
40
|
+
|
|
41
|
+
### TPS - Token Throughput
|
|
42
|
+
- **System TPS** - total output tokens/sec across all requests. Raw capacity; rises with load until GPU-saturated. `TPS = output_tokens / (T_last - T_first)`.
|
|
43
|
+
- **User TPS** - tokens/sec a single user experiences; `≈ 1 / ITL` at long outputs. Drops as concurrency rises because the engine shares the GPU.
|
|
44
|
+
|
|
45
|
+
### RPS - Request Throughput
|
|
46
|
+
Completed requests/sec. Dominant metric for high-volume short-prompt traffic (chatbots, search, API gateways). Shorter prompts + higher `max_num_seqs` raise RPS.
|
|
47
|
+
|
|
48
|
+
### Goodput (the metric that matters most)
|
|
49
|
+
`Goodput = (requests meeting ALL SLOs) / total_requests * 100%`. Unlike TPS/RPS, goodput tells you what fraction of users got an *acceptable* experience. A system can show high TPS while most requests violate latency SLOs. Always define goodput with explicit thresholds, e.g. TTFT < 500ms, TPOT < 15ms, E2E < 2s. As load climbs, goodput falls even as raw throughput keeps rising - that crossover is your real capacity limit.
|
|
50
|
+
|
|
51
|
+
### Percentiles
|
|
52
|
+
Report **p50/p95/p99** for TTFT, ITL, and E2E - never just averages. Averages hide the unlucky 5% whose tokens arrive in bursts. p99 is the near-worst-case; if p99 meets SLO, the system is consistent.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Workload Dimensions
|
|
57
|
+
|
|
58
|
+
Controlling these is what separates a useful LLM test from a misleading one:
|
|
59
|
+
|
|
60
|
+
| Dimension | Effect | How to set |
|
|
61
|
+
|---|---|---|
|
|
62
|
+
| **Prompt length (ISL)** | Longer → higher TTFT, more KV cache | Use real traffic (ShareGPT) or synthetic ranges |
|
|
63
|
+
| **Output length (OSL)** | Longer → higher E2E, more decode cost | Real distributions; `--random-output-len` for synthetic |
|
|
64
|
+
| **Concurrency** | Bounded by KV cache, not sockets | Set `--max-concurrency` to simulate gateway limits |
|
|
65
|
+
| **Request rate** | Open-model arrival (Poisson) | `--request-rate`; `inf` for max throughput |
|
|
66
|
+
| **Burstiness** | Gamma-distributed arrivals | `1.0` realistic, `0.1-0.5` stress, `2-5` uniform |
|
|
67
|
+
| **Batching (`max_num_seqs`)** | Higher → more RPS, worse per-user latency | Tune on the server, not the client |
|
|
68
|
+
|
|
69
|
+
**KV cache math**: `max_concurrency ≈ KV_cache_tokens / max_model_len`. vLLM prints this at startup. Set test concurrency to 80-90% of it for capacity planning; use the full value as the SLA limit.
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Tools
|
|
74
|
+
|
|
75
|
+
Purpose-built LLM benchmarkers surface token metrics natively. Prefer them over generic HTTP load testers.
|
|
76
|
+
|
|
77
|
+
| Tool | Best for | Notes |
|
|
78
|
+
|---|---|---|
|
|
79
|
+
| **vLLM `bench serve`** | Serving benchmarks against an OpenAI-compatible endpoint | Native TTFT/TPOT/ITL/TPS, ShareGPT + synthetic datasets, ramp-up, goodput via percentiles |
|
|
80
|
+
| **GuideLLM** | Production vLLM SLA/capacity studies | Auto reports, live progress, profile-based; recommended by vLLM for production |
|
|
81
|
+
| **NVIDIA GenAI-Perf** | Multi-backend (TRT-LLM, Triton, vLLM), concurrency/rate modes | Emits TTFT, ITL, output TPS, goodput; pairs with Perf Analyzer |
|
|
82
|
+
| **llmperf / LLM-Perf** | Quick pointwise latency/throughput checks | Lightweight, great for smoke and regression |
|
|
83
|
+
| **LLM Locust** | Distributed load on Locust, GenAI metrics | Use when you already run Locust fleets |
|
|
84
|
+
| **k6 + custom metrics** | Unified CI with existing k6 stacks | Must instrument streaming manually: capture TTFT from first SSE chunk, ITL between chunks, count tokens. See `../tools/k6.md` for metric primitives |
|
|
85
|
+
|
|
86
|
+
> **Anti-pattern (per current research):** do not misuse model-server micro-benchmarkers (vLLM `bench`, SGLang bench) as production-level evaluators. They optimize for regression/feature testing, not realistic arrival patterns. Use them for component baselines; use GuideLLM/GenAI-Perf/production-style load for capacity and SLO validation.
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Test Design & Methodology
|
|
91
|
+
|
|
92
|
+
1. **Start from real traffic shapes.** Use ShareGPT or captured production traces for prompt/output length distributions. Synthetic `random` datasets are fine for stress but unrealistic for sizing.
|
|
93
|
+
2. **Define SLOs as goodput thresholds first.** e.g. "TTFT p95 < 500ms, ITL p95 < 20ms, E2E p95 < 3s" → goodput target 99%.
|
|
94
|
+
3. **Run a max-throughput probe** (`--request-rate inf --max-concurrency <limit>`) to find the concurrency ceiling and baseline TPS.
|
|
95
|
+
4. **Sweep concurrency / request rate** to find the goodput cliff - the load where SLO compliance drops. That is capacity.
|
|
96
|
+
5. **Test ramp-up and spikes** (linear/exponential ramp, bursty arrival) to validate autoscaling and queue behavior.
|
|
97
|
+
6. **Always assert on token metrics, not just HTTP 200.** A 200 with a 10s TTFT is a failed request.
|
|
98
|
+
|
|
99
|
+
### Workload pattern recipes (vLLM bench semantics)
|
|
100
|
+
| Goal | `--request-rate` | `--burstiness` | `--max-concurrency` |
|
|
101
|
+
|---|---|---|---|
|
|
102
|
+
| Max throughput | `inf` | n/a | limited |
|
|
103
|
+
| Realistic baseline | 5-20 | 1.0 | inf |
|
|
104
|
+
| Stress / resilience | 20-100 | 0.1-0.5 | inf |
|
|
105
|
+
| Latency profiling | 1-10 | 2-5 | inf |
|
|
106
|
+
| Capacity / SLA | target rate | 1.0 | SLA limit |
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Common Mistakes
|
|
111
|
+
|
|
112
|
+
- **Measuring only HTTP latency** - TTFT and streaming gaps are invisible to `http_req_duration`. You'll report a green test while users stare at a blank cursor.
|
|
113
|
+
- **Ignoring output length variance** - fixed `max_tokens` hides the real E2E spread; use realistic OSL distributions.
|
|
114
|
+
- **Unbounded concurrency** - without `--max-concurrency`, the client fires until the GPU chokes; you measure collapse, not capacity. The KV cache, not sockets, is the real cap.
|
|
115
|
+
- **Confusing TPS with user experience** - high system TPS can coexist with terrible per-user ITL. Track both; report goodput.
|
|
116
|
+
- **Averaging instead of percentiles** - a 200ms mean TTFT with 8s p99 is a broken chat UX.
|
|
117
|
+
- **Using a micro-benchmarker for production SLOs** - `vllm bench`/SGLang bench validate the engine, not your serving capacity under real arrival patterns.
|
|
118
|
+
- **No streaming instrumentation in k6** - a naive `http.get` to a streaming endpoint counts only the final byte; you must parse SSE chunks to get TTFT/ITL.
|
|
119
|
+
- **Forgetting prompt caching** - prefix caching dramatically cuts TTFT for repeated prefixes; test with and without it (e.g. RAG, system prompts) to size the win.
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## Observability
|
|
124
|
+
|
|
125
|
+
Track these server-side alongside the client metrics above:
|
|
126
|
+
- **KV cache utilization** - the true saturation signal; when it pins at 100%, TTFT inflates via queueing.
|
|
127
|
+
- **Batch size / `num_running` vs `num_waiting`** - waiting > 0 means you're over concurrency.
|
|
128
|
+
- **Prefill vs decode time split** - isolates whether slowness is prompt-side or generation-side.
|
|
129
|
+
- **GPU util + memory** - confirms you're compute-bound, not starved.
|
|
130
|
+
|
|
131
|
+
> For dashboards/APM integration patterns, see `observability.md`. For interpreting the goodput cliff and saturation, see `results-analysis.md`.
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# SLO & Capacity Planning
|
|
2
|
+
|
|
3
|
+
> Scope: turning load-test results into service level objectives, error budgets, and capacity decisions. Complements `workload-design.md` (designing the load) and `results-analysis.md` (reading the numbers). Read those first if you have not designed or run the test yet.
|
|
4
|
+
|
|
5
|
+
This topic covers the *decision* layer of performance engineering: how to define SLIs/SLOs that mean something, how to convert a load-test curve into a capacity number and a headroom plan, and how to enforce all of it as an automated CI gate.
|
|
6
|
+
|
|
7
|
+
> For LLM-specific SLOs (TTFT, goodput), see `llm-inference.md`. For the tool syntax that enforces SLOs (`ensure`, `thresholds`), see the relevant tool file.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## SLI / SLO / Error Budget
|
|
12
|
+
|
|
13
|
+
### Definitions
|
|
14
|
+
- **SLI (Service Level Indicator)** - the actual measured signal, e.g. request p95 latency, error rate, availability.
|
|
15
|
+
- **SLO (Service Level Objective)** - the target you commit to for an SLI over a window, e.g. "p95 < 500ms for 99% of requests over 28 days."
|
|
16
|
+
- **Error Budget** - `1 - SLO`. A 99% SLO means a 1% error budget: you may violate the objective 1% of the time before it's a breach. Budgets make trade-offs explicit (ship features vs. burn reliability).
|
|
17
|
+
|
|
18
|
+
### Choosing SLIs
|
|
19
|
+
Pick SLIs users actually feel, not ones that are easy to measure:
|
|
20
|
+
|
|
21
|
+
| User-facing concern | SLI | Typical SLO |
|
|
22
|
+
|---|---|---|
|
|
23
|
+
| Responsiveness | Request p95 latency | < 500ms |
|
|
24
|
+
| Worst-case tail | Request p99 latency | < 1,500ms |
|
|
25
|
+
| Reliability | Error rate (5xx + timeouts) | < 1% (99% success) |
|
|
26
|
+
| Availability | Successfully served requests / total | 99.9% |
|
|
27
|
+
| Throughput | Sustained RPS at SLO latency | ≥ peak + headroom |
|
|
28
|
+
| Freshness (data) | Staleness of served data | < 60s |
|
|
29
|
+
|
|
30
|
+
For streaming/LLM, use token-aware SLIs (TTFT, ITL) and **goodput** (fraction of requests meeting all thresholds) instead of raw latency - see `llm-inference.md`.
|
|
31
|
+
|
|
32
|
+
### Windowing
|
|
33
|
+
- Use a **rolling window** (e.g. 28 days) so a single bad day does not trigger an alert; it consumes budget gradually.
|
|
34
|
+
- Report SLO attainment as `good_events / total_events` over the window. If attainment < SLO, the budget is exhausted.
|
|
35
|
+
- Set **alerts on burn rate**, not on instantaneous SLO pass/fail. Fast burn (e.g. 14× rate) means a major incident; slow burn means a trend to watch.
|
|
36
|
+
|
|
37
|
+
### Multi-window burn-rate alerting (pattern)
|
|
38
|
+
Define fast and slow burn signals so you catch both outages and slow erosion:
|
|
39
|
+
```
|
|
40
|
+
Fast burn: burn rate ≥ 14 over 1h → page
|
|
41
|
+
Slow burn: burn rate ≥ 2 over 6h → ticket
|
|
42
|
+
```
|
|
43
|
+
A burn rate of `X` means the budget is being consumed `X` times faster than the 28-day baseline allows.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## From Load Test to Capacity
|
|
48
|
+
|
|
49
|
+
A load test produces a curve of latency/error vs load. Capacity is the point on that curve where SLOs still hold, **plus** the headroom you keep for safety.
|
|
50
|
+
|
|
51
|
+
### The saturation curve
|
|
52
|
+
```
|
|
53
|
+
latency
|
|
54
|
+
│ ╱‾‾‾‾‾‾‾ ← cliff: SLO breach
|
|
55
|
+
│ ─────────────
|
|
56
|
+
└──────────────────────── load (RPS/VUs)
|
|
57
|
+
[safe zone] [capacity point]
|
|
58
|
+
```
|
|
59
|
+
- **Capacity point** = highest sustained load at which all SLOs (p95, error rate, goodput) still pass.
|
|
60
|
+
- **Saturation point** = where throughput flatlines and latency explodes (the "cliff" in `results-analysis.md`).
|
|
61
|
+
- **Never plan to capacity.** Plan to capacity minus headroom.
|
|
62
|
+
|
|
63
|
+
### Headroom rule
|
|
64
|
+
- Target **peak production load at ~60-70% of measured capacity** under normal operation.
|
|
65
|
+
- Keep **~30-40% headroom** to absorb traffic spikes, node failures, and deployment blips without breaching SLO.
|
|
66
|
+
- For autoscaling systems, headroom is what lets new replicas come up before the SLO burns.
|
|
67
|
+
|
|
68
|
+
### Inverting Little's Law to size capacity
|
|
69
|
+
Given a target arrival rate and response time, concurrency is `N = λ × W` (see `workload-design.md`). Use it both ways:
|
|
70
|
+
- **To size load**: known λ and W → required VUs (already in workload-design).
|
|
71
|
+
- **To size infrastructure**: if the test shows a single replica sustains `C` RPS at SLO, replicas needed = `target_RPS / (C × utilization_target)`. With a 70% utilization target and 1,000 target RPS at 200 RPS/replica → `1000 / (200 × 0.7)` ≈ **8 replicas** (round up, add 1 for failure tolerance).
|
|
72
|
+
|
|
73
|
+
### Capacity from the goodput cliff
|
|
74
|
+
For LLM and streaming services, capacity = load at the goodput cliff (see `llm-inference.md`). Report "sustains 200 concurrent users at 99% goodput; goodput drops to 80% at 350" - that 200 is your planning number, 350 is your hard ceiling.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Worked Example
|
|
79
|
+
|
|
80
|
+
**Inputs:** Peak prod = 800 RPS. Test shows p95 < 500ms and error < 1% hold up to 1,200 RPS on the current 6-replica cluster; saturation at ~1,500 RPS.
|
|
81
|
+
|
|
82
|
+
| Decision | Calculation | Result |
|
|
83
|
+
|---|---|---|
|
|
84
|
+
| Capacity point | measured SLO-holding load | 1,200 RPS |
|
|
85
|
+
| Planning target | 60-70% of capacity | 720-840 RPS |
|
|
86
|
+
| Headroom | 1,200 - 800 peak | 33% (acceptable) |
|
|
87
|
+
| Replicas at growth | if peak grows to 1,500 RPS: `1500 / (200 × 0.7)` | 11 replicas (was 6) |
|
|
88
|
+
| Alert threshold | 80% of capacity as early warning | alert at ~960 RPS |
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## CI Regression Gating
|
|
93
|
+
|
|
94
|
+
SLOs are useless if they are only in a report. Enforce them automatically:
|
|
95
|
+
|
|
96
|
+
1. **Gate on thresholds, not on "it ran."** Every tool has an SLO mechanism - k6 `thresholds`, Artillery `ensure`, Gatling Enterprise assertions, JMeter exit codes. A run with no gate always passes CI (see each tool file's Common Mistakes).
|
|
97
|
+
2. **Compare against baseline, not just absolute.** A p95 of 480ms passes a 500ms SLO but is a +60% regression vs last week's 300ms. Track percentile deltas build-over-build.
|
|
98
|
+
3. **Set a regression threshold.** e.g. "fail the build if p95 rises > 10% vs baseline, or error rate > 0.5%." Avoid zero-tolerance (noise); avoid loose (misses real regressions).
|
|
99
|
+
4. **Require statistical significance for soak/large runs.** A 1-run blip should not fail CI; use multiple iterations or a confidence band.
|
|
100
|
+
5. **Publish the SLO report in the pipeline.** Surface p50/p95/p99, throughput, error rate, and budget consumption on every run so regressions are visible, not buried.
|
|
101
|
+
|
|
102
|
+
> For the per-tool gate syntax, see `../tools/k6.md` (`thresholds`), `../tools/artillery.md` (`ensure`), `../tools/gatling.md`, `../tools/jmeter.md`. For running these in pipelines, see `test-execution.md`.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Common Mistakes
|
|
107
|
+
|
|
108
|
+
- **Averaging for SLOs** - an average of 200ms can hide a p99 of 5s. SLOs must be percentile-based.
|
|
109
|
+
- **SLO with no error budget** - a target with no burn policy becomes a meaningless number; you can never "spend" reliability intentionally.
|
|
110
|
+
- **Planning to the saturation point** - sizing for max measured throughput leaves zero margin; the first spike breaches SLO.
|
|
111
|
+
- **Confusing capacity with peak** - if peak ≈ capacity, you have no headroom. Target 60-70%.
|
|
112
|
+
- **One-shot capacity number** - capacity drifts as code, data, and dependencies change. Re-baseline on major releases.
|
|
113
|
+
- **Gating CI on run success only** - "test passed" ≠ "SLO met." Without a threshold/ensure gate, regressions ship green.
|
|
114
|
+
- **Ignoring tail latency in capacity** - a system can hold p95 at capacity while p99 is 10× worse; gate on the tail users actually feel.
|
|
115
|
+
- **No burn-rate alerting** - alerting on instantaneous SLO state misses slow erosion until the budget is already gone.
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Reporting SLO & Capacity
|
|
120
|
+
|
|
121
|
+
Structure the decision section for stakeholders:
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
1. SLO SUMMARY
|
|
125
|
+
- p95 < 500ms: 99.2% attainment (budget: 1% used 0.8%)
|
|
126
|
+
- Error rate < 1%: 99.97% attainment
|
|
127
|
+
2. CAPACITY
|
|
128
|
+
- Measured capacity: 1,200 RPS @ SLO
|
|
129
|
+
- Current peak: 800 RPS (33% headroom)
|
|
130
|
+
- Saturation: ~1,500 RPS
|
|
131
|
+
3. RECOMMENDATION
|
|
132
|
+
- Scale to 8 replicas before peak season (growth to 1,500 RPS)
|
|
133
|
+
- Alert at 960 RPS (80% of capacity)
|
|
134
|
+
4. REGRESSION GATE
|
|
135
|
+
- p95 > +10% vs baseline fails CI
|
|
136
|
+
```
|