Load tests
Table of contents
- Introduction to Locust
- Where load tests live
- Load testing one feature
- Running without a feature name
- How it runs per environment
- Stopping Locust
- Official documentation
Introduction to Locust
Locust is an open-source load testing tool. You describe user behaviour in Python and Locust swarms your system with simulated users, reporting response times and failures as it goes. It is the top level of the testing pyramid.
Key features
- Scalability: capable of simulating very large numbers of concurrent users.
- Flexibility: user behaviour is plain Python.
- Real-time monitoring: live statistics and charts while the test runs.
Ramp-up in Locust
Ramp-up is the gradual increase in the number of simulated users over a period. It lets you watch how the system degrades as load builds, instead of hitting it with the full load at once.
Where load tests live
One file per feature, at:
app/features/<feature>/tests/locustfile.py
These files are Locust scenarios, not pytest modules. They define HttpUser and TaskSet
subclasses and resolve the target host through the framework helper:
from locust import HttpUser, TaskSet, between, task
from splent_framework.environment.host import get_host_for_locust_testing
The
loadmarker
pyproject.tomldeclares aloadmarker alongside the other five levels (load: locust load tests (run via 'rosemary locust')). Becauselocustfile.pydoes not matchpython_files = ["test_*.py"]and carries nopytestmark, pytest never collects it, and no file in the repository actually applies@pytest.mark.load. The marker names the level;rosemary locustruns it. This is whyrosemary test --loadonly prints a pointer to this command.
Features without a locustfile
Not every feature ships one, and the omissions are deliberate:
| Feature | Why there is no locustfile |
|—|—|
| webhook | Its endpoint performs a real redeployment of the running containers. Putting concurrent traffic on it would repeatedly redeploy the environment. Even its integration tests replace WebhookService.deploy with a recorder. |
| zenodo | Its service is a client for the external Zenodo REST API. Load traffic here would hammer a third-party service rather than
uvlhub
. Its service tests patch requests for the same reason. |
| featuremodel | Its own route renders an empty template. Feature models are only browsable through the dataset page, so that traffic is already covered by the dataset locustfile. |
The features that do ship one are auth, dataset, explore, flamapy, hubfile, profile,
public and team.
Using Locust with your development environment
Load tests can be executed from any environment. To select the right one for the Rosemary CLI, see Using Rosemary.
Load testing one feature
rosemary locust auth
Rosemary checks that app/features/auth/ exists and that
app/features/auth/tests/locustfile.py exists. An unknown feature stops with
Feature 'auth' does not exist.; a feature that exists but has no locustfile stops with an error
naming the exact path it looked for.
What happens inside Docker
Rosemary builds docker/images/Dockerfile.locust, then starts a container from it with three
things wired up for you:
- the repository mounted on
/workspace, which is where the image setsWORKDIRandPYTHONPATH, so the-fpath it passes resolves; - the same Docker network as the running stack, read off
web_app_containerrather than assumed, so it keeps working whatever Compose project name you brought the stack up under; WORKING_DIR=/workspace/, which is what makesget_host_for_locust_testing()resolve tohttp://nginx_web_server_container. Without it the scenario would targethttp://localhost:5000, and inside the Locust container that is the Locust container itself.
Locust is running at
http://localhost:8089
Open that page, set the number of users and the spawn rate, and start the run.
If you would rather drive Locust yourself, the equivalent manual invocation from the host is:
docker build -f docker/images/Dockerfile.locust -t locust-image .
docker run --rm -p 8089:8089 \
-e WORKING_DIR=/workspace/ \
-v "$PWD":/workspace \
--network uvlhub_uvlhub_network \
locust-image -f /workspace/app/features/auth/tests/locustfile.py
Confirm the network name against your own stack, since it carries the Compose project name:
docker network ls --format '{{.Name}}' | grep uvlhub
Running headless
To run without the web interface, which is what you want in a script, add --headless and a
duration:
docker exec web_app_container locust \
-f app/features/auth/tests/locustfile.py \
--headless -u 5 -r 1 -t 30s --only-summary
This runs Locust inside the app container rather than a separate one, so it needs no image build
and picks up WORKING_DIR from the environment already there.
Running without a feature name
rosemary locust
With no argument, every feature that has a locustfile.py is loaded at once. Discovery is done by
splent_framework’s locustfile bootstrap, which since 1.7.1 finds
app/features/*/tests/locustfile.py on its own: in Docker, Rosemary simply omits -f and the
locust entrypoint resolves the bootstrap from site-packages; outside Docker, Rosemary points locust
at the bootstrap module directly. The web interface then lets you pick which user classes to run, or
swarm all of them. Loaded class names carry a per-feature suffix, AuthUser_splent_locustfile_auth
for example, so two features’ classes can never shadow each other.
To load from somewhere else entirely, set SPLENT_LOCUSTFILES to a comma-separated list of glob
patterns, relative to WORKING_DIR unless absolute.
How it runs per environment
rosemary locust branches on WORKING_DIR:
Docker (WORKING_DIR=/workspace/) — Rosemary builds docker/images/Dockerfile.locust into an
image called locust-image and starts a detached locust_container on port 8089. It reads the
volume and the network off the running web_app_container rather than assuming names, so it
survives a non-default Compose project name.
Local (WORKING_DIR unset) — Rosemary starts locust -f <path> as a background process. If a
locust process is already running it says so and does nothing.
Vagrant (WORKING_DIR=/vagrant/) — the same background process as local.
Stopping Locust
You need to stop Locust before you can start a run for a different feature, and after you edit a
locustfile.py:
rosemary locust:stop
locust:stop targets the container Rosemary named. The manual docker run shown earlier runs in
the foreground with --rm, so stop that one with Ctrl-C instead.
In Docker this runs docker stop locust_container followed by docker rm locust_container. Locally
and under Vagrant it sends SIGTERM to the running locust process.
Official documentation
We recommend Locust’s official documentation for designing further tests against uvlhub