skip to content

In a Gatling stepped injection profile, what does separatedByRampsLasting add, and why does the presence of startingFrom change the resulting shape?

level: seniorimportance: should knowfreq 26%

answer

  1. Ramps replace the hard jump between levels
  2. The two optional calls interact
  3. Level first with a non-zero start, ramp first without
  4. Ramp count is levels or levels minus one

basics

~20 s

It inserts a linear ramp between levels instead of a hard jump. With a non-zero startingFrom the shape is level-then-ramp, ending on a level with no trailing ramp; with it omitted or set to zero the shape is ramp-then-level, opening with a ramp out of zero.

solid answer

~1 min

Without `separatedByRampsLasting`, a staircase steps instantly from one level to the next. Adding it inserts a linear ramp of the given length between neighbouring levels — but the shape Gatling expands depends on whether the starting level is non-zero, not merely on whether `startingFrom` was called. With a non-zero starting level, the expansion is `(level, ramp)` repeated, ending on a **level with no trailing ramp**: eight levels of two minutes with thirty-second ramps is 8 levels plus 7 ramps, 19.5 minutes. With `startingFrom` omitted — or declared as `0`, which the expansion cannot tell apart — the expansion is `(ramp, level)` repeated, so the run **opens with a ramp from zero** and every level is preceded by one: 8 levels plus 8 ramps, 20 minutes. With `.startingFrom(20)` — a start equal to the increment — both forms top out at 160/s, so only the total duration and the opening shape differ. That coincidence is not general: the level-first shape peaks at `(levels - 1) x increment + start` and the ramp-first shape at `levels x increment`, so a start unequal to the increment moves the peak too — `.startingFrom(50)` would top out at 190/s against the omitted form's 160/s.

code

kotlin · 9 lines
kotlin
setUp(
  scn.injectOpen(
    incrementUsersPerSec(20.0)
      .times(8)
      .eachLevelLasting(Duration.ofMinutes(2))
      .separatedByRampsLasting(Duration.ofSeconds(30))
      .startingFrom(20.0)
  )
)

go deeper

for a junior

Be ready to say that separatedByRampsLasting replaces the instant jump between levels with a linear ramp of the length you give it.

for a middle

Be ready to expand both shapes on a whiteboard and to count the ramps: one fewer than the levels when a non-zero starting level is set, one per level when it is zero or omitted.

for a senior

Be ready to compute the real duration of a ramped staircase before scheduling it, and to explain why an omitted starting level costs a top step only in the unramped form.

for a principal

Be ready to decide when a profile's shape has outgrown the helper's single level duration and single ramp duration, and what you would write instead.

