skip to content

In k6, which three metrics add up to http_req_duration, and what does it leave out?

level: middleimportance: must knowfreq 74%

answer

  1. seven HTTP timing names, two families
  2. duration is a sum, not a stopwatch
  3. sending plus waiting plus receiving
  4. blocked, connecting, tls sit outside
  5. res.timings mirrors every http_req_ name

basics

~10 s

http_req_duration equals http_req_sending plus http_req_waiting plus http_req_receiving. It excludes http_req_blocked, http_req_connecting and http_req_tls_handshaking, so connection setup never appears in it.

solid answer

~30 s

k6 derives `http_req_duration` as `http_req_sending + http_req_waiting + http_req_receiving` — writing the request, waiting for the first response byte, and reading the body. Everything that happens before the request goes on the wire is recorded in three separate built-in Trends instead: `http_req_blocked` (waiting to acquire a connection), `http_req_connecting` (TCP connect) and `http_req_tls_handshaking` (TLS handshake). None of those three is inside `http_req_duration`, so in k6 v2 a request that paid a costly TLS setup can still report a small `http_req_duration`. The same seven numbers are on the response object as `res.timings.duration`, `.sending`, `.waiting`, `.receiving`, `.blocked`, `.connecting` and `.tls_handshaking`.

code

javascript · 9 lines
javascript
import http from 'k6/http';

export default function () {
  const res = http.get('https://quickpizza.grafana.com/');
  const t = res.timings;
  console.log(`duration=${t.duration}`);
  console.log(`sending=${t.sending} waiting=${t.waiting} receiving=${t.receiving}`);
  console.log(`blocked=${t.blocked} connecting=${t.connecting} tls=${t.tls_handshaking}`);
}

go deeper

for a junior

Memorise the seven HTTP timing names k6 emits and the one identity: duration is sending plus waiting plus receiving. Knowing that connection setup has its own separate names is enough at this stage.

for a middle

Explain what each of the three terms covers in k6's words — writing the request, awaiting the first byte, reading the body — and name the three setup Trends k6 keeps outside the sum.

for a senior

Show that you reach for the per-call res.timings fields when a run's aggregates look wrong, and that you know which of the seven numbers can move without moving http_req_duration at all.

for a principal

Frame the split as a contract: because k6 fixes what http_req_duration contains, any team convention built on it inherits that boundary, and setup cost must be tracked through its own named metrics.

