The K6 terminal output can look overwhelming the first time. There are twenty-odd metric rows, several statistical columns, and unit abbreviations that are not immediately obvious. This lesson decodes every section, explains which numbers to look at first, and shows you how to customise what K6 reports.
The basic run commands
Run with defaults — one VU, one iteration:
k6 run script.jsOverride VUs and duration from the CLI without editing the script:
k6 run --vus 10 --duration 30s script.jsThis runs 10 virtual users for 30 seconds. Each VU continuously loops the export default function until 30 seconds elapse. K6 then waits for any in-flight requests to complete and prints the summary.
The full output, annotated
Here is the output from a 10-VU, 30-second run against a simple HTTPS endpoint:
execution: local
script: script.js
output: -
scenarios: (100.00%) 1 scenario, 10 max VUs, 1m0s max duration
* default: 10 looping VUs for 30s (gracefulStop: 30s)
✓ status is 200
checks.........................: 100.00% ✓ 286 ✗ 0
data_received..................: 3.4 MB 112 kB/s
data_sent......................: 25 kB 832 B/s
http_req_blocked...............: avg=2.41ms min=1µs med=3µs max=211ms p(90)=5µs p(95)=8µs
http_req_connecting............: avg=576µs min=0s med=0s max=65ms p(90)=0s p(95)=0s
http_req_duration..............: avg=134ms min=101ms med=127ms max=612ms p(90)=173ms p(95)=201ms
{ expected_response:true }..: avg=134ms min=101ms med=127ms max=612ms p(90)=173ms p(95)=201ms
http_req_failed................: 0.00% ✓ 0 ✗ 286
http_req_receiving.............: avg=412µs min=56µs med=224µs max=18ms p(90)=860µs p(95)=1.28ms
http_req_sending...............: avg=48µs min=10µs med=38µs max=2.2ms p(90)=80µs p(95)=103µs
http_req_tls_handshaking.......: avg=1.44ms min=0s med=0s max=183ms p(90)=0s p(95)=0s
http_req_waiting...............: avg=133ms min=100ms med=127ms max=611ms p(90)=172ms p(95)=200ms
http_reqs......................: 286 9.47/s
iteration_duration.............: avg=1.14s min=1.10s med=1.13s max=1.62s p(90)=1.18s p(95)=1.22s
iterations.....................: 286 9.47/s
vus............................: 10 min=10 max=10
vus_max........................: 10 min=10 max=10The five metrics that matter most
K6 output — what each key metric tells you
| What it measures | Why it matters | |
|---|---|---|
| http_req_duration | Total time for a complete HTTP request (send + wait + receive) | What users actually experience. Use p(95) as your SLA target, not the average. |
| http_req_failed | Proportion of requests returning a 4xx or 5xx response | Should be 0.00%. Any non-zero rate means your service is returning errors under load. |
| http_reqs | Total requests sent + rate per second | Your actual achieved throughput — not what you asked for, what you measured. |
| checks | Pass rate of all check() assertions | Should be 100.00%. Lower means assertions are failing — not errors, but wrong behaviour. |
| iterations | Total executions of the default function | With sleep(1) and 10 VUs for 30s, expect ~280–300. Divide by duration to get iterations/s. |
Understanding percentiles
Every timing metric reports avg, min, med, max, p(90), and p(95). The percentiles are the numbers to care about.
p(95)=201ms means 95% of requests completed in 201ms or less — the slowest 5% took longer. This is a far more reliable SLA target than the average, because averages are pulled down by fast requests and hide the tail.
In the example output above:
avg=134ms— tells you the middle of the distribution, masking outliersp(95)=201ms— the number to write into your SLA and your K6 thresholdmax=612ms— one request, likely a cold TCP connection or a database stall
Always use p(95) or p(99) for performance thresholds, not the average. This is one of the most common mistakes in performance testing across all tools.
The http_req_duration breakdown
K6 splits each HTTP request into sub-timings that show exactly where time was spent:
| Metric | Measures |
|---|---|
http_req_blocked | Time waiting for a free TCP connection from the pool |
http_req_connecting | TCP handshake time |
http_req_tls_handshaking | TLS setup (0 for HTTP, non-zero for HTTPS) |
http_req_sending | Time to upload the request body |
http_req_waiting | Time from sending request to receiving first byte (server processing) |
http_req_receiving | Time to download the response body |
http_req_duration | Total: sending + waiting + receiving |
If http_req_waiting is high, the server is slow to process. If http_req_receiving is high, the response body is large. If http_req_blocked is consistently high, your connection pool is exhausted and you are queuing requests — a common sign that the server is saturated.
In JMeter, you get "elapsed time" — equivalent to http_req_duration. K6's breakdown is built in and requires no extra configuration, which is one of its significant diagnostic advantages.
Customising K6's output
Show additional percentiles in the summary:
k6 run --summary-trend-stats="avg,p(50),p(90),p(95),p(99),max" script.jsExport the full end-of-test summary to JSON for scripted analysis or CI comparison:
k6 run --summary-export=results.json script.jsStream every data point to InfluxDB for live Grafana dashboards:
k6 run --out influxdb=http://localhost:8086/k6 script.jsWrite a JSON line per metric sample during the test (useful for custom post-run analysis):
k6 run --out json=results.json script.jsMultiple outputs can be combined in one run:
k6 run --out json=results.json --out influxdb=http://localhost:8086/k6 script.jsReading the scenario block
The header block before metrics tells you what K6 actually ran:
scenarios: (100.00%) 1 scenario, 10 max VUs, 1m0s max duration
* default: 10 looping VUs for 30s (gracefulStop: 30s)gracefulStop: 30s is K6's built-in buffer time after the test duration expires. VUs stop accepting new iterations but are given 30 seconds to complete any iteration that was already in progress. This prevents dropped requests at the test boundary.
⚠️ Common mistakes
- Using the average as your SLA target. Averages are pulled down by fast requests and hide the slow tail. The 5% of users who experience the worst latency are the ones who churn. Always define SLAs on p(95) or p(99).
- Ignoring
http_req_failed. A 1% failure rate on 10,000 requests is 100 errors. Always read this metric even when your p(95) looks fine — a low failure rate hiding 4xx responses means your test data or auth setup has a problem. - Confusing
iterationswithhttp_reqs. One iteration of the default function may contain multiple HTTP requests. If your script calls three endpoints per iteration,http_reqswill be approximately 3×iterations. Read both and understand what your test actually does.
🎯 Practice task
Run the same script twice and compare the output. 20 minutes.
- Use a script with
vus: 10, duration: '30s'andsleep(1). Run it and notehttp_req_duration p(95)andhttp_reqsrate. - Change
sleep(1)tosleep(0.5)and run again. How did iterations, request rate, andhttp_req_durationchange? - Add
--summary-trend-stats="avg,p(50),p(90),p(95),p(99),max"to the run command. Which percentile shows the most variation between the two runs? - Run with
--out json=run.jsonand open the file. Find one line wheremetricishttp_req_duration. Identify itstype,data.value, anddata.tagsfields.
This practice makes K6 output readable by feel — the skill you need before interpreting results from complex multi-stage test runs.