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
- ### Claude Code Plugin (Recommended)
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
- **Add the marketplace and install:**
53
- ```bash
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
- After install, the `/perf` skill is available and auto-activates on performance testing questions.
8
+ ---
60
9
 
61
- **Update to latest version:**
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
- ### Install as Skills (npx)
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
- ```bash
72
- npx skills add QAInsights/perf-skills
73
- ```
21
+ ---
74
22
 
75
- ### Install as Skills (Manual)
23
+ ## 🔍 What is perf-skills?
76
24
 
77
- ```bash
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
- ### Windsurf (Skills)
27
+ ---
84
28
 
85
- 1. Clone the repository: `git clone https://github.com/QAInsights/perf-skills.git`
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
- ### Cursor (Rules / Docs)
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
- **Option A - Project Rules:**
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
- **Option B - @Docs indexing:**
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
- ### Claude Code (CLAUDE.md)
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
- 1. Clone the repository: `git clone https://github.com/QAInsights/perf-skills.git`
105
- 2. Copy the skill directory into your project: `cp -r perf-skills/skills/perf ./perf`
106
- 3. In your `CLAUDE.md`, add:
107
- ```markdown
108
- For performance testing questions, read `perf/SKILL.md` for routing,
109
- then load the relevant reference file(s) from `perf/references/`.
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
- ### Cline / Roo Code
54
+ ---
113
55
 
114
- 1. Clone the repository: `git clone https://github.com/QAInsights/perf-skills.git`
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
- ### Aider
58
+ `perf-skills` works seamlessly with any modern AI development tool:
123
59
 
124
- 1. Clone the repository: `git clone https://github.com/QAInsights/perf-skills.git`
125
- 2. Copy the skill directory into your project: `cp -r perf-skills/skills/perf ./perf`
126
- 3. Use `/read perf/SKILL.md` to load the routing file.
127
- 4. Then `/read` the specific reference file(s) relevant to your question.
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
- ### GitHub Copilot
70
+ ---
130
71
 
131
- 1. Clone the repository: `git clone https://github.com/QAInsights/perf-skills.git`
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
- ### OpenCode / Antigravity / Pochi
74
+ ### Claude Code Plugin (Recommended)
140
75
 
141
- 1. Clone the repository: `git clone https://github.com/QAInsights/perf-skills.git`
142
- 2. Copy the skill directory into your project: `cp -r perf-skills/skills/perf ./perf`
143
- 3. Add to your project-level custom instructions or context files:
144
- ```
145
- For performance testing guidance, consult the perf knowledge base.
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
- ### Any Other AI Tool
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
- ### As a Standalone Knowledge Base
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
- Browse the markdown files directly - they're self-contained references useful even without an AI assistant.
93
+ ---
158
94
 
159
- ## File Structure
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
- perf-skills/ # Repository root
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 point - tool selection, lifecycle, key principles
187
+ ├── SKILL.md # [Entry Point] Routing, Key Principles
171
188
  └── references/
172
- ├── tools/ # Tool-specific syntax and configuration
173
- │ ├── jmeter.md # JMeter 5.6+ - samplers, extractors, plugins, Groovy
174
- │ ├── k6.md # k6 v0.50+ - executors, checks, thresholds, modules
175
- │ ├── gatling.md # Gatling 3.10+ - Scala/Java DSL, feeders, injection
176
- │ ├── locust.md # Locust 2.20+ - Python scripts, events, FastHttpUser
177
- │ ├── neoload.md # NeoLoad - GUI workflow, CLI, API execution
178
- │ ├── loadrunner.md # LoadRunner - VuGen, protocols, Controller scenarios
179
- └── octoperf.md # OctoPerf - JMeter-based SaaS, HAR import, cloud
180
- └── topics/ # Cross-cutting concepts (tool-agnostic)
181
- ├── workload-design.md # Concurrency models, load profiles, Little's Law
182
- ├── test-data.md # CSV, DB seeding, Faker, data isolation patterns
183
- ├── script-generation.md # Assertions, error handling, naming, structure
184
- ├── correlation.md # Dynamic values, extractors, regex, tool mappings
185
- ├── test-execution.md # Distributed, CI/CD (GitHub Actions, GitLab, Jenkins)
186
- ├── results-analysis.md # Percentiles, bottleneck framework, reporting
187
- ├── observability.md # APM, Prometheus, Grafana, tracing, JVM metrics
188
- ├── production-testing.md # Canary, shadow traffic, chaos, safety controls
189
- ├── protocol-testing.md # gRPC, GraphQL, WebSocket, Kafka/message queues
190
- ├── database-testing.md # JDBC, connection pools, slow queries, deadlocks
191
- └── modern-architectures.md # Microservices, K8s, serverless, browser/Web Vitals
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
- ## How the Skill Works
195
-
196
- ### Routing Logic
197
-
198
- `SKILL.md` acts as the entry point and router. It contains:
214
+ ---
199
215
 
200
- 1. **Loading Priority Rules** - tells the AI which file(s) to load based on the user's question (never all at once).
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
- ### Design Principles
218
+ To avoid hitting context window limits, the skill operates on a **selective loading** design:
207
219
 
208
- - **Token-efficient**: Tool files contain only tool-specific syntax. Cross-cutting concepts live in topic files. No duplication.
209
- - **Selective loading**: The AI loads 1-2 files per question, not the entire knowledge base.
210
- - **Single source of truth**: Each concept is defined in exactly one place. Tool files cross-reference topic files for shared concepts.
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
- ## Example Questions This Skill Handles
224
+ ### Example Queries and Loaded Files
214
225
 
215
- | Question | Files Loaded |
216
- |---|---|
217
- | "Help me write a k6 load test for our REST API" | `k6.md` |
218
- | "How should I design the workload for our e-commerce app?" | `workload-design.md` |
219
- | "Set up JMeter in our GitHub Actions pipeline" | `test-execution.md` + `jmeter.md` |
220
- | "Our p95 latency is spiking at 500 VUs - how do I debug?" | `results-analysis.md` + `observability.md` |
221
- | "How do I load test a gRPC service?" | `protocol-testing.md` + `k6.md` |
222
- | "What tool should I use? We're a Python team." | `SKILL.md` (Tool Selection Matrix) |
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
- ## Contributing
235
+ ---
228
236
 
229
- To add or update content:
237
+ ## 🤝 Contributing
230
238
 
231
- 1. **Tool-specific content** goes in `references/tools/<tool>.md` - syntax, config, tool-unique tips only.
232
- 2. **Cross-cutting concepts** go in `references/topics/<topic>.md` - patterns that apply across tools.
233
- 3. **Never duplicate** - if a concept exists in a topic file, tool files should cross-reference it, not restate it.
234
- 4. **Update SKILL.md** if you add a new file - add it to the Reference Map and Protocol Routing Table if applicable.
235
- 5. **Add a version indicator** (`> Targets: ...`) to new tool files.
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 repository license.
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": "2.0.0",
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": [
@@ -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, and LoadRunner. Use this skill
5
- whenever writing or reviewing load test scripts, setting thresholds,
6
- choosing executors, configuring CI/CD pipelines, diagnosing latency
7
- issues, designing workloads, analyzing results, or recommending tools
8
- - even if the tool is not named explicitly. Always consult before
9
- suggesting thresholds, executor types, or output configuration.
10
- Prefer this skill over general knowledge for any performance testing
11
- decision, debugging session, or tool comparison.
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
- ## Cross-Tool Concept Mapping
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
+ ```