In Gatling, what load does incrementUsersPerSec(20).times(8).eachLevelLasting(Duration.ofMinutes(2)) actually apply when startingFrom is left off?
answer
- The optional call that is rarely optional
- An omitted start means a zero level
- The peak lands one increment short
- Level rate is index times increment plus start
basics
~20 sLevels of 0, 20, 40, 60, 80, 100, 120 and 140 arrivals per second, two minutes each. Omitting startingFrom spends the first two minutes injecting nobody and tops out one increment short, at 140 rather than 160.
solid answer
~40 sWithout `startingFrom`, the staircase begins at zero. Gatling derives each level as `levelIndex * increment + startingRate`, and with `startingRate` defaulting to 0 the eight levels are 0, 20, 40, 60, 80, 100, 120 and 140 arrivals per second. Two things follow, and both surprise people. The first level is a genuine two-minute window in which **no user is injected at all** — a zero-rate level is chained exactly like `nothingFor(2 minutes)`, not skipped. And the peak is `(levels - 1) * increment`, so eight levels twenty apart top out at 140, not the 160 most readers expect. Adding `.startingFrom(20)` fixes both: the levels become 20 through 160, and the run injects about 86,400 users instead of 67,200 over the same sixteen minutes.
code
java · 10 lines// tops out at 140/s and wastes the first two minutes
setUp(scn.injectOpen(
incrementUsersPerSec(20).times(8).eachLevelLasting(Duration.ofMinutes(2))
));
// tops out at 160/s and starts working immediately
setUp(scn.injectOpen(
incrementUsersPerSec(20).times(8)
.eachLevelLasting(Duration.ofMinutes(2)).startingFrom(20)
));go deeper
Be ready to say that startingFrom is optional but that leaving it off starts the staircase at zero, and to compute the levels from index times increment.
Be ready to explain both consequences precisely: a full-length dead first level, and a peak one increment below what the level count suggests.
Be ready to catch this in review, and to explain why an unchanged run length is not evidence that a capacity profile is correct.
Be ready to set a convention that derives a staircase from a stated peak, so that the intended top of the ramp is checkable in the source.
`startingFrom` is documented as optional, which invites leaving it off. Doing so changes both ends of the profile, and the change is easy to miss because the run still lasts exactly as long as you expected. ## The level arithmetic With no ramps declared, Gatling derives one constant-rate block per level, and the rate of the level at index `k` is: ```text levelRate = k * increment + startingRate // k = 0 .. levels-1 ``` `startingRate` defaults to **0**. For a staircase of eight levels twenty arrivals per second apart, that gives: | level index | rate without `startingFrom` | rate with `.startingFrom(20)` | |---|---|---| | 0 | **0/s** | 20/s | | 1 | 20/s | 40/s | | 2 | 40/s | 60/s | | 3 | 60/s | 80/s | | 4 | 80/s | 100/s | | 5 | 100/s | 120/s | | 6 | 120/s | 140/s | | 7 | **140/s** | **160/s** | ## The two consequences 1. **The first level injects nobody, for its full duration.** A constant-rate block at rate zero is not optimised away or skipped — internally Gatling chains it exactly as it chains `nothingFor(duration)`, shifting everything after it by two minutes. So the run opens with a dead window as long as any other level. 2. **The peak is one increment short.** With `startingFrom` omitted the top level is `(levels - 1) * increment`; with it set the top is `(levels - 1) * increment + startingRate`. Eight levels twenty apart reach 140 arrivals per second, not 160. A capacity run written to "reach 160" quietly never gets there. The total run length is **unchanged** — eight levels of two minutes is sixteen minutes either way — which is precisely why the mistake survives review. Only the injected work differs: * without `startingFrom`: 120 s x (0+20+...+140) = **67,200 users** * with `.startingFrom(20)`: 120 s x (20+40+...+160) = **86,400 users** Nearly twenty thousand users, and the entire top step of the capacity curve, gone from a profile that looks correct and runs for the right length of time. ## Gatling's own wording is looser than its behaviour The injection reference says that without a starting value "the test will start at 0 user per sec and will go to the next step right away". Read literally that suggests the zero level is skipped. It is not, when no ramp is declared: the zero-rate level holds for the full `eachLevelLasting` duration. The sentence only describes what happens when `separatedByRampsLasting` is also present, in which case the profile opens with a ramp out of zero rather than a flat dead level. Trust the arithmetic above over the prose. ## Writing it so the peak is the number you meant ```java setUp( scn.injectOpen( incrementUsersPerSec(20) .times(8) .eachLevelLasting(Duration.ofMinutes(2)) .startingFrom(20) ) ).protocols(httpProtocol); ``` A useful habit: when a staircase is meant to *end* at a known rate, state the peak in a constant and derive the rest. With `PEAK / LEVELS` as the increment and the same value as `startingFrom`, the top level lands exactly on the peak, and reviewers can check the arithmetic without running anything. The closed-model builder behaves identically. `incrementConcurrentUsers(20).times(8).eachLevelLasting(Duration.ofMinutes(2))` holds 0, 20, 40 ... 140 concurrent users, so its first two minutes hold nobody in the system and its top level is 140 concurrent users. The only difference is the argument type — `Int` for the closed builder, `Double` for the open one. ## Three checks before the run 1. **Does the top level equal the peak you were asked for?** Compute `(levels - 1) * increment + startingRate` and compare it against the number in the ticket. This is the check that catches the omission. 2. **Is the first level the load you meant to start at?** If it is zero, the run opens with a dead window as long as every other level, and the report's leading flat stretch is the only evidence. 3. **Does the level duration mean what you think?** A bare number is seconds, so `.eachLevelLasting(10)` is ten seconds and a sixteen-minute plan finishes in eighty. A habit that removes the first two entirely: name the intended peak as a constant, set the increment to `peak / levels`, and pass that same value to `startingFrom`. The top level then lands exactly on the peak by construction, and a reviewer checks one number instead of reconstructing eight. ## How you would catch it after the fact The staircase you actually produced is visible in the run's own output, where the injection rate is plotted over time: a leading flat stretch at zero and a top step one increment below the number you were aiming for are both readable there. Confirming that the declared shape is the shape you meant is part of reading any injection profile, and it costs one glance — but it costs it after the run, which is why the three checks above are worth doing before.
- Does the omitted `startingFrom` make the run shorter?No, and that is what hides the mistake. The level count and the level duration alone decide the span, so eight levels of two minutes is sixteen minutes whether the first level is at zero or at twenty arrivals per second. Only the injected user count and the peak rate change.
- How do you make the top level land exactly on a peak you have been given?Derive the increment from the peak: with a target peak `P` over `L` levels, use an increment of `P / L` and a `startingFrom` of the same value. The last level is then `(L - 1) * (P / L) + P / L`, which is exactly `P`, and the arithmetic is checkable without running the simulation.
- Does the closed-model builder behave the same way?Yes. `incrementConcurrentUsers` uses the identical level formula, so omitting `startingFrom` gives a first level of zero concurrent users held for the full level duration, and a top level of `(levels - 1) * increment`. Only the argument type differs: `Int` rather than `Double`.
saying these in an interview costs you the question
- Assuming an omitted start just shifts the profile up
- Expecting the peak to be levels times increment
- Believing a zero-rate level is skipped rather than held
- Trusting run length as proof the profile is right