Writing a locustfile

A locustfile is a normal python file. The only requirement is that it declares at least one class - let’s call it the locust class - that inherits from the class Locust.

The Locust class

A locust class represents one website user. Locust will spawn one instance of the locust class for each user that is being simulated. The locust class has to declare a tasks attribute which is either a list of python callables or a <callable : int> dict. The task attribute defines the different tasks that a website user might do. The tasks are python callables, that recieves one argument - the locust class instance representing the user that is performing the task. Here is an extremely simple example of a locustfile (this locsutfile won’t actually load test anything):

from locust import Locust

def my_task(l):
    pass

class MyLocust(Locust):
    tasks = [my_task]

The min_wait and max_wait attributes

Additionally to the tasks attribute, one usually want to declare the min_wait and max_wait attributes. These are the minimum and maximum time, in milliseconds, that a simulated user will wait between executing each task. min_wait and max_wait defaults to 1000, and therefore a locust will always wait 1 second between each task if min_wait and max_wait is not declared.

With the following locustfile, each user would wait between 5 and 15 seconds between tasks:

from locust import Locust

def my_task(l):
    print "executing my_task"

class MyLocust(Locust):
    tasks = [my_task]
    min_wait = 5000
    max_wait = 15000

The tasks attribute

As stated above, the tasks attribute defines the different tasks a locust user will perform. If the tasks attribute is specified as a list, each time a task is to be performed, it will be randomly chosen from the tasks attribute. If however, tasks is a dict - with callables as keys and ints as values - the task that is to be executed will be chosen at random but with the int as ratio. So with a tasks that looks like this:

{my_task: 3, another_task:1}

my_task would be 3 times more likely to be executed than another_task.

The host attribute

The host attribute is an adress to the host that is to be loaded. Usually, this is specified on the command line, using the –host option, when locust is started. If one declares a host attribute in the locust class, it will be used in the case when no –host is specified on the command line.

Declaring tasks using the @task decorator

Additionally to specifying a Locust’s tasks using the tasks attribute, one can automatically add class methods to the tasks list using the @task decorator.

Here is an example:

from locust import Locust, task

class MyLocust(Locust):
    min_wait = 5000
    max_wait = 15000

    @task
    def my_task(self):
        print "executing task"

@task takes an optional weight argument that can be used to specify the task execution ratio. In the following example task2 will be executed twice as much as task1:

from locust import Locust, task

class MyLocust(Locust):
    min_wait = 5000
    max_wait = 15000

    @task(3)
    def task1(self):
        pass

    @task(6)
    def task2(self):
        pass

Making HTTP requests

So far, we’ve only covered the task scheduling part of a Locust user. In order to actually load test a system we need to make HTTP requests.

class Locust

Locust class that inherits from LocustBase and creates a client attribute on instantiation.

The client attribute is a simple HTTP client with support for keeping a user session between requests.

Locust.client = None

Instance of HttpBrowser that is created upon instantiation of Locust. The client support cookies, and therefore keeps the session between HTTP requests.

When inheriting from the Locust class, we can use it’s client attribute to make HTTP requests against the server. Here is an example of a locust file that can be used to load test a site with two urls; / and /about/:

from locust import Locust

class MyLocust(Locust):
    min_wait = 5000
    max_wait = 15000

    def index(self):
        self.client.get("/")

    def about(self):
        self.client.get("/about/")

Using the above locust class, each user will wait between 5 and 15 seconds between the requests, and / will be requested twice the amount of times than /about/.

Using the HTTP client

Each instance of Locust has an instance of HttpBrowser in the client attribute.

class HttpBrowser(base_url, gzip=False)

Class for performing web requests and holding session cookie between requests (in order to be able to log in to websites).

Logs each request so that locust can display statistics.

HttpBrowser.get(path, headers={}, name=None, **kwargs)

Make an HTTP GET request.

Arguments:

  • path is the relative path to request.
  • headers is an optional dict with HTTP request headers
  • name is an optional argument that can be specified to use as label in the statistics instead of the path
  • catch_response is an optional boolean argument that, if set, can be used to make a request with a with statement. This will allows the request to be marked as a fail based on the content of the response, even if the response code is ok (2xx).
  • allow_http_error os an optional boolean argument, that, if set, can be used to not mark responses with HTTP errors as failures. If an HTTPError occurs, it will be available in the exception attribute of the response.

Returns an HttpResponse instance, or None if the request failed.

Example:

client = HttpBrowser("http://example.com")
response = client.get("/")

Example using the with statement:

from locust import ResponseError

with self.client.get("/inbox", catch_response=True) as response:
    if response.data == "fail":
        raise ResponseError("Request failed")
HttpBrowser.post(path, data, headers={}, name=None, **kwargs)

Make an HTTP POST request.

Arguments:

  • path is the relative path to request.
  • data dict with the data that will be sent in the body of the POST request
  • headers is an optional dict with HTTP request headers
  • name is an optional argument that can be specified to use as label in the statistics instead of the path
  • catch_response is an optional boolean argument that, if set, can be used to make a request with a with statement. This will allows the request to be marked as a fail based on the content of the response, even if the response code is ok (2xx).
  • allow_http_error os an optional boolean argument, that, if set, can be used to not mark responses with HTTP errors as failures. If an HTTPError occurs, it will be available in the exception attribute of the response.

Returns an HttpResponse instance, or None if the request failed.

Example:

client = HttpBrowser("http://example.com")
response = client.post("/post", {"user":"joe_hill"})

Example using the with statement:

from locust import ResponseError

with self.client.post("/inbox", {"user":"ada", content="Hello!"}, catch_response=True) as response:
    if response.data == "fail":
        raise ResponseError("Posting of inbox message failed")

By default, requests are marked as failed requests unless the HTTP response code is ok (2xx). However, one can mark requests as failed, even when the response code is okay, by using the catch_response argument with a with statement:

from locust import ResponseError

client = HttpBrowser("http://example.com")
with client.get("/", catch_response=True) as response:
    if response.data != "Success":
        raise ResponseError("Got wrong response")

Just as one can mark requests with OK response codes as failures, one can also make requests that results in an HTTP error code still result in a success in the statistics:

client = HttpBrowser("http://example.com")
response = client.get("/does_not_exist/", allow_http_error=True)
if response.exception:
    print "We got an HTTPError exception, but the request will still be marked as OK"

Also, catch_response and allow_http_error can be used together.

The on_start function

A locust class can optionally have an on_start function declared. If so, that function is called when a simulated user starts executing that locust class.

Using SubLocusts

Real websites are usually built up in an hierarchical way, with multiple sub sections. To allow the load testing scripts to more realistically simulate real user behaviour, Locust provides the SubLocust class. A SubLocust class contains normal locust tasks, just like normal Locust classes. However, a SubLocust class can be used as a task under any Locust - or SubLocust - class. This allows us to build tests that simulates users that browses the website in a more hierarchical, and thus more realistic, way.