| name | load-testing |
| description | Run load and performance tests with k6 or vegeta: test scripts, ramp-up patterns, threshold configuration, results analysis. Trigger: when using k6, vegeta, load testing, performance testing, stress testing, k6 run, vegeta attack, ramp-up, throughput testing, latency testing |
| version | 1 |
| argument-hint | [k6 run|vegeta attack] [script.js|--rate=100] [--vus 50 --duration 30s] |
| allowed-tools | ["bash","read","write","grep","glob"] |
Load Testing
You are now operating in load and performance testing mode.
Tool Selection
- k6: JavaScript-based, excellent for complex scenarios, great for APIs and web apps
- vegeta: Go-based, simple CLI, ideal for constant-rate HTTP load testing
k6 — JavaScript Load Testing
Installation
brew install k6
sudo gpg -k
sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69
echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" | sudo tee /etc/apt/sources.list.d/k6.list
sudo apt-get update && sudo apt-get install k6
docker run --rm grafana/k6 run - <script.js
k6 version
Basic k6 Script
import http from 'k6/http'
import { sleep, check } from 'k6'
export const options = {
vus: 10,
duration: '30s',
}
export default function () {
const res = http.get('http://localhost:8080/api/health')
check(res, {
'status is 200': (r) => r.status === 200,
'response time < 500ms': (r) => r.timings.duration < 500,
})
sleep(1)
}
k6 run script.js
k6 run --vus 50 --duration 60s script.js
k6 run --out json=results.json script.js
Ramp-Up Patterns
export const options = {
stages: [
{ duration: '1m', target: 20 },
{ duration: '3m', target: 20 },
{ duration: '1m', target: 50 },
{ duration: '3m', target: 50 },
{ duration: '1m', target: 0 },
],
}
export const options = {
stages: [
{ duration: '1m', target: 10 },
{ duration: '30s', target: 200 },
{ duration: '1m', target: 10 },
{ duration: '1m', target: 0 },
],
}
export const options = {
stages: [
{ duration: '2m', target: 100 },
{ duration: '2m', target: 200 },
{ duration: '2m', target: 300 },
{ duration: '2m', target: 400 },
{ duration: '2m', target: 0 },
],
}
export const options = {
stages: [
{ duration: '5m', target: 50 },
{ duration: '4h', target: 50 },
{ duration: '5m', target: 0 },
],
}
Threshold Configuration
export const options = {
vus: 50,
duration: '2m',
thresholds: {
http_req_duration: ['p(95)<500'],
'http_req_duration{expected_response:true}': ['p(99)<1000'],
http_req_failed: ['rate<0.01'],
'checks{check:status is 200}': ['rate>0.99'],
http_req_duration: ['p(90)<300', 'p(95)<500', 'p(99)<1000'],
},
}
POST Requests and Authentication
import http from 'k6/http'
import { check, sleep } from 'k6'
const BASE_URL = __ENV.BASE_URL || 'http://localhost:8080'
const API_TOKEN = __ENV.API_TOKEN
export const options = {
stages: [
{ duration: '30s', target: 20 },
{ duration: '1m', target: 20 },
{ duration: '30s', target: 0 },
],
thresholds: {
http_req_duration: ['p(95)<500'],
http_req_failed: ['rate<0.05'],
},
}
export default function () {
const headers = {
'Content-Type': 'application/json',
'Authorization': `Bearer ${API_TOKEN}`,
}
const payload = JSON.stringify({
title: 'Test item',
description: 'Load test payload',
})
const createRes = http.post(`${BASE_URL}/api/items`, payload, { headers })
check(createRes, {
'item created': (r) => r.status === 201,
'has item id': (r) => r.json('id') !== undefined,
})
if (createRes.status === 201) {
const id = createRes.json('id')
const getRes = http.get(`${BASE_URL}/api/items/${id}`, { headers })
check(getRes, {
'item found': (r) => r.status === 200,
})
}
sleep(1)
}
Custom Metrics
import { Trend, Counter, Rate, Gauge } from 'k6/metrics'
const loginDuration = new Trend('login_duration')
const loginErrors = new Counter('login_errors')
const loginSuccessRate = new Rate('login_success')
const activeUsers = new Gauge('active_users')
export default function () {
const start = Date.now()
const res = http.post('/api/auth/login', { ... })
loginDuration.add(Date.now() - start)
if (res.status !== 200) {
loginErrors.add(1)
loginSuccessRate.add(false)
} else {
loginSuccessRate.add(true)
}
activeUsers.add(1)
}
Running k6
k6 run script.js
k6 run --env BASE_URL=https://staging.example.com --env API_TOKEN=$TOKEN script.js
k6 run --out json=results.json script.js
k6 run --out influxdb=http://localhost:8086/k6 script.js
k6 run --out csv=results.csv script.js
k6 run --quiet script.js
k6 run --abort-on-fail script.js
k6 run --tag environment=staging script.js
k6 run script.js | tee load-test-results.txt
vegeta — HTTP Load Testing Tool
Installation
brew install vegeta
vegeta -version
Basic Usage
echo "GET http://localhost:8080/api/health" | \
vegeta attack -rate=100 -duration=30s | \
vegeta report
echo "GET http://localhost:8080/" | \
vegeta attack -rate=50 -duration=60s | \
tee results.bin | \
vegeta report
vegeta Targets File
GET http://localhost:8080/api/health
Authorization: Bearer mytoken
GET http://localhost:8080/api/users
POST http://localhost:8080/api/items
Content-Type: application/json
@body.json
vegeta attack -targets=targets.txt -rate=100 -duration=30s | \
vegeta report
vegeta Output Formats
vegeta attack -rate=100 -duration=30s -targets=targets.txt | \
vegeta report
vegeta attack -rate=100 -duration=30s -targets=targets.txt | \
vegeta report -type=json > report.json
vegeta attack -rate=100 -duration=30s -targets=targets.txt | \
vegeta report -type=hdrplot > hdr.txt
vegeta attack -rate=100 -duration=30s -targets=targets.txt | \
vegeta plot > plot.html && open plot.html
vegeta attack -rate=100 -duration=30s -targets=targets.txt > results.bin
vegeta report < results.bin
vegeta plot < results.bin > plot.html
vegeta Ramp-Up with Pacer
vegeta attack \
-rate=10 \
-duration=30s \
-targets=targets.txt | vegeta report
Analyzing Results
vegeta report < results.bin
vegeta report -type=json < results.bin | jq '{
requests: .requests,
success_ratio: .success,
latency_p95: .latencies.p95,
latency_p99: .latencies.p99,
status_codes: .status_codes
}'
CI/CD Integration
#!/bin/bash
set -euo pipefail
BASE_URL="${BASE_URL:-http://localhost:8080}"
PASS_P95_MS="${PASS_P95_MS:-500}"
k6 run \
--env BASE_URL="$BASE_URL" \
--env API_TOKEN="$API_TOKEN" \
--out json=results.json \
load-test.js
if [ $? -ne 0 ]; then
echo "Load test FAILED: thresholds not met"
exit 1
fi
echo "Load test PASSED"
- name: Run load tests
run: |
k6 run \
--env BASE_URL=${{ secrets.STAGING_URL }} \
--env API_TOKEN=${{ secrets.API_TOKEN }} \
load-test.js
if: github.ref == 'refs/heads/main'
Interpreting Results
Key Metrics to Monitor
| Metric | Target | Warning | Critical |
|---|
| p50 latency | <100ms | >200ms | >500ms |
| p95 latency | <500ms | >1s | >2s |
| p99 latency | <1s | >2s | >5s |
| Error rate | <0.1% | >1% | >5% |
| Throughput | >target RPS | <80% target | <50% target |
jq -r '
select(.type == "Point" and .metric == "http_req_duration") |
.data.tags.percentile as $p |
select($p == "p(95)") |
"\(.data.time) p95=\(.data.value)ms"
' results.json | tail -20
jq '.root_group.checks | to_entries[] | {name: .key, passes: .value.passes, fails: .value.fails}' results.json
Troubleshooting
ulimit -n 65535
k6 run --http-debug=full script.js
vegeta attack -rate=10 -duration=5s -targets=targets.txt 2>&1