| name | load-test |
| description | Design and execute load tests to validate system performance under expected and peak traffic. Outputs k6/JMeter scripts, performance baselines, SLO validation, and capacity planning reports. |
| argument-hint | ["system under test","expected TPS","SLOs","test types needed"] |
| allowed-tools | Read, Write, Bash |
Load Testing
Validate that your system meets performance SLOs before production traffic finds the limit. Load testing is not just about finding the breaking point — it's about confirming behavior at expected load, finding the degradation curve, and capacity planning.
Process
- Define test objectives — SLOs to validate, peak load assumptions, test types needed.
- Model production traffic — endpoints hit, request mix, user think time.
- Set up test environment — production-like sizing, isolated from real users.
- Baseline at low load — 10% of expected peak, confirm system is healthy.
- Ramp to target load — verify SLOs hold at 100% expected peak.
- Stress test — find the breaking point (120%+ of peak).
- Soak test — run at sustained load for 1-2 hours to find memory leaks.
- Analyze results — P50/P95/P99 latency, error rates, resource utilization.
- Report findings — pass/fail vs. SLOs, bottlenecks, recommendations.
Output Format
SLO Definition
| SLO | Target | Test Threshold |
|---|
| P50 latency (checkout) | < 200ms | < 180ms |
| P95 latency (checkout) | < 500ms | < 450ms |
| P99 latency (checkout) | < 2000ms | < 1800ms |
| P95 latency (search) | < 300ms | < 270ms |
| Error rate | < 0.1% | < 0.08% |
| Throughput | 500 RPS | 500+ RPS sustained |
| Availability | 99.9% | 99.9% |
k6 Load Test Script
import http from 'k6/http';
import { check, sleep, group } from 'k6';
import { Rate, Trend, Counter } from 'k6/metrics';
import { SharedArray } from 'k6/data';
const errorRate = new Rate('error_rate');
const checkoutDuration = new Trend('checkout_duration', true);
const searchDuration = new Trend('search_duration', true);
const ordersCreated = new Counter('orders_created');
const users = new SharedArray('users', function() {
return JSON.parse(open('./test-data/users.json'));
});
const products = new SharedArray('products', () {
.(());
});
options = {
: {
: {
: ,
: ,
: ,
: ,
: ,
: ,
: ,
},
: {
: ,
: ,
: ,
: [
{ : , : },
{ : , : },
{ : , : },
{ : , : },
],
: ,
: ,
},
: {
: ,
: ,
: ,
: [
{ : , : },
{ : , : },
{ : , : },
{ : , : },
],
: ,
: ,
},
},
: {
: [
, ,
],
: [],
: [],
: [],
: [],
},
};
() {
res = http.(, .({
: user.,
: user.,
}), { : { : } });
(res. === ) {
res.();
}
;
}
() {
http.();
{ : __ENV. || };
}
() {
user = users[.(.() * users.)];
token = (user);
(!token) {
errorRate.();
;
}
headers = {
: ,
: ,
};
(, () {
startTime = .();
query = [, , ][.(.() * )];
res = http.(
,
{ headers, : { : } }
);
searchDuration.(.() - startTime);
ok = (res, {
: r. === ,
: r.() && r.(). > ,
});
errorRate.(!ok);
});
(.() * + );
(, () {
product = products[.(.() * products.)];
res = http.(
,
.({ : product., : }),
{ headers, : { : } }
);
(res, {
: r. === ,
});
errorRate.(res. !== );
});
(.() * + );
(, () {
startTime = .();
res = http.(
,
.({
: ,
: user.,
}),
{ headers, : { : } }
);
checkoutDuration.(.() - startTime);
ok = (res, {
: r. === ,
: r.() !== ,
});
(ok) ordersCreated.();
errorRate.(!ok);
});
(.() * );
}
() {
.();
}
Running Tests
brew install k6
python scripts/generate_test_data.py --users 1000 --products 500 -o test-data/
BASE_URL=https://staging.api.example.com \
k6 run \
--out json=results.json \
--out influxdb=http://localhost:8086/k6 \
load-test.js
k6 run --scenario=spike load-test.js
k6 run --out=dashboard load-test.js
K6_CLOUD_TOKEN=xxx k6 cloud load-test.js
JMeter Alternative (for Java/enterprise environments)
<ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Load Test">
<intProp name="ThreadGroup.num_threads">100</intProp>
<intProp name="ThreadGroup.ramp_time">60</intProp>
<longProp name="ThreadGroup.duration">300</longProp>
<HTTPSamplerProxy testname="GET /products">
<stringProp name="HTTPSampler.path">/products?q=${query}</stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp>
</HTTPSamplerProxy>
<ResponseAssertion testname="Status 200">
<stringProp name="Assertion.test_field">Assertion.response_code</stringProp>
<collectionProp name=>
200
Results Analysis
import json
import statistics
import sys
def analyze_k6_results(json_file: str) -> dict:
latencies = []
errors = 0
total = 0
with open(json_file) as f:
for line in f:
entry = json.loads(line)
if entry["type"] == "Point" and entry["metric"] == "http_req_duration":
latencies.append(entry["data"]["value"])
total += 1
if entry["type"] == "Point" and entry["metric"] == "http_req_failed":
if entry["data"]["value"] == 1:
errors += 1
latencies.sort()
return {
"total_requests": total,
"error_rate": errors / total if total > 0 else 0,
"p50": percentile(latencies, 50),
"p95": percentile(latencies, 95),
"p99": percentile(latencies, 99),
: (latencies) latencies ,
}
() -> :
sorted_data:
k = ((sorted_data) - ) * p /
f = (k)
c = f +
c >= (sorted_data):
sorted_data[-]
sorted_data[f] + (sorted_data[c] - sorted_data[f]) * (k - f)
() -> :
passed =
metric, threshold slos.items():
value = results.get(metric, ())
status = value <= threshold
()
value > threshold:
passed =
passed
__name__ == :
results = analyze_k6_results(sys.argv[])
SLOs = {
: ,
: ,
: ,
}
()
()
()
passed = check_slos(results, SLOs)
sys.exit( passed )
CI Integration
name: Load Test
on:
schedule:
- cron: '0 2 * * *'
workflow_dispatch:
inputs:
target_rps:
description: 'Target RPS'
default: '500'
jobs:
load-test:
runs-on: ubuntu-latest
environment: staging
steps:
- uses: actions/checkout@v4
- name: Setup k6
uses: grafana/setup-k6-action@v1
- name: Generate test data
run: python scripts/generate_test_data.py -o test-data/
- name: Run load test
run: |
k6 run \
--out json=results.json \
--env BASE_URL=${{ secrets.STAGING_URL }} \
--env TARGET_RPS=${{ inputs.target_rps || 500 }} \
load-tests/load-test.js
- name:
Rules
- Never run load tests against production without explicit traffic isolation.
- Start low — baseline at 10% of target before ramping up.
- Use arrival rate, not VU count — constant arrival rate is more realistic than "N threads".
- Model real traffic mix — don't just hammer one endpoint.
- Add think time — real users pause between actions (1-5 seconds typical).
- Test all SLOs explicitly — set thresholds in the script so tests fail automatically.
- Monitor the database — most performance bottlenecks are DB queries, not app code.
- Soak test weekly — memory leaks take hours to manifest, not minutes.
- Store and compare results — a test that doesn't track trends over time has limited value.
- Include spike tests — validate behavior under sudden traffic bursts, not just steady ramp.