Gatling: Getting Started With Simulation Scripts
Gatling is a load testing tool for measuring the performance of web applications. As such, it supports the following protocols:
- HTTP,
- WebSockets,
- Server-sent events.
Other protocols are also supported either by Gatling itself (like JMS) or by community plugins.
Gatling load testing scenarios are defined in code, more specifically using a specific DSL. This guide focuses on the basics of writing a simulation to test an HTTP application: OctoPerf's sample PetStore.
Prerequisites¶
HTTP Overview¶
The first thing you need for this tutorial is to be comfortable with HTTP. I strongly recommend you to read the Mozilla overview of HTTP.
There are a few points to remember for this guide. HTTP (Hypertext Transfer Protocol) is a client–server protocol. The client (usually a web browser) sends a request to the server. This request is sent at a URL (Uniform Resource Locators) that identifies the resource required. The server then replies with a response.
Gatling and other protocol-level load testing tools (JMeter, The Grinder) only simulate these low-level interactions. They emulate the behavior of web browsers while sending requests and fetching the server responses (all the while measuring performance metrics) but do not simulate user interactions on the UI.
This is particularly important to keep in mind when developing and load testing web applications that heavily rely on JavaScript such as Single Page applications (Angular, React, Vue, ...) as performance issues may come from both the server and the javascript client execution.
Let's get back to HTTP! When you open a web browser at https://petstore.octoperf.com:443/actions/Catalog.action?params1=value1¶m2=value#anchor
, the URL is composed of:
https
: The scheme http or https (HTTP Secure) identifies the protocol used,petstore.octoperf.com
: The hostname that identifies the server,443
: The port on which the server responds, defaults to 80 for HTTP and 443 for HTTPS (they are hidden in your web browser unless non-default values are used),/actions/Catalog.action
: The path of the requested resource on the server,?params1=value1¶m2=value
: The query parameters (irrelevant for this first tutorial),#anchor
: The fragment, used only on the client side to identify an HTML element of the page.
Gatling manages the scheme, hostname and port on the HTTP protocol configuration. The path and query parameters are handled at the request level.
HTTP Headers are sent along with this URL. They let the client and the server pass additional information. An HTTP header consists of a name (case-insensitive) followed by a colon (:) and a value. Gatling handles headers both globally in the HTTP protocol configuration or for a specific request.
The server replies with a response. It has a body and HTTP headers. For our sample URL the body is the HTML content of the page.
Open your Web browser console (F12 in Chrome) and the Network tab to have a view of these information:
Gatling Installation¶
The second thing you need for this tutorial is to have installed Gatling (Download link).
Gatling Terminology¶
We will soon dive into a single URL script and how to run our first test with it. That is a perfectly valid way to test a simple API. But whenever you wonder about the performance of an entire application, it is better to simulate the behavior of real users. That means recording/replaying a user journey through the application. We will cover both aspects in this tutorial and the next ones. Whether you test a REST API or an entire application doesn't matter much since you will be using similar mechanisms in both situations. For instance, they require the same level of parameterization as you often need to extract values from a server response to inject it in subsequent requests. The virtual users you create in Gatling are called scenarios.
Concretely a scenario is a list of request definitions interleaved with pauses (think times), conditions and loops. Several scenarios may be used simultaneously during a load test. For example with an e-commerce website, most visitors are probably simply browsing the store while a few are actually adding items to their shopping cart and going to the checkout page.
The other important aspect of load testing is configuring the number of concurrent users that visit the application under test. Typically, you want to specify how many users are expected, for how long, and the frequency of their arrival. The setup of this user load policy is done in Gatling using Injection Profiles.
Gatling's Simulation scripts gather all these information in a single file, using code written in a dedicated language.
The Simulation Class¶
The Simulation is a Class. Behind the code there is an object than contains all required properties for your load tests:
- The scenario definitions (What are the virtual users doing?),
- The injection profiles (How many are they?),
- As well as more technical information in the case of an HTTP application: the protocol and headers definitions.
Here is a sample Simulation class (Download here):
package com.octoperf.tutorials.one
import io.gatling.core.Predef._ // required for Gatling core structure DSL
import io.gatling.http.Predef._ // required for Gatling HTTP DSL
import scala.concurrent.duration._ // used for specifying duration unit, eg "5 second"
class PetStoreSimulation extends Simulation {
}
You do not need to know how to code using an Object oriented programing language such as Scala to write Gatling load testing simulations. Basic scripting competences are more than enough.
Just three simple tips:
- All simulations classes must
extends Simulation
, - Imports are mandatory, see the above example for the minimum set required (additional imports may be added for advanced use cases),
- The package must match the directory where you placed the class.
You will find a folder named user-files
in the installation directory of Gatling.
It contains two sub-folders:
resources
: place your resources files here such as .CSV feeders and post body contents,simulations
: place your.scala
simulations here.
The package of the simulation (com.octoperf.tutorial.one
in the previous example) should match the path of the simulation relative to the simulations
folder.
The Simulation class name should also match the file name.
For instance, the previous script should be written in the file <GATLING_HOME>/user-files/simulations/com/octoperf/tutorials/one/PetStoreSimulation1.scala
.
Use case: JPetStore¶
The PetStore is a sample e-commerce Web Application. We are going to write a Gatling simulation script to load test it with a simple GET request. That's just a start, next steps are coming!
You can download this sample scala file and copy it in the com/octoperf/tutorials/one
folder to get started quickly.
The HTTP Protocol Configuration¶
Before writing a request, we need to configure the HTTP protocol.
In Gatling, this is done with the http
keyword. The simplest HTTP protocol configuration only sets the baseUrl
property:
val httpProtocol = http.baseUrl("https://petstore.octoperf.com")
Here we declare a variable (val
) named httpProtocol
and affect the created HTTP configuration to it (= http.baseUrl()
).
By default, Gatling seeks to simulate web browsers in the most realistic way possible.
You can change this behavior with various configuration options (that should be appended after the baseUrl()
statement):
.maxConnectionsPerHost(5)
: to change the number of concurrent connections per virtual user when fetching resources on the same hosts (defaults to 6),.disableAutoReferer
: to disable the automatic Referer HTTP header computation,.disableCaching
: to disable the caching of responses (Gatling caches responses using the Expires, Cache-Control, Last-Modified and ETag headers)
Much more configurations are manageable at the protocol level: Gatling Documentation.
For example, default HTTP headers can also be defined with the keywords .header("name", "value")
and .headers(Map("name1" -> "value1", "name2" -> "value2"))
.
Specific headers can also be set using dedicated keywords, for instance:
.acceptHeader("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
,.userAgentHeader("Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0")
,- etc.
This protocol configuration will be used when setting up the injection policy.
A First GET Request¶
In the meantime, let's start simple with a GET request to simulate a visitor that would open the homepage of the PetStore. Here is the sample code:
val scn = scenario("PetStoreSimulation").exec(http("request_0").get("/actions/Catalog.action"))
Split point by point, it goes:
- The user journey of a visitor (sequence of HTTP requests sent to the server) is stored in a scenario, here called PetStoreSimulation:
scenario("PetStoreSimulation")
, - A single HTTP request named request_0 that points to the Catalog.action path is created:
http("request_0").get("/actions/Catalog.action")
, - This scenario executes the created request
.exec()
, - The scenario is stored in a variable named scn:
val scn =
.
Notes:
We saw previously how to add headers on the protocol level. You can also add headers for a specific request:
http("request_0").get("/actions/Catalog.action").headers(Map("accept" -> "text/css,*/*;q=0.1"))
.If you want to re-use the same set of headers for multiple requests, simply declare a variable:
val myHeaders = Map("accept" -> "text/css,*/*;q=0.1")
and use it in your requests:.headers(myHeaders)
.
Setting Up an Injection Policy¶
The last thing to configure before running the Gatling script is the injection policy. The injection policy defines how many users you want to simulate on your servers.
Gatling has two kinds of user load models, each with a set of dedicated DSL keywords:
- Closed model: you control the concurrent number of users,
- Open model: you control the arrival rate of users.
Use the .inject()
keyword to tell Gatling to inject users for a specific scenario.
For instance, to configure the execution of a single iteration of one virtual user, you would write:
setUp(scn.inject(atOnceUsers(1))).protocols(httpProtocol)
Where scn
is the name of the scenario variable and httpProtocol
is the name of the HTTP protocol configuration.
This is an open model.
You could also write:
rampUsers(5) during(10 seconds)
: to inject 5 users over 10 seconds,constantUsersPerSec(3) during(15 seconds)
: to inject 3 users every second during a total of 15sec (and expect 45 total iterations).
The number of concurrent users depends on the duration of the execution of the scn
scenario.
What if you want to ensure a fixed number of concurrent users? Then simply use closed injection rules. For example:
constantConcurrentUsers(10) during(30 seconds)
: to inject 10 concurrent users during 30 seconds,rampConcurrentUsers(5) to(15) during(30 seconds)
: to inject 5 concurrent users at the start of the test and up to 15 ccu after 30 seconds.
To understand the difference between the closed and open models, you must think in term of iterations. As previously explained, a scenario (aka virtual user) is a sequence of HTTP requests. The execution of one sequence is called an iteration of the virtual user.
Using an open model, nothing happens when an iteration completes. To simplify, a new iteration starts when one completes in a closed model.
Use this sample scala file for a completed Gatling simulation script that summarize what we have seen until now.
Running Your Gatling Simulation¶
It's time for some action! On Linux, run the Gatling simulation with the command:
<GATLING_HOME>/bin/gatling.sh -s com.test.PetStoreSimulation
Also, you can see in the Chrome console Waterfall that requests to these resources are executed in parallel.
In Gatling, this behavior can be simulated with the .resources
keyword.
For example, the following script will fetch the "/actions/Catalog.action" HTML page then call the resource_1, resource_2 and resource_3 requests in parallel:
val scn = scenario("PetStoreSimulation")
.exec(http("request_0")
.get("/actions/Catalog.action")
.resources(
http("resource_1").get("/css/jpetstore.css"),
http("resource_2").get("/images/birds_icon.gif"),
http("resource_3").get("/images/splash.gif")
)
)
HTML Resources Inferring¶
Resources inferring is configured at the protocol level:
val httpProtocol = http
.baseUrl("https://petstore.octoperf.com")
.inferHtmlResources(BlackList(), WhiteList("https://petstore.octoperf.com/.*"))
Resources inferring tells Gatling to parse HTML pages and fetch resources in parallel to emulate the behavior of real browsers.
Here, the inferHtmlResources
takes two arguments: BlackList()
and WhiteList()
to exclude / include all resources that match the patterns specified.
A list of patterns can be given: WhiteList("https://.*.octoperf.com/.*", "https://.*.my-website.com/.*")
.
You can also switch the BlackList and WhiteList parameters, excluding all blacklisted resources before keeping only the whitelisted ones.
Let's run our sample PetStoreSimulation script (download here with this option activated.
CPU Usage Comparison¶
Resources inferring implies that Gatling will parse every HTML response to extract links to images and css file. You may think that this will consume CPU and will potentially limit the number of concurrent user simulated.
Let's check this out by starting a load test with 100 CCU during 3 minutes:
setUp(scn.inject(constantConcurrentUsers(100) during(3 minutes))).protocols(httpProtocol)
The first test (download script here uses the .resources
keyword to statically download the same resources.
There is no significant difference in CPU usage between the two tests:
In any case, maintaining a load testing script that references every static resource can become a headache when modifications are made to the Web Application under test.
So you should use the inferHtmlResources
configuration preferably.
Next Steps¶
We have seen how to create a simple Simulation script in Gatling (with a single static HTTP request) and how to execute it.
There are many things to improve in order to make our scripts closer to the behavior of real users. As well as many subjects to explore regarding Gatling scripting:
- Script parameterization with response extractions and value injections,
- Loops, pauses, think times and pacing,
- Post requests and their body types,
- Modular scripts and how to reuse parts of your virtual users,
- Authentication methods,
- WebSockets and Server-sent Events,
- An assertions guide,
- etc.