skip to content

Tags and Groups

System tags, user tags, test-wide tags and the group tag that group() builds with ::. Interviewers probe it because a tag is what makes a sub-metric selector possible at all.

on this pageshow

explore

questions

6

In k6, what does wrapping requests in group('name', callback) add to the metrics they emit?

level: juniorimportance: must knowfreq 70%

answer

  1. one tag plus one metric
  2. the tag is a path
  3. two colons join the levels
  4. the root group is nameless
  5. group_duration is a built-in Trend

basics

~10 s

k6 tags every sample emitted inside the callback with a group tag holding the full nested group path joined by ::, and it emits one group_duration Trend sample timing the callback.

solid answer

~40 s

`group(name, callback)` from the built-in `k6` module does exactly two things. First, for the duration of the callback it sets the `group` system tag on every metric sample the VU emits — requests, checks and custom metrics alike. The value is not the bare name but the whole path: k6 joins the enclosing group names with `::`, and because the root group is named with the empty string, a single top-level group `checkout` yields `::checkout` while a `pay` nested inside it yields `::checkout::pay`. Second, when the callback returns, k6 pushes one sample onto the built-in `group_duration` Trend, timing the whole callback including any `sleep()` inside it. The previous group tag is restored afterwards, so nesting unwinds correctly. Group names may not contain `::`, and the callback may not be an async function.

code

javascript · 15 lines
javascript
import http from 'k6/http';
import { group, check } from 'k6';

export const options = { vus: 1, iterations: 1 };

export default function () {
  group('checkout', function () {
    const res = http.get('https://quickpizza.grafana.com/');
    check(res, { 'cart loads': (r) => r.status === 200 });

    group('pay', function () {
      http.get('https://quickpizza.grafana.com/api/headers');
    });
  });
}

go deeper

for a junior

Recall the two effects: a group tag on everything emitted inside, and one group_duration sample per call. Know the tag holds the whole nested path, not just the name you passed.

for a middle

Explain how the path is built - k6 appends two colons plus the name to the current value and restores it when the callback returns - and why the empty-named root group gives a leading separator.

for a senior

Show you know where group paths come from besides your own calls: setup and teardown run under ::setup and ::teardown, and the tag vanishes entirely if systemTags drops group.

for a principal

Weigh what the group dimension buys. Every distinct path is another tag value on every metric inside it, and a group wrapped around a single request duplicates what the name tag already says.

