Load Testing (JMeter/k6) Cheat Sheet
Designing and running load tests with JMeter and k6 to validate performance, throughput, and breaking points.
Types of Performance Tests
Common categories of load testing by goal.
- Load test- Validate behavior under expected peak traffic
- Stress test- Push beyond expected capacity to find the breaking point
- Soak test- Sustain moderate load over a long duration to find memory leaks/degradation
- Spike test- Sudden burst of traffic to test elasticity and autoscaling
Basic k6 Script
A k6 load test script ramping virtual users and asserting response time.
import http from 'k6/http';import { check, sleep } from 'k6';export const options = { stages: [ { duration: '30s', target: 50 }, // ramp up { duration: '1m', target: 50 }, // stay at 50 VUs { duration: '10s', target: 0 }, // ramp down ], thresholds: { http_req_duration: ['p(95)<500'], // 95% of requests under 500ms http_req_failed: ['rate<0.01'], // error rate under 1% },};export default function () { const res = http.get('https://api.example.com/products'); check(res, { 'status is 200': (r) => r.status === 200 }); sleep(1);}
Running k6
Execute a k6 test and export results.
k6 run script.js# Override VUs and duration from the CLIk6 run --vus 100 --duration 30s script.js# Output results to InfluxDB for Grafana dashboardsk6 run --out influxdb=http://localhost:8086/k6 script.js
Running JMeter Non-GUI (CI mode)
Run a JMeter test plan headlessly and generate an HTML report, recommended for CI.
jmeter -n -t test-plan.jmx \ -l results.jtl \ -e -o report/# -n non-GUI mode, -t test plan, -l results log# -e -o generate HTML dashboard report after the run
Key Metrics to Watch
Metrics that matter most when interpreting load test results.
- Throughput (RPS)- Requests successfully processed per second
- Latency percentiles (p50/p95/p99)- Tail latency matters more than averages for user experience
- Error rate- Percentage of failed requests, watch for it rising as load increases
- Saturation point- The load level at which latency/error rate begins degrading non-linearly
JMeter Test Plan Building Blocks
Core elements used to build a realistic, data-driven JMeter test plan beyond a single hardcoded sampler.
- CSV Data Set Config- Feeds each thread a row from a CSV file for parameterized, non-repetitive test data
- Regular Expression Extractor- Correlation: pulls a dynamic value (e.g. session token, CSRF token) out of a prior response to reuse in later requests
- JSON Extractor / JSONPath- Extracts a field from a JSON response body for chaining requests together
- Assertions- Response/Duration/JSON assertions that fail a sample when content or latency doesn't match expectations
- Timers- Constant/Gaussian Random Timer inserts think-time between requests to mimic real user pacing
- Listeners- Collect results (View Results Tree, Summary Report); disable in load runs — they add heavy overhead
- Backend Listener- Streams live metrics to InfluxDB/Grafana during the run instead of only producing a report afterward
JMeter Correlation + Data-Driven Params (JMX excerpt)
Extract a session token from a login response and feed unique usernames from a CSV file into subsequent requests.
<CSVDataSet guiclass="TestBeanGUI" testclass="CSVDataSet" testname="users.csv"> <stringProp name="filename">users.csv</stringProp> <stringProp name="variableNames">username,password</stringProp> <boolProp name="ignoreFirstLine">true</boolProp> <stringProp name="shareMode">shareMode.all</stringProp></CSVDataSet><RegexExtractor guiclass="RegexExtractorGui" testname="Extract sessionToken"> <stringProp name="RegexExtractor.refname">sessionToken</stringProp> <stringProp name="RegexExtractor.regex">"token":"(.+?)"</stringProp> <stringProp name="RegexExtractor.template">$1$</stringProp> <stringProp name="RegexExtractor.default">TOKEN_NOT_FOUND</stringProp></RegexExtractor><!-- later sampler uses header: Authorization: Bearer ${sessionToken} -->
k6 Scenarios with Multiple Executors
Run a steady ramping-VU login flow and a separate constant-arrival-rate API flow concurrently in one script, each with independent load profiles.
export const options = { scenarios: { browsing: { executor: 'ramping-vus', exec: 'browseProducts', startVUs: 0, stages: [ { duration: '1m', target: 30 }, { duration: '3m', target: 30 }, { duration: '30s', target: 0 }, ], }, checkout_api: { executor: 'constant-arrival-rate', exec: 'checkout', rate: 50, // 50 iterations per timeUnit timeUnit: '1s', duration: '3m', preAllocatedVUs: 20, maxVUs: 100, }, },};export function browseProducts() { /* ... */ }export function checkout() { /* ... */ }
Custom Metrics and Abort-on-Threshold
Track a business-specific Trend metric and configure k6 to abort the whole test early once error-rate SLOs are breached, saving CI time.
import http from 'k6/http';import { Trend, Rate } from 'k6/metrics';const checkoutDuration = new Trend('checkout_duration', true);const businessErrorRate = new Rate('business_errors');export const options = { thresholds: { // 'abortOnFail' stops the whole run instead of just failing the check checkout_duration: [{ threshold: 'p(95)<800', abortOnFail: true }], business_errors: ['rate<0.02'], },};export default function () { const res = http.post('https://api.example.com/checkout', JSON.stringify({ cartId: 'c1' }), { headers: { 'Content-Type': 'application/json' }, }); checkoutDuration.add(res.timings.duration); businessErrorRate.add(res.json('status') !== 'ok');}
Distributed Load Generation
Shard a k6 run across multiple containers (or use JMeter's own distributed mode) when a single machine's NIC/CPU becomes the bottleneck.
# k6: shard by execution segment so N machines together simulate one testk6 run --execution-segment '0:1/3' --execution-segment-sequence '0,1/3,2/3,1' script.js # machine 1k6 run --execution-segment '1/3:2/3' --execution-segment-sequence '0,1/3,2/3,1' script.js # machine 2k6 run --execution-segment '2/3:1' --execution-segment-sequence '0,1/3,2/3,1' script.js # machine 3# JMeter distributed mode: 1 controller drives N remote engines# on each remote host:jmeter-server -Dserver_port=1099# on the controller:jmeter -n -t test-plan.jmx -R 10.0.0.11,10.0.0.12,10.0.0.13 -l results.jtl
Always load-test against an environment sized and configured like production (same instance types, connection pools, autoscaling rules) — results from an undersized staging environment routinely mislead capacity planning.