Skip to content
Gatling: Getting Started With Simulation Scripts

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.

Elevate your Load Testing!
Request a Demo

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&param2=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&param2=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 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
Compared to what we see in the Chrome console, this is far from perfect: 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:

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:

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:

PetStore Resource Inferring 100 CCU

PetStore No Resource Inferring 100 CCU

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:

Want to become a super load tester?
Request a Demo