k6-load-testing
Grafana k6 Load Testing
You are an expert at writing and running Grafana k6 load testing scripts. k6 is an open-source,
developer-friendly load testing tool that uses JavaScript for scripting and runs from the CLI
via k6 run script.js.
Quick start
Before writing any script, confirm:
- What URL(s) / endpoint(s) to test
- What kind of test the user wants (smoke, average-load, stress, spike, soak, breakpoint)
- Any specific thresholds (e.g., "p95 < 500ms", "error rate < 1%")
If the user is vague, default to a smoke test first (3 VUs, 1 minute) so they can validate the script works, then suggest scaling up.
k6 Script Anatomy
Every k6 script follows this lifecycle:
// 1. Init — runs once per VU, before the test
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
// test configuration goes here
};
// 2. (Optional) Setup — runs once before all VUs
export function setup() {
// e.g., authenticate and return a token
}
// 3. VU code — runs repeatedly for each VU
export default function (data) {
// the actual requests to test
}
// 4. (Optional) Teardown — runs once after all VUs finish
export function teardown(data) {
// cleanup
}
Key rules:
importstatements and file reads (open()) work only in init context- HTTP requests work in VU code,
setup(), andteardown()— but not in init context - Data returned from
setup()is passed as an argument todefaultandteardown
Test Types and Load Profiles
Choose the right test type based on what the user wants to learn. Read references/test-types.md
for full details on each type with ready-to-use templates.
| Type | Purpose | VUs | Duration |
|---|---|---|---|
| Smoke | Validate script works, baseline metrics | 1–5 | 30s–3m |
| Average-load | Normal day traffic | Production average | 5–60m |
| Stress | Above-average load | 1.5–2× average | 5–60m |
| Spike | Sudden massive traffic | Very high | 1–5m |
| Soak | Extended reliability | Average | Hours |
| Breakpoint | Find system limits | Ramps until break | Until failure |
Executors (How to Model Load)
k6 has two models for generating load:
Closed model (VU-based) — use when simulating users
constant-vus— fixed VU count for a durationramping-vus— ramp VUs up/down through stagesshared-iterations— fixed total iterations split across VUsper-vu-iterations— each VU runs N iterations
Open model (arrival-rate) — use when simulating requests per second
constant-arrival-rate— fixed iteration rate (e.g., 50 req/s)ramping-arrival-rate— ramp iteration rate through stages
When to use which: If the user talks about "concurrent users", use VU-based. If they talk
about "requests per second" or "throughput", use arrival-rate. For breakpoint tests, prefer
ramping-arrival-rate because it keeps increasing load regardless of response time.
Read references/executors.md for configuration details and examples.
Writing HTTP Requests
import http from 'k6/http';
// GET
const res = http.get('https://api.example.com/users');
// POST with JSON
const payload = JSON.stringify({ name: 'test', email: 'test@example.com' });
const params = { headers: { 'Content-Type': 'application/json' } };
const res = http.post('https://api.example.com/users', payload, params);
// All methods: get, post, put, patch, del, head, options, request
Authentication patterns
// Bearer token
const params = {
headers: { Authorization: `Bearer ${token}` },
};
// Basic auth (in setup, pass token to VU code via return value)
export function setup() {
const loginRes = http.post('https://api.example.com/login', JSON.stringify({
username: 'user', password: 'pass'
}), { headers: { 'Content-Type': 'application/json' } });
return { token: loginRes.json('token') };
}
export default function (data) {
const res = http.get('https://api.example.com/protected', {
headers: { Authorization: `Bearer ${data.token}` },
});
}
URL grouping (for dynamic paths)
When testing endpoints with dynamic IDs, group them so metrics aren't fragmented:
// Option 1: Use http.url tagged template
http.get(http.url`https://api.example.com/users/${userId}`);
// Option 2: Use name tag
http.get(`https://api.example.com/users/${userId}`, {
tags: { name: 'GetUser' },
});
Checks and Thresholds
Checks validate individual responses (don't abort on failure):
import { check } from 'k6';
check(res, {
'status is 200': (r) => r.status === 200,
'response time < 500ms': (r) => r.timings.duration < 500,
'body contains expected data': (r) => r.body.includes('success'),
});
Thresholds define pass/fail criteria for the entire test:
export const options = {
thresholds: {
http_req_failed: ['rate<0.01'], // <1% errors
http_req_duration: ['p(95)<500'], // 95th percentile < 500ms
http_req_duration: ['p(95)<500', 'p(99)<1000'], // multiple on same metric
'http_req_duration{name:GetUser}': ['p(95)<300'], // per-endpoint
checks: ['rate>0.95'], // 95% of checks must pass
},
};
Threshold expressions use: avg, min, max, med, p(N) for Trends; rate for Rates; count/rate for Counters; value for Gauges.
Key Built-in Metrics
| Metric | Type | What it measures |
|---|---|---|
http_reqs |
Counter | Total HTTP requests |
http_req_duration |
Trend | Total request time (send + wait + receive) |
http_req_failed |
Rate | Ratio of failed requests |
http_req_waiting |
Trend | Time to first byte (TTFB) |
iterations |
Counter | Completed VU iterations |
iteration_duration |
Trend | Time per full iteration |
checks |
Rate | Ratio of successful checks |
data_received / data_sent |
Counter | Network data volume |
Custom metrics
import { Trend, Counter, Rate, Gauge } from 'k6/metrics';
const apiDuration = new Trend('api_duration');
const errorCount = new Counter('errors');
const successRate = new Rate('success_rate');
export default function () {
const res = http.get('https://api.example.com/data');
apiDuration.add(res.timings.duration);
errorCount.add(res.status !== 200 ? 1 : 0);
successRate.add(res.status === 200);
}
Scenarios (Multiple Workloads)
Scenarios let you define multiple independent workloads in a single script:
export const options = {
scenarios: {
browse: {
executor: 'constant-vus',
vus: 10,
duration: '5m',
exec: 'browseProducts',
},
purchase: {
executor: 'ramping-arrival-rate',
startRate: 1,
timeUnit: '1s',
preAllocatedVUs: 20,
stages: [
{ duration: '2m', target: 10 },
{ duration: '3m', target: 10 },
{ duration: '1m', target: 0 },
],
exec: 'makePurchase',
},
},
};
export function browseProducts() { /* ... */ }
export function makePurchase() { /* ... */ }
Environment Variables
Make scripts reusable across environments:
const BASE_URL = __ENV.BASE_URL || 'https://api.staging.example.com';
export default function () {
http.get(`${BASE_URL}/users`);
}
Run with: k6 run -e BASE_URL=https://api.prod.example.com script.js
Groups and Tags
Use groups to organize requests into logical transactions:
import { group } from 'k6';
export default function () {
group('user_login', function () {
http.post(/*...*/);
});
group('browse_products', function () {
http.get(/*...*/);
http.get(/*...*/);
});
}
Use tags to filter metrics:
http.get(url, { tags: { type: 'API' } });
// Then set thresholds on tagged sub-metrics
export const options = {
thresholds: {
'http_req_duration{type:API}': ['p(95)<300'],
},
};
Other Protocols
k6 supports more than HTTP:
- WebSockets:
import { WebSocket } from 'k6/websockets';— for real-time connections - gRPC:
import grpc from 'k6/net/grpc';— for gRPC services - Read
references/protocols.mdfor WebSocket and gRPC examples
Running k6
# Basic run
k6 run script.js
# With options
k6 run --vus 10 --duration 30s script.js
# With environment variables
k6 run -e BASE_URL=https://api.example.com script.js
# Output to JSON
k6 run --out json=results.json script.js
# Output to CSV
k6 run --out csv=results.csv script.js
Best Practices
- Always start with a smoke test — validate the script before scaling up
- Use
sleep()between requests in VU-based executors to simulate realistic think time. Don't use sleep in arrival-rate executors (they already pace iterations). - Set
discardResponseBodies: trueif you don't need response bodies — reduces memory - Use
check()+ thresholds together — checks alone don't fail the test - Group dynamic URLs with
http.urlor name tags to prevent metric explosion - Use
setup()for auth — authenticate once, share the token across VUs - Use
SharedArrayfor large test data — shares memory across VUs:import { SharedArray } from 'k6/data'; const users = new SharedArray('users', function () { return JSON.parse(open('./users.json')); });
Reference Documentation
For detailed information, consult these reference files:
references/test-types.md— Complete templates for each test typereferences/executors.md— All executor configurations with examplesreferences/protocols.md— WebSocket and gRPC usagereferences/options-reference.md— Full list of k6 options
Source documentation: https://grafana.com/docs/k6/latest/
More from alahmadiq8/skills
revealjs-slides
>
59hugo
Build, configure, and develop Hugo static sites and themes. Use when the user wants to create a new Hugo site, develop or customize a Hugo theme, write Hugo templates (layouts, partials, shortcodes), configure hugo.toml/yaml/json, work with Hugo's asset pipeline (images, CSS/Sass, JS bundling), manage content (pages, sections, taxonomies, menus), or deploy a Hugo site. Triggers on mentions of "Hugo", "hugo.toml", "static site generator", Hugo-related template syntax (Go templates, baseof, partials), or Hugo content workflows.
20obsidian
Edit and manage Obsidian vaults — create, read, update, and delete notes, manage properties/frontmatter, handle links and backlinks, work with tags, tasks, daily notes, templates, bases, and more. Uses the Obsidian CLI for safe vault operations when available, with direct file editing as fallback. Use this skill whenever the user mentions Obsidian, vault, knowledge base notes with wikilinks, frontmatter/properties on markdown files, daily notes, or any task involving an Obsidian vault — even if they just say "my notes" or reference a folder that looks like a vault (has .obsidian/ directory).
7fabric-icons
Search and fetch official Microsoft Fabric / Azure SVG icons for use in diagrams, documentation, and UI. Use when building architecture diagrams (HTML, PPTX, or any visual format) that need icons for Fabric concepts like Lakehouse, Data Warehouse, Pipeline, Eventhouse, Power BI, OneLake, Dataflow, Notebook, Semantic Model, KQL Database, and other Fabric items. Also use when the user asks for Microsoft Fabric or Azure data platform icons.
3obsidian-dataview
Write Obsidian Dataview queries (DQL, inline DQL, and DataviewJS) from natural language descriptions. Use this skill whenever the user wants to query, filter, list, table, or summarize their Obsidian notes using Dataview — even if they just describe what data they want to see without mentioning "dataview" explicitly. Trigger on phrases like "show me all notes tagged...", "list my tasks due...", "table of books by rating", "query my vault for...", "create a dataview query", "dataview", "DQL", or any request to dynamically display, filter, sort, or aggregate note metadata in Obsidian.
2