skip to content

Threshold Expression Syntax

Writing the rule itself: a metric name mapped to expressions of the form aggregation, comparator, number, in either the short string form or the object form. Asked because the grammar is exact.

on this pageshow

explore

questions

4

How do you declare a threshold in k6's exported options object, and what entry forms exist?

level: juniorimportance: must knowfreq 72%

answer

  1. a map, not a list
  2. metric name to array of entries
  3. string entry or object entry
  4. object adds abort controls
  5. threshold key holds the expression

basics

~10 s

k6's options.thresholds maps a metric name to an array of entries. Each entry is either an expression string like 'p(95)<500' or an object carrying threshold, abortOnFail and delayAbortEval. One array may mix both forms.

solid answer

~40 s

In k6 you add a `thresholds` key to the exported `options` object. Its keys are metric names and each value is an **array** - even for a single rule. An array entry can be the short form, a plain expression string such as `'p(95)<500'`, or the object form `{ threshold: 'rate<0.01', abortOnFail: true, delayAbortEval: '10s' }`. The two forms are interchangeable and can appear side by side in the same array, because k6 tries to decode each entry as a string first and falls back to the object shape. Several entries under one metric name all have to hold for that metric to pass. The `threshold` string is the only required part of the object form; omit it and k6 fails configuration validation before the test starts.

code

javascript · 11 lines
javascript
export const options = {
  thresholds: {
    // short form: a bare expression string
    http_req_duration: ['p(95)<500'],
    // object form, mixed with a short-form entry in one array
    http_req_failed: [
      { threshold: 'rate<0.01', abortOnFail: true, delayAbortEval: '30s' },
      'rate<0.05',
    ],
  },
};

go deeper

for a junior

Recall the shape: options.thresholds, metric name as key, array of entries as value, and a plain expression string is a legal entry. Write one for latency and one for error rate without looking it up.

for a middle

Explain why both an array is required and why the object form exists at all, and show that the two forms can be mixed in a single array. Know that every entry under a metric must hold.

for a senior

Show that thresholds are validated before the run, so a bad declaration costs no test time, and that a misspelled object key is dropped in silence. That last one is the failure people ship without noticing.

for a principal

Weigh short form against object form as a house convention: the object form buys early aborts but hides its own typos, so decide whether the team standardises on it and how declarations get reviewed.

