Load Testing is a crucial part of any application development cycle, ensuring that your systems can handle the expected load and function correctly under stress. Gatling is one of the most popular tools for load testing, thanks to its rich features and easy-to-use DSL (domain-specific language). Among these features, pauses and error handling are essential for making tests more realistic and resilient, allowing you to simulate real-world usage patterns and account for unexpected failures.
In this blog post, we’ll discuss how to use pauses and implement error handling in Gatling to ensure your
performance tests run smoothly and reflect real-world scenarios. The complete code is available at the GitHub repository.
Pauses in Gatling: Simulating Real-World Behavior
In load testing, pauses are used to simulate
realistic user interaction patterns. Without pauses, tests would execute every
action as fast as possible, which doesn’t reflect how real users behave. For
example, after clicking a button, a real user might wait a few seconds to read
the results, but a performance test would immediately perform the next action
unless a pause is introduced.
Why Use Pauses?
- Simulating User
Interaction: Real users rarely execute requests with no delay. Introducing
pauses between actions creates more natural traffic and helps simulate a realistic load on your server.
- Avoiding
Overload: Pauses help ensure that the test load doesn’t overwhelm the
system too quickly. It’s also useful for controlling the frequency of
requests to match expected real-world conditions.
Types of Pauses in Gatling
In Gatling, there are several ways to introduce pauses between requests. The most commonly used ones are:
a) pause
When a user views a page, they typically take time to read the content before deciding to click on another link. To replicate this behavior in a test, the pause method is used. Pauses can be fixed or random. This pause is used at the scenario level.
Global Pause Configuration:
Gatling Provides different types of pause behaviors globally
and at a population level.Some of them are
●
disablePauses():
Disables any pauses for the entire simulation, making requests happen without
delays.
●
constantPauses():
Applies a constant pause duration specified in the pause(duration)
element. All virtual users will pause for the exact same amount of time.
●
uniformPauses():
Pauses are distributed uniformly, with the mean duration being the value
specified in the pause(duration)
element. For example, if you set uniformPauses(0.5), the
pause duration will vary uniformly around the value of 0.5 seconds.
●
uniformPauses(Duration.ofSeconds(2)): Here,
a pause is distributed uniformly with a mean of 2 seconds.
●
exponentialPauses():
Pauses follow an exponential distribution. This kind of distribution is often
used to model the "think time" behavior of users who might take
longer to interact after an initial request, simulating a more natural delay
between actions.
Global pause configurations override the the
pause defined at scenario level. For example in your scenario if you have added
a fixed pause at multiple places and you are using disablePauses() in setup
then all the pauses defined in the scenario will be disabled. But if you have a
scenario where you have passed the optional parameter PauseType while defining
the pause in scenario then it will not be overridden by global configuration.
ScenarioBuilder fixedPause = scenario("Fixed pause")
.exec(carsClient.getCarByBrand())
.pause(3, PauseType.Constant)
.exec(carsClient.getCarById("1"));
{
setUp(
pace.injectOpen(constantUsersPerSec(1 * multiplier).during(1)
).protocols(httpProtocol).disablePauses()
);
}
In the given scenario, the first pause is a fixed pause of 3 seconds, and the second pause is a random pause with a range of 2 to 10 seconds. The global configuration disables all pauses using disablePauses(). However, because the first pause is explicitly defined as a fixed pause (with a constant duration of 3 seconds), it will still be applied, overriding the global pause disabling. The second pause, defined with a random range, will be disabled due to the global disablePauses() setting.
b) pace
pace is a wait time added at the iteration level. Pace adjusts the wait time depending on the last time a given virtual user passed here. Let's understand it using a code example.
ScenarioBuilder pace = scenario("pace")
.exec(repeat(3)
.on(pace(7)
.exec(carsClient.getCarByBrand("tata"))
.pause(2)
.exec(carsClient.getCarById("1"))));
In the given scenario, the first iteration will include a 5-second pause. The second and third iterations will each take 7 seconds, including the 5-second pause defined in the scenario. If a random pause (ranging from 1 to 5 seconds) had been used instead of the fixed 5-second pause, the total duration of the second and third iterations would still be 7 seconds. This is because the pace (7 seconds) is fixed, and would be adjusted to ensure the total duration remains constant.
c) renedezVous
The concept of "Rendezvous" is used
to simulate a scenario where a certain number of users should arrive at a
specific point in time, rather than just being injected gradually or all at
once. This allows you to model scenarios where users must "meet" at a
certain stage (like entering a checkout page at the same time or joining a live
event).
The rendezvous
approach controls the timing of virtual users in a way that ensures that a
fixed number of users arrive at a specific point at roughly the same time.
ScenarioBuilder renedezVous = scenario("renedezVous")
.exec(carsClient.getCarByBrand())
.rendezVous(10)
.exec(carsClient.getCarById("1"));
Error Handling in Gatling: Making Tests Resilient
No test is complete without proper error handling, and performance tests are no exception. In real-world applications, errors and failures happen all the time. If your performance test fails unexpectedly due to an unhandled error, you might miss important insights about your system’s reliability under load.
Why is Error Handling Important?
- Test Robustness: Not every request in a load test will succeed. Systems often exhibit
transient failures that need to be handled gracefully to ensure your test
continues without abrupt stops.
- Identifying
Issues: Handling errors can help you isolate specific points of failure,
be it network issues, timeouts, or unexpected server errors, and allow you
to analyze them separately.
- Custom
Responses: Instead of letting a failure cause a test crash, you can
customize how failures are logged or reported, helping you gather more
detailed insights.
In Gatling, you can configure various error handling strategies to manage and capture failures effectively during your tests.
a) tryMax()
The tryMax() function is used to attempt a given action a specified number of times until it succeeds. If the action fails within the specified number of attempts, Gatling will retry the action. It’s particularly useful for scenarios where temporary failures might occur (e.g., transient network issues or server unavailability), and you want to give the action multiple chances before moving to he next step. All requests performed in failing iterations will be logged, including the failing one.
ScenarioBuilder tryMax = scenario("try max")
.tryMax(5).on(exec(carsClient.getCarById("1"))
.exec(carsClient.getCarByBrand("maruti")))
.exec(carsClient.getCarByBrand("tata"));
In the given scenario, Gatling will attempt to execute the carsClient.getCarById("1") action up to 5 times if it fails before moving on to the carsClient.getCarByBrand("maruti") action. If carsClient.getCarById("1") fails 5 times, Gatling will record 5 failed attempts and exit the tryMax block. Similarly, Gatling will try to execute carsClient.getCarByBrand("maruti") up to 5 times before leaving the tryMax block. After all retry attempts for both actions are exhausted, Gatling will proceed with the next action, carsClient.getCarByBrand("tata").
b) exitBlockOnFail()
The exitBlockOnFail function is similar to tryMax but there will be no retries.
ScenarioBuilder exitBlockOnFail = scenario("Exit block on fail")
.exitBlockOnFail().on(exec(carsClient.getCarById("1"))
.exec(carsClient.getCarByBrand("maruti")))
.exec(carsClient.getCarByBrand("tata"));
Here if carsClient.getCarById("1") failed, Gatling will exit the block without any retries and will proceed with the next action, carsClient.getCarByBrand("tata").
The exitHere() function halts the scenario at the point where it is invoked, preventing any further actions from being executed, regardless of whether previous actions succeed or fail.
ScenarioBuilder exitHere = scenario("exit here")
.exec(carsClient.getCarById("1"))
.exec(carsClient.getCarByBrand("maruti"))
.exitHere()
.exec(carsClient.getCarByBrand("tata"));
The exitHere() function is used to stop the execution of the scenario after the
second request carsClient.getCarByBrand("maruti").
d) exitHereIf()
The exitHereIf() function is used to stop the execution of the scenario if the condition equates to true.
ScenarioBuilder exitHereIf = scenario("exit here if")
.exec(carsClient.getCarById("1"))
.exitHereIf(session -> session.getString("brand").equals("Tata"))
.exec(carsClient.getCarByBrand("tata"));
e) exitHereIfFailed()
Make the user exit the
scenario from this point if it previously had an error.
ScenarioBuilder exitHereIfFailed = scenario("exit here if failed")
.exec(carsClient.getCarById("1"))
.exec(carsClient.getCarByBrand("tata"))
.exitHereIfFailed()
.exec(carsClient.getCarById("1"));
f) stopLoadGenerator and crashLoadGenerator
stopLoadGenerator the user abruptly stops the load generator with a successful status
crashLoadGenerator the user abruptly stops the load generator with a failed status
ScenarioBuilder stopLoadGenerator = scenario("stop load generator")
.exec(carsClient.getCarById("1"))
.stopLoadGenerator("stop the load generator on demand")
.exec(carsClient.getCarByBrand("tata"));
ScenarioBuilder crashLoadGenerator = scenario("crash load generator")
.exec(carsClient.getCarById("1"))
.crashLoadGenerator("crash the load generator on demand")
.exec(carsClient.getCarByBrand("tata"));
f) stopLoadGeneratorIf and crashLoadGeneratorIf
stopLoadGeneratorIf the user abruptly stops the load generator with a successful status if a condition is met.
crashLoadGeneratorIf the user abruptly stops the load generator with a failed status if a condition is met.
ScenarioBuilder stopLoadGeneratorIf = scenario("stop load generator if")
.exec(carsClient.getCarById("1"))
.stopLoadGeneratorIf("stop the load generator forcefully",
session -> session.getString("brand").equals("Tata"))
.exec(carsClient.getCarByBrand("tata"));
ScenarioBuilder crashLoadGeneratorIf = scenario("crash load generator if")
.exec(carsClient.getCarById("1"))
.crashLoadGeneratorIf("crash the load generator forcefully",
session -> session.getString("brand").equals("Tata") )
.exec(carsClient.getCarByBrand("tata"));