`separatedByRampsLasting` is the call that turns a flight of instant steps into the shape most capacity runs actually want: hold, rise smoothly, hold, rise smoothly. What is less obvious is that it does not compose independently with `startingFrom`. The two optional calls interact, and Gatling expands the staircase into two genuinely different lists of blocks depending on whether the starting level is **non-zero**. The branch is on the value, not on the call: the starting level defaults to `0`, so `.startingFrom(0)` is indistinguishable from omitting `startingFrom` altogether and gets the same ramp-first expansion. ## The two expansions Take eight levels twenty arrivals per second apart, each held two minutes, with thirty-second ramps. **With `.startingFrom(20)` — the pattern is `(level, ramp)` repeated, plus a final level:** 1. hold 20/s for 2 min 2. ramp 20/s to 40/s over 30 s 3. hold 40/s for 2 min 4. ... and so on ... 5. hold 160/s for 2 min — **no trailing ramp** That is **8 levels and 7 ramps**: 16 minutes of levels plus 3.5 minutes of ramps, 19 minutes 30 seconds in all. **With `startingFrom` omitted, or declared as `0` — the pattern is `(ramp, level)` repeated:** 1. ramp 0/s to 20/s over 30 s 2. hold 20/s for 2 min 3. ramp 20/s to 40/s over 30 s 4. hold 40/s for 2 min 5. ... ending with a ramp to 160/s and a hold at 160/s That is **8 levels and 8 ramps**: 16 minutes plus 4 minutes, exactly 20 minutes. ## Reading the differences | | starting level non-zero | starting level `0` or omitted | |---|---|---| | opens with | a flat level | a ramp out of zero | | ends with | a level, no trailing ramp | a level, preceded by a ramp | | ramps in the profile | `levels - 1` | `levels` | | total duration | `levels x level + (levels-1) x ramp` | `levels x (ramp + level)` | | peak | `(levels-1) x increment + start` | `levels x increment` | The last row is the one that catches people twice. **With ramps present and the starting level at zero, the peak is not one increment short** — unlike the no-ramp case, where an omitted start leaves a zero-rate level and costs you the top step. Here the ramp-first expansion counts levels from `1 * increment` up to `levels * increment`, so eight levels twenty apart still reach 160 arrivals per second. The same omission therefore has *opposite* effects on the peak depending on whether ramps are declared, which is exactly the kind of detail worth stating out loud in a review. ## The ramps inject users too A ramp is not dead time between levels; it injects at a rate rising linearly from the level below to the level above, which works out as the average of the two for its duration. A thirty-second ramp from 20 to 40 arrivals per second therefore contributes roughly 900 users that the unramped version of the same staircase never injects. Summed over the whole profile, the ramped eight-level staircase applies noticeably more work than the flat-jump one with the same levels, and it applies it over a longer wall-clock window. If a run is being compared against an earlier one, adding or removing `separatedByRampsLasting` changes both the duration and the injected total, so the two runs are no longer the same profile. Whether that makes them incomparable is a question about run comparability rather than about the DSL — but the change itself is a Gatling fact, and a one-word edit can cause it. ## Why the ramp matters beyond aesthetics A hard jump from one level to the next asks the system under test to absorb the entire increment in one instant. The ramp spreads that increment across its duration instead, which is the difference between a profile that exercises a system's steady behaviour at each level and one that repeatedly exercises its reaction to a step change. Which of those you want is a decision about the run's purpose, not about the DSL — but Gatling gives you exactly one knob for it, and the knob is on or off for the whole staircase. That uniformity is the builder's main constraint. One ramp duration applies to **every** gap; one level duration applies to **every** level. A profile that wants a longer dwell at the top, or a gentler ramp only at the highest transition, has outgrown the helper and needs hand-chained steps. ## Writing both forms ```java // level first, 7 ramps, 19m30s incrementUsersPerSec(20) .times(8) .eachLevelLasting(Duration.ofMinutes(2)) .separatedByRampsLasting(Duration.ofSeconds(30)) .startingFrom(20); // ramp first, 8 ramps, 20m incrementUsersPerSec(20) .times(8) .eachLevelLasting(Duration.ofMinutes(2)) .separatedByRampsLasting(Duration.ofSeconds(30)); ``` The two optional calls may be written in either order — both are declared on the same final stage of the chain and each returns that stage again — so `.startingFrom(20).separatedByRampsLasting(...)` builds the identical profile. ## The closed builder `incrementConcurrentUsers` expands by the same rule, substituting held concurrency for arrival rate: `(level, ramp)` with a non-zero starting level, `(ramp, level)` without one. Its ramps are `rampConcurrentUsers`-shaped, so the usual caution applies — a downward transition would not interrupt users already in the system — but a staircase built with a **positive** increment only ascends, so every ramp it generates is upward. The increment itself is not validated, so a negative one paired with a `startingFrom` high enough to keep every level at or above zero does build a descending staircase with downward ramps, and there the ramp-down caution genuinely applies. What a negative increment cannot do is open downward: with `startingFrom` omitted the leading ramp out of zero would need a negative endpoint, and `RampConcurrentUsersInjection` requires both ends to be `>= 0`.

  • How long is the profile if `separatedByRampsLasting` is omitted entirely?
    Exactly `levels * levelDuration`, because no ramp blocks are generated at all and the profile jumps from one level straight to the next. Eight levels of two minutes is sixteen minutes whether or not `startingFrom` is present; only the rates differ.
  • Can the ramp between two particular levels be made longer than the others?
    Not with this builder. `separatedByRampsLasting` takes one duration and applies it to every gap, just as `eachLevelLasting` applies one duration to every level. A profile needing an uneven ramp has to be written as explicit alternating `constantUsersPerSec` and `rampUsersPerSec` steps.
  • Does the ramp change how many users the run injects?
    Yes. Each ramp injects at the average of its start and end rate for its duration, so ramps add users on top of the levels. A thirty-second ramp from 20 to 40 arrivals per second contributes roughly 900 users, which the flat-jump version of the same staircase never injects.

saying these in an interview costs you the question

  • Treating the two optional calls as independent of each other
  • Assuming the last level is always followed by a ramp
  • Expecting one ramp duration to vary between levels
  • Carrying the no-ramp peak arithmetic into the ramped case