Skip to main content

Locustfile

Our locustfiles live in the locustfiles/ directory. We likely already have all the ones you need, but if not, this is where you would add a new one.

This where you will define all of the User classes for your load tests. A common user might look like:

# -*- coding: utf-8 -*-
"""
Example of a locustfile...
"""
from locust import HttpUser, between

from tasks.prime import PrimeTasks


class PrimeUser(HttpUser):
"""
A user that can test things...
"""

tasks = {PrimeTasks: 1}

# The time (in seconds) Locust waits in between tasks. Can use decimals.
wait_time = between(0.25, 9)

You will need to specify what tasks this user should run. This can be done via a tasks attribute on the class (like in the example above) or defining tasks directly on the user class. In our repo, we use the tasks attribute and pass it task sets.

Locust Event Hooks

In addition to the User classes, another thing that the locustfiles will have is hooks. Hooks provide a way to run code when a given event occurs, e.g. locust initializes, load testing is starting, etc. You can see more info on the locust event hooks docs.

We have a few hooks we use in our locusfiles to help out with making requests to the Prime API. They set up certs needed to auth our requests. You'll see them look something like this:

# -*- coding: utf-8 -*-
"""
Example of a locustfile with event hooks...
"""
from locust import HttpUser, between, events
from locust.env import Environment, RunnerType

from tasks import PrimeTasks
from utils.auth import remove_certs, set_up_certs
from utils.base import ImplementationError, MilMoveEnv


class PrimeUser(HttpUser):
"""
A user that can test things...
"""

tasks = {PrimeTasks: 1}

# The time (in seconds) Locust waits in between tasks. Can use decimals.
wait_time = between(0.25, 9)


@events.init.add_listener
def on_init(environment: Environment, runner: RunnerType, **_kwargs) -> None:
"""
Event hook that gets run after the locust environment has been set up. See docs for more info:
https://docs.locust.io/en/stable/api.html?#locust.event.Events.init

In our case, we're setting up certs.
:param environment: locust environment.
:param runner: locust runner that can be used to shut down the test run.
:param _kwargs: Other kwargs we aren't using that are passed to hook functions.
:return: None
"""
# Here we're taking locust's `host` variable from the `environment` and turning it into a
# MilMoveEnv instance, e.g. MilMoveEnv.LOCAL.
try:
milmove_env = MilMoveEnv(value=environment.host)
except ValueError as err:
# For some reason exceptions don't stop the runner automatically, so we have to do it
# ourselves.
runner.quit()

raise err

# Then we use our MilMoveEnv instance, milmove_env, to set up the certs needed for making requests
# to the Prime API.
try:
set_up_certs(env=milmove_env)
except ImplementationError as err:
runner.quit()

raise err


@events.quitting.add_listener
def on_quitting(environment: Environment, **_kwargs):
"""
Event hook that gets run when locust is shutting down.

We're using it to clean up certs that were created during setup.
:param environment: locust environment.
:param _kwargs: Other kwargs we aren't using that are passed to hook functions.
:return: None
"""
# Here again we'll need our instance of a MilMoveEnv, so we'll turn the `environment.host` into
# our version of it.
try:
milmove_env = MilMoveEnv(value=environment.host)
except ValueError as err:
# This should in theory never happen since a similar check is done on init, but just in
# case...
environment.runner.quit()

raise err

# Then we need to use that MilMoveEnv instance to remove the certs that the init hook created.
remove_certs(env=milmove_env)

Here you can see two examples of hooks, one that runs after locust has finished initializing, but before load testing has begun, and one that runs when locust is shutting down. In this case we're setting up and removing certs needed for interacting with the Prime API.

One thing to note is that because the locustfile is the entry point for locust if you have a hook that you want to run across locustfiles, you'll need to hook up your function in each file.

We have a sample locustfile located at locustfiles/lifecycle.py that you can run to see some printed statements showing you some locust hooks and the order they run in. You can test it by running:

locust -f locustfiles/lifecycle.py --headless -u 1 -r 1 -t 1s

Note that we don't define a host because it sets one up in the file because of the way it's using a Postman test API.

Making Requests Outside A User Or TaskSet Class

Another use for event hooks is if you need to make some requests before the load tests begin at all. Here is an example:

# -*- coding: utf-8 -*-
"""
Example of a locustfile making a request in an event hook...
"""
import requests
from locust import HttpUser, between, events
from locust.env import Environment, RunnerType

from tasks.prime import PrimeTasks
from utils.base import MilMoveEnv
from utils.request import MilMoveRequestPreparer
from utils.rest import parse_response_json
from utils.types import JSONArray


class PrimeUser(HttpUser):
"""
A user that can test the Prime API
"""

# These are locust HttpUser attributes that help define and shape the load test:

tasks = {PrimeTasks: 1} # the set of tasks to be executed and their relative weight

# the time period to wait in between tasks (in seconds, accepts decimals and 0)
wait_time = between(0.25, 9)


def set_up_for_prime_load_tests(env: MilMoveEnv) -> None:
"""
Sample of func that can set up or get data before tests start.
:param env: MilMoveEnv that we're targeting, e.g. MilMoveEnv.LOCAL
"""
# Note how we can use the `MilMoveRequestPreparer` class combined with the `MilMoveEnv` instance
# to make it easier to prepare for making requests.
request_preparer = MilMoveRequestPreparer(env=env)

# Here request_kwargs will include the necessary headers for making a json request and the certs
# needed to auth to the Prime API.
moves_path, request_kwargs = request_preparer.prep_prime_request(endpoint="/moves")

response = requests.get(url=moves_path, **request_kwargs)

# This function will handle parsing the response content for us, so we can work with the data more
# easily and creates a nice error message. Alternatively, we could call response.json() and let
# the caller catch any exceptions (since it's already set up to do that).
moves, error_msg = parse_response_json(response=response)

# `moves` comes back as a JSONType which is more generic so this states we specifically expect it
# to be a JSONArray
moves: JSONArray

if error_msg:
print(error_msg)

return

if moves:
print(f"\n{moves[0]=}\n")
else:
print("No moves found.")


@events.init.add_listener
def on_init(environment: Environment, runner: RunnerType, **_kwargs) -> None:
"""
Event hook that gets run after the locust environment has been set up. See docs for more info:
https://docs.locust.io/en/stable/api.html?#locust.event.Events.init

In our case, we're setting up for the load tests.

:param environment: locust environment.
:param runner: locust runner that can be used to shut down the test run.
:param _kwargs: Other kwargs we aren't using that are passed to hook functions.
:return: None
"""
try:
milmove_env = MilMoveEnv(value=environment.host)
except ValueError as err:
# For some reason exceptions don't stop the runner automatically, so we have to do it
# ourselves.
runner.quit()

raise err

# For the sake of brevity, the code for setting up and removing the certs was excluded from this
# example.

try:
set_up_for_prime_load_tests(env=milmove_env)
except Exception as err:
runner.quit()

raise err

Note that we're using the requests package directly to make our requests. The nice thing about using the MilMoveRequestPreparer class is that it enables us to set up requests in a similar manner both in and out of the User/TaskSet context. Examples of using it in the TaskSet context will be in the TaskSets page.