Agent skill
load-testing
Auto-activates when user mentions load test, performance test, stress test, k6, Artillery, benchmark, or scalability testing. Expert in designing and executing performance tests.
Stars
163
Forks
31
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/data/load-testing
SKILL.md
Load Testing & Performance Testing
Creates comprehensive load tests using k6, Artillery, and other tools to validate application performance under stress.
When This Activates
- User says: "load test this", "performance test", "stress test", "benchmark"
- User mentions: "k6", "Artillery", "JMeter", "Gatling", "scalability"
- Performance validation needed
- Pre-production testing
- Capacity planning questions
Load Testing Tools Comparison
k6 (Recommended)
- Best for: Modern apps, APIs, microservices
- Language: JavaScript/TypeScript
- Pros: Cloud-native, great metrics, scriptable
- Use when: Testing REST APIs, GraphQL, WebSocket
Artillery
- Best for: Quick tests, CI/CD integration
- Language: YAML + JavaScript
- Pros: Simple config, plugins, HTTP/WS/Socket.io
- Use when: Simple scenarios, rapid testing
Apache JMeter
- Best for: Enterprise apps, complex scenarios
- Language: GUI-based + Java
- Pros: Mature, extensive protocols, GUI
- Use when: Legacy systems, complex workflows
k6 Load Testing
Basic Load Test
javascript
// load-test.js
import http from 'k6/http';
import { check, sleep } from 'k6';
import { Rate, Trend, Counter } from 'k6/metrics';
// Custom metrics
const errorRate = new Rate('errors');
const checkDuration = new Trend('check_duration');
const requests = new Counter('requests');
// Test configuration
export const options = {
stages: [
{ duration: '2m', target: 10 }, // Ramp up to 10 users
{ duration: '5m', target: 10 }, // Stay at 10 users
{ duration: '2m', target: 50 }, // Ramp up to 50 users
{ duration: '5m', target: 50 }, // Stay at 50 users
{ duration: '2m', target: 100 }, // Ramp up to 100 users
{ duration: '5m', target: 100 }, // Stay at 100 users
{ duration: '2m', target: 0 }, // Ramp down
],
thresholds: {
http_req_duration: ['p(95)<500', 'p(99)<1000'], // 95% < 500ms, 99% < 1s
http_req_failed: ['rate<0.01'], // Error rate < 1%
errors: ['rate<0.1'], // Custom error rate < 10%
},
};
const BASE_URL = __ENV.BASE_URL || 'http://localhost:3000';
const API_TOKEN = __ENV.API_TOKEN || '';
export default function () {
const params = {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${API_TOKEN}`,
},
tags: { name: 'GetUsers' },
};
// GET request
let response = http.get(`${BASE_URL}/api/users`, params);
const checkResult = check(response, {
'status is 200': (r) => r.status === 200,
'response time < 500ms': (r) => r.timings.duration < 500,
'has users': (r) => JSON.parse(r.body).data.length > 0,
});
errorRate.add(!checkResult);
checkDuration.add(response.timings.duration);
requests.add(1);
// POST request
const payload = JSON.stringify({
name: 'Test User',
email: `test-${Date.now()}@example.com`,
});
response = http.post(`${BASE_URL}/api/users`, payload, params);
check(response, {
'user created': (r) => r.status === 201,
});
sleep(1); // Think time between requests
}
export function handleSummary(data) {
return {
'summary.json': JSON.stringify(data),
stdout: textSummary(data, { indent: ' ', enableColors: true }),
};
}
Advanced Scenarios
javascript
// advanced-test.js
import http from 'k6/http';
import { check, group, sleep } from 'k6';
import { SharedArray } from 'k6/data';
import papaparse from 'https://jslib.k6.io/papaparse/5.1.1/index.js';
// Load test data
const users = new SharedArray('users', function () {
return papaparse.parse(open('./users.csv'), { header: true }).data;
});
export const options = {
scenarios: {
// Scenario 1: Constant load
constant_load: {
executor: 'constant-vus',
vus: 50,
duration: '5m',
},
// Scenario 2: Ramping load
ramping_load: {
executor: 'ramping-vus',
startVUs: 0,
stages: [
{ duration: '5m', target: 100 },
{ duration: '10m', target: 100 },
{ duration: '5m', target: 0 },
],
gracefulRampDown: '30s',
},
// Scenario 3: Stress test
stress_test: {
executor: 'ramping-arrival-rate',
startRate: 50,
timeUnit: '1s',
preAllocatedVUs: 500,
maxVUs: 1000,
stages: [
{ duration: '2m', target: 100 },
{ duration: '5m', target: 200 },
{ duration: '2m', target: 500 },
{ duration: '5m', target: 500 },
{ duration: '2m', target: 0 },
],
},
},
};
export default function () {
const user = users[Math.floor(Math.random() * users.length)];
group('User Flow', function () {
// 1. Login
group('Login', function () {
const loginRes = http.post(`${BASE_URL}/api/auth/login`, JSON.stringify({
email: user.email,
password: user.password,
}), {
headers: { 'Content-Type': 'application/json' },
});
check(loginRes, {
'login successful': (r) => r.status === 200,
});
const token = loginRes.json('token');
// 2. Get profile
group('Get Profile', function () {
const profileRes = http.get(`${BASE_URL}/api/profile`, {
headers: {
'Authorization': `Bearer ${token}`,
},
});
check(profileRes, {
'profile loaded': (r) => r.status === 200,
});
});
// 3. Create post
group('Create Post', function () {
const postRes = http.post(`${BASE_URL}/api/posts`, JSON.stringify({
title: 'Load Test Post',
content: 'This is a test post from k6',
}), {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
},
});
check(postRes, {
'post created': (r) => r.status === 201,
});
});
});
});
sleep(Math.random() * 3 + 2); // Random think time 2-5s
}
GraphQL Load Test
javascript
// graphql-test.js
import http from 'k6/http';
import { check } from 'k6';
export const options = {
vus: 50,
duration: '5m',
thresholds: {
http_req_duration: ['p(95)<1000'],
},
};
const GRAPHQL_ENDPOINT = `${__ENV.BASE_URL}/graphql`;
export default function () {
const query = `
query GetPosts($first: Int!) {
posts(first: $first) {
edges {
node {
id
title
author {
name
}
}
}
}
}
`;
const variables = { first: 20 };
const response = http.post(
GRAPHQL_ENDPOINT,
JSON.stringify({ query, variables }),
{
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${__ENV.API_TOKEN}`,
},
}
);
check(response, {
'no errors': (r) => !r.json('errors'),
'has data': (r) => r.json('data.posts.edges.length') > 0,
});
}
Artillery Load Testing
Basic Configuration
yaml
# artillery.yml
config:
target: "http://localhost:3000"
phases:
- duration: 60
arrivalRate: 10 # 10 users per second
name: "Warm up"
- duration: 300
arrivalRate: 50 # 50 users per second
name: "Sustained load"
- duration: 120
arrivalRate: 100 # 100 users per second
name: "Spike"
processor: "./helpers.js"
variables:
apiToken: "{{ $processEnvironment.API_TOKEN }}"
plugins:
expect: {}
metrics-by-endpoint: {}
scenarios:
- name: "User Registration and Login"
flow:
- post:
url: "/api/auth/register"
json:
email: "user-{{ $randomString() }}@example.com"
name: "Test User"
password: "password123"
capture:
- json: "$.token"
as: "authToken"
expect:
- statusCode: 201
- contentType: json
- hasProperty: token
- get:
url: "/api/profile"
headers:
Authorization: "Bearer {{ authToken }}"
expect:
- statusCode: 200
- post:
url: "/api/posts"
headers:
Authorization: "Bearer {{ authToken }}"
json:
title: "Test Post"
content: "This is a test"
expect:
- statusCode: 201
- name: "Browse Posts"
weight: 3
flow:
- get:
url: "/api/posts?page=1&limit=20"
expect:
- statusCode: 200
- hasProperty: data
- think: 2 # Wait 2 seconds
- get:
url: "/api/posts/{{ $randomNumber(1, 100) }}"
expect:
- statusCode: [200, 404]
javascript
// helpers.js
module.exports = {
generateRandomEmail,
logResponse,
};
function generateRandomEmail(context, events, done) {
context.vars.email = `user-${Date.now()}@example.com`;
return done();
}
function logResponse(requestParams, response, context, ee, next) {
if (response.statusCode >= 400) {
console.error(`Error ${response.statusCode}: ${response.body}`);
}
return next();
}
Running Tests
bash
# k6
k6 run load-test.js
k6 run --vus 100 --duration 5m load-test.js
k6 run --env BASE_URL=https://api.example.com load-test.js
# k6 Cloud
k6 cloud load-test.js
# Artillery
artillery run artillery.yml
artillery run --target https://api.example.com artillery.yml
# Artillery with environment
API_TOKEN=xxx artillery run artillery.yml
# Generate HTML report
artillery run --output report.json artillery.yml
artillery report report.json
CI/CD Integration
GitHub Actions
yaml
# .github/workflows/load-test.yml
name: Load Test
on:
schedule:
- cron: '0 2 * * *' # Run daily at 2 AM
workflow_dispatch:
jobs:
load-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run k6 load test
uses: grafana/k6-action@v0.3.0
with:
filename: tests/load-test.js
cloud: true
token: ${{ secrets.K6_CLOUD_TOKEN }}
env:
BASE_URL: ${{ secrets.STAGING_URL }}
API_TOKEN: ${{ secrets.API_TOKEN }}
- name: Upload results
uses: actions/upload-artifact@v3
if: always()
with:
name: load-test-results
path: summary.json
Performance Benchmarking
javascript
// benchmark.js
import http from 'k6/http';
import { check } from 'k6';
export const options = {
scenarios: {
baseline: {
executor: 'constant-vus',
vus: 1,
duration: '1m',
tags: { test_type: 'baseline' },
},
},
};
export default function () {
const endpoints = [
'/api/users',
'/api/posts',
'/api/comments',
];
endpoints.forEach(endpoint => {
const response = http.get(`${BASE_URL}${endpoint}`);
check(response, {
[`${endpoint} status 200`]: (r) => r.status === 200,
});
});
}
export function handleSummary(data) {
const baseline = {
endpoints: {},
timestamp: new Date().toISOString(),
};
for (const [name, metric] of Object.entries(data.metrics)) {
if (name.includes('http_req_duration')) {
baseline.endpoints[name] = {
avg: metric.values.avg,
p95: metric.values['p(95)'],
p99: metric.values['p(99)'],
};
}
}
return {
'baseline.json': JSON.stringify(baseline, null, 2),
};
}
Analyzing Results
Key Metrics to Monitor
-
Response Time
- Average, p95, p99
- Target: p95 < 500ms, p99 < 1s
-
Error Rate
- HTTP 4xx, 5xx errors
- Target: < 1%
-
Throughput
- Requests per second
- Target: Based on requirements
-
Concurrent Users
- Maximum sustainable load
- Target: Based on traffic projections
Identifying Bottlenecks
javascript
// Find slow endpoints
const slowRequests = data.metrics.http_req_duration.values;
console.log(`Average: ${slowRequests.avg}ms`);
console.log(`p95: ${slowRequests['p(95)']}ms`);
console.log(`p99: ${slowRequests['p(99)']}ms`);
// Check error patterns
const failedRequests = data.metrics.http_req_failed.values.rate;
if (failedRequests > 0.01) {
console.error(`Error rate: ${failedRequests * 100}%`);
}
Best Practices
Test Types
-
Smoke Test: 1-2 VUs, 1-2 minutes
- Verify system works under minimal load
-
Load Test: Expected normal/peak load, 5-15 minutes
- Validate performance under typical conditions
-
Stress Test: Beyond peak load, gradually increase
- Find breaking point
-
Spike Test: Sudden large increase, then drop
- Test auto-scaling, recovery
-
Soak Test: Moderate load, 1+ hours
- Find memory leaks, degradation
Checklist
- Define performance requirements (SLAs)
- Identify critical user flows
- Prepare test data (users, content)
- Set up realistic scenarios
- Configure appropriate thresholds
- Test in staging environment first
- Monitor system resources during tests
- Analyze results against baselines
- Document bottlenecks and fixes
- Run tests regularly (CI/CD)
- Compare results over time
Design load tests, execute tests, analyze results, provide optimization recommendations.
Didn't find tool you were looking for?