skip to content

In a Gatling assertion, how do the `count` and `percent` metrics on `failedRequests` differ, and what is each one measured against?

level: seniorimportance: should knowfreq 46%

answer

  1. count is absolute, percent is a share
  2. percent is relative to the same scope
  3. Absolute ceilings loosen as a run grows
  4. allRequests().count() proves the run happened
  5. count is Long, percent is Double

basics

~20 s

count() is the absolute number of failures in the scope; percent() is that number as a share of all requests in the same scope. An absolute ceiling gets more permissive as a run grows longer, faster or spreads across machines; a percentage does not.

solid answer

~40 s

Both hang off the same count statistic. `count()` is a raw `Long` tally of matching requests within the scope; `percent()` is a `Double` share, 0 to 100, of all requests **in that same scope** - so a `details(...)` percentage is relative to that one request, not to the run. The practical difference is scale sensitivity: a ten-failure ceiling is twice as permissive when the run lasts twice as long, and Gatling's Community Edition gives each load generator its own verdict, so the ceiling is spent once per machine. A percentage is normalised and immune to all three. `count()` still wins for exact zero tolerance and for proving the run produced traffic at all, which no percentage rule can detect.

code

java · 19 lines
java
import io.gatling.javaapi.core.*;

import static io.gatling.javaapi.core.CoreDsl.*;

public class CountGate extends Simulation {
  public CountGate() {
    ScenarioBuilder scn = scenario("checkout");

    setUp(scn.injectOpen(atOnceUsers(1)))
      .assertions(
        // scale-free: a share of Checkout's own traffic
        details("Checkout").failedRequests().percent().lte(1.0),
        // exact: no failure is acceptable anywhere
        global().failedRequests().count().is(0L),
        // guard: a run that produced almost nothing must not pass
        global().allRequests().count().gt(1000L)
      );
  }
}

go deeper

for a junior

Be ready to say that count is an absolute number and percent a share, and that both are only available on the three count statistics.

for a middle

Be ready to explain that percent is relative to the scope's own requests and to state the Long and Double argument types the Java API expects.

for a senior

Be ready to explain why an absolute ceiling loosens as a run grows or spreads across machines, and why a percentage alone cannot catch an empty run.

for a principal

Be ready to argue for a suite convention pairing a normalised rule with a volume floor, so a run that produced no load can never report success.

