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:
Chrome Console PetStore Homepage
Gatling Installation
The second thing you need for this tutorial is to either:
Why Kraken? Kraken is a load testing IDE that provides a complete development environment to load testers that seek to make the most out of Gatling:
- A code editor to maintain Gatling simulations with autocomplete suggestions and code snippets,
- Load testing scripts debugger and comparison with HAR imports,
- Live load testing reports for performance analysis,
- Distributed load on several injectors using the nodes of a Kubernetes cluster.
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):
1
2
3
4
5
6
7
8
9
|
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
.
Creating a Simulation in Kraken
The default file embedded in Kraken already include the Simulation files for this tutorial.
You can simply open them from the Simulation tree. It’s still good to know how to create new scripts though.
In Kraken, dedicated file trees for simulations and resources give you quick access to these files:
Kraken Simulations and Resources Trees
They behave like any file system explorer. To create a new simulation:
- Right click at the root of the Simulations tree and click on the New Directory menu item,
- Type
com
for the directory name in the dialog than opens and press Ok,
- Right click on the created directory do the same to create several sub-folders (
octoperf
, tutorials
and one
)
- Right click on the created directory and click on the New File menu item,
- Type
PetStoreSimulation0.scala
for the file name and press Ok.
The file is automatically opened in the center pane.
Use code snippets to quickly create the file content:
Type pck
and press CTRL + SPACE
. Type the package name com.test
and press TAB
.
Kraken Package Code Snippet
You can add the default imports in the same way with the imps
code snippet:
Kraken Package Imports Snippet
As well as generate the simulation class with the class
snippet:
Kraken Package Class Snippet
The resulting script should look like this:
Kraken PetStore Script
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:
1
|
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:
1
|
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 much 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:
1
|
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 or open com.octoperf.tutorials.one.PetStoreSimulation1.scala in Kraken for a completed Gatling simulation script that summarize what we have seen until now.
Kraken PetStore Injection Policy
Running Your Gatling Simulation
It’s time for some action!
On Linux, run the Gatling simulation with the command:
1
|
<GATLING_HOME>/bin/gatling.sh -s com.test.PetStoreSimulation
|
Using Kraken, debugging a simulation allows you to see what request is sent and what response is received from the server.
To debug a simulation in Kraken, simply open the simulation file and click on the Debug icon in the upper right corner of the file editor.
Warning: Be careful with the injection policy when debugging on Kraken. Always set it to atOnceUsers(1)
or you might record too many requests/responses and crash the application.
HTTP is a client-server protocol that allows the fetching of resources, such as HTML documents.
Usually the client is a Web browser that reconstructs a complete document from the different sub-documents fetched, for instance text (.html), layout description (.css), images (.gif), etc.
When the debug of the Gatling has completed, you can see that only the request to the HTML page is executed:
Kraken Debug Simulation
Note:
In the debug view of Kraken you can see the different parts of the HTTP request (URL, Headers) and of the response (Code, headers and body).
Compared to what we see in the Chrome console, this is far from perfect:
PetStore Chrome Console
Indeed, HTTP requests to the resources are missing: .gif images, .css files, etc.
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:
1
2
3
4
5
6
7
8
9
|
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")
)
)
|
An easier way to simulate this behavior is by using Resources Inferring.
HTML Resources Inferring
Resources inferring is configured at the protocol level:
1
2
3
|
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 or open com.octoperf.tutorials.one.PetStoreSimulation2.scala in Kraken) with this option activated.
In Kraken, we can quickly see that resources are fetched after the HTML page is downloaded:
PetStore Resource Inferring
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:
1
|
setUp(scn.inject(constantConcurrentUsers(100) during(3 minutes))).protocols(httpProtocol)
|
The first test (download script here or open com.octoperf.tutorials.one.PetStoreSimulation3.scala in Kraken) uses the inferHtmlResources
configuration on the HTTP protocol while the second test (download script here or open com.octoperf.tutorials.one.PetStoreSimulation4.scala in Kraken) uses the .resources
keyword to statically download the same resources.
There is no significant difference in CPU usage between the two tests:
inferHtmlResources
resources
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 using Kraken.
It you want to get started with Gatling quickly, feel free to check Kraken’s demonstration SaaS. It’s free and your can run simulations and tests without installing anything.
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:
If you have any feedback or question, please leave a comment bellow!