## What `group()` is in k6 In k6 v2, `group()` is one of the five functions the built-in `k6` module exports — the complete list is `check`, `fail`, `group`, `randomSeed` and `sleep`. You call it as `group(name, callback)` and k6 runs the callback immediately and synchronously. It is not a scheduler, not a test-case boundary and not an assertion: everything `group()` does happens to the **metric samples** the VU emits while the callback is running. There are exactly two effects. 1. For the lifetime of the callback, k6 sets the `group` **system tag** on every sample that VU emits — HTTP timings, `checks` entries, custom metrics, all of them. 2. When the callback returns, k6 pushes exactly one sample onto the built-in `group_duration` Trend metric, whose value is the wall-clock time the callback took. Nothing else changes. The requests inside a group are not batched, retried, isolated or re-ordered, and the group does not become its own metric. If you delete every `group()` call from a script, the same requests run in the same order and produce the same measurements — only the `group` tag value and the `group_duration` samples disappear. ## The `group` tag holds a path, not a name The value of the `group` tag is the whole chain of enclosing group names joined by the **group separator `::`** (two colons). k6's root group is named with the empty string, which is why the first separator is always leading — a common surprise when you first filter on the tag. | Where the sample is emitted | `group` tag value | | --- | --- | | Outside any `group()` call | `` (the empty string — the root group) | | Inside `group('checkout', …)` | `::checkout` | | Inside `group('pay', …)` nested in `checkout` | `::checkout::pay` | Mechanically, k6 reads the VU's current `group` tag, appends `::` plus the new name, sets the result, runs the callback, and restores the previous value in a deferred step on the way out. Because the restore is deferred, the nesting unwinds correctly even if the callback throws — the sibling group after a failed one is not tagged with the failed one's path. ## The `group_duration` metric - `group_duration` is a built-in **Trend** carrying time values, alongside `iteration_duration`, `http_req_duration` and the rest of k6's built-in set. - It times the callback end to end: request time, your own JavaScript, and any `sleep()` you put inside it. A group with a one-second sleep in it reports at least a second. - The sample carries the same tags as everything else emitted at that instant, **including the `group` tag the call just set** — so nested groups' durations stay distinguishable by tag value. - One sample per `group()` call per iteration: ten iterations through one group give ten samples, and the summary's Trend statistics are computed over all of them. ## What k6 rejects - **A name containing `::`.** k6 fails the call with "group and check names may not contain '::'". The separator is reserved for building the path, so you cannot smuggle a hierarchy in through the name. The same restriction applies to `check()` names, for the same reason. - **An async callback.** `group()` refuses a function that returns a promise and points you at the `group` documentation. You cannot `await` inside the built-in helper. - **The init context.** Calling `group()` at module top level, outside a VU, raises "Using group() in the init context is not supported" — there is no VU whose tags could be modified there. ## Group paths you did not write Not every `group` tag value comes from a `group()` call in your default function. k6 runs the exported lifecycle functions inside a group named after the function, so samples emitted from `setup()` carry `group: "::setup"` and samples from `teardown()` carry `group: "::teardown"`. That is what lets you tell a one-off fixture request apart from the load itself when you read the results. ## Why the tag is the point `group` is an ordinary indexed tag, which means its value is what a sub-metric selector of the form `metric{key:value}` matches against: `http_req_duration{group:::checkout::pay}` names the durations recorded inside that one group, the triple colon being the selector's own `key:value` colon followed by the path's leading `::`. The tag is the mechanism; `group()` is just a convenient way to generate a consistent value for it without repeating yourself at every call site. And because it is a system tag, it can be switched off. An `options.systemTags` list that omits `group` stops the tagging entirely, at which point `group()` still emits `group_duration` samples but they all carry the same empty group value — worth remembering before you trim that list.

  • What is the group tag value on a k6 sample emitted outside any group() call?
    The empty string. k6's root group is named `''`, and every VU starts an activation with `group` set to that root path. That is also why the first `group()` level produces a leading `::` rather than a bare name.
  • What group path do metrics emitted from k6's setup() carry?
    `::setup`, and teardown's carry `::teardown`. k6 runs each exported lifecycle function inside a group named after the function, so fixture traffic stays separable from load traffic by the `group` tag alone.
  • Can group() take an async callback in k6 v2?
    No. `group()` rejects a function that returns a promise and points at the group documentation. `check()` has the same restriction and points at the jslib `check` helper instead.

Nested group() calls build the group tag the way nested folders build a file path: each level appends a separator and its own name, and what gets recorded is the whole path, not just the last segment.

saying these in an interview costs you the question

  • Thinks group() creates a separate metric for each group
  • Says the group tag holds only the innermost group's name
  • Expects a group name containing :: to nest automatically
  • Believes group() can wrap an async function or await inside it
  • Assumes group_duration excludes sleep() calls inside the callback
open as a page

In k6, how do you stop a parameterised product URL from creating a new name tag value per request?

level: seniorimportance: must knowfreq 55%

basics

~20 s

Set the name tag yourself: either tags: { name: 'ProductItem' } in the request params, or k6's http.url tagged template, which renders the name as .../products/${}. k6 then sets the url tag to the same value.

open as a page

Which tags does k6 attach to metric samples by default, and what does options.systemTags change?

level: middleimportance: should knowfreq 58%

basics

~10 s

k6 v2 emits fourteen system tags by default: proto, subproto, status, method, url, name, group, check, error, error_code, tls_version, scenario, service and expected_response. The systemTags option replaces that set rather than extending it.

open as a page

In k6, how do you tag one HTTP request versus tagging every sample the run emits?

level: middleimportance: should knowfreq 62%

basics

~10 s

Per-entity tags go at the call site: a tags object in an HTTP params argument, check()'s optional third argument, or a custom metric's add(). Test-wide tags go in options.tags, --tag NAME=VALUE, or K6_TAGS=name:value.

open as a page

In a k6 suite, when do you use group(), a name tag, or options.tags to carry a dimension?

level: principalimportance: should knowfreq 40%

basics

~20 s

Match the scope: options.tags for run identity, a name tag for operation identity, group() for a multi-step stretch, exec.vu.tags for a runtime variant. A group around a single request duplicates name and adds group_duration for nothing.

open as a page

In k6, how does exec.vu.tags set a metric tag mid-iteration, and which values does it accept?

level: seniorimportance: nice to knowfreq 44%

basics

~20 s

exec.vu.tags, from k6/execution, is a live view of the running VU's tag set: assigning to a key tags every sample the VU emits from then on, until you delete it. Only string, boolean and number values are accepted.

open as a page