skip to content

In Gatling, which builders declare a whole stepped load profile in one expression, and what does each call in that chain set?

level: juniorimportance: must knowfreq 48%

answer

  1. One builder per workload model
  2. A staircase in a single chain
  3. times then eachLevelLasting are mandatory
  4. separatedByRampsLasting and startingFrom optional
  5. The argument is the per-level increment

basics

~10 s

Gatling has one staircase builder per workload model: incrementUsersPerSec(rateIncrement) and incrementConcurrentUsers(usersIncrement). Both chain .times(levels).eachLevelLasting(duration), plus optional .separatedByRampsLasting(duration) and .startingFrom(x).

solid answer

~40 s

Gatling ships two stepped-level builders, one per workload model: `incrementUsersPerSec(rateIncrement)` for the open model and `incrementConcurrentUsers(usersIncrement)` for the closed one. The argument is the **difference between consecutive levels**, not the load itself. From there the chain is fixed: `.times(levels)` says how many levels, `.eachLevelLasting(duration)` says how long each one holds, and those two are mandatory. Two optional calls follow: `.separatedByRampsLasting(duration)` inserts a linear ramp between levels instead of a hard jump, and `.startingFrom(x)` sets the first level so the staircase does not begin at zero. The step names are the same in Java, Kotlin, Scala, JavaScript and TypeScript; only the registration call differs (`injectOpen`/`injectClosed` versus Scala's `inject`).

code

java · 9 lines
java
setUp(
  scn.injectOpen(
    incrementUsersPerSec(20)
      .times(8)
      .eachLevelLasting(Duration.ofMinutes(2))
      .separatedByRampsLasting(Duration.ofSeconds(30))
      .startingFrom(20)
  )
).protocols(httpProtocol);

go deeper

for a junior

Be ready to name both builders and write the chain in order: the increment, then times, then eachLevelLasting, then the two optional calls.

for a middle

Be ready to explain why the chain's order is fixed — each call returns a different type, and only the last one exposes startingFrom and separatedByRampsLasting.

for a senior

Be ready to say when a staircase belongs in a suite at all, and how you parameterise its increment, level count and level duration rather than hard-coding them.

for a principal

Be ready to own the tradeoff between one readable staircase expression and an explicit chain of steps when a profile needs unequal levels or unequal dwell times.

Gatling calls a stepped load profile a **staircase**, and it ships one builder per workload model so that a capacity profile is a single chained expression rather than a hand-written run of alternating ramps and holds. ## The two entry points | builder | model | what the argument means | type | |---|---|---|---| | `incrementUsersPerSec(rateIncrement)` | open | the difference in **arrivals per second** between consecutive levels | `Double` | | `incrementConcurrentUsers(usersIncrement)` | closed | the difference in **concurrent users** between consecutive levels | `Int` | The commonest misreading is treating that first number as the load itself. It is not. Gatling's own API documentation calls the parameter *"the difference of users per second rate between levels of the stairs"*. So `incrementUsersPerSec(20)` does not ask for twenty arrivals a second — it asks for levels **twenty apart**. The builder you pick fixes which model you are declaring, and every step inside one injection call must be of the **same** model kind, so an `incrementUsersPerSec` staircase cannot sit beside a `constantConcurrentUsers` hold in the same call. (What an open or a closed workload model *is*, and which one your system deserves, is performance-testing theory that this page does not settle.) ## The chain is a type ladder, not a bag of options Each call returns a different type, and that is what enforces the order at compile time: 1. `incrementUsersPerSec(20)` yields a `Stairs` whose **only** method is `times`. 2. `.times(8)` yields a stage whose **only** method is `eachLevelLasting`. 3. `.eachLevelLasting(d)` yields a `Composite` — and this is the first point at which you hold a usable injection step. 4. `.startingFrom(x)` and `.separatedByRampsLasting(d)` are declared on that `Composite`, return another `Composite`, and may be written in either order. Two consequences follow. You cannot skip `times` or `eachLevelLasting` — there is no overload that lets you — and you cannot call `.startingFrom(...)` before `.eachLevelLasting(...)`, because the earlier stages do not declare it. ## What the four calls set * **`times(levels)`** — the number of levels in the staircase. * **`eachLevelLasting(duration)`** — how long every level holds. This is the same for all levels; the builder has no per-level duration. * **`separatedByRampsLasting(duration)`** — *optional*. Inserts a linear ramp of that length between levels. Omit it and the profile jumps from one level straight to the next. * **`startingFrom(x)`** — *optional*. The level the staircase begins at. Omit it and it begins at zero, which is almost never what a capacity run wants. The duration arguments follow the SDK's usual spelling: a bare number means **seconds** everywhere, Java and Kotlin also take a `java.time.Duration`, Scala takes a `scala.concurrent.duration` literal such as `2.minutes`, and the JavaScript/TypeScript SDK takes an object literal such as `{ amount: 2, unit: "minutes" }`. ## A worked profile A capacity staircase of eight levels twenty arrivals per second apart, each level held for two minutes: ```java setUp( scn.injectOpen( incrementUsersPerSec(20) .times(8) .eachLevelLasting(Duration.ofMinutes(2)) .startingFrom(20) ) ).protocols(httpProtocol); ``` That declares levels of 20, 40, 60, 80, 100, 120, 140 and 160 arrivals per second, sixteen minutes of run time, and roughly 86,400 injected users — one expression in place of eight hand-written steps. With no ramps declared, the staircase expands to exactly one `constantUsersPerSec(rate).during(2 min)` block per level, so eight levels means eight blocks. (Adding `separatedByRampsLasting` to this `startingFrom(20)` profile would make it fifteen: eight levels and seven ramps.) ## What the staircase expands into The helper is shorthand. Gatling turns the finished chain into an ordinary list of injection blocks — but which list depends on which branch the expansion takes, and there are two of them. **With no ramps, or with ramps over a non-zero starting level**, it is one constant-rate block per level plus one ramp block per gap (`levels - 1` ramps, and none at all without `separatedByRampsLasting`), and the rate of the level at index `k` is simply: ```text levelRate = k * increment + startingRate // k = 0 .. levels-1 ``` **With ramps and a starting level of zero** — which is what you get when `startingFrom` is omitted — Gatling takes the other branch. It emits `levels` ramps rather than `levels - 1`, because there is a leading ramp out of zero that sits in no gap, and the rates are: ```text levelRate = (k + 1) * increment // k = 0 .. levels-1 ``` so that profile peaks at `levels x increment`, one full increment above what the first formula would predict. Carrying the first formula into the ramped-from-zero case is the commonest arithmetic slip on this builder. Two things follow from the expansion. Everything true of `constantUsersPerSec` and `rampUsersPerSec` is true of the blocks a staircase produces, because they *are* those blocks. And the total run length is derived rather than declared: `levels x levelDuration` with no ramps, plus the ramp time when ramps are present. Nothing in the chain states a total duration, so a reviewer computes it from the level count and the level duration. ## Where it sits in the simulation The finished `Composite` **is** an injection step, so it goes wherever any other step goes: alone inside `injectOpen(...)`, or chained beside other open steps such as a trailing `constantUsersPerSec(...).during(...)`. In Java, Kotlin, JavaScript and TypeScript you pass it to `scn.injectOpen(...)` or `scn.injectClosed(...)`; in Scala you pass it to the single `inject(...)`, which resolves open versus closed from the step type rather than from the method name. One limit worth knowing up front: there is no `.randomized()` on a staircase. That modifier exists on `constantUsersPerSec` and `rampUsersPerSec` only, so the levels of a staircase always inject at regular intervals.

  • Can a staircase step be chained with other injection steps in the same call?
    Yes. The finished chain is an ordinary injection step, so it can be followed by, say, `constantUsersPerSec(160).during(Duration.ofMinutes(10))` inside the same `injectOpen(...)`. The one constraint is that every step in the call must be of the same workload model kind — you cannot mix open and closed steps.
  • Is there a `.randomized()` modifier on a staircase, as there is on `constantUsersPerSec`?
    No. `randomized()` is declared only on the constant-rate and ramp-rate open steps. The staircase builder's final stage exposes just `startingFrom` and `separatedByRampsLasting`, so its levels always inject at regular intervals.

Read it like a flight of stairs specified by its rise and its count: the argument is the height of one step, times is how many steps, eachLevelLasting is how long you stand on each, and startingFrom is the landing you begin on.

saying these in an interview costs you the question

  • Reading the builder argument as the load rather than the increment
  • Thinking times or eachLevelLasting can be omitted
  • Calling startingFrom before eachLevelLasting in the chain
  • Mixing an open staircase with closed steps in one call