skip to content

In a k6 run, why is iteration_duration usually far larger than http_req_duration?

level: middleimportance: should knowfreq 53%

answer

  1. two Trends, two different spans
  2. per iteration versus per request
  3. one sample can hold many requests
  4. sleeps and setup live outside a request
  5. iteration_duration wraps the default function

basics

~20 s

iteration_duration times one whole run of the default function, sampled once per iteration; http_req_duration times one request, sampled once per request. The iteration span also holds sleeps, connection setup and your own JavaScript, none of which http_req_duration contains.

solid answer

~30 s

They measure different spans at different cadences. `iteration_duration` is a Trend sampled once per completed iteration of the exported default function, covering the whole call. `http_req_duration` is a Trend sampled once per HTTP request, and k6 defines it as `http_req_sending + http_req_waiting + http_req_receiving` only. So an iteration's sample contains every request it made, every `sleep()`, all the connection setup that `http_req_blocked`, `http_req_connecting` and `http_req_tls_handshaking` record separately, and any JavaScript you run. A large gap between the two is k6 behaving as specified, not a defect.

code

javascript · 9 lines
javascript
import http from 'k6/http';
import { sleep } from 'k6';

export default function () {
  const a = http.get('https://quickpizza.grafana.com/');
  const b = http.get('https://quickpizza.grafana.com/api/tools');
  console.log(`durations: ${a.timings.duration} and ${b.timings.duration}`);
  sleep(1);
}

go deeper

for a junior

Know that iteration_duration covers one whole run of the default function and http_req_duration covers one request, and that the first is naturally the larger of the two.

for a middle

Explain the cadence difference — one sample per iteration versus one per request — and list what the iteration span holds that no request duration can: sleeps, setup and your own script code.

for a senior

When a run's two Trends diverge, be able to account for the gap by naming the other built-ins that hold the missing time rather than guessing at it.

for a principal

Be precise about which of the two any shared reporting convention refers to, since the same word 'duration' names two spans of very different scope in k6.

## What each of the two metrics covers `iteration_duration` and `http_req_duration` are both built-in **Trend** metrics with a time unit, but they measure spans of very different scope: - **`iteration_duration`** — the wall-clock time k6 measured for one complete iteration of the exported default function. k6 stamps the start when it calls the function and the end when the call returns, and emits one sample per completed iteration. - **`http_req_duration`** — the time for one HTTP request, computed as `http_req_sending + http_req_waiting + http_req_receiving`. k6 emits one sample **per request**. They are not even sampled at the same cadence: one sample per iteration versus one sample per request. Comparing their aggregates directly compares two different populations. ## Everything the iteration contains that a request does not An iteration is the whole body of your default function, so its measured span swallows work that no `http_req_duration` sample can contain: - **Every request in the iteration.** An iteration that issues four requests has all four inside its single `iteration_duration` sample, but produces four separate `http_req_duration` samples. - **Every `sleep()` call.** Time a VU spends paused is inside the iteration span; it belongs to no request at all. - **Connection setup.** `http_req_blocked`, `http_req_connecting` and `http_req_tls_handshaking` are excluded from `http_req_duration` by construction, but the wall-clock time they represent still elapsed inside the iteration. - **Your own JavaScript.** Building payloads, parsing responses, looping and logging all run inside the function call k6 is timing. ## The arithmetic gap, step by step For a single iteration that makes N requests: 1. k6 records N samples on `http_req_duration`, one per request. 2. It records N samples on each of `http_req_blocked`, `http_req_connecting`, `http_req_tls_handshaking`, `http_req_sending`, `http_req_waiting` and `http_req_receiving`. 3. It records **one** sample on `iteration_duration` covering the entire function call. 4. That one sample is at least the sum of the N durations, and normally larger by the setup time, the sleeps and the script's own work. ## Side by side | | `iteration_duration` | `http_req_duration` | |---|---|---| | Type | Trend (time) | Trend (time) | | Sampled | Once per completed iteration | Once per HTTP request | | Scope | The whole default-function call | One request, after the connection exists | | Includes `sleep()` | Yes | No | | Includes connection setup | Yes, as elapsed wall-clock time | No, by construction | | Exists without HTTP | Yes | No | ## Related built-ins in the same family Two more built-ins sit near this pair and are frequently confused with it: - **`iterations`** is the Counter of completed iterations — the tally that `iteration_duration` is the timing counterpart to. - **`group_duration`** is a Trend for time spent inside a group, so it measures a span that is narrower than an iteration but wider than a single request. - **`dropped_iterations`** is a Counter of iterations that were never started, and so contributes no `iteration_duration` sample at all. None of these is derived from another: k6 records each one from its own observation, and no built-in is computed by subtracting one Trend from another. The single exception in the whole built-in set is `http_req_duration`, which k6 defines as the sum of `http_req_sending`, `http_req_waiting` and `http_req_receiving`. And two built-ins are emitted per run rather than per iteration: `vus` and `vus_max`, the two Gauges k6's scheduler samples on a one-second ticker. ## What one completed iteration emits Putting the cadences together makes the relationship concrete. A single completed iteration that issues N HTTP requests causes k6 to record: - **One** `iteration_duration` sample and **one** increment of the `iterations` Counter. - **N** samples on `http_req_duration` and on each of the six other `http_req_*` timing Trends. - **N** increments of the `http_reqs` Counter and **N** samples on the `http_req_failed` Rate. - One `data_sent` and one `data_received` sample for the bytes that iteration moved. Meanwhile `vus` and `vus_max` continue on their own one-second cadence, unrelated to iteration boundaries entirely. Four different cadences — per iteration, per request, per byte-accounting point and per second — coexist in the same run, and knowing which built-in follows which is what keeps their numbers from looking contradictory. ## The practical upshot If `iteration_duration` is far larger than any `http_req_duration`, that is the metrics behaving exactly as k6 defines them, not a defect. The gap is the iteration's non-request time plus its connection setup plus the fact that one iteration holds many requests. In k6 v2, to account for that gap you read the other built-ins by name — the setup Trends `http_req_blocked`, `http_req_connecting` and `http_req_tls_handshaking`, and the per-request `http_reqs` Counter that tells you how many requests each iteration actually made.

  • How many iteration_duration samples does an iteration that makes six requests produce?
    One. k6 emits a single `iteration_duration` sample per completed iteration, whatever the iteration did. The same iteration produces six samples on `http_req_duration` and on each of the other `http_req_*` timing Trends.
  • Which built-in k6 Counter is the tally counterpart of iteration_duration?
    `iterations`. It counts completed iterations of the default function, while `iteration_duration` is the Trend of how long each one took. The two are emitted together for the same completed iteration.
  • Does connection setup time show up anywhere inside iteration_duration?
    Yes, as elapsed wall-clock time. `iteration_duration` measures the whole function call, so any time spent acquiring a connection or handshaking is inside it — even though `http_req_duration` excludes that time and records it on `http_req_blocked`, `http_req_connecting` and `http_req_tls_handshaking`.

saying these in an interview costs you the question

  • Expecting iteration_duration to equal the sum of http_req_duration values
  • Calling the gap between the two metrics a k6 measurement bug
  • Forgetting that sleep() time is inside the iteration span
  • Assuming one iteration produces one http_req_duration sample
  • Thinking connection setup is missing from both metrics