## The identity k6 actually computes `http_req_duration` is a built-in **Trend** metric, and k6 does not measure it with its own stopwatch. It is derived, by addition, from three other Trends that k6 records for the same request: ``` http_req_duration = http_req_sending + http_req_waiting + http_req_receiving ``` Each term names one phase of the exchange, in k6's own vocabulary: - **`http_req_sending`** — time spent writing the request onto an already-established connection. - **`http_req_waiting`** — time between the request being written and the first response byte arriving. k6's documentation calls this *time to first byte*. - **`http_req_receiving`** — time from the first response byte until k6 has read the end of the response body. Because the sum starts only once the connection exists, `http_req_duration` answers a narrow question: **once k6 had a usable connection, how long did this request take?** ## What k6 leaves out of it Three further built-in Trends cover everything that happened *before* the request went onto the wire, and **none of them is inside `http_req_duration`**: - **`http_req_blocked`** — time spent blocked before k6 could initiate the request, waiting to acquire a connection. - **`http_req_connecting`** — time establishing the TCP connection to the remote host. - **`http_req_tls_handshaking`** — time performing the TLS handshake. k6's own reference states the exclusion in the same sentence as the identity: `http_req_duration` is the sending, waiting and receiving time *"without the initial DNS lookup/connection times"*. There is no built-in metric named `http_req_looking_up`, `http_req_total`, or `http_req_connection` — those names do not exist in k6. ## The two families side by side | Built-in metric | Type | Phase it covers | Inside `http_req_duration`? | |---|---|---|---| | `http_req_blocked` | Trend | Waiting to acquire a connection | No | | `http_req_connecting` | Trend | Establishing the TCP connection | No | | `http_req_tls_handshaking` | Trend | TLS handshake | No | | `http_req_sending` | Trend | Writing the request | **Yes** | | `http_req_waiting` | Trend | Awaiting the first response byte | **Yes** | | `http_req_receiving` | Trend | Reading the response body | **Yes** | | `http_req_duration` | Trend | The three rows above, summed | — (it *is* the sum) | ## Reading the breakdown of one call Every one of those seven metric names has a matching field on the response object that `k6/http` returns, so you can print the same numbers for a single call instead of waiting for aggregates. The mapping is one-to-one: `res.timings.blocked`, `.connecting`, `.tls_handshaking`, `.sending`, `.waiting`, `.receiving` and `.duration` correspond to `http_req_blocked`, `http_req_connecting`, `http_req_tls_handshaking`, `http_req_sending`, `http_req_waiting`, `http_req_receiving` and `http_req_duration`. To read one call: 1. Keep the value `http.get()` returns rather than discarding it. 2. Read `res.timings.duration` — the same number k6 fed into the `http_req_duration` Trend. 3. Read `.sending`, `.waiting` and `.receiving`; they add up to `.duration`. 4. Read `.blocked`, `.connecting` and `.tls_handshaking` separately — they will not appear in that sum, however large they are. ## When and how k6 records them k6 does not sample these seven metrics on a timer. It records one sample on **each** of them for **every** HTTP request the run makes, and it stamps all of them with the same instant: k6's reference is explicit that for every `http_req_*` metric the timestamp is emitted at the **end** of the request — the moment k6 has read the end of the response body, or the moment the request timed out. So a slow request's samples all land together at its completion, not at its start. Two more built-ins are emitted on the same per-request cadence and are easy to confuse with the timings: - **`http_reqs`** — a **Counter**, incremented by one for every request k6 generated. - **`http_req_failed`** — a **Rate**, recording whether k6 judged that request a failure. Neither is a timing metric, and neither participates in the `http_req_duration` identity. All nine of these names exist without you writing a line of measurement code; they are registered by k6 at startup, not created by your script. ## Why the split is worth memorising Two facts follow directly from the identity, and both surface constantly in k6 runs: - **A `http_req_duration` figure never contains connection setup.** A request that spent hundreds of milliseconds opening a TLS connection can still report a small `http_req_duration`, because the setup landed in `http_req_blocked`, `http_req_connecting` and `http_req_tls_handshaking` instead. - **The three terms are separate Trends in their own right.** k6 emits a sample on each of them for every request, so all three are available in the run's metric stream, not just their sum. Every one of these names is a metric k6 registers itself; you write no code to obtain them. In k6 v2 the built-in HTTP set is exactly `http_reqs`, `http_req_failed`, `http_req_duration`, `http_req_blocked`, `http_req_connecting`, `http_req_tls_handshaking`, `http_req_sending`, `http_req_waiting` and `http_req_receiving`.

  • Which built-in k6 metric holds the TLS handshake time, and is it part of http_req_duration?
    `http_req_tls_handshaking`, a built-in Trend that records the time spent performing the TLS handshake with the remote host. It is emitted alongside `http_req_duration` but is not one of its three terms, so handshake cost never inflates the duration figure.
  • Does k6 register a built-in metric for the DNS lookup phase of an HTTP request?
    No. k6 v2's built-in HTTP set has no `http_req_looking_up` or similar metric; the reference describes `http_req_duration` as excluding the initial DNS lookup and connection times, and the lookup phase gets no Trend of its own.
  • How do you see these phases for one specific request rather than as run-wide aggregates?
    Keep the response that `http.get()` returns and read its `timings` object. `res.timings.duration`, `.sending`, `.waiting`, `.receiving`, `.blocked`, `.connecting` and `.tls_handshaking` are the per-call values k6 fed into the correspondingly named `http_req_*` metrics.

saying these in an interview costs you the question

  • Claiming http_req_duration covers the whole call including connection setup
  • Saying http_req_blocked is one of the three terms of http_req_duration
  • Adding http_req_connecting to the sum to get total request time
  • Believing k6 measures http_req_duration with its own separate timer
  • Inventing a built-in http_req_total or http_req_looking_up metric