In k6, which three metrics add up to http_req_duration, and what does it leave out?
answer
- seven HTTP timing names, two families
- duration is a sum, not a stopwatch
- sending plus waiting plus receiving
- blocked, connecting, tls sit outside
- res.timings mirrors every http_req_ name
basics
~10 shttp_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 sk6 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 linesimport 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
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.
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.
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.
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