LoadWave
Guides

Thresholds

Turning a run into a verdict — how thresholds are evaluated, why a breach latches, and what "not measured" means.

A threshold is an assertion about the whole run. Breach any one of them and LoadWave exits 2, which is what makes it usable as a gate rather than a report somebody has to read.

Writing them

thresholds:
  - { metric: http_req_duration, stat: p95, op: "<", value: 500 }
  - { metric: http_req_failed, stat: rate, op: "<", value: 0.01 }
  - { metric: checks, stat: rate, op: ">", value: 0.99 }
FieldMeaning
metricAny metric name, built-in or custom.
statcount, rate, avg, min, max, p50, p90, p95, p99, p999.
op<, <=, >, >=.
valueMilliseconds for durations; a fraction of one for rates.
abortOnFailStop the run immediately on breach rather than only failing at the end.

Or from the command line, which is how you vary one number per environment without editing the file:

loadwave run test.yaml --threshold 'http_req_duration:p95<500'
loadwave run test.yaml --threshold 'http_req_failed:rate<0.01'

Three properties worth understanding

They are evaluated against whole-run aggregates

Not against the last tick, not against a rolling window. That is the question a CI gate is asking: did this build meet the bar across the run I just did?

A breach latches

A p95 that spikes at minute three and recovers by minute ten still breached. A gate looking only at the final instant would let a real regression through — the run got slow, and the fact that it got better again does not unmake that.

"Not measured" is not a pass

A threshold on a metric the run never produced is reported as not measured, and it does not silently succeed.

"We never measured it" and "it was fine" are very different answers to hand back to a pipeline. This is the failure mode that lets a threshold on a typo'd metric name sit green in CI for a year. loadwave validate catches most of them before a run ever starts.

abortOnFail

thresholds:
  - { metric: http_req_failed, stat: rate, op: "<", value: 0.25, abortOnFail: true }

Stops the run the moment the condition holds, rather than waiting for the end.

Use it for conditions where finishing teaches you nothing. An error rate of 25% means the environment is broken, not slow — there is no information in the next nine minutes of hammering it, and there is a real cost to a load test continuing against a service that has already fallen over.

Keep the abort threshold well clear of the pass/fail one. A pair like this reads correctly:

thresholds:
  # The bar this build has to clear.
  - { metric: http_req_failed, stat: rate, op: "<", value: 0.01 }
  # The point at which continuing is pointless.
  - { metric: http_req_failed, stat: rate, op: "<", value: 0.25, abortOnFail: true }

Thresholds on custom metrics

A metric emitted by a Go scenario is treated exactly like a built-in one:

vu.Metrics().Trend("cart_value", vu.Labels(), receipt.Total)
thresholds:
  - { metric: cart_value, stat: p50, op: ">", value: 0 }

That particular one is a useful trick: it asserts the scenario is actually completing purchases, not just returning quickly. A test where every checkout 500s in two milliseconds passes a latency threshold beautifully.

A starting set

Worth having on almost every test:

thresholds:
  # Latency, at a percentile that reflects the experience rather than the average.
  - { metric: http_req_duration, stat: p95, op: "<", value: 500 }

  # Errors. Note the rate is a fraction of one, so this is 1%.
  - { metric: http_req_failed, stat: rate, op: "<", value: 0.01 }

  # Checks — this is what catches "fast, but wrong".
  - { metric: checks, stat: rate, op: ">", value: 0.99 }

  # Stop early when the environment is broken rather than slow.
  - { metric: http_req_failed, stat: rate, op: "<", value: 0.25, abortOnFail: true }

Prefer p95 or p99 over avg. An average hides exactly the tail that users notice, and it is the statistic most likely to stay green through a regression that a percentile would catch immediately.

What the verdict looks like

  THRESHOLD                    ACTUAL  RESULT
http_req_duration p95 < 500  46.34   pass
http_req_failed rate < 0.01  0.0159  FAIL

The same table appears in the dashboard and in the HTML report, and the structured form is in --out results.json for anything that needs to read it programmatically.

Next

On this page