## Two metrics on the same statistic `failedRequests` is one of three **count statistics** in Gatling's assertion chain, alongside `allRequests` and `successfulRequests`. Every one of them exposes exactly two metrics, and no others: | metric | type | what it produces | |---|---|---| | `count()` | `Long` | the absolute number of matching requests in the scope | | `percent()` | `Double` | that number as a share, 0 to 100, of **all** requests in the same scope | So `global().failedRequests().count().lt(10L)` says *fewer than ten failures in the whole run*, while `global().failedRequests().percent().lt(1.0)` says *fewer than one failure in a hundred*. They agree only at one run size, and diverge everywhere else. The denominator is the detail that gets missed. `percent()` is relative to the **same scope**, not to the run. `details("Purchase", "Checkout").failedRequests().percent().lte(5.0)` allows five percent of the *Checkout* request's own traffic to fail, which is a completely different budget from five percent of the run. And when the scope names a group rather than a request, the things being counted are group **executions**: Gatling records how many times the group ran and how many of those runs were KO, not the requests inside it. ## Why an absolute count quietly changes meaning `count()` is a raw tally, so its strictness is a function of how much traffic the run produced — something the assertion itself never sees: - **Run the same profile for longer** and the ceiling stays at ten while the traffic doubles, so the rule has become twice as permissive without anyone editing it. - **Raise the arrival rate** and the same thing happens for the same reason. - **Run it on several load generators.** Gatling's Community Edition produces one report and one verdict per machine, so a ten-failure ceiling is spent independently on each; four machines tolerate up to forty failures between them and every one of them reports a pass. `percent()` has none of these properties, because it is normalised by construction. That is the whole argument for it, and it is a property of the grammar rather than a matter of taste. ## Where an absolute count is the right tool `count()` is not the weaker metric — it answers questions `percent()` cannot express: 1. **Zero tolerance.** `global().failedRequests().count().is(0L)` is exact and unambiguous. The percentage form can round, and it inherits a denominator you then have to reason about. 2. **Proving the run happened.** `global().allRequests().count().gt(1000L)` catches a run that failed to generate load - a feeder that ran dry, a profile that injected nobody, a target that refused connections early. A percentage rule cannot detect an empty run, because a run with no requests has nothing to be a percentage of. 3. **A budget that genuinely is absolute**, such as a hard cap on writes to a downstream system. Point 2 is the one worth remembering: a suite gated only on percentages can pass a run that did almost nothing, so pairing a percentage rule with a `count()` floor on `allRequests` is a cheap and common guard. ## One degenerate combination `allRequests().percent()` compares a scope's request count with itself, so it is 100 by construction and tells you nothing. If you are targeting `allRequests`, `count()` is the metric that carries information. Reach for `failedRequests().percent()` or `successfulRequests().percent()` when a share is what you want. ## `requestsPerSec` is not a third count metric `requestsPerSec` looks like it belongs with the count statistics but it has no metric link at all - it goes straight from the statistic to the condition, and the number it produces is the **mean** rate over the run: ```java details("Purchase").requestsPerSec().between(100.0, 1000.0); ``` Two things follow. It cannot be asked for a peak or a percentile, because the only reduction available is the mean; and a mean rate over a profile that ramps is not the rate at any particular moment. Reading a `requestsPerSec` assertion as a statement about sustained throughput is a misreading of what the grammar computes. ## The conditions available to both Whichever metric you pick, the same condition link finishes the chain, and a few of them are worth knowing because they express a budget more directly than a bare `lt`: - `between(min, max)` bounds a value on both sides, with the bounds **included** unless you pass the optional third argument as false. - `around(value, plusOrMinus)` is the same thing written as a centre and a margin, and it simply delegates to `between`. - `deviatesAround(value, deviation)` expresses the margin as a **fraction** of the value, not as a number out of a hundred. The margin is computed by multiplying the target by the deviation, so a ten-percent band around 500 is `deviatesAround(500, 0.1)`, giving 450 to 550. Writing `deviatesAround(500, 10)` asks for a band of plus or minus 5000, which nothing will ever breach. That last one catches people, because the parameter is named after a percentage while behaving as a ratio. ## Typing, in Java and Kotlin `count()` is typed on `Long` and `percent()` on `Double`, so `count().lt(10)` and `percent().gt(95)` do not compile — they need `10L` and `95.0`. Scala's numeric widening accepts the bare literals, which is why the Scala examples in the reference look shorter than their Java counterparts for the same rule.

  • Why can a suite gated only on percentages pass a run that did almost nothing?
    Because a percentage has no opinion about volume. A run that issued twelve requests and failed none satisfies a one-percent failure budget perfectly. Adding an `allRequests().count()` floor gives the suite something that an empty or truncated run cannot satisfy, which is the cheapest way to catch a feeder that ran dry or a profile that injected nobody.
  • What does `details("Checkout").failedRequests().percent().lte(5.0)` allow?
    Five percent of the Checkout request's own traffic to fail. The denominator is the scope, not the run, so if Checkout is one request in ten this rule permits about half a percent of the run's total requests to fail - a much tighter rule than the same number written on `global()` would be.
  • Can `requestsPerSec` be asserted at a peak rather than an average?
    No. `requestsPerSec` carries no metric link: it goes straight from the statistic to the condition, and the single number it produces is the mean rate over the run. There is no peak or percentile reduction available for it, so a rule written on it is always a statement about the average.

An absolute count is a fixed number of allowed spoiled items; a percentage is an allowed spoilage rate. Double the size of the batch and the fixed number has become a looser standard without anyone changing it.

saying these in an interview costs you the question

  • Reading a details percentage as a share of the whole run
  • Assuming an absolute failure ceiling stays as strict when a run grows
  • Expecting one count ceiling to cover a multi-machine Community Edition run
  • Asserting allRequests().percent(), which is always 100
  • Treating a requestsPerSec assertion as a peak throughput rule