In Gatling's open injection model, what do constantUsersPerSec(20).during(15) and rampUsersPerSec(10).to(20).during(10) each declare, and how many virtual users does each one start?
answer
- A rate per second, not a total
- Flat hold, or a straight line
- Ramp head-count uses the mean rate
- Rates are doubles; fractions are legal
basics
~10 sBoth declare an arrival rate in users per second, not a headcount. constantUsersPerSec(20).during(15) starts 300 users, 20 every second. rampUsersPerSec(10).to(20).during(10) climbs linearly from 10 to 20 a second, starting 150.
solid answer
~40 sBoth are open-model injection steps, and both name a **rate** rather than a stock of users. `constantUsersPerSec(rate).during(d)` holds one flat arrival rate for the window, so `constantUsersPerSec(20).during(15)` starts 20 users in each of 15 seconds, 300 in all. `rampUsersPerSec(r1).to(r2).during(d)` moves the arrival rate linearly from `r1` to `r2` across the window, so its head-count is the *mean* of the two rates times the duration: `rampUsersPerSec(10).to(20).during(10)` averages 15 a second and starts 150. The rate parameter is a `double` in every SDK, so fractional rates are legal; the head-count Gatling derives from one is always a whole number. You pass these steps to `injectOpen(...)` in Java, Kotlin, JavaScript and TypeScript, and to `inject(...)` in Scala.
code
java · 24 linesimport io.gatling.javaapi.core.*;
import io.gatling.javaapi.http.*;
import static io.gatling.javaapi.core.CoreDsl.*;
import static io.gatling.javaapi.http.HttpDsl.*;
import java.time.Duration;
public class RateStepsSimulation extends Simulation {
private final HttpProtocolBuilder httpProtocol = http.baseUrl("https://example.com");
private final ScenarioBuilder scn = scenario("rate steps")
.exec(http("home").get("/"));
{
setUp(
scn.injectOpen(
constantUsersPerSec(20).during(15),
rampUsersPerSec(10).to(20).during(Duration.ofSeconds(10))
)
).protocols(httpProtocol);
}
}go deeper
Be ready to say out loud that the argument is an arrival rate per second, and to multiply it by the window to get the user count.
Be ready to derive a ramp's head-count from the mean of its two rates, and to explain why the target rate alone doubles the answer on a ramp from zero.
Be ready to review a colleague's profile and catch a bare duration read as minutes, a fractional rate written as an Int in Kotlin, or a ramp sized off one endpoint.
Be ready to say which parts of a profile should be expressed as a rate at all, and to defend that choice when someone proposes restating it as a count.
## Two step families, and this is the rate one Gatling's open injection steps split into two families. One names a **stock of users** and spreads it over a window. The other names an **arrival rate** in users per second and holds or moves it. `constantUsersPerSec` and `rampUsersPerSec` are the rate family, and they are the only two open steps whose first argument is a rate. Both are ordinary injection steps: you list them, in order, as arguments to a single registration call. In Java, Kotlin, JavaScript and TypeScript that call is `scn.injectOpen(...)`; in Scala it is `scn.inject(...)`. The step *names* are identical in all five languages — only the entry point differs. ## `constantUsersPerSec(rate).during(d)` This declares a flat arrival rate held for the whole window. `constantUsersPerSec(20).during(15)` says: start 20 new virtual users every second, for 15 seconds. The head-count follows directly. Gatling multiplies the rate by the window in seconds and rounds to a whole number of users: * `constantUsersPerSec(20).during(15)` → 20 × 15 = **300 users** * `constantUsersPerSec(100).during(Duration.ofMinutes(10))` → 100 × 600 = **60,000 users** * `constantUsersPerSec(1).during(5)` → **5 users** Note what the rate does *not* say. It says how many users **start** each second. It says nothing about how many are still running at any moment — that depends entirely on how long your scenario takes, and it is not something the step declares. ## `rampUsersPerSec(r1).to(r2).during(d)` This declares a straight line between two arrival rates. `rampUsersPerSec(10).to(20).during(10)` says: begin at 10 arrivals a second, finish at 20 arrivals a second, and move between them linearly over 10 seconds. The head-count is the **mean** of the two rates times the duration — the area under a straight line, not the sum of its endpoints: | step | mean rate | window | users started | |---|---|---|---| | `rampUsersPerSec(10).to(20).during(10)` | 15/s | 10 s | 150 | | `rampUsersPerSec(2).to(4).during(10)` | 3/s | 10 s | 30 | | `rampUsersPerSec(0).to(100).during(60)` | 50/s | 60 s | 3,000 | | `rampUsersPerSec(10).to(100).during(300)` | 55/s | 300 s | 16,500 | The common mistake is to reach for one endpoint. Using the target rate overstates the count by a factor of two on a ramp from zero; using the start rate understates it just as badly; adding the two rates together doubles it. A ramp whose two rates are equal degenerates to a flat hold. `rampUsersPerSec(100).to(100).during(d)` produces the same even schedule as `constantUsersPerSec(100).during(d)`; Gatling's own unit tests assert exactly that, that a zero-acceleration ramp yields a single repeated interval. ## Fractional rates are legal The rate parameter is a `double`, and Gatling's reference states plainly that rates may be expressed as fractional values. `constantUsersPerSec(0.5).during(Duration.ofMinutes(1))` is a valid way to say *one arrival every two seconds*. The head-count is still a whole number: Gatling rounds `rate × durationInSeconds`. Gatling's own test suite asserts that a rate of `0.4978` held for 100 seconds resolves to 50 users. One language-level consequence: because the parameter is a `double` and Kotlin performs no implicit widening from `Int`, a Kotlin simulation must write `constantUsersPerSec(20.0)` and `rampUsersPerSec(10.0).to(20.0)`. Java, Scala, JavaScript and TypeScript all accept a bare `20`. This is exactly why Gatling's own Kotlin sample carries `.0` on the rate steps but not on the count-based ones. ## Spelling the duration The duration argument differs by SDK even though the step names do not: 1. **Java and Kotlin** take a `java.time.Duration` — `Duration.ofSeconds(15)`, `Duration.ofMinutes(10)`. 2. **Scala** takes a `scala.concurrent.duration` literal — `15.seconds`, `10.minutes`. 3. **JavaScript and TypeScript** take an object literal — `{ amount: 10, unit: "minutes" }`. 4. **All four** also accept a **bare number, which means seconds**. `during(15)` is fifteen seconds everywhere. That last point is the one that bites: `during(10)` is ten *seconds*, not ten minutes, in every SDK. ## Getting it right in review When you read one of these steps, do three things. Read the first argument as a rate per second, never as a total. For a ramp, average the two rates before multiplying. And check the duration unit — a bare number is seconds, so a profile that looks like a ten-minute soak may be a ten-second blip. ## A worked review Take a profile someone submits as *"a gentle warm-up, then a short soak"*: ```java setUp( scn.injectOpen( rampUsersPerSec(0).to(50).during(Duration.ofMinutes(2)), constantUsersPerSec(50).during(120) ) ).protocols(httpProtocol); ``` Work it out step by step. The ramp runs 120 seconds at a mean rate of 25 a second, so it starts **3,000** users — not the 6,000 you get if you reach for the 50. The hold is `during(120)`, a bare number, so it is 120 **seconds**, not 120 minutes: 50 a second for two minutes is another **6,000** users, and the whole "soak" is four minutes long. Both numbers are correct Gatling; neither is what the covering note claimed. That is the value of being able to do this arithmetic in your head during a review.
- Where do these steps sit relative to each other if you list both in one call?They run in the order written, and every step in a single `injectOpen` or `inject` call must belong to the same model kind — you cannot mix these open-model rate steps with the closed-model concurrent-user steps in one call.
- Does `constantUsersPerSec(20)` mean twenty users are running at once?No. It means twenty new virtual users are *started* each second. How many are running concurrently depends on how long one pass through your scenario takes, and the step does not declare it.
- What is the head-count of `rampUsersPerSec(0).to(100).during(Duration.ofMinutes(1))`?3,000. The mean of 0 and 100 is 50 arrivals a second, held over 60 seconds. Reading the target rate instead would give 6,000 — double the truth.
A turnstile counter set to admit twenty people a minute is not the same as a ticket for twenty people. The rate steps set the turnstile; how crowded the hall gets depends on how long each visitor stays.
saying these in an interview costs you the question
- Reading the first argument as a total user count rather than a rate
- Summing a ramp's two rates instead of averaging them
- Multiplying a ramp by its target rate to get the head-count
- Assuming the rate argument has to be a whole number
- Treating a bare during(10) as ten minutes rather than ten seconds