Simulation
Learn about the main parts of a Gatling simulation: DSL imports, scenario definitions, simulation definitions, and hooks.
Simulation
is the parent class (function in JavaScript|TypeScript) your tests must extend so Gatling can launch them.
Test
.
Some tools such as Maven Surefire aggressively consider that all classes with such naming patterns are for them to handle, and they will try to launch them.DSL imports
The Gatling DSL requires some imports:
// required for Gatling core structure DSL
import io.gatling.javaapi.core.*;
import static io.gatling.javaapi.core.CoreDsl.*;
// required for Gatling HTTP DSL
import io.gatling.javaapi.http.*;
import static io.gatling.javaapi.http.HttpDsl.*;
// can be omitted if you don't use jdbcFeeder
import io.gatling.javaapi.jdbc.*;
import static io.gatling.javaapi.jdbc.JdbcDsl.*;
// used for specifying durations with a unit, eg Duration.ofMinutes(5)
import java.time.Duration;
// required for Gatling core structure DSL
import { scenario, simulation } from "@gatling.io/core";
// required for Gatling HTTP DSL
import { http } from "@gatling.io/http";
// required for Gatling core structure DSL
import io.gatling.javaapi.core.*
import io.gatling.javaapi.core.CoreDsl.*
// required for Gatling HTTP DSL
import io.gatling.javaapi.http.*
import io.gatling.javaapi.http.HttpDsl.*
// can be omitted if you don't use jdbcFeeder
import io.gatling.javaapi.jdbc.*
import io.gatling.javaapi.jdbc.JdbcDsl.*
// used for specifying durations with a unit, eg Duration.ofMinutes(5)
import java.time.Duration
// required for Gatling core structure DSL
import io.gatling.core.Predef._
// required for Gatling HTTP DSL
import io.gatling.http.Predef._
// can be omitted if you don't use jdbcFeeder
import io.gatling.jdbc.Predef._
// used for specifying durations with a unit, eg "5 minutes"
import scala.concurrent.duration._
setUp
Most pieces of your tests can possibly be extracted into other helper classes (helper functions in JavaScript|TypeScript) so you can bring your own test libraries: scenarios, protocols, headers, injection profiles, etc.
The only mandatory piece in your Simulations is that they must call the setUp
method exactly once in their constructor to register the test components.
ScenarioBuilder scn = scenario("scn"); // etc...
setUp(
scn.injectOpen(atOnceUsers(1))
);
export default simulation((setUp) => {
const scn = scenario("scn"); // etc...
setUp(
scn.injectOpen(atOnceUsers(1))
);
});
val scn = scenario("scn") // etc...
setUp(
scn.injectOpen(atOnceUsers(1))
)
val scn = scenario("scn") // etc...
setUp(
scn.inject(atOnceUsers(1))
)
which correspond to injecting one single user into the scn
scenario.
It’s possible to have multiple populations, ie scenarios with an associated injection profile, in the same simulation:
ScenarioBuilder scn1 = scenario("scn1"); // etc...
ScenarioBuilder scn2 = scenario("scn2"); // etc...
setUp(
scn1.injectOpen(atOnceUsers(1)),
scn2.injectOpen(atOnceUsers(1))
);
const scn1 = scenario("scn1"); // etc...
const scn2 = scenario("scn2"); // etc...
setUp(
scn1.injectOpen(atOnceUsers(1)),
scn2.injectOpen(atOnceUsers(1))
);
val scn1 = scenario("scn1") // etc...
val scn2 = scenario("scn2") // etc...
setUp(
scn1.injectOpen(atOnceUsers(1)),
scn2.injectOpen(atOnceUsers(1))
)
val scn1 = scenario("scn1") // etc...
val scn2 = scenario("scn2") // etc...
setUp(
scn1.inject(atOnceUsers(1)),
scn2.inject(atOnceUsers(1))
);
For more information regarding scenarios, see the dedicated section here.
For more information regarding injection profiles, see the dedicated section here.
Protocols configuration
Protocols configurations can be attached
- either on the setUp, in which case they are applied on all the populations
- or on each population, so they can have different configurations
// HttpProtocol configured globally
setUp(
scn1.injectOpen(atOnceUsers(1)),
scn2.injectOpen(atOnceUsers(1))
).protocols(httpProtocol);
// different HttpProtocols configured on each population
setUp(
scn1.injectOpen(atOnceUsers(1))
.protocols(httpProtocol1),
scn2.injectOpen(atOnceUsers(1))
.protocols(httpProtocol2)
);
// HttpProtocol configured globally
setUp(
scn1.injectOpen(atOnceUsers(1)),
scn2.injectOpen(atOnceUsers(1))
).protocols(httpProtocol);
// different HttpProtocols configured on each population
setUp(
scn1.injectOpen(atOnceUsers(1))
.protocols(httpProtocol1),
scn2.injectOpen(atOnceUsers(1))
.protocols(httpProtocol2)
);
// HttpProtocol configured globally
setUp(
scn1.injectOpen(atOnceUsers(1)),
scn2.injectOpen(atOnceUsers(1))
).protocols(httpProtocol)
// different HttpProtocols configured on each population
setUp(
scn1.injectOpen(atOnceUsers(1))
.protocols(httpProtocol1),
scn2.injectOpen(atOnceUsers(1))
.protocols(httpProtocol2)
)
// HttpProtocol configured globally
setUp(
scn1.inject(atOnceUsers(1)),
scn2.inject(atOnceUsers(1))
).protocols(httpProtocol)
// different HttpProtocols configured on each population
setUp(
scn1.inject(atOnceUsers(1))
.protocols(httpProtocol1),
scn2.inject(atOnceUsers(1))
.protocols(httpProtocol2)
)
For more information regarding protocols configurations, see the HttpProtocol section here.
Acceptance criteria
Assertions are configured on the setUp.
setUp(scn.injectOpen(atOnceUsers(1)))
.assertions(global().failedRequests().count().is(0L));
setUp(scn.injectOpen(atOnceUsers(1)))
.assertions(global().failedRequests().count().is(0));
setUp(scn.injectOpen(atOnceUsers(1)))
.assertions(global().failedRequests().count().shouldBe(0L))
setUp(scn.inject(atOnceUsers(1)))
.assertions(global.failedRequests.count.is(0))
For more information regarding assertions, see the dedicated section here.
Global pause configuration
The pauses can be configured on Simulation
with a bunch of methods:
// pause configuration configured globally
setUp(scn.injectOpen(atOnceUsers(1)))
// disable the pauses for the simulation
.disablePauses()
// the duration of each pause is what's specified
// in the `pause(duration)` element.
.constantPauses()
// make pauses follow a uniform distribution
// where the mean is the value specified in the `pause(duration)` element.
.uniformPauses(0.5)
.uniformPauses(Duration.ofSeconds(2))
// make pauses follow a normal distribution
// where the mean is the value specified in the `pause(duration)` element.
// and the standard deviation is the duration configured here.
.normalPausesWithStdDevDuration(Duration.ofSeconds(2))
// make pauses follow a normal distribution
// where the mean is the value specified in the `pause(duration)` element.
// and the standard deviation is a percentage of the mean.
.normalPausesWithPercentageDuration(20)
// make pauses follow an exponential distribution
// where the mean is the value specified in the `pause(duration)` element.
.exponentialPauses()
// the pause duration is computed by the provided function (in milliseconds).
// In this case the filled duration is bypassed.
.customPauses(session -> 5L);
// different pause configurations configured on each population
setUp(
scn1.injectOpen(atOnceUsers(1))
.disablePauses(),
scn2.injectOpen(atOnceUsers(1))
.exponentialPauses()
);
// pause configuration configured globally
setUp(scn.injectOpen(atOnceUsers(1)))
// disable the pauses for the simulation
.disablePauses()
// the duration of each pause is what's specified
// in the `pause(duration)` element.
.constantPauses()
// make pauses follow a uniform distribution
// where the mean is the value specified in the `pause(duration)` element.
.uniformPauses(0.5)
.uniformPauses(Duration.ofSeconds(2))
// make pauses follow a normal distribution
// where the mean is the value specified in the `pause(duration)` element.
// and the standard deviation is the duration configured here.
.normalPausesWithStdDevDuration(Duration.ofSeconds(2))
// make pauses follow a normal distribution
// where the mean is the value specified in the `pause(duration)` element.
// and the standard deviation is a percentage of the mean.
.normalPausesWithPercentageDuration(20)
// make pauses follow an exponential distribution
// where the mean is the value specified in the `pause(duration)` element.
.exponentialPauses()
// the pause duration is computed by the provided function (in milliseconds).
// In this case the filled duration is bypassed.
.customPauses((session) => 5);
// different pause configurations configured on each population
setUp(
scn1.injectOpen(atOnceUsers(1))
.disablePauses(),
scn2.injectOpen(atOnceUsers(1))
.exponentialPauses()
);
// pause configuration configured globally
setUp(scn.injectOpen(atOnceUsers(1)))
// disable the pauses for the simulation
.disablePauses()
// the duration of each pause is what's specified
// in the `pause(duration)` element.
.constantPauses()
// make pauses follow a uniform distribution
// where the mean is the value specified in the `pause(duration)` element.
.uniformPauses(0.5)
.uniformPauses(Duration.ofSeconds(2))
// make pauses follow a normal distribution
// where the mean is the value specified in the `pause(duration)` element.
// and the standard deviation is the duration configured here.
.normalPausesWithStdDevDuration(Duration.ofSeconds(2))
// make pauses follow a normal distribution
// where the mean is the value specified in the `pause(duration)` element.
// and the standard deviation is a percentage of the mean.
.normalPausesWithPercentageDuration(20.0)
// make pauses follow an exponential distribution
// where the mean is the value specified in the `pause(duration)` element.
.exponentialPauses()
// the pause duration is computed by the provided function (in milliseconds).
// In this case the filled duration is bypassed.
.customPauses { session -> 5L }
// different pause configurations configured on each population
setUp(
scn1.injectOpen(atOnceUsers(1))
.disablePauses(),
scn2.injectOpen(atOnceUsers(1))
.exponentialPauses()
)
// pause configuration configured globally
setUp(scn.inject(atOnceUsers(1)))
// disable the pauses for the simulation
.disablePauses
// the duration of each pause is what's specified
// in the `pause(duration)` element.
.constantPauses
// make pauses follow a uniform distribution
// where the mean is the value specified in the `pause(duration)` element.
.uniformPauses(0.5)
.uniformPauses(2.seconds)
// make pauses follow a normal distribution
// where the mean is the value specified in the `pause(duration)` element.
// and the standard deviation is the duration configured here.
.normalPausesWithStdDevDuration(2.seconds)
// make pauses follow a normal distribution
// where the mean is the value specified in the `pause(duration)` element.
// and the standard deviation is a percentage of the mean.
.normalPausesWithPercentageDuration(20.0)
// make pauses follow an exponential distribution
// where the mean is the value specified in the `pause(duration)` element.
.exponentialPauses
// the pause duration is computed by the provided function (in milliseconds).
// In this case the filled duration is bypassed.
.customPauses(session => 5L)
// different pause configurations configured on each population
setUp(
scn1.inject(atOnceUsers(1))
.disablePauses,
scn2.inject(atOnceUsers(1))
.exponentialPauses
)
Shaping throughput
Some users might want to reason in terms of throughput/requests per second instead of virtual users.
If your virtual users perform only one request each, you should use an open workload model for this, such as constantUsersPerSec
.
Otherwise, in our opinion, you’re going to run into trouble because you won’t have any means of controlling which request gets executed.
Still, you can try and use the throttle
method, that can be defined either globally or per scenario.
What throttle
do is that it:
- disables all the pauses
- caps your throughput. It can’t generate a throughput that’s higher than the one normally generated by your simulation once pauses are disabled.
Throttling is currently only supported for HTTP requests and JMS.
- Throttling should only be used with single request scenarios. Otherwise, distribution between multiple requests is likely to be unbalanced.
- Beware that all excess traffic gets pushed into an unbounded queue, possibly resulting in an OutOfMemoryError if your normal throughput is way higher than the normal one.
- Gatling will automatically interrupt your test at the end of the throttle, just like it does with
maxDuration
.
// throttling profile configured globally
setUp(scn.injectOpen(constantUsersPerSec(100).during(Duration.ofMinutes(30))))
.throttle(
reachRps(100).in(10),
holdFor(Duration.ofMinutes(1)),
jumpToRps(50),
holdFor(Duration.ofHours(2))
);
// different throttling profiles configured globally
setUp(
scn1.injectOpen(atOnceUsers(1))
.throttle(reachRps(100).in(10)),
scn2.injectOpen(atOnceUsers(1))
.throttle(reachRps(20).in(10))
);
// throttling profile configured globally
setUp(scn.injectOpen(constantUsersPerSec(100).during(Duration.ofMinutes(30))))
.throttle(
reachRps(100).in(10),
holdFor(Duration.ofMinutes(1)),
jumpToRps(50),
holdFor(Duration.ofHours(2))
);
// different throttling profiles configured globally
setUp(
scn1.injectOpen(atOnceUsers(1))
.throttle(reachRps(100).in(10)),
scn2.injectOpen(atOnceUsers(1))
.throttle(reachRps(20).in(10))
);
// throttling profile configured globally
setUp(scn.injectOpen(constantUsersPerSec(100.0).during(Duration.ofMinutes(30))))
.throttle(
reachRps(100).during(10),
holdFor(Duration.ofMinutes(1)),
jumpToRps(50),
holdFor(Duration.ofHours(2))
)
// different throttling profiles configured globally
setUp(
scn1.injectOpen(atOnceUsers(1))
.throttle(reachRps(100).during(10)),
scn2.injectOpen(atOnceUsers(1))
.throttle(reachRps(20).during(10))
)
// throttling profile configured globally
setUp(scn.inject(constantUsersPerSec(100).during(30.minutes)))
.throttle(
reachRps(100).in(10),
holdFor(1.minute),
jumpToRps(50),
holdFor(2.hours)
)
// different throttling profiles configured globally
setUp(
scn1.inject(atOnceUsers(1))
.throttle(reachRps(100).in(10)),
scn2.inject(atOnceUsers(1))
.throttle(reachRps(20).in(10))
)
This simulation will reach 100 req/s with a ramp of 10 seconds, then hold this throughput for 1 minute, jump to 50 req/s and finally hold this throughput for 2 hours.
The building blocks for throttling are:
reachRps(target).in(duration)
: target a throughput with a ramp over a given duration.jumpToRps(target)
: jump immediately to a given targeted throughput.holdFor(duration)
: hold the current throughput for a given duration.
in
is a reserved keyword in Kotlin.
You can either protect it with backticks `in`
or use the during
alias instead.Maximum duration
Finally, with maxDuration
you can force your run to terminate based on a duration limit, even if some virtual users are still running.
It is useful if you need to bound the duration of your simulation when you can’t predict it.
setUp(scn.injectOpen(rampUsers(1000).during(Duration.ofMinutes(20))))
.maxDuration(Duration.ofMinutes(10));
setUp(scn.injectOpen(rampUsers(1000).during({ amount: 20, unit: "minutes" })))
.maxDuration({ amount: 10, unit: "minutes" });
setUp(scn.injectOpen(rampUsers(1000).during(Duration.ofMinutes(20))))
.maxDuration(Duration.ofMinutes(10))
setUp(scn.inject(rampUsers(1000).during(20.minutes)))
.maxDuration(10.minutes)
Hooks
Gatling provides two hooks:
before
for executing some arbitrary code before the simulation actually runsafter
for executing some arbitrary code after the simulation actually runs
The lifecycle is as follows:
- Gatling starts
- Simulation constructor is called, and all the code in the class body that is not delayed in the
before
andafter
hooks is executed. before
hook is executed- Simulation runs
- Simulation terminates
after
hook is executed- HTML reports are generated if enabled
- Gatling shuts down
@Override
public void before() {
System.out.println("Simulation is about to start!");
}
@Override
public void after() {
System.out.println("Simulation is finished!");
}
override fun before() {
println("Simulation is about to start!")
}
override fun after() {
println("Simulation is finished!")
}
before {
println("Simulation is about to start!")
}
after {
println("Simulation is finished!")
}
Deployment information (Gatling Enterprise only)
When running tests on Gatling Enterprise, you might be interested in some information about the Load Generator running the test.
deploymentInfo
is only effective when running with Gatling Enterprise, otherwise it’s just a noop.// the UUID of the run
// null when not deploying on Gatling Enterprise
UUID runId = deploymentInfo.runId;
// the name of the Location where this Load Generator is deployed
// null when not deploying on Gatling Enterprise
String locationName = deploymentInfo.locationName;
// the number of Load Generators deployed on this Location in this run
// 1 when not deploying on Gatling Enterprise
int numberOfLoadGeneratorsInLocation = deploymentInfo.numberOfLoadGeneratorsInLocation;
// the index of this Load Generators within the Load Generators deployed on this Location in this run
// 0 when not deploying on Gatling Enterprise
int indexOfLoadGeneratorInLocation = deploymentInfo.indexOfLoadGeneratorInLocation;
// the total number of Load Generators deployed in this run
// 1 when not deploying on Gatling Enterprise
int numberOfLoadGeneratorsInRun = deploymentInfo.numberOfLoadGeneratorsInRun;
// the index of this Load Generators within the total number of Load Generators deployed in this run
// 0 when not deploying on Gatling Enterprise
int indexOfLoadGeneratorInRun = deploymentInfo.indexOfLoadGeneratorInRun;
// the UUID of the run
// null when not deploying on Gatling Enterprise
val runId = deploymentInfo.runId
// the name of the Location where this Load Generator is deployed
// null when not deploying on Gatling Enterprise
val locationName = deploymentInfo.locationName
// the number of Load Generators deployed on this Location in this run
// 1 when not deploying on Gatling Enterprise
val numberOfLoadGeneratorsInLocation = deploymentInfo.numberOfLoadGeneratorsInLocation
// the index of this Load Generators within the Load Generators deployed on this Location in this run
// 0 when not deploying on Gatling Enterprise
val indexOfLoadGeneratorInLocation = deploymentInfo.indexOfLoadGeneratorInLocation
// the total number of Load Generators deployed in this run
// 1 when not deploying on Gatling Enterprise
val numberOfLoadGeneratorsInRun = deploymentInfo.numberOfLoadGeneratorsInRun
// the index of this Load Generators within the total number of Load Generators deployed in this run
// 0 when not deploying on Gatling Enterprise
val indexOfLoadGeneratorInRun = deploymentInfo.indexOfLoadGeneratorInRun
// the UUID of the run
// null when not deploying on Gatling Enterprise
val runId = deploymentInfo.runId
// the name of the Location where this Load Generator is deployed
// None when not deploying on Gatling Enterprise
val locationName = deploymentInfo.locationName
// the number of Load Generators deployed on this Location in this run
// 1 when not deploying on Gatling Enterprise
val numberOfLoadGeneratorsInLocation = deploymentInfo.numberOfLoadGeneratorsInLocation
// the index of this Load Generators within the Load Generators deployed on this Location in this run
// 0 when not deploying on Gatling Enterprise
val indexOfLoadGeneratorInLocation = deploymentInfo.indexOfLoadGeneratorInLocation
// the total number of Load Generators deployed in this run
// 1 when not deploying on Gatling Enterprise
val numberOfLoadGeneratorsInRun = deploymentInfo.numberOfLoadGeneratorsInRun
// the index of this Load Generators within the total number of Load Generators deployed in this run
// 0 when not deploying on Gatling Enterprise
val indexOfLoadGeneratorInRun = deploymentInfo.indexOfLoadGeneratorInRun
}