Deep Dive8 min read12 марта 2025 г.

k6 Metrics Explained: What to Watch and What to Ignore

k6 outputs dozens of metrics. Learn which ones actually matter, how percentiles work, and how to set meaningful thresholds.

Автор: Perfscale Team

The metrics k6 always reports

Every k6 run produces these built-in metrics:

MetricTypeWhat it measures
http_req_durationTrendFull round-trip time from request sent to body received
http_req_waitingTrendTime to first byte (TTFB) — server processing time
http_req_sendingTrendTime to send the request body
http_req_receivingTrendTime to download the response body
http_req_failedRateFraction of requests that returned an error
http_reqsCounterTotal requests sent
vusGaugeActive virtual users right now
vus_maxGaugeMax VUs allocated during the test
iteration_durationTrendTime for one full VU iteration
data_receivedCounterTotal bytes downloaded
data_sentCounterTotal bytes uploaded

Trend metrics: never look at the average

http_req_duration is a Trend — k6 collects every sample and reports:

http_req_duration..............: avg=142ms min=89ms med=134ms max=2103ms p(90)=201ms p(99)=890ms

The average is misleading. A few very slow requests inflate it, masking the fact that 90% of users had a fast experience. Or the opposite — a bimodal distribution with half your users at 50ms and half at 2000ms averages out to 1025ms, hiding the problem entirely.

The percentiles that matter

PercentileWhat it tells you
p(50) / medianTypical experience
p(90)9 out of 10 users are faster than this
p(95)Common SLO boundary
p(99)The "tail" — your worst regular experience
p(99.9)Rare edge cases — only relevant at very high volume

Rule of thumb: Set your SLO on p95 or p99, not average.


Setting thresholds

Thresholds turn a load test into a pass/fail gate:

export const options = {
  thresholds: {
    // p99 latency under 500ms
    http_req_duration: ['p(99)<500'],

    // error rate under 1%
    http_req_failed: ['rate<0.01'],

    // at least 100 req/s throughput
    http_reqs: ['rate>100'],

    // only for the /api/checkout endpoint
    'http_req_duration{url:https://api.example.com/checkout}': ['p(95)<300'],
  },
};

Threshold syntax

metric_name[{tag:value,...}]: ['aggregation_function operator value']

Available aggregation functions: avg, min, max, med, p(n), count, rate, value


Custom metrics

You can define your own metrics and track business logic:

import { Trend, Counter, Rate, Gauge } from 'k6/metrics';

const checkoutLatency = new Trend('checkout_duration');
const cartErrors      = new Counter('cart_errors');
const loginSuccessRate = new Rate('login_success');

export default function () {
  const start = Date.now();
  const res = http.post('/api/checkout', payload);
  checkoutLatency.add(Date.now() - start);

  if (res.status !== 200) {
    cartErrors.add(1);
  }

  loginSuccessRate.add(res.status === 200);
}

Custom metrics appear in the output alongside built-in ones, and you can use them in thresholds too.


What to ignore

  • http_req_blocked — DNS lookup + TCP connect time. Only interesting if you're testing from a cold start or see values > 10ms consistently.
  • http_req_tls_handshaking — TLS setup time. Usually < 50ms and amortised across keep-alive connections.
  • data_received / data_sent — Useful for capacity planning, not SLO validation.

The one metric dashboard you need

In Grafana, visualise:

  1. RPS (http_reqs rate) — is the load hitting the target?
  2. p99 latency (http_req_duration p99) — is the service staying fast?
  3. Error rate (http_req_failed rate) — is anything breaking?
  4. Active VUs (vus) — confirms the ramp-up is working

These four panels tell you everything you need to make a pass/fail decision.