## Where the option lives `thresholds` is a root key of the `options` object a k6 script exports. It is a **map**: every key is a metric name (`http_req_duration`, `http_req_failed`, or a custom metric you created), and every value is an **array of entries**. The array is not optional sugar - k6 decodes the value straight into a slice, so writing `http_req_duration: 'p(95)<500'` without the brackets is a configuration error, not a shorthand. ```javascript export const options = { thresholds: { http_req_duration: ['p(95)<500'], http_req_failed: ['rate<0.01'], }, }; ``` ## The two entry forms Each array entry may be written two ways, and k6 accepts both in the same array. When it reads an entry it first tries to decode it as a plain JSON string; only if that fails does it decode it as an object. | | short form | object form | |---|---|---| | written as | `'p(95)<500'` | `{ threshold: 'p(95)<500' }` | | carries the expression | yes | in the `threshold` key | | can stop the run early | no | via `abortOnFail` | | can delay that stop | no | via `delayAbortEval` | | required keys | the string itself | `threshold` | A mixed array is legal and useful when only one rule needs to cut the run short: ```javascript thresholds: { http_req_duration: ['p(95)<500'], http_req_failed: [ { threshold: 'rate<0.01', abortOnFail: true, delayAbortEval: '30s' }, 'rate<0.05', ], } ``` ## What the object keys mean - **`threshold`** - the expression string, in exactly the same grammar the short form uses. It is the only key that carries meaning on its own. - **`abortOnFail`** - a boolean. When true and the expression fails while the test is still running, k6 stops the run early instead of letting it play out. - **`delayAbortEval`** - a duration such as `'10s'` or `'1m'`. It holds the abort back until the run has been going that long, so an early, data-poor evaluation cannot kill the test. `abortOnFail` and `delayAbortEval` only exist in the object form. There is no way to express them in the short string form, which is the practical reason the object form exists at all. ## Several entries under one metric Entries in one array are independent rules, and **all of them must hold**. k6 evaluates every entry - it does not stop at the first failure - so an end-of-test summary reports each expression separately with its own pass or fail marker, then marks the whole metric failed if any one of them failed. ```javascript thresholds: { http_req_duration: ['p(95)<500', 'p(99)<1500', 'max<5000'], } ``` That is also why you must never repeat the metric name as a second key in the `thresholds` map. It is a plain JavaScript object literal, so a duplicate key silently replaces the earlier one and the first set of rules disappears without a warning. Put the extra expressions in the same array instead. An empty array is legal and does something subtler than nothing: `http_req_failed: []` declares no rule, so there is nothing to pass or fail, but the metric is still registered as one carrying thresholds and is therefore marked observed - which means it shows up in the end-of-test summary even on a run where it never received a sample. ## What k6 checks before the run starts Threshold declarations are parsed and validated **before** the test executes, so a bad rule costs you no run time at all. Three things are checked in order: 1. **The value decodes.** A non-array value, or an object entry whose `threshold` key is missing, leaves k6 with nothing to parse and configuration validation fails. 2. **Each expression parses.** The expression string has to match k6's threshold grammar. A typo like `'p(95) 500'` is rejected here. 3. **The metric exists and accepts the aggregation.** The metric name is looked up in the registry; an unknown name is reported as `no metric name "..." found`. One thing is *not* checked: the object form's own key names. k6 hands the whole `thresholds` value to a custom decoder that ignores keys it does not recognise, so a misspelling such as `abortOnFailure` or `delayAbortEvaluation` is silently dropped - no error, and not even the "unknown fields in the options" warning that a stray key at the root of `options` produces. The threshold still works; it just quietly loses the behaviour you thought you had configured. Spelling the two object keys correctly is on you.

  • Can one k6 metric carry several threshold expressions, and what happens if only one of them fails?
    Yes - put them all in the same array under one metric name. k6 evaluates every entry rather than stopping at the first failure, prints each expression's own result in the summary, and marks the metric failed if any single expression failed.
  • Why does repeating the same metric name twice in k6's thresholds object lose rules?
    `thresholds` is an ordinary JavaScript object literal, so a repeated key overwrites the earlier one before k6 ever sees it. The first array is gone with no warning. Merge the expressions into a single array under one key instead.
  • What does k6 do with an object-form entry that has no threshold key?
    The expression string defaults to empty, and parsing an empty expression fails, so the run never starts. `abortOnFail` and `delayAbortEval` cannot stand on their own - `threshold` is the only key that carries the rule.

saying these in an interview costs you the question

  • thinks a single expression can be written without the array
  • believes abortOnFail works in the short string form
  • declares the same metric name twice instead of one array
  • assumes k6 catches misspelled abortOnFail or delayAbortEval keys
  • thinks thresholds are checked only after the run finishes
open as a page

In a k6 threshold expression such as p(95)<500, what may legally appear on each side?

level: middleimportance: should knowfreq 54%

basics

~20 s

A k6 threshold expression is exactly one aggregation token, one comparison operator from > >= < <= == === and !=, and a bare number. No units, no second condition, no JavaScript. Time metrics take their number in milliseconds.

open as a page

What do abortOnFail and delayAbortEval do to a k6 threshold, and when exactly does the abort fire?

level: seniorimportance: should knowfreq 41%

basics

~20 s

abortOnFail makes k6 stop a running test once that threshold fails; delayAbortEval holds the abort back until the run has lasted longer than the given duration. k6 re-evaluates thresholds every two seconds, so the abort lands on the next tick.

open as a page

Do k6's p(95) latency and http_req_failed rate thresholds pass on a run that made zero requests?

level: seniorimportance: nice to knowfreq 28%

basics

~20 s

Both pass. k6's final evaluation judges every declared threshold, and a metric with no samples offers the comparison either nothing at all, which counts as a pass, or a zero that beats any upper bound.

open as a page