# Locust
> Developer-friendly load testing framework. Write scalable load tests in plain Python.
---
# What is Locust?
Locust is an open source performance/load testing tool for HTTP and other protocols. Its developer-friendly approach lets you define your tests in regular Python code.
Locust tests can be run from command line or using its web-based UI. Throughput, response times and errors can be viewed in real time and/or exported for later analysis.
You can import regular Python libraries into your tests, and with Locust’s pluggable architecture it is infinitely expandable. Unlike when using most other tools, your test design will never be limited by a GUI or domain-specific language.
To start using Locust, go to installation
## Features
VIDEO
* **Write test scenarios in plain old Python**
> If you want your users to loop, perform some conditional behavior or do some calculations, you just use the regular programming constructs provided by Python.
> Locust runs every user inside its own greenlet (a lightweight process/coroutine). This enables you to write your tests like normal (blocking) Python code instead of having to use callbacks or some other mechanism.
> Because your scenarios are “just python” you can use your regular IDE, and version control your tests as regular code (as opposed to some other tools that use XML or binary formats)
* **Distributed and scalable - supports hundreds of thousands of concurrent users**
> Locust makes it easy to run load tests distributed over multiple machines.
> It is event-based (using [gevent](http://www.gevent.org/)), which makes it possible for a single process to handle many thousands concurrent users.
> While there may be other tools that are capable of doing more requests per second on a given hardware, the low overhead of each Locust user makes it very suitable for testing highly concurrent workloads.
* **Web-based UI**
> Locust has a user friendly web interface that shows the progress of your test in real-time. You can even change the load while the test is running. It can also be run without the UI, making it easy to use for CI/CD testing.
* **Can test any system**
> Even though Locust primarily works with websites/services, it can be used to test almost any system or protocol. Just write a client
> for what you want to test, or [explore some created by the community](https://github.com/SvenskaSpel/locust-plugins#users).
* **Hackable**
> Locust is small and very flexible and we intend to keep it that way. If you want to [send reporting data to that database & graphing system you like](https://github.com/SvenskaSpel/locust-plugins/tree/master/locust_plugins/dashboards), wrap calls to a REST API to handle the particulars of your system or run a totally custom load pattern, there is nothing stopping you!
## Name & background
Locust was born out of a frustration with existing solutions. No existing load testing tool was well-equipped to generate realistic
load against a dynamic website where most pages had different content for different users. Existing tools used clunky interfaces or
verbose configuration files to declare the tests. In Locust we took a different approach. Instead of configuration formats or UIs
you’d get a python framework that would let you define the behavior of your users using Python code.
Locust takes its name from the [grasshopper species](https://en.wikipedia.org/wiki/Locust), known for their swarming behavior.
history
## Authors
- Jonatan Heyman ([@heyman](https://github.com/heyman))
- Lars Holmberg ([@cyberw](https://github.com/cyberw))
- Andrew Baldwin ([@andrewbaldwin44](https://github.com/andrewbaldwin44))
Many thanks to our other great [contributors](https://github.com/locustio/locust/graphs/contributors)!
## License
Open source licensed under the MIT license (see LICENSE file for details).
---
# Installation
#### NOTE
Check [Troubleshooting Installation]() if you encounter issues.
1. [Install Python](https://docs.python-guide.org/starting/installation/) (if you dont already have it)
2. Install Locust
```console
$ pip install locust
```
1. Validate your installation
```console
$ locust -V
locust 2.44.2 from /usr/local/lib/python3.12/site-packages/locust (Python 3.12.5)
```
## Using uvx (alternative)
1. [Install uv](https://github.com/astral-sh/uv?tab=readme-ov-file#installation)
2. Install and run locust in an ephemeral environment
```console
$ uvx locust -V
locust 2.44.2 from /.../uv/.../locust (Python 3.12.5)
```
## Done!
Now you can create and run your first test
---
## Pre-release builds
If you need the latest and greatest version of Locust and cannot wait for the next release, you can install a dev build like this:
```console
$ pip3 install -U --pre locust
```
Pre-release builds are published every time a branch/PR is merged into master.
## Install for development
If you want to modify Locust, or contribute to the project, see developing-locust.
## Troubleshooting installation
> ##### Some solutions for common installation issues
>
> * [psutil/_psutil_common.c:9:10: fatal error: Python.h: No such file or directory](#psutil-psutil-common-c-9-10-fatal-error-python-h-no-such-file-or-directory)
> * [ERROR: Failed building wheel for xxx](#error-failed-building-wheel-for-xxx)
> * [Windows](#windows)
> * [Installation works, but the `locust` command is not found](#installation-works-but-the-locust-command-is-not-found)
> * [Increasing Maximum Number of Open Files Limit](#increasing-maximum-number-of-open-files-limit)
### psutil/_psutil_common.c:9:10: fatal error: Python.h: No such file or directory
[Answered in Stackoverflow thread 63440765](https://stackoverflow.com/questions/63440765/locust-installation-error-using-pip3-error-command-errored-out-with-exit-statu)
### ERROR: Failed building wheel for xxx
While Locust itself is a pure Python package, it has some dependencies
(e.g. gevent and geventhttpclient) that are compiled from C code. Pretty
much all common platforms have binary packages on PyPi, but sometimes
there is a new release that doesn’t, or you are running on some exotic
platform. You have two options:
- (on macos) Install xcode: `xcode-select --install`
- Use `pip install --prefer-binary locust` to select a pre-compiled
version of packages even if there is a more recent version available
as source.
- Try googling the error message for the specific package that failed
(not Locust), ensure you have the appropriate build tools installed
etc.
### Windows
[Answered in Stackoverflow thread 61592069](https://stackoverflow.com/questions/61592069/locust-is-not-installing-on-my-windows-10-for-load-testing)
### Installation works, but the `locust` command is not found
When running pip, did you get a warning saying `The script locust is installed in '...' which is not on PATH`?
Add that directory to your PATH environment variable.
### Increasing Maximum Number of Open Files Limit
Every User/HTTP connection from Locust opens a new file (technically
a file descriptor). Many operating systems by default set a low limit
for the maximum number of files that can be open at the same time.
Locust will try to adjust this automatically for you, but in a lot of
cases your operating system will not allow it (in which case you will
get a warning in the log). Instead you will have to do it manually.
How to do this depends on your operating system, but you might find
some useful information here:
[https://www.tecmint.com/increase-set-open-file-limits-in-linux/](https://www.tecmint.com/increase-set-open-file-limits-in-linux/) and
practical examples
[https://www.ibm.com/support/knowledgecenter/SS8NLW_11.0.2/com.ibm.discovery.es.in.doc/iiysiulimits.html](https://www.ibm.com/support/knowledgecenter/SS8NLW_11.0.2/com.ibm.discovery.es.in.doc/iiysiulimits.html)
For systemd-based systems (e.g. Debian/Ubuntu) different limits are
used for graphical login sessions. See
[https://unix.stackexchange.com/a/443467](https://unix.stackexchange.com/a/443467) for additional settings.
---
# Your first test
A Locust test is essentially just a Python program making requests to the system you want to test. This makes it very flexible and particularly good at implementing complex user flows. But it can do simple tests as well, so let’s start with that:
```python
from locust import HttpUser, task
class HelloWorldUser(HttpUser):
@task
def hello_world(self):
self.client.get("/hello")
self.client.get("/world")
```
This user will make an HTTP request to `/hello`, then to `/world`, and then repeat. For a full explanation and a more realistic example see writing-a-locustfile.
Change `/hello` and `/world` to some actual paths on the website/service you want to test, put the code in a file named `locustfile.py` in your current directory and then run `locust`:
```console
$ locust
[2021-07-24 09:58:46,215] .../INFO/locust.main: Starting web interface at http://0.0.0.0:8089
[2021-07-24 09:58:46,285] .../INFO/locust.main: Starting Locust 2.44.2
```
## Locust’s web interface
Open [http://localhost:8089](http://localhost:8089)

Provide the host name of your server and try it out!
The following screenshots show what it might look like when running this test using 50 concurrent users, with a ramp up rate of 1 user/s

Under the *Charts* tab you’ll find things like requests per second (RPS), response times and number of running users:

#### NOTE
Interpreting performance test results is quite complex (and mostly out of scope for this manual), but if your graphs start looking like this, the target service/system cannot handle the load and you have found a bottleneck.
When we get to around 20 users, response times start increasing so fast that even though Locust is still spawning more users, the number of requests per second is no longer increasing. The target service is “overloaded” or “saturated”.
If your response times are *not* increasing then add even more users until you find the service’s breaking point, or celebrate that your service is already performant enough for your expected load.
If you’re having trouble generating enough load to saturate your system, take a look at increaserr.
## Direct command line usage / headless
Using the Locust web UI is entirely optional. You can supply the load parameters on the command line and get reports on the results in text form:
```console
$ locust --headless --users 10 --spawn-rate 1 -H http://your-server.com
[2021-07-24 10:41:10,947] .../INFO/locust.main: No run time limit set, use CTRL+C to interrupt.
[2021-07-24 10:41:10,947] .../INFO/locust.main: Starting Locust 2.44.2
[2021-07-24 10:41:10,949] .../INFO/locust.runners: Ramping to 10 users using a 1.00 spawn rate
Name # reqs # fails | Avg Min Max Median | req/s failures/s
----------------------------------------------------------------------------------------------
GET /hello 1 0(0.00%) | 115 115 115 115 | 0.00 0.00
GET /world 1 0(0.00%) | 119 119 119 119 | 0.00 0.00
----------------------------------------------------------------------------------------------
Aggregated 2 0(0.00%) | 117 115 119 117 | 0.00 0.00
(...)
[2021-07-24 10:44:42,484] .../INFO/locust.runners: All users spawned: {"HelloWorldUser": 10} (10 total users)
(...)
```
See running-without-web-ui for more details.
## More options
To run Locust distributed across multiple Python processes or machines, you start a single Locust master process
with the `--master` command line parameter, and then any number of Locust worker processes using the `--worker`
command line parameter. See running-distributed for more info.
To see all available options type: `locust --help` or check configuration.
## Next steps
Now, let’s have a more in-depth look at locustfiles and what they can do: writing-a-locustfile.
---
# Writing a locustfile
Now, lets look at a more complete/realistic example of what your tests might look like:
```python
import time
from locust import HttpUser, task, between
class QuickstartUser(HttpUser):
wait_time = between(1, 5)
@task
def hello_world(self):
self.client.get("/hello")
self.client.get("/world")
@task(3)
def view_items(self):
for item_id in range(10):
self.client.get(f"/item?id={item_id}", name="/item")
time.sleep(1)
def on_start(self):
self.client.post("/login", json={"username":"foo", "password":"bar"})
```
### Let’s break it down
```python
import time
from locust import HttpUser, task, between
```
A locust file is just a normal Python module, it can import code from other files or packages.
```python
class QuickstartUser(HttpUser):
```
Here we define a class for the users that we will be simulating. It inherits from
`HttpUser` which gives each user a `client` attribute,
which is an instance of `HttpSession`, that
can be used to make HTTP requests to the target system that we want to load test. When a test starts,
locust will create an instance of this class for every user that it simulates, and each of these
users will start running within their own green gevent thread.
For a file to be a valid locustfile it must contain at least one class inheriting from `User`.
```python
wait_time = between(1, 5)
```
Our class defines a `wait_time` that will make the simulated users wait between 1 and 5 seconds after each task (see below)
is executed. For more info see wait-time.
```python
@task
def hello_world(self):
self.client.get("/hello")
self.client.get("/world")
```
Methods decorated with `@task` are the core of your locust file. For every running User,
Locust creates a [greenlet](https://greenlet.readthedocs.io/en/stable/greenlet.html) (a coroutine or “micro-thread”), that will call those methods.
Code within a task is executed sequentially (it is just regular Python code),
so `/world` won’t be called until the response from `/hello` has been received.
```python
@task
def hello_world(self):
...
@task(3)
def view_items(self):
...
```
We’ve declared two tasks by decorating two methods with `@task`, one of which has been given a higher weight (3).
When our `QuickstartUser` runs it’ll pick one of the declared tasks - in this case either `hello_world` or
`view_items` - and execute it. Tasks are picked at random, but you can give them different weighting. The above
configuration will make Locust three times more likely to pick `view_items` than `hello_world`. When a task has
finished executing, the User will then sleep for its specified wait time (in this case between 1 and 5 seconds).
Then it will pick a new task.
Note that only methods decorated with `@task` will be picked, so you can define your own internal helper methods any way you like.
```python
self.client.get("/hello")
```
The `self.client` attribute makes it possible to make HTTP calls that will be logged by Locust. For information on how
to make other kinds of requests, validate the response, etc, see
[Using the HTTP Client](writing-a-locustfile.html#client-attribute-httpsession).
#### NOTE
HttpUser is not a real browser, and thus will not parse an HTML response to load resources or render the page. It will keep track of cookies though.
```python
@task(3)
def view_items(self):
for item_id in range(10):
self.client.get(f"/item?id={item_id}", name="/item")
time.sleep(1)
```
In the `view_items` task we load 10 different URLs by using a variable query parameter.
In order to not get 10 separate entries in Locust’s statistics - since the stats is grouped on the URL - we use
the name parameter to group all those requests under an entry named `"/item"` instead.
```python
def on_start(self):
self.client.post("/login", json={"username":"foo", "password":"bar"})
```
Additionally we’ve declared an on_start method. A method with this name will be called for each simulated
user when they start. For more info see on-start-on-stop.
## Auto-generating a locustfile
You can use [har2locust](https://github.com/SvenskaSpel/har2locust) to generate locustfiles based on a browser recording (HAR-file).
It is particularly useful for beginners that are not used to writing their own locustfile, but also highly customizable for more advanced use cases.
#### NOTE
har2locust is still in beta. It may not always generate correct locustfiles, and its interface may change between versions.
## User class
A user class represents one type of user/scenario for your system. When you do a test run you specify the number of concurrent
users you want to simulate and Locust will create an instance per user. You can add any attributes you like to these
classes/instances, but there are some that have special meaning to Locust:
### wait_time attribute
A User’s `wait_time` method makes it easy to introduce delays after
each task execution. If no wait_time is specified, the next task will be executed as soon as one finishes.
* `constant` for a fixed amount of time
* `between` for a random time between a min and max value
For example, to make each user wait between 0.5 and 10 seconds between every task execution:
```python
from locust import User, task, between
class MyUser(User):
@task
def my_task(self):
print("executing my_task")
wait_time = between(0.5, 10)
```
* `constant_throughput` for an adaptive time that ensures the task runs (at most) X times per second.
* `constant_pacing` for an adaptive time that ensures the task runs (at most) once every X seconds (it is the mathematical inverse of constant_throughput).
#### NOTE
For example, if you want Locust to run 500 task iterations per second at peak load, you could use wait_time = constant_throughput(0.1) and a user count of 5000.
Wait time can only constrain the throughput, not launch new Users to reach the target. So, in our example, the throughput will be less than 500 if the time for the task iteration exceeds 10 seconds.
Wait time is applied *after* task execution, so if you have a high spawn rate/ramp up you may end up exceeding your target during ramp-up.
Wait times apply to *tasks*, not requests. For example, if you specify wait_time = constant_throughput(2) and do two requests in your tasks, your request rate/RPS will be 4 per User.
It’s also possible to declare your own wait_time method directly on your class.
For example, the following User class would sleep for one second, then two, then three, etc.
```python
class MyUser(User):
last_wait_time = 0
def wait_time(self):
self.last_wait_time += 1
return self.last_wait_time
...
```
### weight and fixed_count attributes
If more than one user class exists in the file, and no user classes are specified on the command line,
Locust will spawn an equal number of each of the user classes. You can also specify which of the
user classes to use from the same locustfile by passing them as command line arguments:
```console
$ locust -f locust_file.py WebUser MobileUser
```
If you wish to simulate more users of a certain type than another you can set a weight attribute on those
classes. The code below will make Locust spawn 3 times as many WebUsers as MobileUsers:
```python
class WebUser(User):
weight = 3
...
class MobileUser(User):
weight = 1
...
```
Also, you can set the `fixed_count` attribute.
In this case, the weight attribute will be ignored and only that exact number users will be spawned.
These users are spawned before any regular, weighted ones. In the example below, only one instance of AdminUser
will be spawned, to make some specific work with more accurate control
of request count independently of total user count.
```python
class AdminUser(User):
wait_time = constant(600)
fixed_count = 1
@task
def restart_app(self):
...
class WebUser(User):
...
```
### host attribute
The host attribute is a URL prefix (e.g. `https://google.com`) to the host you want to test. It is automatically added to requests, so you can do `self.client.get("/")` for example.
You can overwrite this value in Locust’s web UI or on the command line, using the
`--host` option.
### tasks attribute
A User class can have tasks declared as methods under it using the `@task` decorator, but one can also
specify tasks using the *tasks* attribute, which is described in more details below.
### environment attribute
A reference to the `environment` in which the user is running. Use this to interact with
the environment, or the `runner` which it contains. E.g. to stop the runner from a task method:
```python
self.environment.runner.quit()
```
If run on a standalone locust instance, this will stop the entire run. If run on worker node, it will stop that particular node.
### on_start and on_stop methods
Users (and TaskSets) can declare an `on_start` method and/or
`on_stop` method. A User will call its
`on_start` method when it starts running, and its
`on_stop` method when it stops running. For a TaskSet, the
`on_start` method is called when a simulated user starts executing
that TaskSet, and `on_stop` is called when the simulated user stops
executing that TaskSet (when `interrupt()` is called, or the
user is killed).
## Tasks
When a load test is started, an instance of a User class will be created for each simulated user
and they will start running within their own greenlet. When these users run they pick tasks that
they execute, sleep for awhile, and then pick a new task and so on.
### @task decorator
The easiest way to add a task for a User is by using the `task` decorator.
```python
from locust import User, task, constant
class MyUser(User):
wait_time = constant(1)
@task
def my_task(self):
print("User instance (%r) executing my_task" % self)
```
**@task** takes an optional weight argument that can be used to specify the task’s execution ratio. In
the following example, *task2* will be twice as likely to be selected as *task1*:
```python
from locust import User, task, between
class MyUser(User):
wait_time = between(5, 15)
@task(3)
def task1(self):
pass
@task(6)
def task2(self):
pass
```
### tasks attribute
Another way to define the tasks of a User is by setting the `tasks` attribute.
The *tasks* attribute is either a list of Tasks, or a ** dict, where Task is either a
python callable or a TaskSet class. If the task is a normal python function they
receive a single argument which is the User instance that is executing the task.
Here is an example of a User task declared as a normal python function:
```python
from locust import User, constant
def my_task(user):
pass
class MyUser(User):
tasks = [my_task]
wait_time = constant(1)
```
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 task that looks like this:
```
{my_task: 3, another_task: 1}
```
*my_task* would be 3 times as likely to be executed as *another_task*.
Internally the above dict will actually be expanded into a list (and the `tasks` attribute is updated)
that looks like this:
```
[my_task, my_task, my_task, another_task]
```
and then Python’s `random.choice()` is used to pick tasks from the list.
### @tag decorator
By tagging tasks using the `@tag` decorator, you can be picky about what tasks are
executed during the test using the `--tags` and `--exclude-tags` arguments. Consider
the following example:
```python
from locust import User, constant, task, tag
class MyUser(User):
wait_time = constant(1)
@tag('tag1')
@task
def task1(self):
pass
@tag('tag1', 'tag2')
@task
def task2(self):
pass
@tag('tag3')
@task
def task3(self):
pass
@task
def task4(self):
pass
```
If you started this test with `--tags tag1`, only *task1* and *task2* would be executed
during the test. If you started it with `--tags tag2 tag3`, only *task2* and *task3* would be
executed.
`--exclude-tags` will behave in the exact opposite way. So, if you start the test with
`--exclude-tags tag3`, only *task1*, *task2*, and *task4* will be executed. Exclusion always
wins over inclusion, so if a task has a tag you’ve included and a tag you’ve excluded, it will not
be executed.
## Events
If you want to run some setup code as part of your test, it is often enough to put it at the module
level of your locustfile, but sometimes you need to do things at particular times in the run. For
this need, Locust provides event hooks.
### test_start and test_stop
If you need to run some code at the start or stop of a load test, you should use the
`test_start` and `test_stop`
events. You can set up listeners for these events at the module level of your locustfile:
```python
from locust import events
@events.test_start.add_listener
def on_test_start(environment, **kwargs):
print("A new test is starting")
@events.test_stop.add_listener
def on_test_stop(environment, **kwargs):
print("A new test is ending")
```
### init
The `init` event is triggered at the beginning of each Locust process. This is especially useful in distributed mode
where each worker process (not each user) needs a chance to do some initialization. For example, let’s say you have some
global state that all users spawned from this process will need:
```python
from locust import events
from locust.runners import MasterRunner
@events.init.add_listener
def on_locust_init(environment, **kwargs):
if isinstance(environment.runner, MasterRunner):
print("I'm on master node")
else:
print("I'm on a worker or standalone node")
```
### Other events
See extending locust using event hooks for other events and more examples of how to use them.
## HttpUser class
`HttpUser` is the most commonly used `User`. It adds a `client` attribute which is used to make HTTP requests.
```python
from locust import HttpUser, task, between
class MyUser(HttpUser):
wait_time = between(5, 15)
@task(4)
def index(self):
self.client.get("/")
@task(1)
def about(self):
self.client.get("/about/")
```
### client attribute / HttpSession
`client` is an instance of `HttpSession`. HttpSession is a subclass/wrapper for
`requests.Session`, so its features are well documented and should be familiar to many. What HttpSession adds is mainly reporting of the request results into Locust (success/fail, response time, response length, name).
It contains methods for all HTTP methods: `get`,
`post`, `put`,
…
Just like `requests.Session`, it preserves cookies between requests so it can easily be used to log in to websites.
```python
response = self.client.post("/login", {"username":"testuser", "password":"secret"})
print("Response status code:", response.status_code)
print("Response text:", response.text)
response = self.client.get("/my-profile")
```
HttpSession catches any `requests.RequestException` thrown by Session (caused by connection errors, timeouts or similar), instead returning a dummy
Response object with *status_code* set to 0 and *content* set to None.
### Validating responses
Requests are considered successful if the HTTP response code is OK (<400), but it is often useful to
do some additional validation of the response.
You can mark a request as failed by using the *catch_response* argument, a *with*-statement and
a call to *response.failure()*
```python
with self.client.get("/", catch_response=True) as response:
if response.text != "Success":
response.failure("Got wrong response")
elif response.elapsed.total_seconds() > 0.5:
response.failure("Request took too long")
```
You can also mark a request as successful, even if the response code was bad:
```python
with self.client.get("/does_not_exist/", catch_response=True) as response:
if response.status_code == 404:
response.success()
```
You can even avoid logging a request at all by throwing an exception and then catching it outside the with-block. Or you can throw a locust exception, like in the example below, and let Locust catch it.
```python
from locust.exception import RescheduleTask
...
with self.client.get("/does_not_exist/", catch_response=True) as response:
if response.status_code == 404:
raise RescheduleTask()
```
### REST/JSON APIs
FastHttpUser provides a ready-made `rest` method, but you can also do it yourself:
```python
from json import JSONDecodeError
...
with self.client.post("/", json={"foo": 42, "bar": None}, catch_response=True) as response:
try:
if response.json()["greeting"] != "hello":
response.failure("Did not get expected value in greeting")
except JSONDecodeError:
response.failure("Response could not be decoded as JSON")
except KeyError:
response.failure("Response did not contain expected key 'greeting'")
```
### Grouping requests
It’s very common for websites to have pages whose URLs contain some kind of dynamic parameter(s).
Often it makes sense to group these URLs together in User’s statistics. This can be done
by passing a *name* argument to the `HttpSession's`
different request methods.
Example:
```python
# Statistics for these requests will be grouped under: /blog/?id=[id]
for i in range(10):
self.client.get("/blog?id=%i" % i, name="/blog?id=[id]")
```
There may be situations where passing in a parameter into request function is not possible, such as when interacting with libraries/SDK’s that
wrap a Requests session. An alternative way of grouping requests is provided by setting the `client.request_name` attribute.
```python
# Statistics for these requests will be grouped under: /blog/?id=[id]
self.client.request_name="/blog?id=[id]"
for i in range(10):
self.client.get("/blog?id=%i" % i)
self.client.request_name=None
```
If you want to chain multiple groupings with minimal boilerplate, you can use the `client.rename_request()` context manager.
```python
@task
def multiple_groupings_example(self):
# Statistics for these requests will be grouped under: /blog/?id=[id]
with self.client.rename_request("/blog?id=[id]"):
for i in range(10):
self.client.get("/blog?id=%i" % i)
# Statistics for these requests will be grouped under: /article/?id=[id]
with self.client.rename_request("/article?id=[id]"):
for i in range(10):
self.client.get("/article?id=%i" % i)
```
Using catch_response and accessing [request_meta](https://github.com/locustio/locust/blob/master/locust/clients.py#L145) directly, you can even rename requests based on something in the response.
```python
with self.client.get("/", catch_response=True) as resp:
resp.request_meta["name"] = resp.json()["name"]
```
### HTTP Proxy settings
To improve performance, we configure requests to not look for HTTP proxy settings in the environment by setting
requests.Session’s trust_env attribute to `False`. If you don’t want this, you can manually set
`locust_instance.client.trust_env` to `True`. For further details, refer to the
[documentation of requests](https://requests.readthedocs.io/en/master/api/#requests.Session.trust_env).
### Connection reuse
By default, connections are reused by an HttpUser, even across tasks runs. To avoid connection reuse you can do:
```python
self.client.get("/", headers={"Connection": "close"})
self.client.get("/new_connection_here")
```
Or you can close the entire requests.Session object (this also deletes cookies, closes the SSL session etc). This has some CPU overhead
(and the response time of the next request will be higher due to SSL renegotiation etc), so dont use this unless you really need it.
```python
self.client.get("/")
self.client.close()
self.client.get("/new_connection_here")
```
### Connection pooling
As every `HttpUser` creates new `HttpSession`,
every user instance has its own connection pool. This is similar to how real users (browsers) would interact with a web server.
If you instead want to share connections, you can use a single pool manager. To do this, set `pool_manager`
class attribute to an instance of `urllib3.PoolManager`.
```python
from locust import HttpUser
from urllib3 import PoolManager
class MyUser(HttpUser):
# All instances of this class will be limited to 10 concurrent connections at most.
pool_manager = PoolManager(maxsize=10, block=True)
```
For more configuration options, refer to the
[urllib3 documentation](https://urllib3.readthedocs.io/en/stable/reference/urllib3.poolmanager.html).
## TaskSets
TaskSets is a way to structure tests of hierarchical websites/systems. You can read more about it here.
## Examples
There are lots of locustfile examples [here](https://github.com/locustio/locust/tree/master/examples)
## How to structure your test code
It’s important to remember that the locustfile.py is just an ordinary Python module that is imported
by Locust. From this module you’re free to import other python code just as you normally would
in any Python program. The current working directory is automatically added to python’s `sys.path`,
so any python file/module/packages that resides in the working directory can be imported using the
python `import` statement.
For small tests, keeping all the test code in a single `locustfile.py` should work fine, but for
larger test suites, you’ll probably want to split the code into multiple files and directories.
How you structure the test source code is of course entirely up to you, but we recommend that you
follow Python best practices. Here’s an example file structure of an imaginary Locust project:
* Project root
* `common/`
* `__init__.py`
* `auth.py`
* `config.py`
* `locustfile.py`
* `requirements.txt` (External Python dependencies is often kept in a requirements.txt)
A project with multiple locustfiles could also keep them in a separate subdirectory:
* Project root
* `common/`
* `__init__.py`
* `auth.py`
* `config.py`
* `my_locustfiles/`
* `api.py`
* `website.py`
* `requirements.txt`
With any of the above project structure, your locustfile can import common libraries using:
```python
import common.auth
```
---
# Configuration
## Command Line Options
Locust is configured mainly through command line arguments.
```console
$ locust --help
```
```console
Usage: locust [options] [UserClass ...]
Common options:
-h, --help show this help message and exit
-f , --locustfile
The Python file or module that contains your test,
e.g. 'my_test.py'. Accepts multiple comma-separated
.py files, a package name/directory or a url to a
remote locustfile. Defaults to 'locustfile.py'.
--config File to read additional configuration from. See https:
//docs.locust.io/en/stable/configuration.html#configur
ation-file
-H , --host
Host to load test, in the following format:
https://www.example.com
-u , --users
Peak number of concurrent Locust users. Primarily used
together with --headless or --autostart. Can be
changed during a test by keyboard inputs w, W (spawn
1, 10 users) and s, S (stop 1, 10 users)
-r , --spawn-rate
Rate to spawn users at (users per second). Primarily
used together with --headless or --autostart
-t , --run-time
Stop after the specified amount of time, e.g. (300s,
20m, 3h, 1h30m, etc.). Only used together with
--headless or --autostart. Defaults to run forever.
-l, --list Show list of possible User classes and exit
--config-users [CONFIG_USERS ...]
User configuration as a JSON string or file. A list of
arguments or an Array of JSON configuration may be
provided
Web UI options:
--web-host Host to bind the web interface to. Defaults to '*'
(all interfaces)
--web-port , -P
Port on which to run web host
--headless Disable the web interface, and start the test
immediately. Use -u and -t to control user count and
run time
--autostart Starts the test immediately (like --headless, but
without disabling the web UI)
--autoquit Quits Locust entirely, X seconds after the run is
finished. Only used together with --autostart. The
default is to keep Locust running until you shut it
down using CTRL+C
--web-login Protects the web interface with a login page. See
https://docs.locust.io/en/stable/extending-
locust.html#authentication
--tls-cert
Optional path to TLS certificate to use to serve over
HTTPS
--tls-key Optional path to TLS private key to use to serve over
HTTPS
--class-picker Enable select boxes in the web interface to choose
from all available User classes and Shape classes
--web-base-path WEB_BASE_PATH
Base path for the web interface (e.g., '/locust').
Default is empty (root path).
Master options:
Options for running a Locust Master node when running Locust distributed. A Master node need Worker nodes that connect to it before it can run load tests.
--master Launch locust as a master node, to which worker nodes
connect.
--master-bind-host
IP address for the master to listen on, e.g
'192.168.1.1'. Defaults to * (all available
interfaces).
--master-bind-port
Port for the master to listen on. Defaults to 5557.
--expect-workers
Delay starting the test until this number of workers
have connected (only used in combination with
--headless/--autostart).
--expect-workers-max-wait
How long should the master wait for workers to connect
before giving up. Defaults to wait forever
--enable-rebalancing Re-distribute users if new workers are added or
removed during a test run. Experimental.
Worker options:
Options for running a Locust Worker node when running Locust distributed.
Typically ONLY these options (and --locustfile) need to be specified on workers, since other options (-u, -r, -t, ...) are controlled by the master node.
--worker Set locust to run in distributed mode with this
process as worker. Can be combined with setting
--locustfile to '-' to download it from master.
--processes Number of times to fork the locust process, to enable
using system. Combine with --worker flag or let it
automatically set --worker and --master flags for an
all-in-one-solution. Not available on Windows.
Experimental.
--master-host
Hostname of locust master node to connect to. Defaults
to 127.0.0.1.
--master-port
Port to connect to on master node. Defaults to 5557.
Tag options:
Locust tasks can be tagged using the @tag decorator. These options let specify which tasks to include or exclude during a test.
-T [ ...], --tags [ ...]
List of tags to include in the test, so only tasks
with at least one matching tag will be executed
-E [ ...], --exclude-tags [ ...]
List of tags to exclude from the test, so only tasks
with no matching tags will be executed
Request statistics options:
--csv Store request stats to files in CSV format. Setting
this option will generate three files:
_stats.csv, _stats_history.csv and
_failures.csv. Any folders part of the
prefix will be automatically created
--csv-full-history Store each stats entry in CSV format to
_stats_history.csv file. You must also specify the '--
csv' argument to enable this.
--print-stats Enable periodic printing of request stats in UI runs
--only-summary Disable periodic printing of request stats during
--headless run
--reset-stats Reset statistics once spawning has been completed.
Should be set on both master and workers when running
in distributed mode
--html Store HTML report to file path specified. Able to
parse certain tags - {u}, {r}, {t} and convert them to
number of users, spawn rate and run time respectively.
--json Prints the final stats in JSON format to stdout.
Useful for parsing the results in other
programs/scripts. Use together with --headless and
--skip-log for an output only with the json data.
--json-file
Prints the final stats in JSON format to file path
specified.
Logging options:
--skip-log-setup Disable Locust's logging setup. Instead, the
configuration is provided by the Locust test or Python
defaults.
--loglevel , -L
Choose between DEBUG/INFO/WARNING/ERROR/CRITICAL.
Default is INFO.
--logfile Path to log file. If not set, log will go to stderr
Other options:
--show-task-ratio Print table of the User classes' task execution ratio.
Use this with non-zero --user option if some classes
define non-zero fixed_count attribute.
--show-task-ratio-json
Print json data of the User classes' task execution
ratio. Use this with non-zero --user option if some
classes define non-zero fixed_count attribute.
--version, -V Show program's version number and exit
--exit-code-on-error
Sets the process exit code to use when a test result
contain any failure or error. Defaults to 1.
-s , --stop-timeout
Number of seconds to wait for a simulated user to
complete any executing task before exiting. Default is
to terminate immediately. When running distributed,
this only needs to be specified on the master.
--equal-weights Use equally distributed task weights, overriding the
weights specified in the locustfile.
--profile PROFILE Set a profile to group the testruns together
--otel Instrument the Locust test run with OpenTelemetry.
User classes:
At the end of the command line, you can list User
classes to be used (available User classes can be
listed with --list). LOCUST_USER_CLASSES environment
variable can also be used to specify User classes.
Default is to use all available User classes
Examples:
locust -f my_test.py -H https://www.example.com
locust --headless -u 100 -t 20m --processes 4 MyHttpUser AnotherUser
locust --headless -u 100 -r 10 -t 50 --print-stats --html "test_report_{u}_{r}_{t}.html"
(The above run would generate an html file with the name "test_report_100_10_50.html")
See documentation for more details, including how to set options using a file or environment variables: https://docs.locust.io/en/stable/configuration.html
```
## Environment Variables
Options can also be set through environment variables. They are typically the same as the command line argument
but capitalized and prefixed with `LOCUST_`:
On Linux/macOS:
```default
$ LOCUST_LOCUSTFILE=custom_locustfile.py locust
```
On Windows:
```default
> set LOCUST_LOCUSTFILE=custom_locustfile.py
> locust
```
## Configuration File
Options can also be set in a configuration file in the
[config or TOML file format](https://github.com/bw2/ConfigArgParse#config-file-syntax).
Locust will look for `~/.locust.conf`, `./locust.conf` and `./pyproject.toml` by default.
You can specify an additional file using the `--config` flag.
```console
$ locust --config custom_config.conf
```
Here’s an example:
### locust.conf
```ini
locustfile = locust_files/my_locust_file.py
headless = true
master = true
expect-workers = 5
host = https://target-system
users = 100
spawn-rate = 10
run-time = 10m
tags = [Critical, Normal]
```
Have a look later in this page for [All available configuration options]()
### pyproject.toml
When using a TOML file, configuration options should be defined within the `[tool.locust]` section.
```toml
[tool.locust]
locustfile = "locust_files/my_locust_file.py"
headless = true
master = true
expect-workers = 5
host = "https://target-system"
users = 100
spawn-rate = 10
run-time = "10m"
tags = ["Critical", "Normal"]
```
#### NOTE
Configuration values are read (and overridden) in the following order:
```console
./pyproject.toml -> ./locust.conf -> (file specified using --config) -> env vars -> cmd args
```
## All available configuration options
Here’s a table of all the available configuration options, and their corresponding Environment and config file keys:
| Command line | Environment | Config file | Description |
|-----------------------------|----------------------------------|---------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `-f`, `--locustfile` | `LOCUST_LOCUSTFILE` | `locustfile` | The Python file or module that contains your test, e.g. ‘my_test.py’. Accepts multiple comma-separated .py files, a package name/directory or a url to a remote locustfile. Defaults to ‘locustfile.py’. |
| `-H`, `--host` | `LOCUST_HOST` | `host` | Host to load test, in the following format: [https://www.example.com](https://www.example.com) |
| `-u`, `--users` | `LOCUST_USERS` | `users` | Peak number of concurrent Locust users. Primarily used together with –headless or –autostart. Can be changed during a test by keyboard inputs w, W (spawn 1, 10 users) and s, S (stop 1, 10 users) |
| `-r`, `--spawn-rate` | `LOCUST_SPAWN_RATE` | `spawn-rate` | Rate to spawn users at (users per second). Primarily used together with –headless or –autostart |
| `-t`, `--run-time` | `LOCUST_RUN_TIME` | `run-time` | Stop after the specified amount of time, e.g. (300s, 20m, 3h, 1h30m, etc.). Only used together with –headless or –autostart. Defaults to run forever. |
| `--config-users` | `LOCUST_CONFIG_USERS` | `config-users` | User configuration as a JSON string or file. A list of arguments or an Array of JSON configuration may be provided |
| `--web-host` | `LOCUST_WEB_HOST` | `web-host` | Host to bind the web interface to. Defaults to ‘\*’ (all interfaces) |
| `--web-port`, `-P` | `LOCUST_WEB_PORT` | `web-port` | Port on which to run web host |
| `--headless` | `LOCUST_HEADLESS` | `headless` | Disable the web interface, and start the test immediately. Use -u and -t to control user count and run time |
| `--autostart` | `LOCUST_AUTOSTART` | `autostart` | Starts the test immediately (like –headless, but without disabling the web UI) |
| `--autoquit` | `LOCUST_AUTOQUIT` | `autoquit` | Quits Locust entirely, X seconds after the run is finished. Only used together with –autostart. The default is to keep Locust running until you shut it down using CTRL+C |
| `--web-login` | `LOCUST_WEB_LOGIN` | `web-login` | Protects the web interface with a login page. See [https://docs.locust.io/en/stable/extending-locust.html#authentication](https://docs.locust.io/en/stable/extending-locust.html#authentication) |
| `--tls-cert` | `LOCUST_TLS_CERT` | `tls-cert` | Optional path to TLS certificate to use to serve over HTTPS |
| `--tls-key` | `LOCUST_TLS_KEY` | `tls-key` | Optional path to TLS private key to use to serve over HTTPS |
| `--class-picker` | `LOCUST_USERCLASS_PICKER` | `class-picker` | Enable select boxes in the web interface to choose from all available User classes and Shape classes |
| `--master` | `LOCUST_MODE_MASTER` | `master` | Launch locust as a master node, to which worker nodes connect. |
| `--master-bind-host` | `LOCUST_MASTER_BIND_HOST` | `master-bind-host` | IP address for the master to listen on, e.g ‘192.168.1.1’. Defaults to \* (all available interfaces). |
| `--master-bind-port` | `LOCUST_MASTER_BIND_PORT` | `master-bind-port` | Port for the master to listen on. Defaults to 5557. |
| `--expect-workers` | `LOCUST_EXPECT_WORKERS` | `expect-workers` | Delay starting the test until this number of workers have connected (only used in combination with –headless/–autostart). |
| `--expect-workers-max-wait` | `LOCUST_EXPECT_WORKERS_MAX_WAIT` | `expect-workers-max-wait` | How long should the master wait for workers to connect before giving up. Defaults to wait forever |
| `--worker` | `LOCUST_MODE_WORKER` | `worker` | Set locust to run in distributed mode with this process as worker. Can be combined with setting –locustfile to ‘-’ to download it from master. |
| `--processes` | `LOCUST_PROCESSES` | `processes` | Number of times to fork the locust process, to enable using system. Combine with –worker flag or let it automatically set –worker and –master flags for an all-in-one-solution. Not available on Windows. Experimental. |
| `--master-host` | `LOCUST_MASTER_NODE_HOST` | `master-host` | Hostname of locust master node to connect to. Defaults to 127.0.0.1. |
| `--master-port` | `LOCUST_MASTER_NODE_PORT` | `master-port` | Port to connect to on master node. Defaults to 5557. |
| `--web-base-path` | `LOCUST_WEB_BASE_PATH` | `web-base-path` | Base path for the web interface (e.g., ‘/locust’). Default is empty (root path). |
| `-T`, `--tags` | `LOCUST_TAGS` | `tags` | List of tags to include in the test, so only tasks with at least one matching tag will be executed |
| `-E`, `--exclude-tags` | `LOCUST_EXCLUDE_TAGS` | `exclude-tags` | List of tags to exclude from the test, so only tasks with no matching tags will be executed |
| `--csv` | `LOCUST_CSV` | `csv` | Store request stats to files in CSV format. Setting this option will generate three files: _stats.csv, _stats_history.csv and _failures.csv. Any folders part of the prefix will be automatically created |
| `--csv-full-history` | `LOCUST_CSV_FULL_HISTORY` | `csv-full-history` | Store each stats entry in CSV format to \_stats_history.csv file. You must also specify the ‘–csv’ argument to enable this. |
| `--print-stats` | `LOCUST_PRINT_STATS` | `print-stats` | Enable periodic printing of request stats in UI runs |
| `--only-summary` | `LOCUST_ONLY_SUMMARY` | `only-summary` | Disable periodic printing of request stats during –headless run |
| `--reset-stats` | `LOCUST_RESET_STATS` | `reset-stats` | Reset statistics once spawning has been completed. Should be set on both master and workers when running in distributed mode |
| `--html` | `LOCUST_HTML` | `html` | Store HTML report to file path specified. Able to parse certain tags - {u}, {r}, {t} and convert them to number of users, spawn rate and run time respectively. |
| `--skip-log-setup` | `LOCUST_SKIP_LOG_SETUP` | `skip-log-setup` | Disable Locust’s logging setup. Instead, the configuration is provided by the Locust test or Python defaults. |
| `--loglevel`, `-L` | `LOCUST_LOGLEVEL` | `loglevel` | Choose between DEBUG/INFO/WARNING/ERROR/CRITICAL. Default is INFO. |
| `--logfile` | `LOCUST_LOGFILE` | `logfile` | Path to log file. If not set, log will go to stderr |
| `--exit-code-on-error` | `LOCUST_EXIT_CODE_ON_ERROR` | `exit-code-on-error` | Sets the process exit code to use when a test result contain any failure or error. Defaults to 1. |
| `-s`, `--stop-timeout` | `LOCUST_STOP_TIMEOUT` | `stop-timeout` | Number of seconds to wait for a simulated user to complete any executing task before exiting. Default is to terminate immediately. When running distributed, this only needs to be specified on the master. |
| `--otel` | `LOCUST_ENABLE_OPENTELEMETRY` | `otel` | Instrument the Locust test run with OpenTelemetry. |
## Running without the web UI
See running-without-web-ui
## Using multiple Locustfiles at once
`-f/--locustfile` accepts multiple, comma-separated locustfiles.
Example:
With the following file structure:
```default
├── locustfiles/
│ ├── locustfile1.py
│ ├── locustfile2.py
│ └── more_files/
│ ├── locustfile3.py
│ ├── _ignoreme.py
```
```console
$ locust -f locustfiles/locustfile1.py,locustfiles/locustfile2.py,locustfiles/more_files/locustfile3.py
```
Locust will use `locustfile1.py`, `locustfile2.py` & `more_files/locustfile3.py`
Additionally, `-f/--locustfile` accepts directories as an option. Locust will recursively
search specified directories for `*.py` files, ignoring files that start with “_”.
Example:
```console
$ locust -f locustfiles
```
Locust will use `locustfile1.py`, `locustfile2.py` & `more_files/locustfile3.py`
You can also use `-f/--locustfile` for web urls. This will download the file and use it as any normal locustfile.
Example:
```console
$ locust -f https://raw.githubusercontent.com/locustio/locust/master/examples/basic.py
```
## Pick User classes, Shapes and tasks from the UI
You can select which Shape class and which User classes to run in the WebUI when running locust with the `--class-picker` flag.
No selection uses all the available User classes.
For example, with a file structure like this:
```default
├── src/
│ ├── some_file.py
├── locustfiles/
│ ├── locustfile1.py
│ ├── locustfile2.py
│ └── more_files/
│ ├── locustfile3.py
│ ├── _ignoreme.py
│ └── shape_classes/
│ ├── DoubleWaveShape.py
│ ├── StagesShape.py
```
```console
$ locust -f locustfiles --class-picker
```
The Web UI will display:

The class picker additionally allows for disabling individual User tasks, changing the weight or fixed count, and configuring the host.
It is even possible to add custom attributes that you wish to be configurable for each User. Simply add a `json` classmethod
to your user:
```python
class Example(HttpUser):
@task
def example_task(self):
self.client.get(f"/example/{self.some_custom_arg}")
@classmethod
def json(self):
return {
"host": self.host,
"some_custom_arg": "example"
}
```
## Configure Users from command line
You can update User class attributes from the command line too, using the `--config-users` argument:
```console
$ locust --config-users '{"user_class_name": "Example", "fixed_count": 1, "some_custom_attribute": false}'
```
To configure multiple users you pass multiple arguments to `--config-users`, or use a JSON Array. You can also pass a path to a JSON file.
```console
$ locust --config-users '{"user_class_name": "Example", "fixed_count": 1}' '{"user_class_name": "ExampleTwo", "fixed_count": 2}'
$ locust --config-users '[{"user_class_name": "Example", "fixed_count": 1}, {"user_class_name": "ExampleTwo", "fixed_count": 2}]'
$ locust --config-users my_user_config.json
```
When using this way to configure your users, you can set any attribute.
#### NOTE
`--config-users` is a somewhat experimental feature and the json format may change even between minor Locust revisions.
## Configuring Profiles
Profiles are an advanced feature that allow for grouping and filtering testruns. A profile may be set using the `--profile` argument
or by inputting a value in the advanced options from the web ui.
It may be useful to see a list of existing profiles as options in the form. If you have such a list, you may set it in your locustfile:
```python
from locust import events
@events.init.add_listener
def on_locust_init(environment, **kwargs):
environment.web_ui.template_args["all_profiles"] = ["one", "two", "three"]
```
## Custom load shapes
Sometimes a completely custom shaped load test is required that cannot be achieved by simply setting or changing the user count and spawn rate. For example, you might want to generate a load spike or ramp up and down at custom times. By using a LoadTestShape class you have full control over the user count and spawn rate at all times.
Define a class inheriting the LoadTestShape class in your locust file. If this type of class is found then it will be automatically used by Locust.
In this class you define a tick() method that returns a tuple with the desired user count and spawn rate (or None to stop the test). Locust will call the tick() method approximately once per second.
In the class you also have access to the get_run_time() method, for checking how long the test has run for.
### Example
This shape class will increase user count in blocks of 100 and then stop the load test after 10 minutes:
```python
class MyCustomShape(LoadTestShape):
time_limit = 600
spawn_rate = 20
def tick(self):
run_time = self.get_run_time()
if run_time < self.time_limit:
# User count rounded to nearest hundred.
user_count = round(run_time, -2)
return (user_count, self.spawn_rate)
return None
```
This functionality is further demonstrated in the [examples on github](https://github.com/locustio/locust/tree/master/examples/custom_shape) including:
- Generating a double wave shape
- Time based stages like K6
- Step load pattern like Visual Studio
One further method may be helpful for your custom load shapes: get_current_user_count(), which returns the total number of active users. This method can be used to prevent advancing to subsequent steps until the desired number of users has been reached. This is especially useful if the initialization process for each user is slow or erratic in how long it takes. If this sounds like your use case, see the [example on github](https://github.com/locustio/locust/tree/master/examples/custom_shape/wait_user_count.py).
Note that if you want to defined your own custom base shape, you need to define the abstract attribute to True to avoid it being picked as Shape when imported:
```python
class MyBaseShape(LoadTestShape):
abstract = True
def tick(self):
# Something reusable but needing inheritance
return None
```
### Combining Users with different load profiles
If you use the Web UI, you can add the —class-picker parameter to select which shape to use. But it often more flexible to have your User definitions in one file and your LoadTestShape in a separate one. For example, if you a high/low load Shape class defined in low_load.py and high_load.py respectively:
```console
$ locust -f locustfile.py,low_load.py
$ locust -f locustfile.py,high_load.py
```
### Restricting which user types to spawn in each tick
Adding the element `user_classes` to the return value gives you more detailed control:
```python
class StagesShapeWithCustomUsers(LoadTestShape):
stages = [
{"duration": 10, "users": 10, "spawn_rate": 10, "user_classes": [UserA]},
{"duration": 30, "users": 50, "spawn_rate": 10, "user_classes": [UserA, UserB]},
{"duration": 60, "users": 100, "spawn_rate": 10, "user_classes": [UserB]},
{"duration": 120, "users": 100, "spawn_rate": 10, "user_classes": [UserA,UserB]},
]
def tick(self):
run_time = self.get_run_time()
for stage in self.stages:
if run_time < stage["duration"]:
try:
tick_data = (stage["users"], stage["spawn_rate"], stage["user_classes"])
except:
tick_data = (stage["users"], stage["spawn_rate"])
return tick_data
return None
```
This shape would create in the first 10 seconds 10 User of `UserA`. In the next twenty seconds 40 of type `UserA / UserB` and this continues until the stages end.
### Reusing common options in custom shapes
When using shapes, the *Users*, *Spawn Rate* and *Run Time* options will be hidden from the UI, and if you specify them on command line Locust will log a warning. This is because those options don’t directly apply to shapes, and specifying them might be a mistake.
If you really want to combine a shape with these options, set the `use_common_options` attribute and access them from `self.runner.environment.parsed_options`:
```python
class MyCustomShape(LoadTestShape):
use_common_options = True
def tick(self):
run_time = self.get_run_time()
if run_time < self.runner.environment.parsed_options.run_time:
# User count rounded to nearest hundred, just like in previous example
user_count = round(run_time, -2)
return (user_count, self.runner.environment.parsed_options.spawn_rate)
return None
```
## Save test statistics in CSV format
When running Locust with the web UI, you can retrieve CSV files under the Download Data tab.
Or you can run Locust with a flag which will periodically save four CSV files. This is particularly useful
if you plan on running Locust in an automated way with the `--headless` flag:
```console
$ locust -f examples/basic.py --csv example --headless -t10m
```
The files will be named `example_stats.csv`, `example_failures.csv`, `example_exceptions.csv` and `example_stats_history.csv`
(when using `--csv example`). The first two files will contain the stats and failures for the whole
test run, with a row for every stats entry (URL endpoint) and an aggregated row. The `example_stats_history.csv`
will get new rows with the *current* (10 seconds sliding window) stats appended during the whole test run.
By default only the Aggregate row is appended regularly to the history stats, but if Locust is started with
the `--csv-full-history` flag, a row for each stats entry (and the Aggregate) is appended every time
the stats are written (once every 2 seconds by default).
You can also customize how frequently this is written:
```python
import locust.stats
locust.stats.CSV_STATS_INTERVAL_SEC = 5 # default is 1 second
```
## Custom arguments
See custom-arguments
## Customization of statistics settings
Default configuration for Locust statistics is set in constants of stats.py file.
It can be tuned to specific requirements by overriding these values.
To do this, import locust.stats module and override required settings:
```python
import locust.stats
locust.stats.CONSOLE_STATS_INTERVAL_SEC = 15
```
It can be done directly in Locust file or extracted to separate file for common usage by all Locust files.
The list of statistics parameters that can be modified is:
| Parameter name | Purpose | Default value |
|-----------------------------------------|-------------------------------------------------------------------------------------------------|----------------------------------------------------------------------|
| STATS_NAME_WIDTH | Width of column for request name in console output | terminal size or 80 |
| STATS_TYPE_WIDTH | Width of column for request type in console output | 8 |
| CSV_STATS_INTERVAL_SEC | Interval for how frequently the CSV file is written if this option is configured | 1 |
| CONSOLE_STATS_INTERVAL_SEC | Interval for how frequently results are written to console / chart UI | 2 |
| HISTORY_STATS_INTERVAL_SEC | Interval for how frequently results are written to history | 5 |
| CURRENT_RESPONSE_TIME_PERCENTILE_WINDOW | Window size/resolution - in seconds - when calculating the current response time percentile | 10 |
| PERCENTILES_TO_REPORT | List of response time percentiles to be calculated & reported | [0.50, 0.66, 0.75, 0.80, 0.90, 0.95, 0.98, 0.99, 0.999, 0.9999, 1.0] |
| PERCENTILES_TO_STATISTICS | List of response time percentiles in the screen of statistics for UI | [0.95, 0.99] |
| PERCENTILES_TO_CHART | List of response time percentiles in the screen of chart for UI | [0.5, 0.95] |
## Customizing response time bucketing
Response times are grouped into histogram buckets before being stored in the
`response_times` dict. By default, Locust rounds to approximately 2 significant
digits (e.g. 147 becomes 150, 3432 becomes 3400). This keeps the dict small,
which matters in distributed mode where the dict is serialized from workers to master.
You can replace the bucketing function to change this behaviour:
```python
import locust.stats
from math import floor, log10
def my_bucket_function(response_time: int | float) -> int:
"""Example: bucket to 3 significant figures."""
if response_time == 0:
return 0
return int(round(response_time, -int(floor(log10(abs(response_time)))) + 2))
locust.stats.bucket_response_time = my_bucket_function
```
The replacement function receives a single numeric argument (the response time in
milliseconds) and must return a numeric value to use as the dict key. Keep in mind
that more unique keys means more data transferred in distributed mode.
## Customization of additional static variables
This table lists the constants that are set within locust and may be overridden.
| Parameter name | Purpose |
|-------------------------------------------|---------------------------------------------------------------------------------------------------------------------|
| locust.runners.WORKER_LOG_REPORT_INTERVAL | Interval for how frequently worker logs are reported to master. Can be disabled by setting to a negative number |
| locust.web.HOST_IS_REQUIRED | Makes the host field for the webui required |
---
# Increasing the request rate
If you’re not getting the desired/expected throughput there are a number of things you can do.
## Concurrency
Increase the number of Users. To fully utilize your target system you may need a lot of concurrent requests. Note that spawn rate/ramp up does not change peak load, it only changes how fast you get there. High [wait times](writing-a-locustfile.html#wait-time) and sleeps *do* impact throughput, so that may make it necessary to launch even more Users. You can find a whole blog post on this topic [here](https://medium.com/locust-cloud/locusts-and-honey-badgers-closed-vs-open-workload-models-in-load-testing-9f59abfc6d9f).
## Load generation performance
If Locust prints a warning about high CPU usage (`WARNING/root: CPU usage above 90%! ...`) try the following:
> - Run Locust [distributed](https://docs.locust.io/en/stable/running-distributed.html) to utilize multiple cores & multiple machines
> - Try switching to [FastHttpUser](https://docs.locust.io/en/stable/increase-performance.html#increase-performance) to reduce CPU usage
> - Check to see that there are no strange/infinite loops in your code
Also, if you are using a custom client (not HttpUser or FastHttpUser), make sure any client library you are using is [gevent-friendly](https://www.gevent.org/api/gevent.monkey.html) otherwise it will block the entire Python process (essentially limiting you to one user per worker)
If you’re doing really high throughput or using a lot of bandwidth, you may also want to check out your network utilization and other OS level metrics.
## Actual issues with the system under test
If response times are high and/or increasing as the number of users go up, then you have probably saturated the system you are testing. This is not a Locust problem, but here are some things you may want to check:
> - resource utilization (e.g. CPU, memory & network)
> - configuration (e.g. max threads for your web server)
> - back end response times (e.g. DB)
There are a few common pitfalls specific to load testing too:
> - load balancing (make sure locust isn’t hitting only a few of your instances)
> - flood protection (sometimes a load test with the high amount of load from only a few machines will trigger this)
---
# Distributed load generation
A single process running Locust can simulate a reasonably high throughput. For a simple test plan and small payloads it can make more than a thousand requests per second, possibly over ten thousand if you use FastHttpUser.
But if your test plan is complex or you want to run even more load, you’ll need to scale out to multiple processes, maybe even multiple machines. Fortunately, Locust supports distributed runs out of the box.
To do this, you start one instance of Locust with the `--master` flag and one or more using the `--worker` flag. The master instance runs Locust’s web interface, and tells the workers when to spawn/stop Users. The worker instances run your Users and send statistics back to the master. The master instance doesn’t run any Users itself.
To simplify startup, you can use the `--processes` flag. It will launch a master process and the specified number of worker processes. It can also be used in combination with `--worker`, then it will only launch workers. This feature relies on [fork()](https://linux.die.net/man/3/fork) so it doesn’t work on Windows.
#### NOTE
Because Python cannot fully utilize more than one core per process (see [GIL](https://realpython.com/python-gil/)), you need to run one worker instance per processor core in order to have access to all your computing power.
#### NOTE
There is almost no limit to how many Users you can run per worker. Locust/gevent can run thousands or even tens of thousands of Users per process just fine, as long as their total request rate (RPS) is not too high.
If Locust *is* getting close to running out of CPU resources, it will log a warning. If there is no warning but you are still unable to generate the expected load, then the problem must be increaserr.
## Single machine
It is really simple to launch a master and 4 worker processes:
```
locust --processes 4
```
You can even auto-detect the number of logical cores in your machine and launch one worker for each of them:
```
locust --processes -1
```
## Multiple machines
Start locust in master mode on one machine:
```
locust -f my_locustfile.py --master
```
And then on each worker machine:
```bash
locust -f - --worker --master-host --processes 4
```
#### NOTE
The `-f -` argument tells Locust to get the locustfile from master instead of from its local filesystem. This only works for single locustfiles.
## Multiple machines, using locust-swarm
#### NOTE
locust-swarm is no longer actively maintained.
When you make changes to the locustfile you’ll need to restart all Locust processes. [locust-swarm](https://github.com/SvenskaSpel/locust-swarm) automates this for you. It also solves the issue of firewall/network access from workers to master using SSH tunnels (this is often a problem if the master is running on your workstation and workers are running in some datacenter).
```bash
pip install locust-swarm
swarm -f my_locustfile.py --loadgen-list worker-server1,worker-server2
```
## Options for distributed load generation
### `--master-host `
Optionally used together with `--worker` to set the hostname/IP of the master node (defaults
to localhost)
### `--master-port `
Optionally used together with `--worker` to set the port number of the master node (defaults to 5557).
### `--master-bind-host `
Optionally used together with `--master`. Determines which network interface the master node
will bind to. Defaults to \* (all available interfaces).
### `--master-bind-port `
Optionally used together with `--master`. Determines what network ports that the master node will
listen to. Defaults to 5557.
### `--expect-workers `
Used when starting the master node with `--headless`. The master node will then wait until X worker
nodes has connected before the test is started.
## Communicating across nodes
When running Locust in distributed mode, you may want to communicate between master and worker nodes in
order to coordinate the test. This can be easily accomplished with custom messages using the built in messaging hooks:
```python
from locust import events
from locust.runners import MasterRunner, WorkerRunner
# Fired when the worker receives a message of type 'test_users'
def setup_test_users(environment, msg, **kwargs):
for user in msg.data:
print(f"User {user['name']} received")
environment.runner.send_message('acknowledge_users', f"Thanks for the {len(msg.data)} users!")
# Fired when the master receives a message of type 'acknowledge_users'
def on_acknowledge(msg, **kwargs):
print(msg.data)
@events.init.add_listener
def on_locust_init(environment, **_kwargs):
if not isinstance(environment.runner, MasterRunner):
environment.runner.register_message('test_users', setup_test_users)
if not isinstance(environment.runner, WorkerRunner):
environment.runner.register_message('acknowledge_users', on_acknowledge)
@events.test_start.add_listener
def on_test_start(environment, **_kwargs):
if not isinstance(environment.runner, WorkerRunner):
users = [
{"name": "User1"},
{"name": "User2"},
{"name": "User3"},
]
environment.runner.send_message('test_users', users)
```
Note that when running locally (i.e. non-distributed), this functionality will be preserved;
the messages will simply be handled by the runner that sends them.
#### NOTE
Using the default options while registering a message handler will run the listener function
in a **blocking** way, resulting in the heartbeat and other messages being delayed for the amount
of the execution.
If you think that your message handler will need to run for more than a second then you can register it
as **concurrent**. Locust will then make it run in its own greenlet. Note that these greenlets will never
be join():ed.
```default
environment.runner.register_message('test_users', setup_test_users, concurrent=True)
```
For more details, see the [complete example](https://github.com/locustio/locust/tree/master/examples/custom_messages.py).
## Running distributed with Docker
See running-in-docker
## Running Locust distributed without the web UI
See running-distributed-without-web-ui
## Increase Locust’s performance
If you’re planning to run large-scale load tests, you might be interested to use the alternative
HTTP client that’s shipped with Locust. You can read more about it here: increase-performance.
---
# Running tests in a debugger
Running Locust in a debugger is extremely useful when developing your tests. Among other things, you can examine a particular response or check some User instance variable.
But debuggers sometimes have issues with complex gevent-applications like Locust, and there is a lot going on in the framework itself that you probably aren’t interested in. To simplify this, Locust provides a method called `run_single_user`:
```python
from locust import HttpUser, run_single_user, task
class QuickstartUser(HttpUser):
host = "http://localhost"
@task
def hello_world(self):
with self.client.get("/hello", catch_response=True) as resp:
pass # maybe set a breakpoint here to analyze the resp object?
# if launched directly, e.g. "python3 debugging.py", not "locust -f debugging.py"
if __name__ == "__main__":
run_single_user(QuickstartUser)
```
It implicitly registers an event handler for the request event to print some stats about every request made:
```console
type name resp_ms exception
GET /hello 38 ConnectionRefusedError(61, 'Connection refused')
GET /hello 4 ConnectionRefusedError(61, 'Connection refused')
```
You can configure exactly what is printed by specifying parameters to `run_single_user`.
Make sure you have enabled gevent in your debugger settings.
Debugging Locust is quite easy with Vscode:
- Place breakpoints
- Select a python file or a scenario (ex: ``examples/basic.py`)
- Check that the desired virtualenv is correctly detected (bottom right)
- Open the action *Debug using launch.json*. You will have the choice between debugging the python file, the scenario with WebUI or in headless mode
- It could be rerun with the F5 shortkey
VS Code’s `launch.json` looks like this:
```json
{
"version": "0.2.0",
"configurations": [
{
"name": "Run current file",
"type": "debugpy",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal",
"gevent": true
},
{
"name": "Run current locust scenario headless",
"type": "debugpy",
"request": "launch",
"module": "locust",
"args": [
"-f",
"${file}",
"--headless",
"--users=5"
],
"console": "integratedTerminal",
"gevent": true
},
{
"name": "Run current locust scenario, autostart",
"type": "debugpy",
"request": "launch",
"module": "locust",
"args": [
"-f",
"${file}",
"--users=5",
"--autostart",
"--print-stats",
"-L=ERROR"
],
"console": "integratedTerminal",
"gevent": true
},
{
"name": "(test debug only)",
"type": "debugpy",
"request": "launch",
"gevent": true,
"purpose": ["debug-test"]
}
]
}
```
If you want to the whole Locust runtime (with ramp up, command line parsing etc), you can do that too:
```json
{
"version": "0.2.0",
"configurations": [
{
"name": "Locust: 5 users, with specific config file",
"type": "python",
"request": "launch",
"module": "locust",
"args": [
"-f",
"${file}",
"--headless",
"--users=5",
"--config=${fileDirname}/../locust.conf"
],
"console": "integratedTerminal",
"gevent": true
}
]
}
```
There is a similar setting in [PyCharm](https://www.jetbrains.com/help/pycharm/debugger-python.html).
#### NOTE
VS Code/pydev may give you warnings about:
`sys.settrace() should not be used when the debugger is being used`
It can safely be ignored (and if you know how to get rid of it, please let us know)
You can execute run_single_user multiple times, as shown in [debugging_advanced.py](https://github.com/locustio/locust/tree/master/examples/debugging_advanced.py).
## Print HTTP communication
Sometimes it can be hard to understand why an HTTP request fails in Locust when it works from a regular browser/other application. Here’s how to examine the communication in detail:
For `HttpUser` ([python-requests](https://requests.readthedocs.io/)):
```python
# put this at the top of your locustfile (or just before the request you want to trace)
import logging
from http.client import HTTPConnection
HTTPConnection.debuglevel = 1
logging.basicConfig()
logging.getLogger().setLevel(logging.DEBUG)
requests_log = logging.getLogger("requests.packages.urllib3")
requests_log.setLevel(logging.DEBUG)
requests_log.propagate = True
```
For `FastHttpUser` ([geventhttpclient](https://github.com/gwik/geventhttpclient/)):
```python
import sys
...
class MyUser(FastHttpUser):
@task
def t(self):
self.client.get("http://example.com/", debug_stream=sys.stderr)
```
Example output (for FastHttpUser):
```console
REQUEST: http://example.com/
GET / HTTP/1.1
user-agent: python/gevent-http-client-1.5.3
accept-encoding: gzip, deflate, br, zstd
host: example.com
RESPONSE: HTTP/1.1 200
Content-Encoding: gzip
Accept-Ranges: bytes
Age: 460461
Cache-Control: max-age=604800
Content-Type: text/html; charset=UTF-8
Date: Fri, 12 Aug 2022 09:20:07 GMT
Etag: "3147526947+ident"
Expires: Fri, 19 Aug 2022 09:20:07 GMT
Last-Modified: Thu, 17 Oct 2019 07:18:26 GMT
Server: ECS (nyb/1D20)
Vary: Accept-Encoding
X-Cache: HIT
Content-Length: 648
...
```
These approaches can of course be used when doing a full load test, but you might get a lot of output :)
---
# Running in Docker
The official Docker image is at [locustio/locust](https://hub.docker.com/r/locustio/locust).
Use it like this (assuming that the `locustfile.py` exists in the current working directory):
```
docker run -p 8089:8089 -v $PWD:/mnt/locust locustio/locust -f /mnt/locust/locustfile.py
```
On Windows, this command will sometimes cause errors. Windows users should try using this instead:
```
docker run -p 8089:8089 --mount type=bind,source=$pwd,target=/mnt/locust locustio/locust -f /mnt/locust/locustfile.py
```
## Docker Compose
Here’s an example Docker Compose file that could be used to start both a master node, and worker nodes:
```yaml
version: '3'
services:
master:
image: locustio/locust
ports:
- "8089:8089"
volumes:
- ./:/mnt/locust
command: -f /mnt/locust/locustfile.py --master -H http://master:8089
worker:
image: locustio/locust
volumes:
- ./:/mnt/locust
command: -f /mnt/locust/locustfile.py --worker --master-host master
```
The above compose configuration could be used to start a master node and 4 workers using the following command:
```
docker-compose up --scale worker=4
```
## OpenTelemetry image
If you need OpenTelemetry support, use the `locustio/locust-otel` image which has the `locust[otel]` extras
pre-installed:
```
docker run -p 8089:8089 -v $PWD:/mnt/locust locustio/locust-otel -f /mnt/locust/locustfile.py --otel
```
## Use docker image as a base image
It’s very common to have test scripts that rely on third party python packages. In those cases you can use the
official Locust docker image as a base image:
```
FROM locustio/locust
RUN pip install some-package # some dependency you need
```
---
# Running without the web UI
You can run locust without the web UI by using the `--headless` flag together with `-u/--users` and `-r/--spawn-rate`:
```console
$ locust -f locust_files/my_locust_file.py --headless -u 100 -r 5
[2021-07-24 10:41:10,947] .../INFO/locust.main: No run time limit set, use CTRL+C to interrupt.
[2021-07-24 10:41:10,947] .../INFO/locust.main: Starting Locust 2.44.2
[2021-07-24 10:41:10,949] .../INFO/locust.runners: Ramping to 100 users using a 5.00 spawn rate
Name # reqs # fails | Avg Min Max Median | req/s failures/s
----------------------------------------------------------------------------------------------
GET /hello 1 0(0.00%) | 115 115 115 115 | 0.00 0.00
GET /world 1 0(0.00%) | 119 119 119 119 | 0.00 0.00
----------------------------------------------------------------------------------------------
Aggregated 2 0(0.00%) | 117 115 119 117 | 0.00 0.00
(...)
[2021-07-24 10:44:42,484] .../INFO/locust.runners: All users spawned: {"HelloWorldUser": 100} (100 total users)
(...)
```
Even in headless mode you can change the user count while the test is running. Press `w` to add 1 user or `W` to add 10. Press `s` to remove 1 or `S` to remove 10.
## Setting a time limit for the test
To specify the run time for a test, use `-t/--run-time`:
```console
$ locust --headless -u 100 --run-time 1h30m
$ locust --headless -u 100 --run-time 60 # default unit is seconds
```
Locust will shut down once the time is up. Time is calculated from the start of the test (not from when ramp up has finished).
## Allow tasks to finish their iteration on shutdown
By default, Locust will stop your tasks immediately (without even waiting for requests to finish).
To give running tasks some time to finish their iteration, use `-s/--stop-timeout`:
```console
$ locust --headless --run-time 1h30m --stop-timeout 10s
```
## Running Locust distributed without the web UI
If you want to run Locust distributed without the web UI,
you should specify the `--expect-workers` option when starting the master node, to specify
the number of worker nodes that are expected to connect. It will then wait until that many worker
nodes have connected before starting the test.
## Controlling the exit code of the Locust process
By default the locust process will give an exit code of 1 if there were any failed samples
(use the `--exit-code-on-error` to change that behaviour).
You can also manually control the exit code in your test scripts by setting the `process_exit_code` of the
`Environment` instance. This is particularly useful when running Locust as an automated/scheduled test, for example as part of a CI pipeline.
Below is an example that’ll set the exit code to non zero if any of the following conditions are met:
* More than 1% of the requests failed
* The average response time is longer than 200 ms
* The 95th percentile for response time is larger than 800 ms
```python
import logging
from locust import events
@events.quitting.add_listener
def _(environment, **kw):
if environment.stats.total.fail_ratio > 0.01:
logging.error("Test failed due to failure ratio > 1%")
environment.process_exit_code = 1
elif environment.stats.total.avg_response_time > 200:
logging.error("Test failed due to average response time ratio > 200 ms")
environment.process_exit_code = 1
elif environment.stats.total.get_response_time_percentile(0.95) > 800:
logging.error("Test failed due to 95th percentile response time > 800 ms")
environment.process_exit_code = 1
else:
environment.process_exit_code = 0
```
Note that this code could go into the locustfile.py or in any other file that is imported in the locustfile.
## Running in CI/CD
You can easily run a single instance of Locust in headless mode as part of a CI/CD pipeline.
Here’s an example using GitHub Actions. Use it in combination with the above snippet to fail the run based on metrics.
```yaml
env:
PYTHONUNBUFFERED: 1 # ensure we see output right away
jobs:
loadtest:
runs-on: ubuntu-latest
timeout-minutes: 15 # just in case something goes wrong
steps:
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
with:
python-version: '3.11'
- run: pip install locust
- run: locust --headless --run-time 5m
```
---
# Hosted load testing
Azure Load Testing is a Microsoft-managed load testing service that lets you execute Locust tests in the cloud.
It is the easiest way to get started with large-scale tests and adds a lot of benefits:
* Scalable and flexible, with no need to set up your own infrastructure.
* Efficient, with no load generator costs while tests are not running.
* Built in reporting and analysis tools, with [Azure Application Insights](https://learn.microsoft.com/azure/azure-monitor/app/app-insights-overview) integration.
* CI/CD support, with Azure DevOps and GitHub Actions.

Microsoft contributes to and sponsors the maintenance of Locust ❤️
If you’re using VS Code, you can use the [Azure Load Testing extension](https://github.com/microsoft/azureloadtesting-extension/blob/main/README.md) to create and run tests in Azure Load Testing directly from your editor.
---
# Testing other systems/protocols
Locust only comes with built-in support for HTTP/HTTPS but it can be extended to test almost any system. This is normally done by wrapping the protocol library and triggering a `request` event after each call has completed, to let Locust know what happened.
#### NOTE
It is important that the protocol libraries you use can be [monkey-patched](https://www.gevent.org/intro.html#monkey-patching) by gevent.
Almost any libraries that are pure Python (using the Python `socket` module or some other standard library function like `subprocess`) should work fine out of the box - but if they do their I/O calls from compiled code C, gevent will be unable to patch it. This will block the whole Locust/Python process (in practice limiting you to running a single User per worker process).
Some C libraries allow for other workarounds. For example, if you want to use psycopg2 to performance test PostgreSQL, you can use [psycogreen](https://github.com/psycopg/psycogreen/). If you are willing to get your hands dirty, you may be able to patch a library yourself, but that is beyond the scope of this documentation.
## XML-RPC
Lets assume we have an XML-RPC server that we want to load test.
```
import random
import time
from xmlrpc.server import SimpleXMLRPCServer
def get_time():
time.sleep(random.random())
return time.time()
def get_random_number(low, high):
time.sleep(random.random())
return random.randint(low, high)
server = SimpleXMLRPCServer(("localhost", 8877))
print("Listening on port 8877...")
server.register_function(get_time, "get_time")
server.register_function(get_random_number, "get_random_number")
server.serve_forever()
```
We can build a generic XML-RPC client, by wrapping `xmlrpc.client.ServerProxy`.
```
from locust import User, task
import time
from xmlrpc.client import Fault, ServerProxy
class XmlRpcClient(ServerProxy):
"""
XmlRpcClient is a wrapper around the standard library's ServerProxy.
It proxies any function calls and fires the *request* event when they finish,
so that the calls get recorded in Locust.
"""
def __init__(self, host, request_event):
super().__init__(host)
self._request_event = request_event
def __getattr__(self, name):
func = ServerProxy.__getattr__(self, name)
def wrapper(*args, **kwargs):
request_meta = {
"request_type": "xmlrpc",
"name": name,
"start_time": time.time(),
"response_length": 0, # calculating this for an xmlrpc.client response would be too hard
"response": None,
"context": {}, # see HttpUser if you actually want to implement contexts
"exception": None,
}
start_perf_counter = time.perf_counter()
try:
request_meta["response"] = func(*args, **kwargs)
except Fault as e:
request_meta["exception"] = e
request_meta["response_time"] = (time.perf_counter() - start_perf_counter) * 1000
self._request_event.fire(**request_meta) # This is what makes the request actually get logged in Locust
return request_meta["response"]
return wrapper
class XmlRpcUser(User):
"""
A minimal Locust user class that provides an XmlRpcClient to its subclasses
"""
abstract = True # dont instantiate this as an actual user when running Locust
def __init__(self, environment):
super().__init__(environment)
self.client = XmlRpcClient(self.host, request_event=environment.events.request)
# The real user class that will be instantiated and run by Locust
# This is the only thing that is actually specific to the service that we are testing.
class MyUser(XmlRpcUser):
host = "http://127.0.0.1:8877/"
@task
def get_time(self):
self.client.get_time()
@task
def get_random_number(self):
self.client.get_random_number(0, 100)
```
## gRPC
Lets assume we have a [gRPC](https://github.com/grpc/grpc) server that we want to load test:
```
import logging
import time
from concurrent import futures
import grpc
import hello_pb2
import hello_pb2_grpc
logger = logging.getLogger(__name__)
class HelloServiceServicer(hello_pb2_grpc.HelloServiceServicer):
def SayHello(self, request, context):
name = request.name
time.sleep(1)
return hello_pb2.HelloResponse(message=f"Hello from Locust, {name}!")
def start_server():
server = grpc.server(futures.ThreadPoolExecutor(max_workers=100))
hello_pb2_grpc.add_HelloServiceServicer_to_server(HelloServiceServicer(), server)
server.add_insecure_port("localhost:50051")
server.start()
logger.info("gRPC server started")
server.wait_for_termination()
if __name__ == "__main__":
start_server()
```
The generic GrpcUser base class sends events to Locust using an [interceptor](https://pypi.org/project/grpc-interceptor/):
```
from locust import User
from locust.exception import LocustError
import time
from collections.abc import Callable
from typing import Any
import grpc
import grpc.experimental.gevent as grpc_gevent
from grpc_interceptor import ClientInterceptor
# patch grpc so that it uses gevent instead of asyncio
grpc_gevent.init_gevent()
class LocustInterceptor(ClientInterceptor):
def __init__(self, environment, *args, **kwargs):
super().__init__(*args, **kwargs)
self.env = environment
def intercept(
self,
method: Callable,
request_or_iterator: Any,
call_details: grpc.ClientCallDetails,
):
response = None
exception = None
start_perf_counter = time.perf_counter()
response_length = 0
try:
response = method(request_or_iterator, call_details)
response_length = response.result().ByteSize()
except grpc.RpcError as e:
exception = e
self.env.events.request.fire(
request_type="grpc",
name=call_details.method,
response_time=(time.perf_counter() - start_perf_counter) * 1000,
response_length=response_length,
response=response,
context=None,
exception=exception,
)
return response
class GrpcUser(User):
abstract = True
stub_class = None
def __init__(self, environment):
super().__init__(environment)
for attr_value, attr_name in ((self.host, "host"), (self.stub_class, "stub_class")):
if attr_value is None:
raise LocustError(f"You must specify the {attr_name}.")
self._channel = grpc.insecure_channel(self.host)
interceptor = LocustInterceptor(environment=environment)
self._channel = grpc.intercept_channel(self._channel, interceptor)
self.stub = self.stub_class(self._channel)
```
And a locustfile using the above would look like this:
```
from locust import events, task
import gevent
import grpc_user
import hello_pb2
import hello_pb2_grpc
from hello_server import start_server
# Start the dummy server. This is not something you would do in a real test.
@events.init.add_listener
def run_grpc_server(environment, **_kwargs):
gevent.spawn(start_server)
class HelloGrpcUser(grpc_user.GrpcUser):
host = "localhost:50051"
stub_class = hello_pb2_grpc.HelloServiceStub
@task
def sayHello(self):
self.stub.SayHello(hello_pb2.HelloRequest(name="Test"))
```
## requests-based libraries/SDKs
If you want to use a library that uses a [requests.Session](https://requests.readthedocs.io/en/latest/api/#requests.Session) object under the hood you will most likely be able to skip all the above complexity.
Some libraries allow you to pass a Session explicitly, like for example the SOAP client provided by [Zeep](https://docs.python-zeep.org/en/master/transport.html#tls-verification). In that case, just pass it your `HttpUser`’s `client`, and any requests made using the library will be logged in Locust.
Even if your library doesn’t expose that in its interface, you may be able to get it working by overwriting some internally used Session. Here’s an example of how to do that for the [Archivist](https://github.com/jitsuin-inc/archivist-python) client.
```
import locust
from locust.user import task
from archivist.archivist import Archivist # Example library under test
class ArchivistUser(locust.HttpUser):
def on_start(self):
AUTH_TOKEN = None
with open("auth.text") as f:
AUTH_TOKEN = f.read()
# Start an instance of of the library-provided client
self.arch: Archivist = Archivist(url=self.host, auth=AUTH_TOKEN)
# overwrite the internal _session attribute with the locust session
self.arch._session = self.client
@task
def Create_assets(self):
"""User creates assets as fast as possible"""
while True:
self.arch.assets.create(behaviours=["Builtin", "RecordEvidence", "Attachments"], attrs={"foo": "bar"})
```
## REST
See FastHttpUser
## SocketIO
See SocketIOUser
#### NOTE
SocketIO support is experimental and may change without notice.
## pytest
Locust allows you to use [pytest](https://docs.pytest.org/) syntax to define Locust Users using pytest fixtures (currently `HttpSession` and `FastHttpSession`). It has multiple benefits:
* Simpler syntax than regular Locustfiles
* Run or debug easily from any editor that supports pytest
* Reliably reuse functional test cases for load testing
```
from locust.clients import HttpSession # this import is just for type hints
import time
# pytest/locust will discover any functions prefixed with "test_" as test cases.
# session and fastsession are pytest fixtures provided by Locust's pytest plugin.
def test_stuff(session):
resp = session.get("https://www.locust.io/")
# Bad HTTP status codes in the response dont automatically raise an exception,
# so if that is what you want, you need to call:
resp.raise_for_status()
# In Locust, request-related exceptions are caught (and the test case restarted),
# in pytest any exceptions fail the test case
# Just like with Locust, you can set a base URL using --host/-H when using pytest.
# Or you can set a default:
if not session.base_url:
session.base_url = "https://www.locust.io"
# catch_response works just like in regular locustfiles
with session.get("/", catch_response=True) as resp:
if not resp.text or not "Locust" in resp.text:
resp.failure("important text was missing in response")
# raise_for_status also respects calls to resp.failure()/.success()
# so this will raise an exception and fail the test case if "Load" was missing
resp.raise_for_status()
# you can call helper functions as needed
helper_function(session)
# unlike regular Locust Users, there's no wait_time, so use time.sleep instead
time.sleep(0.1)
# this is not a test case and won't be detected by pytest/locust
def helper_function(session: HttpSession):
session.get("/")
```
Example usage:
```sh
$ locust -f test_pytest.py
$ pytest test_pytest.py
# if you have issues with gevent patching (RecursionError: maximum recursion depth exceeded)
$ python -m gevent.monkey -m pytest test_pytest.py
```
Limitations:
* Each test case becomes a Locust User under the hood. We don’t (yet) support weighting users.
* Locust will only look for pytest-style tests if there are no regular User classes defined.
* Adding other pytest fixtures or pytest plugins may cause issues (do let us know though)
* For a more complex example, see [https://github.com/locustio/locust/blob/master/locust/test/test_pytest_locustfile.py](https://github.com/locustio/locust/blob/master/locust/test/test_pytest_locustfile.py)
#### NOTE
pytest support is experimental and may change without a new major release version.
## OpenAI
Performance/load testing AI services is a little different. While you could call the OpenAI API using HttpUser or FastHttpUser, it is often convenient to use [the SDK](https://github.com/openai/openai-python).
```
# You need to install the openai package and set OPENAI_API_KEY env var to run this
# OpenAIUser tracks the number of output tokens in the response_length field,
# because it is more useful than the actual payload size. This field is available to event handlers.
from locust import run_single_user, task
from locust.contrib.oai import OpenAIUser
class MyUser(OpenAIUser):
@task
def t(self):
self.client.responses.create(
model="gpt-4o",
instructions="You are a coding assistant that speaks like it were a Monty Python skit.",
input="How do I check if a Python object is an instance of a class?",
)
# print(response.output_text)
with self.client.rename_request("mini"): # here's how to rename requests
self.client.responses.create(
model="gpt-4o-mini",
instructions="You are a coding assistant that speaks like it were a Monty Python skit.",
input="How do I check if a Python object is an instance of a class?",
)
if __name__ == "__main__":
run_single_user(MyUser)
```
#### NOTE
OpenAIUser is experimental and may change without notice.
## MQTT
Locust uses to [paho-mqtt](https://github.com/eclipse-paho/paho.mqtt.python) to provide Mqtt connection capabilities.
```
from locust import task
from locust.contrib.mqtt import MqttUser
from locust.user.wait_time import between
import time
class MyUser(MqttUser):
host = "localhost"
port = 1883
# We could uncomment below to use the WebSockets transport
# transport = "websockets"
# ws_path = "/mqtt/custom/path"
# We'll probably want to throttle our publishing a bit: let's limit it to
# 10-100 messages per second.
wait_time = between(0.01, 0.1)
# Uncomment below if you need to set MQTTv5
# protocol = paho.mqtt.client.MQTTv5
# Sleep for a while to allow the client time to connect.
# This is probably not the most "correct" way to do this: a better method
# might be to add a gevent.event.Event to the MqttClient's on_connect
# callback and wait for that (with a timeout) here.
# However, this works well enough for the sake of an example.
def on_start(self):
time.sleep(5)
@task
def say_hello(self):
self.client.publish("hello/locust", b"hello world")
```
Alternatively, if you need more control over the Mqtt client you can use a custom implementation.
```
from locust import task
from locust.contrib.mqtt import MqttClient, MqttUser
from locust.user.wait_time import between
import time
# extend the MqttClient class with your own custom implementation
class MyMqttClient(MqttClient):
# you can override the event name with your custom implementation
def _generate_event_name(self, event_type: str, qos: int, topic: str):
return f"mqtt:{event_type}:{qos}"
class MyUser(MqttUser):
host = "localhost"
port = 1883
# We could uncomment below to use the WebSockets transport
# transport = "websockets"
# ws_path = "/mqtt/custom/path"
# We'll probably want to throttle our publishing a bit: let's limit it to
# 10-100 messages per second.
wait_time = between(0.01, 0.1)
# override the client_cls with your custom MqttClient implementation
client_cls = MyMqttClient
# Sleep for a while to allow the client time to connect.
# This is probably not the most "correct" way to do this: a better method
# might be to add a gevent.event.Event to the MqttClient's on_connect
# callback and wait for that (with a timeout) here.
# However, this works well enough for the sake of an example.
def on_start(self):
time.sleep(5)
@task
def say_hello(self):
self.client.publish("hello/locust", b"hello world locust custom client")
```
#### NOTE
MqttUser is experimental and may change without notice.
## Other examples
See [locust-plugins](https://github.com/SvenskaSpel/locust-plugins#users) it has users for Kafka, Selenium/WebDriver, Playwright and more.
---
# Increase performance with a more efficient HTTP client
Locust’s default HTTP client uses [python-requests](https://requests.readthedocs.io/).
It provides a nice API that many python developers are familiar with, and is very well-maintained. But if you’re planning to run tests with very high throughput and have limited hardware for running Locust, it is sometimes not efficient enough.
Because of this, Locust also comes with `FastHttpUser` which
uses [geventhttpclient](https://github.com/gwik/geventhttpclient/) instead.
It provides a very similar API and uses significantly less CPU time, sometimes increasing the maximum number of requests per second on a given hardware by as much as 5x-6x.
It is impossible to say how many requests Locust can do on your particular hardware, using your particular test plan, so you’ll need to test it out. Check Locust’s console output, it will log a warning if it is limited by CPU.
In a *best case* scenario (doing small requests inside a `while True`-loop) a single Locust process (limited to one CPU core) can do around **16000 requests per second using FastHttpUser, and 4000 using HttpUser** (tested on a 2021 M1 MacBook Pro and Python 3.11)
The relative improvement may be even bigger with bigger request payloads, but it may also be smaller if your test is doing CPU intensive things not related to requests.
Of course, in reality, you should run one locust process per CPU core.
#### NOTE
As long as your load generator CPU is not overloaded, FastHttpUser’s response times should be almost identical to those of HttpUser. It does not make individual requests faster.
## How to use FastHttpUser
Just subclass FastHttpUser instead of HttpUser:
```
from locust import task, FastHttpUser
class MyUser(FastHttpUser):
@task
def index(self):
response = self.client.get("/")
```
## Concurrency
A single FastHttpUser/geventhttpclient session can run concurrent requests, you just have to launch greenlets for each request:
```
@task
def t(self):
def concurrent_request(url):
self.client.get(url)
pool = gevent.pool.Pool()
urls = ["/url1", "/url2", "/url3"]
for url in urls:
pool.spawn(concurrent_request, url)
pool.join()
```
#### NOTE
FastHttpUser/geventhttpclient is very similar to HttpUser/python-requests, but sometimes there are subtle differences. This is particularly true if you work with the client library’s internals, e.g. when manually managing cookies.
## REST
FastHttpUser provides a `rest` method for testing REST/JSON HTTP interfaces. It is a wrapper for `self.client.request` that:
* Parses the JSON response to a dict called `js` in the response object. Marks the request as failed if the response was not valid JSON.
* Defaults `Content-Type` and `Accept` headers to `application/json`
* Sets `catch_response=True` (so always use a with-block)
* Catches any unhandled exceptions thrown inside your with-block, marking the sample as failed (instead of exiting the task immediately without even firing the request event)
```python
from locust import task, FastHttpUser
class MyUser(FastHttpUser):
@task
def t(self):
with self.rest("POST", "/", json={"foo": 1}) as resp:
if resp.js is None:
pass # no need to do anything, already marked as failed
elif "bar" not in resp.js:
resp.failure(f"'bar' missing from response {resp.text}")
elif resp.js["bar"] != 42:
resp.failure(f"'bar' had an unexpected value: {resp.js['bar']}")
```
For a complete example, see [rest.py](https://github.com/locustio/locust/blob/master/examples/rest.py). That also shows how you can use inheritance to provide behaviors specific to your REST API that are common to multiple requests/testplans.
#### NOTE
This feature is new and details of its interface/implementation may change in new versions of Locust.
## Connection Handling
By default, a User will reuse the same TCP/HTTP connection (unless it breaks somehow). To more realistically simulate new browsers connecting to your application this connection can be manually closed.
```python
@task
def t(self):
self.client.client.clientpool.close() # self.client.client is not a typo
self.client.get("/") # Here a new connection will be created
```
## API
### FastHttpUser class
### *class* FastHttpUser(environment)
FastHttpUser provides the same API as HttpUser, but uses geventhttpclient instead of python-requests
as its underlying client. It uses considerably less CPU on the load generator, and should work
as a simple drop-in-replacement in most cases.
#### client_pool *= None*
HTTP client pool to use. If not given, a new pool is created per single user.
For example, to have all instances of MyUser share a single HTTP client pool with concurrency of 5, you would do:
```python
from geventhttpclient.client import HTTPClientPool
class MyUser(FastHttpUser):
client_pool = HTTPClientPool(concurrency=5)
```
#### concurrency *= 10*
Parameter passed to FastHttpSession. Describes number of concurrent requests allowed by the FastHttpSession. Default 10.
Note that setting this value has no effect when custom client_pool was given, and you need to spawn a your own gevent pool
to use it (as Users only have one greenlet). See test_fasthttp.py / test_client_pool_concurrency for an example.
#### connection_timeout *= 60.0*
Parameter passed to FastHttpSession
#### insecure *= True*
Parameter passed to FastHttpSession. Default True, meaning no SSL verification.
#### max_redirects *= 30*
Parameter passed to FastHttpSession.
#### max_retries *= 0*
Parameter passed to FastHttpSession.
#### network_timeout *= 60.0*
Parameter passed to FastHttpSession
#### proxy_host *= None*
Parameter passed to FastHttpSession
#### proxy_port *= None*
Parameter passed to FastHttpSession
#### rest(method, url, headers=None, \*\*kwargs)
A wrapper for self.client.request that:
* Parses the JSON response to a dict called `js` in the response object. Marks the request as failed if the response was not valid JSON.
* Defaults `Content-Type` and `Accept` headers to `application/json`
* Sets `catch_response=True` (so always use a with-block)
* Catches any unhandled exceptions thrown inside your with-block, marking the sample as failed (instead of exiting the task immediately without even firing the request event)
#### rest_(method, url, name=None, \*\*kwargs)
Some REST api:s use a timestamp as part of their query string (mainly to break through caches).
This is a convenience method for that, appending a \_= parameter automatically
### FastHttpSession class
### *class* FastHttpSession(base_url, request_event, user, insecure=True, client_pool=None, ssl_context_factory=None, \*\*kwargs)
#### delete(url, \*\*kwargs)
Sends a DELETE request
#### get(url, \*\*kwargs)
Sends a GET request
#### head(url, \*\*kwargs)
Sends a HEAD request
#### options(url, \*\*kwargs)
Sends a OPTIONS request
#### patch(url, data=None, \*\*kwargs)
Sends a PATCH request
#### post(url, data=None, json=None, \*\*kwargs)
Sends a POST request
#### put(url, data=None, \*\*kwargs)
Sends a PUT request
#### request(method, url, name=None, data=None, catch_response=False, stream=False, headers=None, auth=None, json=None, allow_redirects=True, context={}, \*\*kwargs)
Send an HTTP request
* **Parameters:**
* **method** – method for the new `Request` object.
* **url** – path that will be concatenated with the base host URL that has been specified.
Can also be a full URL, in which case the full URL will be requested, and the base host
is ignored.
* **name** – (optional) An argument that can be specified to use as label in Locust’s
statistics instead of the URL path. This can be used to group different URL’s
that are requested into a single entry in Locust’s statistics.
* **catch_response** – (optional) Boolean argument that, if set, can be used to make a request
return a context manager to work as argument to a with statement. This will allow the
request to be marked as a fail based on the content of the response, even if the response
code is ok (2xx). The opposite also works, one can use catch_response to catch a request
and then mark it as successful even if the response code was not (i.e. 500 or 404).
* **data** – (optional) String/bytes to send in the body of the request.
* **json** – (optional) Json to send in the body of the request.
Automatically sets Content-Type and Accept headers to “application/json”.
Only used if data is not set.
* **headers** – (optional) Dictionary of HTTP Headers to send with the request.
* **auth** – (optional) Auth (username, password) tuple to enable Basic HTTP Auth.
* **stream** – (optional) If set to true the response body will not be consumed immediately
and can instead be consumed by accessing the stream attribute on the Response object.
Another side effect of setting stream to True is that the time for downloading the response
content will not be accounted for in the request time that is reported by Locust.
* **allow_redirects** – (optional) Set to True by default.
* **Returns:**
A `FastResponse` object if catch_response is False, and
`ResponseContextManager` if True.
### *class* FastResponse(ghc_response, request=None, sent_request=None)
#### *property* content
Unzips if necessary and buffers the received body. Careful with large files!
#### headers
Dict like object containing the response headers
#### json()
Parses the response as json and returns a dict
#### *property* text
Returns the text content of the response as a decoded string
---
# Event hooks
Locust comes with a number of event hooks that can be used to extend Locust in different ways.
For example, here’s how to set up an event listener that will trigger after a request is completed:
```
from locust import events
@events.request.add_listener
def my_request_handler(request_type, name, response_time, response_length, response,
context, exception, start_time, url, **kwargs):
if exception:
print(f"Request to {name} failed with exception {exception}")
else:
print(f"Successfully made a request to: {name}")
print(f"The response was {response.text}")
```
#### NOTE
In the above example the wildcard keyword argument (\*\*kwargs) will be empty, because we’re handling all arguments, but it prevents the code from breaking if new arguments are added in some future version of Locust.
Also, it is entirely possible to implement a client that does not supply all parameters for this event.
For example, non-HTTP protocols might not even have the a concept of url or response object.
Remove any such missing field from your listener function definition or use default arguments.
When running locust in distributed mode, it may be useful to do some setup on worker nodes before running your tests.
You can check to ensure you aren’t running on the master node by checking the type of the node’s `runner`:
```
from locust import events
from locust.runners import MasterRunner
@events.test_start.add_listener
def on_test_start(environment, **kwargs):
if not isinstance(environment.runner, MasterRunner):
print("Beginning test setup")
else:
print("Started test from Master node")
@events.test_stop.add_listener
def on_test_stop(environment, **kwargs):
if not isinstance(environment.runner, MasterRunner):
print("Cleaning up test data")
else:
print("Stopped test from Master node")
```
You can also use events [to add custom command line arguments](https://github.com/locustio/locust/tree/master/examples/add_command_line_argument.py).
To see a full list of available events see events.
## Request context
The `request event` has a context parameter that enable you to pass data about the request (things like username, tags etc). It can be set directly in the call to the request method or at the User level, by overriding the User.context() method.
Context from request method:
```
class MyUser(HttpUser):
@task
def t(self):
self.client.post("/login", json={"username": "foo"})
self.client.get("/other_request", context={"username": "foo"})
@events.request.add_listener
def on_request(context, **kwargs):
if context:
print(context["username"])
```
Context from User instance:
```
class MyUser(HttpUser):
def context(self):
return {"username": self.username}
@task
def t(self):
self.username = "foo"
self.client.post("/login", json={"username": self.username})
@events.request.add_listener
def on_request(context, **kwargs):
print(context["username"])
```
Context from a value in the response, using catch_response:
```
with self.client.get("/", catch_response=True) as resp:
resp.request_meta["context"]["requestId"] = resp.json()["requestId"]
```
#### NOTE
Request context doesn’t change how Locust’s regular statistics are calculated.
## Adding Web Routes
Locust uses Flask to serve the web UI and therefore it is easy to add web end-points to the web UI.
By listening to the `init` event, we can retrieve a reference
to the Flask app instance and use that to set up a new route:
```
from locust import events
@events.init.add_listener
def on_locust_init(environment, **kw):
if environment.web_ui:
@environment.web_ui.app.route("/added_page")
def my_added_page():
return "Another page"
```
You should now be able to start locust and browse to [http://127.0.0.1:8089/added_page](http://127.0.0.1:8089/added_page). Note that it doesn’t get automatically added as a new tab - you’ll need to enter the URL directly.
The `if environment.web_ui` guard is needed when running workers, headless mode, or `--processes`,
where the Web UI only exists in the master process.
## Extending Web UI
As an alternative to adding simple web routes, you can use [Flask Blueprints](https://flask.palletsprojects.com/en/1.1.x/blueprints/) and [templates](https://flask.palletsprojects.com/en/1.1.x/tutorial/templates/) to not only add routes but also extend
the web UI to allow you to show custom data along side the built-in Locust stats. This is more advanced but can
greatly enhance the utility and customizability of the web UI.
Working examples of extending the web UI can be found
in the [examples directory](https://github.com/locustio/locust/tree/master/examples) of the Locust
source code.
* `extend_modern_web_ui.py`: Display a table with content-length for each call.
* `web_ui_cache_stats.py`: Display Varnish Hit/Miss stats for each call. This could easily be extended to other CDN or cache proxies and gather other cache statistics such as cache age, control, …
> 
## Adding Authentication to the Web UI
Locust uses [Flask-Login](https://pypi.org/project/Flask-Login/) to handle authentication when the `--web-login` flag is present.
The `login_manager` is exposed on `environment.web_ui.app`, allowing the flexibility for you to implement any kind of auth that
you would like!
To use username / password authentication, simply provide a `username_password_callback` to the `environment.web_ui.auth_args`.
You are responsible for defining the route for the callback and implementing the authentication.
Authentication providers can additionally be configured to allow authentication from 3rd parties such as GitHub or an SSO provider.
Simply provide a list of desired `auth_providers`. You may specify the `label` and `icon` for display on the button.
The `callback_url` will be the url that the button directs to. You will be responsible for defining the callback route as
well as the authentication with the 3rd party.
Whether you are using username / password authentication, an auth provider, or both, a `user_loader` needs to be proivded
to the `login_manager`. The `user_loader` should return `None` to deny authentication or return a User object when
authentication to the app should be granted.
To display errors on the login page, such as an incorrect username / password combination, you may store the `auth_error`
on the session object: `session["auth_error"] = "Incorrect username or password"`. If you have non-erroneous information
you would like to display to the user, you can opt instead to set `auth_info` on the session object:
`session["auth_info"] = "Successfully created new user!"`
A full example can be seen [in the auth example](https://github.com/locustio/locust/tree/master/examples/web_ui_auth/basic.py).
In certain situations you may wish to further extend the fields present in the auth form. To achieve this, pass a `custom_form` dict
to the `environment.web_ui.auth_args`. In this case, the fields will be represented by a list of `inputs`, the callback url is
configured by the `custom_form.callback_url`, and the submit button may optionally be configured using the `custom_form.submit_button_text`.
The fields in the auth form may be a text, select, checkbox, or secret password field. You may additionally override the HTML input type for
specific field validation (e.g. type=email).
For a full example see [configuring the custom_form in the auth example](https://github.com/locustio/locust/tree/master/examples/web_ui_auth/custom_form.py).
## Run a background greenlet
Because a locust file is “just code”, there is nothing preventing you from spawning your own greenlet to
run in parallel with your actual load/Users.
For example, you can monitor the fail ratio of your test and stop the run if it goes above some threshold:
```python
import gevent
from locust import events
from locust.runners import STATE_STOPPING, STATE_STOPPED, STATE_CLEANUP, MasterRunner, LocalRunner
def checker(environment):
while not environment.runner.state in [STATE_STOPPING, STATE_STOPPED, STATE_CLEANUP]:
time.sleep(1)
if environment.runner.stats.total.fail_ratio > 0.2:
print(f"fail ratio was {environment.runner.stats.total.fail_ratio}, quitting")
environment.runner.quit()
return
@events.init.add_listener
def on_locust_init(environment, **_kwargs):
# dont run this on workers, we only care about the aggregated numbers
if isinstance(environment.runner, MasterRunner) or isinstance(environment.runner, LocalRunner):
gevent.spawn(checker, environment)
```
## Parametrizing locustfiles
There are two main ways to parametrize your locustfile.
### Basic environment variables
Like with any program, you can use environment variables:
On linux/mac:
```bash
MY_FUNKY_VAR=42 locust ...
```
On windows:
```bash
SET MY_FUNKY_VAR=42
locust ...
```
… and then access them in your locustfile.
```python
import os
print(os.environ['MY_FUNKY_VAR'])
```
### Custom arguments
You can add your own command line arguments to Locust, using the `init_command_line_parser` Event. Custom arguments are also presented and editable in the web UI.
If choices are specified for the argument, they will be presented as a dropdown in the web UI.
If is_multiple is set to True together with choices, the argument will be presented as a multi-select dropdown in the web UI.
```python
from locust import HttpUser, events, task
@events.init_command_line_parser.add_listener
def _(parser):
parser.add_argument("--my-argument", type=str, env_var="LOCUST_MY_ARGUMENT", default="", help="It's working")
# Choices will validate command line input and show a dropdown in the web UI
parser.add_argument("--env", choices=["dev", "staging", "prod"], default="dev", help="Environment")
# In combination with choices, is_multiple makes the dropdown in the web UI allow multiple selections
parser.add_argument("--endpoints", choices=["a", "b", "c"], is_multiple=True, default=["a"], help="Endpoints")
# Set `include_in_web_ui` to False if you want to hide from the web UI
parser.add_argument("--my-ui-invisible-argument", include_in_web_ui=False, default="I am invisible")
# Set `is_secret` to True if you want the text input to be password masked in the web UI
parser.add_argument("--my-ui-password-argument", is_secret=True, default="I am a secret")
# Use a boolean default value if you want the input to be a checkmark
parser.add_argument("--my-ui-boolean-argument", default=True)
# Set `is_required` to mark a form field as required
parser.add_argument("--my-ui-required-argument", is_required=True, default="I am required")
@events.test_start.add_listener
def _(environment, **kw):
print(f"Custom argument supplied: {environment.parsed_options.my_argument}")
class WebsiteUser(HttpUser):
@task
def my_task(self):
print(f"my_argument={self.environment.parsed_options.my_argument}")
print(f"my_ui_invisible_argument={self.environment.parsed_options.my_ui_invisible_argument}")
```
When running Locust distributed, custom arguments are automatically forwarded to workers when the run is started (but not before then, so you cannot rely on forwarded arguments *before* the test has actually started).
## Test data management
There are a number of ways to get test data into your tests (after all, your test is just a Python program and it can do whatever Python can). Locust’s events give you fine-grained control over *when* to fetch/release test data. You can find a [detailed example here](https://github.com/locustio/locust/tree/master/examples/test_data_management.py).
## Per-request CSV logging
The built-in `--csv` flag exports only **aggregated** statistics. For post-run analysis that requires individual data-points (outlier detection, per-endpoint latency histograms, failure forensics, Pandas/BI pipelines), use `CsvRequestLogger`:
```python
from locust import HttpUser, task, events
from locust.contrib.csv_request_logger import CsvRequestLogger
logger = CsvRequestLogger("results/requests.csv")
@events.init.add_listener
def on_locust_init(environment, **kwargs):
logger.register(environment)
class MyUser(HttpUser):
@task
def index(self):
self.client.get("/")
```
This writes one CSV row per completed request with columns: `timestamp`, `request_type`, `name`, `response_time_ms`, `response_length`, `status_code`, and `exception`. The logger works with any protocol (HTTP, WebSocket, gRPC, custom) and closes automatically on shutdown. See `locust.contrib.csv_request_logger` for full API documentation.
## More examples
See [locust-plugins](https://github.com/SvenskaSpel/locust-plugins#listeners)
---
# Logging
Locust uses Python’s [built in logging framework](https://docs.python.org/3/library/logging.html) for
handling logging.
The default logging configuration that Locust applies, writes log messages directly to stderr. `--loglevel`
and `--logfile` can be used to change the verbosity and/or make the log go to a file instead.
The default logging configuration installs handlers for the `root` logger as well as the `locust.*` loggers,
so using the root logger in your own test scripts will put the message into the log file if `--logfile` is used:
```python
import logging
logging.info("this log message will go wherever the other locust log messages go")
```
It’s also possible to control the whole logging configuration in your own test scripts by using the
`--skip-log-setup` option. You will then have to
[configure the logging](https://docs.python.org/3/library/logging.config.html) yourself.
## Options
### `--skip-log-setup`
Disable Locust’s logging setup. Instead, the configuration is provided by the Locust test or Python defaults.
### `--loglevel`
Choose between DEBUG/INFO/WARNING/ERROR/CRITICAL. Default is INFO. The short-hand version is `-L`.
### `--logfile`
Path to log file. If not set, log will go to stdout/stderr.
## Locust loggers
Here’s a table of the loggers used within Locust (for reference when configuring logging settings manually):
| Logger name | Purpose |
|---------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|
| locust | The locust namespace is used for all loggers such as `locust.main`, `locust.runners`, etc. |
| locust.stats_logger | This logger is used to periodically print the current stats to the console. The stats does *not* go into the log file when `--logfile` is used by default. |
---
# Using Locust as a library
It is possible to start a load test from your own Python code, instead of running Locust using the `locust` command.
Start by creating an `Environment` instance:
```python
from locust.env import Environment
env = Environment(user_classes=[MyTestUser])
```
The `Environment` instance’s
`create_local_runner`,
`create_master_runner` can then be used to start a
`Runner` instance, which can be used to start a load test:
```python
env.create_local_runner()
env.runner.start(5000, spawn_rate=20)
env.runner.greenlet.join()
```
It is also possible to bypass the dispatch and distribution logic, and manually control the spawned users:
```python
new_users = env.runner.spawn_users({MyUserClass.__name__: 2})
new_users[1].my_custom_token = "custom-token-2"
new_users[0].my_custom_token = "custom-token-1"
```
The above example only works on standalone/local runner mode and is an experimental feature. A more common/better approach would be to use `init` or `test_start` events to get/create a list of tokens and use on-start-on-stop to read from that list and set them on your individual User instances.
#### NOTE
While it is possible to create locust workers this way (using `create_worker_runner`), that almost never makes sense. Every worker needs to be in a separate Python process and interacting directly with the worker runner might break things. Just launch workers using the regular `locust --worker ...` command.
We could also use the `Environment` instance’s
`create_web_ui` method to start a Web UI that can be used
to view the stats, and to control the runner (e.g. start and stop load tests):
```python
env.create_local_runner()
env.create_web_ui()
env.web_ui.greenlet.join()
```
## Skipping monkey patching
Some packages such as boto3 may have incompatibility when using Locust as a library, where monkey patching is already applied. In this case monkey patching may be disabled by setting `LOCUST_SKIP_MONKEY_PATCH=1` as env variable.
## Full example
```python
#!/usr/bin/env python3
from locust import HttpUser, events, task
from locust.env import Environment
from locust.log import setup_logging
from locust.stats import stats_history, stats_printer
import gevent
setup_logging("INFO")
class MyUser(HttpUser):
host = "https://docs.locust.io"
@task
def t(self):
self.client.get("/")
# setup Environment and Runner
env = Environment(user_classes=[MyUser], events=events)
runner = env.create_local_runner()
# start a WebUI instance
web_ui = env.create_web_ui("127.0.0.1", 8089)
# execute init event handlers (only really needed if you have registered any)
env.events.init.fire(environment=env, runner=runner, web_ui=web_ui)
# start a greenlet that periodically outputs the current stats
gevent.spawn(stats_printer(env.stats))
# start a greenlet that save current stats to history
gevent.spawn(stats_history, env.runner)
# start the test
runner.start(1, spawn_rate=10)
# in 30 seconds stop the runner
gevent.spawn_later(30, runner.quit)
# wait for the greenlets
runner.greenlet.join()
# stop the web server for good measures
web_ui.stop()
```
---
# OpenTelemetry Integration
Locust now optionally integrates with OpenTelemetry (OTel), enabling you to automatically export traces,
metrics, and logs from your load tests to any OTel-compatible backend (OTLP, Prometheus, Jaeger, Tempo, etc.).
This makes it easy to correlate load-test activity with application and infrastructure telemetry in your observability stack.
The configuration is done via environment variables. See the [OpenTelemetry documentation](https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/)
for details on how to configure exporters, resource attributes, sampling, etc.
## Setup
To enable OpenTelemetry, you need to download `locust` with the OpenTelemetry dependencies:
```console
$ pip install locust[otel]
```
Then, pass the command line argument `--otel` to enable OpenTelemetry:
```console
$ locust --otel
...
```
If you’re using Docker, the `locustio/locust-otel` image has the OpenTelemetry dependencies pre-installed.
See running-in-docker for details.
## Exporters
Locust supports the following OpenTelemetry exporters, for traces, metrics, and logs, out of the box:
- OTLP (gRPC and HTTP) - this is the default exporter using gRPC protocol
- Console (useful for debugging)
For traces, `BatchSpanProcessor` is used and can be configured with these [variables](https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/#batch-span-processor).
For metrics, `PeriodicExportingMetricReader` is used and is configurable with the corresponding [variables](https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/#periodic-exporting-metricreader).
For logs, `BatchLogRecordProcessor` is used for OTLP and `SimpleLogRecordProcessor` for console export.
Set `OTEL_LOGS_EXPORTER` to configure log export (default is `otlp`).
## Auto Instrumentation
#### NOTE
Currently, only the `requests` library is auto-instrumented. This mean that only `HttpUser` will have it’s HTTP requests made during your load tests automatically generate spans,
and metrics with no additional configuration needed.
We plan to add auto-instrumentation for more libraries in future releases.
### Supported Users
| User Class | Instrumented Library |
|--------------|------------------------|
| `HttpUser` | `requests` |
If you need instrumentation for other libraries (e.g., database clients, messaging libraries), you can manually set up additional instrumentation
using the OpenTelemetry Python SDK as per the [OpenTelemetry Python documentation](https://opentelemetry.io/docs/instrumentation/python/).
## Example
```console
$ export OTEL_TRACES_EXPORTER=otlp
$ export OTEL_EXPORTER_OTLP_ENDPOINT=https://...
$ export OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
$ locust --otel
[2025-11-28 16:27:01,916] locust/INFO/locust.main: Starting Locust, OpenTelemetry enabled
[2025-11-28 16:27:01,916] locust/INFO/locust.main: Starting web interface at http://0.0.0.0:8089, press enter to open your default browser.
...
```
---
# Third party extensions
## Support for load testing other protocols, reporting etc
- [locust-plugins](https://github.com/SvenskaSpel/locust-plugins/)
- request logging & graphing
- new protocols like selenium/webdriver, http users that
load html page resources
- readers (ways to get test data into your tests)
- wait time (custom wait time functions)
- checks (adds command line parameters to set locust exit code based
on requests/s, error percentage and average response times)
## Automatically translate a browser recording (HAR-file) to a locustfile
- [har2locust](https://github.com/SvenskaSpel/har2locust)
## Workers written in other languages than Python
A Locust master and a Locust worker communicate by exchanging
[msgpack](http://msgpack.org/) messages, which is supported by many
languages. So, you can write your User tasks in any languages you like.
For convenience, some libraries do the job as a worker runner. They run
your User tasks, and report to master regularly.
- [Boomer](https://github.com/myzhan/boomer/) - Go
- [Locust4j](https://github.com/myzhan/locust4j) - Java
---
# Kubernetes Operator
The Locust Operator for Kubernetes is an operator that manages the lifecyle of running-distributed inside a Kubernetes cluster.
It is a Custom Resource Definition (CRD) and a Controller that run on your Kubernetes cluster and allow you to create and manage your Locust tests as Kubernetes resources.
Automatically creates master/worker jobs, mounts locustfiles, exposes the web UI, collects metrics, and handles restarts when the spec changes.
## Installation
### Helm Charts
[Helm](https://helm.sh/) is a package manager for Kubernetes that installs and manages Kubernetes applications.
1. Add the Helm repository
```bash
$ helm repo add locust-operator https://locustio.github.io/k8s-operator
"locust-operator" has been added to your repositories
$ helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "locust-operator" chart repository
Update Complete. ⎈Happy Helming!⎈
```
1. Install Locust Operator for Kubernetes
```bash
$ helm install locust-operator locust-operator/locust-operator \
--namespace locust-operator --create-namespace
```
1. Check that the operator is running and the CRD is installed
```bash
$ kubectl get pods -A -l app.kubernetes.io/name=locust-operator
NAMESPACE NAME READY STATUS RESTARTS AGE
locust-operator locust-operator-xxxxxxxxx-xxxxx 1/1 Running 0 18s
$ kubectl get crd [none 🚀]
NAME CREATED AT
locusttests.locust.io ...
```
### Manifest Files
Locust Operator for Kubernetes can be installed using raw manifest files with [kubectl](https://kubernetes.io/docs/reference/kubectl/).
To generate the raw resources, you can use the Helm chart and output the manifests without installing them.
```bash
$ helm repo add locust-operator https://locustio.github.io/k8s-operator
$ helm repo update
$ helm template locust-operator locust-operator/locust-operator \
--namespace locust-operator > locust-operator.yaml
```
Then apply the generated manifest file:
```bash
$ kubectl apply -f locust-operator.yaml
```
## Quickstart
1. Create a YAML file defining a `LocustTest` resource.
```yaml
apiVersion: locust.io/v1
kind: LocustTest
metadata:
name: load-test
spec:
workers: 2
locustfile:
content: |
from locust import HttpUser, task
class TestUser(HttpUser):
@task
def index(self):
self.client.get("/")
```
1. Apply it using `kubectl apply -f .yaml`.
2. You can verify the created resource using `kubectl get locusttests` and `kubectl get pods` to see the master and worker pods.
```bash
$ kubectl apply -f locust-test.yaml
locusttest.locust.io/load-test created
$ kubectl get locusttests
NAME STATE WORKERS FAIL_RATIO RPS USERS AGE
load-test READY 2/2 0% 0 0 30s
$ kubectl get pods -l locust.io/test-run=load-test
NAME READY STATUS RESTARTS AGE
load-test-master-xxxxx 1/1 Running 0 35s
load-test-worker-xxxxx 1/1 Running 0 35s
load-test-worker-xxxxx 1/1 Running 0 35s
```
1. Access the Locust web UI by port-forwarding the service to your local machine. Then open your browser and navigate to http://localhost:8089.
```bash
$ kubectl port-forward svc/load-test-webui 8089:8089
Forwarding from 127.0.0.1:8089 -> 8089
Forwarding from [::1]:8089 -> 8089
```
1. You can see the master logs by running:
```bash
# Tailing the master pod logs directly
$ kubectl logs -f pod/load-test-master-xxxxx
# Using a selector to follow the master pod by labels
$ kubectl logs -f -l locust.io/test-run=load-test,locust.io/component=master
```
1. Cleanup by deleting the `LocustTest` resource (this will also delete all managed resources):
```bash
$ kubectl delete loadtest load-test
```
## LocustTest CRD Configuration
### General
`spec.image` (string, required, default: `locustio/locust:latest`)
> Container image for master and workers pods.
`spec.workers` (integer, required, default: `1`)
> Number of worker pods to run.
`spec.args` (string, optional)
> Additional CLI flags, e.g. `--run-time=5m --users=200 --spawn-rate=20`.
`spec.env` (array, optional)
> List of environment variables to set in the container.
### Locustfile source
`spec.locustfile` (object, optional; choose **one**)
* `content` (string): inline locustfile.py
* `configMap`: reference an existing ConfigMap
* `name` (string, required)
* `key` (string, default: `locustfile.py`)
* Built into image
The image contains a locustfile.
If the filename isn’t `locustfile.py` (default locustfile name), pass `-f ` via `spec.args`.
### Metadata
`spec.labels` / `spec.annotations` (object, optional)
> User-provided labels/annotations merged onto all managed resources.
### Per-role overrides
This allows customizing **master** and **worker** pods separately.
`spec.master` / `spec.worker` (object, optional)
* `labels` / `annotations` (object, optional)
* `resources` (object, optional).
Example:
: ```yaml
master:
labels:
my.custom.label/is-locust-master: "true"
resources:
requests:
cpu: "500m"
memory: "256Mi"
limits:
cpu: "1"
memory: "512Mi"
```
### Extended
`spec.imagePullPolicy` (string, optional)
> The [image pull policy](https://kubernetes.io/docs/concepts/containers/images/#image-pull-policy) for master/worker pods.
`spec.imagePullSecrets` (array, optional)
> The [image pull secrets](https://kubernetes.io/docs/concepts/containers/images/#specifying-imagepullsecrets-on-a-pod) for master/worker pods.
> e.g. `[{ name: my-regcred }]` for private registries.
## Examples
### Inline locustfile
```yaml
apiVersion: locust.io/v1
kind: LocustTest
metadata:
name: load-test-v1
spec:
image: locustio/locust:latest
workers: 5
args:
--host http://my.site.com/api/v1
--run-time=10m
--users=500
--spawn-rate=50
env:
- name: LOCUST_LOGLEVEL
value: INFO
locustfile:
content: |
from locust import HttpUser, task
class TestUser(HttpUser):
@task
def index(self):
self.client.get("/")
```
### External ConfigMap locustfile
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: v2-locustfile
data:
mytest.py: |
from locust import HttpUser, task
class PingUser(HttpUser):
@task
def ping(self):
self.client.get("/ping")
---
apiVersion: locust.io/v1
kind: LocustTest
metadata:
name: load-test-v2
spec:
workers: 5
args:
-f mytest.py
--host http://my.site.com/api/v2
--run-time=10m
--users=500
--spawn-rate=50
locustfile:
configMap:
name: v2-locustfile
```
### Custom Master/Worker pod configuration
```yaml
apiVersion: locust.io/v1
kind: LocustTest
metadata:
name: locust-test
spec:
labels:
# Merged into all managed resources labels
my.custom.label/group: group1
image: my-private-registry/custom-image:1.0.0
imagePullSecrets:
- name: regcred
workers: 5
master:
annotations:
# Merged into master pod annotations
my.custom.annotations/version: "1.0.0"
labels:
# Merged into master pod labels
my.custom.label/is-locust-master: "true"
resources:
requests:
cpu: "500m"
memory: "512Mi"
limits:
cpu: "1"
memory: "1Gi"
worker:
annotations:
# Merged into worker pod annotations
my.custom.annotations/version: "1.0.0"
labels:
# Merged into worker pod labels
my.custom.label/is-locust-master: "false"
resources:
requests:
cpu: "500m"
memory: "512Mi"
limits:
cpu: "1"
memory: "1Gi"
```
### Headless run
```yaml
apiVersion: locust.io/v1
kind: LocustTest
metadata:
name: headless-test
spec:
workers: 2
args:
--host http://locust.io/
--headless
--run-time=5m
--users=300
--spawn-rate=30
locustfile:
content: |
from locust import HttpUser, task
class TestUser(HttpUser):
@task
def index(self):
self.client.get("/")
```
## Upgrade
### Helm
```bash
$ helm repo update
$ helm upgrade locust-operator locust-operator/locust-operator \
--namespace locust-operator \
--reuse-values
```
Helm [does not support updating or deleting CRDs](https://helm.sh/docs/chart_best_practices/custom_resource_definitions/#some-caveats-and-explanations).
You may need to update the CRD manually when upgrading the operator.
```bash
$ kubectl apply -f https://raw.githubusercontent.com/locustio/k8s-operator/refs/tags/helm-chart-/charts/locust-operator/crds/locusttest.yaml
```
## Uninstall
1. Delete all `LocustTest` resources (optional but recommended)
```bash
$ kubectl get locusttests --all-namespaces
$ kubectl delete locusttests --all --all-namespaces
```
### Helm
```bash
# Uninstall the Helm release
$ helm uninstall locust-operator --namespace locust-operator
# Remove the LocustTest CRD
$ kubectl delete crd locusttests.locust.io
```
### Manifest Files
```bash
$ kubectl delete -f locust-operator.yaml
```
---
# VS Code Extension
Microsoft maintains an excellent [VS Code extension](https://github.com/microsoft/azureloadtesting-extension/blob/main/README.md) based on Copilot that helps you create and run Locust tests.

Among other things, it can:
* Create locustfiles from .http files, Postman collections or Insomnia collections
* Run tests locally or scale up using [Azure Load Testing](https://learn.microsoft.com/azure/app-testing/load-testing/overview-what-is-azure-load-testing)
* Fetch insights and help implement performance suggestions based on test results
VIDEO
See also [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=ms-azure-load-testing.microsoft-testing) and [Quickstart: Create and run a load test with Visual Studio Code and GitHub Copilot](https://learn.microsoft.com/azure/app-testing/load-testing/quickstart-create-run-load-tests-from-visual-studio-code).
---
# aiolocust / asyncio
[aiolocust](https://github.com/cyberw/aiolocust) is a 2026 reimagining of Locust.
It uses [asyncio](https://docs.python.org/3/library/asyncio.html)/[aiohttp](https://docs.aiohttp.org/en/stable/) instead of gevent/requests and leverages modern Python.
```python
import asyncio
from aiolocust import HttpUser
async def run(user: HttpUser):
async with user.client.get("http://example.com/") as resp:
pass
async with user.client.get("http://example.com/") as resp:
# extra validation, not just HTTP response code:
assert "expected text" in await resp.text()
await asyncio.sleep(0.1)
```
It has less features than Locust and aims to be more minimalistic, relying on OTEL for more complex visualization and analysis.
It is currently maintained as [a separate project](https://github.com/cyberw/aiolocust), but may be merged and/or shipped with Locust in the future.
---
# Developing and Documenting Locust
You want to contribute to Locust? Great! Here is a list of [open bugs/feature requests](https://github.com/locustio/locust/issues).
## Install Locust for development
Fork Locust on [GitHub](https://github.com/locustio/locust/) and then
```sh
# clone the repo:
$ git clone git://github.com//locust.git
# install the `uv` build system, https://docs.astral.sh/uv/getting-started/installation/
# [optional] create a virtual environment and activate it
$ uv venv
$ . .venv/bin/activate
# perform an editable install of the "locust" package along with the dev and test packages:
$ uv sync
```
Now the `uv --directory locust run locust` command will run *your* code (with no need for reinstalling after making changes). If you have installed the project to a virtual environment, you can simply call locust.
To contribute your changes, push to a branch in your repo and then [open a PR on github](https://github.com/locustio/locust/compare).
If you install [pre-commit](https://pre-commit.com/), linting and format checks/fixes will be automatically performed before each commit.
Before you open a pull request, make sure all the tests work. And if you are adding a feature, make sure it is documented (in `docs/*.rst`).
If you’re in a hurry or don’t have access to a development environment, you can simply use [Codespaces](https://github.com/features/codespaces), the github cloud development environment. On your fork page, just click on *Code* then on *Create codespace on *, and voila, your ready to code and test.
## Testing your changes
We use [hatch](https://hatch.pypa.io/1.13/) to automate tests across multiple Python versions.
All tests:
```console
$ hatch test
...
py39: commands[1]> pytest locust/test
...
```
You can also run these tests against a specific Python version
```console
$ hatch test -py=3.10
...
py39: commands[1]> pytest locust/test
...
```
To only run a specific suite or specific test you can call [pytest](https://docs.pytest.org/) directly.
All tests:
```console
$ pytest locust/test
```
Individual test:
```console
$ pytest locust/test/test_main.py::DistributedIntegrationTests::test_distributed_tags
```
## Debugging
See: running-in-debugger.
## Formatting and linting
Locust uses [ruff](https://github.com/astral-sh/ruff/) for formatting and linting. The build will fail if code does not adhere to it. If you run vscode it will automatically run every time you save a file, but if your editor doesn’t support it you can run it manually:
```console
$ ruff check --fix
$ ruff format
```
You can validate the whole project using hatch:
```console
$ hatch run lint:format
ruff: commands[0]> ruff check .
ruff: commands[1]> ruff format --check
104 files already formatted
ruff: OK (1.41=setup[1.39]+cmd[0.01,0.01] seconds)
congratulations :) (1.47 seconds)
```
## Build documentation
The documentation source is in the [docs/](https://github.com/locustio/locust/tree/master/docs/) directory. To build the documentation you’ll need to [Install Locust for development]() then
1. Install the documentation requirements:
> ```console
> $ uv sync --all-groups
> ```
2. Build the documentation locally:
> ```console
> $ make build_docs
> ```
View your generated documentation by opening `docs/_build/index.html` or running make serve_docs
## Making changes to Locust’s Web UI
The Web UI is built using React and Typescript
### Setup
#### Node
Install node using nvm to easily switch between node version
- Copy and run the install line from [nvm](https://github.com/nvm-sh/nvm) (starts with curl/wget …)
- Verify nvm was installed correctly
```console
$ nvm --version
```
- Install the proper Node version according to engines in the `locust/webui/package.json`
```console
$ nvm install {version}
$ nvm alias default {version}
```
#### Yarn
- Install Yarn from their official website (avoid installing through Node if possible)
- Verify yarn was installed correctly
```console
$ yarn --version
```
- Next, install all dependencies
```console
$ cd locust/webui
$ yarn
```
### Developing
To develop while running a locust instance, run `yarn watch`. This will output the static files to the `dist` directory. Vite will automatically detect any changed files and re-build as needed. Simply refresh the page to view the changes
In certain situations (usually when styling), you may want to develop the frontend without running a locust instance. Running `yarn dev` will start the Vite dev server and allow for viewing your changes.
To compile the webui, run `yarn build`
The frontend can additionally be built using make:
```console
$ make frontend_build
```
### Linting
Run `yarn lint` to detect lint failures in the frontend project. Running `yarn lint --fix` will resolve any issues that are automatically resolvable. Your IDE can additionally be configured with ESLint to resolve these issues on save.
### Formatting
Run `yarn format` to fix any formatting issues in the frontend project. Once again your IDE can be configured to automatically format on save.
### Typechecking
We use Typescript in the frontend project. Run `yarn type-check` to find any issues.
---
# API
## User class
### *class* User(environment)
Represents a “user” which is to be spawned and attack the system that is to be load tested.
The behaviour of this user is defined by its tasks. Tasks can be declared either directly on the
class by using the `@task decorator` on methods, or by setting
the `tasks attribute`.
This class should usually be subclassed by a class that defines some kind of client. For
example when load testing an HTTP system, you probably want to use the
`HttpUser` class.
#### abstract *= True*
If abstract is True, the class is meant to be subclassed, and locust will not spawn users of this class during a test.
#### context()
Adds the returned value (a dict) to the context for request event.
Override this in your User class to customize the context.
#### environment
A reference to the `Environment` in which this user is running
#### fixed_count *= 0*
If the value > 0, the weight property will be ignored and the ‘fixed_count’-instances will be spawned.
These Users are spawned first. If the total target count (specified by the –users arg) is not enough
to spawn all instances of each User class with the defined property, the final count of each User is undefined.
#### on_start()
Called when a User starts running.
#### on_stop()
Called when a User stops running (is killed)
#### tasks *= []*
Collection of python callables and/or TaskSet classes that the Locust user(s) will run.
If tasks is a list, the task to be performed will be picked randomly.
If tasks is a *(callable,int)* list of two-tuples, or a {callable:int} dict,
the task to be performed will be picked randomly, but each task will be weighted
according to its corresponding int value. So in the following case, *ThreadPage* will
be fifteen times more likely to be picked than *write_post*:
```
class ForumPage(TaskSet):
tasks = {ThreadPage:15, write_post:1}
```
#### wait()
Make the running user sleep for a duration defined by the User.wait_time
function.
The user can also be killed gracefully while it’s sleeping, so calling this
method within a task makes it possible for a user to be killed mid-task even if you’ve
set a stop_timeout. If this behaviour is not desired, you should make the user wait using
gevent.sleep() instead.
#### wait_time()
Method that returns the time (in seconds) between the execution of locust tasks.
Can be overridden for individual TaskSets.
Example:
```
from locust import User, between
class MyUser(User):
wait_time = between(3, 25)
```
#### weight *= 1*
Probability of user class being chosen. The higher the weight, the greater the chance of it being chosen.
## HttpUser class
### *class* HttpUser(\*args, \*\*kwargs)
Represents an HTTP “user” which is to be spawned and attack the system that is to be load tested.
The behaviour of this user is defined by its tasks. Tasks can be declared either directly on the
class by using the `@task decorator` on methods, or by setting
the `tasks attribute`.
This class creates a *client* attribute on instantiation which is an HTTP client with support
for keeping a user session between requests.
#### abstract *= True*
If abstract is True, the class is meant to be subclassed, and users will not choose this locust during a test
#### client
Instance of HttpSession that is created upon instantiation of Locust.
The client supports cookies, and therefore keeps the session between HTTP requests.
#### tasks *= []*
Collection of python callables and/or TaskSet classes that the Locust user(s) will run.
If tasks is a list, the task to be performed will be picked randomly.
If tasks is a *(callable,int)* list of two-tuples, or a {callable:int} dict,
the task to be performed will be picked randomly, but each task will be weighted
according to its corresponding int value. So in the following case, *ThreadPage* will
be fifteen times more likely to be picked than *write_post*:
```
class ForumPage(TaskSet):
tasks = {ThreadPage:15, write_post:1}
```
#### wait_time()
## HttpSession class
### *class* HttpSession(base_url, request_event, user, \*args, pool_manager=None, \*\*kwargs)
Class for performing web requests and holding (session-) cookies between requests (in order
to be able to log in and out of websites). Each request is logged so that locust can display
statistics.
This is a slightly extended version of [python-request](https://requests.readthedocs.io/)’s
`requests.Session` class and mostly this class works exactly the same. However
the methods for making requests (get, post, delete, put, head, options, patch, request)
can now take a *url* argument that’s only the path part of the URL, in which case the host
part of the URL will be prepended with the HttpSession.base_url which is normally inherited
from a User class’ host attribute.
Each of the methods for making requests also takes two additional optional arguments which
are Locust specific and doesn’t exist in python-requests. These are:
* **Parameters:**
* **name** – (optional) An argument that can be specified to use as label in Locust’s statistics instead of the URL path.
This can be used to group different URL’s that are requested into a single entry in Locust’s statistics.
* **catch_response** – (optional) Boolean argument that, if set, can be used to make a request return a context manager
to work as argument to a with statement. This will allow the request to be marked as a fail based on the content of the
response, even if the response code is ok (2xx). The opposite also works, one can use catch_response to catch a request
and then mark it as successful even if the response code was not (i.e 500 or 404).
#### \_\_init_\_(base_url, request_event, user, \*args, pool_manager=None, \*\*kwargs)
#### delete(url, \*, data=None, json=None, \*\*kwargs)
Sends a DELETE request
#### get(url, \*, data=None, json=None, \*\*kwargs)
Sends a GET request
#### head(url, \*, data=None, json=None, \*\*kwargs)
Sends a HEAD request
#### options(url, \*, data=None, json=None, \*\*kwargs)
Sends a OPTIONS request
#### patch(url, data=None, \*, json=None, \*\*kwargs)
Sends a PATCH request
#### post(url, data=None, json=None, \*\*kwargs)
Sends a POST request
#### put(url, data=None, \*, json=None, \*\*kwargs)
Sends a PUT request
#### request(method, url, name=None, catch_response=False, context={}, \*, data=None, json=None, \*\*kwargs)
Constructs and sends a `requests.Request`.
Returns `requests.Response` object.
* **Parameters:**
* **method** – method for the new `Request` object.
* **url** – URL for the new `Request` object.
* **name** – (optional) An argument that can be specified to use as label in Locust’s statistics instead of the URL path.
This can be used to group different URL’s that are requested into a single entry in Locust’s statistics.
* **catch_response** – (optional) Boolean argument that, if set, can be used to make a request return a context manager
to work as argument to a with statement. This will allow the request to be marked as a fail based on the content of the
response, even if the response code is ok (2xx). The opposite also works, one can use catch_response to catch a request
and then mark it as successful even if the response code was not (i.e 500 or 404).
* **params** – (optional) Dictionary or bytes to be sent in the query string for the `Request`.
* **data** – (optional) Dictionary, list of tuples, bytes, or file-like object to send in the body of the `Request`.
* **json** – (optional) json to send in the body of the `Request`.
* **headers** – (optional) Dictionary of HTTP Headers to send with the `Request`.
* **cookies** – (optional) Dict or CookieJar object to send with the `Request`.
* **files** – (optional) Dictionary of `'filename': file-like-objects` for multipart encoding upload.
* **auth** – (optional) Auth tuple or callable to enable Basic/Digest/Custom HTTP Auth.
* **timeout** (*float* *or* *tuple*) – (optional) How long to wait for the server to send data before giving up, as a float, or a (connect timeout, read timeout) tuple.
* **allow_redirects** (*bool*) – (optional) Set to True by default.
* **proxies** – (optional) Dictionary mapping protocol or protocol and hostname to the URL of the proxy.
* **hooks** – (optional) Dictionary mapping hook name to one event or list of events, event must be callable.
* **stream** – (optional) whether to immediately download the response content. Defaults to `False`.
* **verify** – (optional) Either a boolean, in which case it controls whether we verify
the server’s TLS certificate, or a string, in which case it must be a path
to a CA bundle to use. Defaults to `True`. When set to
`False`, requests will accept any TLS certificate presented by
the server, and will ignore hostname mismatches and/or expired
certificates, which will make your application vulnerable to
man-in-the-middle (MitM) attacks. Setting verify to `False`
may be useful during local development or testing.
* **cert** – (optional) if String, path to ssl client cert file (.pem). If Tuple, (‘cert’, ‘key’) pair.
## FastHttpUser class
### *class* FastHttpUser(environment)
FastHttpUser provides the same API as HttpUser, but uses geventhttpclient instead of python-requests
as its underlying client. It uses considerably less CPU on the load generator, and should work
as a simple drop-in-replacement in most cases.
#### abstract *= True*
Dont register this as a User class that can be run by itself
#### client
Instance of FastHttpSession that is created upon instantiation of User.
The client support cookies, and therefore keeps the session between HTTP requests.
#### rest(method, url, headers=None, \*\*kwargs)
A wrapper for self.client.request that:
* Parses the JSON response to a dict called `js` in the response object. Marks the request as failed if the response was not valid JSON.
* Defaults `Content-Type` and `Accept` headers to `application/json`
* Sets `catch_response=True` (so always use a with-block)
* Catches any unhandled exceptions thrown inside your with-block, marking the sample as failed (instead of exiting the task immediately without even firing the request event)
#### tasks *= []*
Collection of python callables and/or TaskSet classes that the Locust user(s) will run.
If tasks is a list, the task to be performed will be picked randomly.
If tasks is a *(callable,int)* list of two-tuples, or a {callable:int} dict,
the task to be performed will be picked randomly, but each task will be weighted
according to its corresponding int value. So in the following case, *ThreadPage* will
be fifteen times more likely to be picked than *write_post*:
```
class ForumPage(TaskSet):
tasks = {ThreadPage:15, write_post:1}
```
#### wait_time()
## MqttUser class
## SocketIOUser class
### *class* SocketIOUser(\*args, \*\*kwargs)
SocketIOUser creates an instance of `socketio.Client` to log requests.
See example in [examples/socketio/socketio_ex.py](https://github.com/locustio/locust/blob/master/examples/socketio/socketio_ex.py).
#### options *= {}*
socketio.Client options, e.g. {“reconnection_attempts”: 1, “reconnection_delay”: 2, “logger”: True, “engineio_logger”: True}
## FastHttpSession class
### *class* FastHttpSession(base_url, request_event, user, insecure=True, client_pool=None, ssl_context_factory=None, \*\*kwargs)
#### \_\_init_\_(base_url, request_event, user, insecure=True, client_pool=None, ssl_context_factory=None, \*\*kwargs)
#### delete(url, \*\*kwargs)
Sends a DELETE request
#### get(url, \*\*kwargs)
Sends a GET request
#### head(url, \*\*kwargs)
Sends a HEAD request
#### iter_lines(url, method='GET', \*\*kwargs)
Sends a iter_lines request for streaming responses
#### options(url, \*\*kwargs)
Sends a OPTIONS request
#### patch(url, data=None, \*\*kwargs)
Sends a PATCH request
#### post(url, data=None, json=None, \*\*kwargs)
Sends a POST request
#### put(url, data=None, \*\*kwargs)
Sends a PUT request
#### request(method, url, name=None, data=None, catch_response=False, stream=False, headers=None, auth=None, json=None, allow_redirects=True, context={}, \*\*kwargs)
Send an HTTP request
* **Parameters:**
* **method** – method for the new `Request` object.
* **url** – path that will be concatenated with the base host URL that has been specified.
Can also be a full URL, in which case the full URL will be requested, and the base host
is ignored.
* **name** – (optional) An argument that can be specified to use as label in Locust’s
statistics instead of the URL path. This can be used to group different URL’s
that are requested into a single entry in Locust’s statistics.
* **catch_response** – (optional) Boolean argument that, if set, can be used to make a request
return a context manager to work as argument to a with statement. This will allow the
request to be marked as a fail based on the content of the response, even if the response
code is ok (2xx). The opposite also works, one can use catch_response to catch a request
and then mark it as successful even if the response code was not (i.e. 500 or 404).
* **data** – (optional) String/bytes to send in the body of the request.
* **json** – (optional) Json to send in the body of the request.
Automatically sets Content-Type and Accept headers to “application/json”.
Only used if data is not set.
* **headers** – (optional) Dictionary of HTTP Headers to send with the request.
* **auth** – (optional) Auth (username, password) tuple to enable Basic HTTP Auth.
* **stream** – (optional) If set to true the response body will not be consumed immediately
and can instead be consumed by accessing the stream attribute on the Response object.
Another side effect of setting stream to True is that the time for downloading the response
content will not be accounted for in the request time that is reported by Locust.
* **allow_redirects** – (optional) Set to True by default.
* **Returns:**
A `FastResponse` object if catch_response is False, and
`ResponseContextManager` if True.
## PostgresUser class
### *class* PostgresUser(\*args, \*\*kwargs)
#### abstract *= True*
If abstract is True, the class is meant to be subclassed, and locust will not spawn users of this class during a test.
#### tasks *= []*
Collection of python callables and/or TaskSet classes that the Locust user(s) will run.
If tasks is a list, the task to be performed will be picked randomly.
If tasks is a *(callable,int)* list of two-tuples, or a {callable:int} dict,
the task to be performed will be picked randomly, but each task will be weighted
according to its corresponding int value. So in the following case, *ThreadPage* will
be fifteen times more likely to be picked than *write_post*:
```
class ForumPage(TaskSet):
tasks = {ThreadPage:15, write_post:1}
```
#### wait_time()
## MongoDBUser class
### *class* MongoDBUser(\*args, \*\*kwargs)
#### abstract *= True*
If abstract is True, the class is meant to be subclassed, and locust will not spawn users of this class during a test.
#### tasks *= []*
Collection of python callables and/or TaskSet classes that the Locust user(s) will run.
If tasks is a list, the task to be performed will be picked randomly.
If tasks is a *(callable,int)* list of two-tuples, or a {callable:int} dict,
the task to be performed will be picked randomly, but each task will be weighted
according to its corresponding int value. So in the following case, *ThreadPage* will
be fifteen times more likely to be picked than *write_post*:
```
class ForumPage(TaskSet):
tasks = {ThreadPage:15, write_post:1}
```
#### wait_time()
## MilvusUser class
### *class* MilvusUser(environment, uri='http://localhost:19530', token='root:Milvus', collection_name='test_collection', db_name='default', timeout=60, schema=None, index_params=None, \*\*kwargs)
Locust User implementation for Milvus operations.
This class wraps the MilvusV2Client implementation and translates
client method results into Locust request events so that performance
statistics are collected properly.
### Parameters
host
: Milvus server URI, e.g. `"http://localhost:19530"`.
collection_name
: The name of the collection to operate on.
```
**
```
client_kwargs
: Additional keyword arguments forwarded to the client.
#### abstract *= True*
If abstract is True, the class is meant to be subclassed, and locust will not spawn users of this class during a test.
#### tasks *= []*
Collection of python callables and/or TaskSet classes that the Locust user(s) will run.
If tasks is a list, the task to be performed will be picked randomly.
If tasks is a *(callable,int)* list of two-tuples, or a {callable:int} dict,
the task to be performed will be picked randomly, but each task will be weighted
according to its corresponding int value. So in the following case, *ThreadPage* will
be fifteen times more likely to be picked than *write_post*:
```
class ForumPage(TaskSet):
tasks = {ThreadPage:15, write_post:1}
```
#### wait_time()
## QdrantUser class
### *class* QdrantUser(environment)
Locust User implementation for Qdrant operations.
This class wraps the QdrantLocustClient implementation and translates
client method results into Locust request events so that performance
statistics are collected properly.
### Parameters
host
: Qdrant server URL, e.g. `"http://localhost:6333"`.
collection_name
: The name of the collection to operate on.
```
**
```
client_kwargs
: Additional keyword arguments forwarded to the client.
```
**
```
collection_kwargs
: Additional keyword arguments forwarded to `create_collection`.
#### abstract *= True*
If abstract is True, the class is meant to be subclassed, and locust will not spawn users of this class during a test.
#### tasks *= []*
Collection of python callables and/or TaskSet classes that the Locust user(s) will run.
If tasks is a list, the task to be performed will be picked randomly.
If tasks is a *(callable,int)* list of two-tuples, or a {callable:int} dict,
the task to be performed will be picked randomly, but each task will be weighted
according to its corresponding int value. So in the following case, *ThreadPage* will
be fifteen times more likely to be picked than *write_post*:
```
class ForumPage(TaskSet):
tasks = {ThreadPage:15, write_post:1}
```
#### wait_time()
## DNSUser class
### *class* DNSUser(environment)
DNSUser provides a locust client class for dnspython’s `dns.query` methods.
See example in [examples/dns_ex.py](https://github.com/locustio/locust/blob/master/examples/dns_ex.py).
#### client
Example (inside task method):
```
message = dns.message.make_query("example.com", dns.rdatatype.A)
self.client.udp(message, "1.1.1.1")
self.client.https(message, "1.1.1.1")
```
## TaskSet class
### *class* TaskSet(parent)
Class defining a set of tasks that a User will execute.
When a TaskSet starts running, it will pick a task from the *tasks* attribute,
execute it, and then sleep for the number of seconds returned by its *wait_time*
function. If no wait_time method has been declared on the TaskSet, it’ll call the
wait_time function on the User by default. It will then schedule another task
for execution and so on.
TaskSets can be nested, which means that a TaskSet’s *tasks* attribute can contain
another TaskSet. If the nested TaskSet is scheduled to be executed, it will be
instantiated and called from the currently executing TaskSet. Execution in the
currently running TaskSet will then be handed over to the nested TaskSet which will
continue to run until it throws an InterruptTaskSet exception, which is done when
`TaskSet.interrupt()` is called. (execution
will then continue in the first TaskSet).
#### *property* client
Shortcut to the client `client` attribute of this TaskSet’s `User`
#### interrupt(reschedule=True)
Interrupt the TaskSet and hand over execution control back to the parent TaskSet.
If *reschedule* is True (default), the parent User will immediately re-schedule,
and execute, a new task.
#### on_start()
Called when a User starts executing this TaskSet
#### on_stop()
Called when a User stops executing this TaskSet. E.g. when TaskSet.interrupt() is called
or when the User is killed
#### *property* parent
Parent TaskSet instance of this TaskSet (or `User` if this is not a nested TaskSet)
#### schedule_task(task_callable, first=False)
Add a task to the User’s task execution queue.
* **Parameters:**
* **task_callable** – User task to schedule.
* **first** – Optional keyword argument. If True, the task will be put first in the queue.
#### tasks *= []*
Collection of python callables and/or TaskSet classes that the User(s) will run.
If tasks is a list, the task to be performed will be picked randomly.
If tasks is a *(callable,int)* list of two-tuples, or a {callable:int} dict,
the task to be performed will be picked randomly, but each task will be weighted
according to its corresponding int value. So in the following case, *ThreadPage* will
be fifteen times more likely to be picked than *write_post*:
```
class ForumPage(TaskSet):
tasks = {ThreadPage:15, write_post:1}
```
#### *property* user
`User` instance that this TaskSet was created by
#### wait()
Make the running user sleep for a duration defined by the Locust.wait_time
function (or TaskSet.wait_time function if it’s been defined).
The user can also be killed gracefully while it’s sleeping, so calling this
method within a task makes it possible for a user to be killed mid-task, even if you’ve
set a stop_timeout. If this behaviour is not desired you should make the user wait using
gevent.sleep() instead.
#### wait_time()
Method that returns the time (in seconds) between the execution of tasks.
Example:
```
from locust import TaskSet, between
class Tasks(TaskSet):
wait_time = between(3, 25)
```
## task decorator
### task(weight=1)
Used as a convenience decorator to be able to declare tasks for a User or a TaskSet
inline in the class. Example:
```
class ForumPage(TaskSet):
@task(100)
def read_thread(self):
pass
@task(7)
def create_thread(self):
pass
@task(25)
class ForumThread(TaskSet):
@task
def get_author(self):
pass
@task
def get_created(self):
pass
```
## tag decorator
### tag(\*tags)
Decorator for tagging tasks and TaskSets with the given tag name. You can
then limit the test to only execute tasks that are tagged with any of the
tags provided by the `--tags` command-line argument. Example:
```
class ForumPage(TaskSet):
@tag('thread')
@task(100)
def read_thread(self):
pass
@tag('thread')
@tag('post')
@task(7)
def create_thread(self):
pass
@tag('post')
@task(11)
def comment(self):
pass
```
## SequentialTaskSet class
### *class* SequentialTaskSet(\*args, \*\*kwargs)
Class defining a sequence of tasks that a User will execute.
Works like TaskSet, but task weight is ignored, and all tasks are executed in order. Tasks can
either be specified by setting the *tasks* attribute to a list of tasks, or by declaring tasks
as methods using the @task decorator. The order of declaration decides the order of execution.
It’s possible to combine a task list in the *tasks* attribute, with some tasks declared using
the @task decorator. The order of declaration is respected also in that case.
#### *property* client
Shortcut to the client `client` attribute of this TaskSet’s `User`
#### interrupt(reschedule=True)
Interrupt the TaskSet and hand over execution control back to the parent TaskSet.
If *reschedule* is True (default), the parent User will immediately re-schedule,
and execute, a new task.
#### on_start()
Called when a User starts executing this TaskSet
#### on_stop()
Called when a User stops executing this TaskSet. E.g. when TaskSet.interrupt() is called
or when the User is killed
#### *property* parent
Parent TaskSet instance of this TaskSet (or `User` if this is not a nested TaskSet)
#### schedule_task(task_callable, first=False)
Add a task to the User’s task execution queue.
* **Parameters:**
* **task_callable** – User task to schedule.
* **first** – Optional keyword argument. If True, the task will be put first in the queue.
#### tasks *= []*
Collection of python callables and/or TaskSet classes that the User(s) will run.
If tasks is a list, the task to be performed will be picked randomly.
If tasks is a *(callable,int)* list of two-tuples, or a {callable:int} dict,
the task to be performed will be picked randomly, but each task will be weighted
according to its corresponding int value. So in the following case, *ThreadPage* will
be fifteen times more likely to be picked than *write_post*:
```
class ForumPage(TaskSet):
tasks = {ThreadPage:15, write_post:1}
```
#### *property* user
`User` instance that this TaskSet was created by
#### wait_time()
Method that returns the time (in seconds) between the execution of tasks.
Example:
```
from locust import TaskSet, between
class Tasks(TaskSet):
wait_time = between(3, 25)
```
## Built in wait_time functions
### between(min_wait, max_wait)
Returns a function that will return a random number between min_wait and max_wait.
Example:
```
class MyUser(User):
# wait between 3.0 and 10.5 seconds after each task
wait_time = between(3.0, 10.5)
```
### constant(wait_time)
Returns a function that just returns the number specified by the wait_time argument
Example:
```
class MyUser(User):
wait_time = constant(3)
```
### constant_pacing(wait_time)
Returns a function that will track the run time of the tasks, and for each time it’s
called it will return a wait time that will try to make the total time between task
execution equal to the time specified by the wait_time argument.
In the following example the task will always be executed once every 10 seconds, no matter
the task execution time:
```
class MyUser(User):
wait_time = constant_pacing(10)
@task
def my_task(self):
time.sleep(random.random())
```
If a task execution exceeds the specified wait_time, the wait will be 0 before starting
the next task.
### constant_throughput(task_runs_per_second)
Returns a function that will track the run time of the tasks, and for each time it’s
called it will return a wait time that will try to make the number of task runs per second
execution equal to the time specified by the task_runs_per_second argument.
If you have multiple requests in a task your RPS will of course be higher than the
specified throughput.
This is the mathematical inverse of constant_pacing.
In the following example the task will always be executed once every 10 seconds, no matter
the task execution time:
```
class MyUser(User):
wait_time = constant_throughput(0.1)
@task
def my_task(self):
time.sleep(random.random())
```
If a task execution exceeds the specified wait_time, the wait will be 0 before starting
the next task.
## Response class
This class actually resides in the [requests](https://requests.readthedocs.io/) library,
since that’s what Locust is using to make HTTP requests, but it’s included in the API docs
for locust since it’s so central when writing locust load tests. You can also look at the
`Response` class at the
[requests documentation](https://requests.readthedocs.io/).
### *class* Response
The `Response` object, which contains a
server’s response to an HTTP request.
#### *property* apparent_encoding
The apparent encoding, provided by the charset_normalizer or chardet libraries.
#### close()
Releases the connection back to the pool. Once this method has been
called the underlying `raw` object must not be accessed again.
*Note: Should not normally need to be called explicitly.*
#### *property* content
Content of the response, in bytes.
#### cookies
A CookieJar of Cookies the server sent back.
#### elapsed
The amount of time elapsed between sending the request
and the arrival of the response (as a timedelta).
This property specifically measures the time taken between sending
the first byte of the request and finishing parsing the headers. It
is therefore unaffected by consuming the response content or the
value of the `stream` keyword argument.
#### encoding
Encoding to decode with when accessing r.text.
#### headers
Case-insensitive Dictionary of Response Headers.
For example, `headers['content-encoding']` will return the
value of a `'Content-Encoding'` response header.
#### history
A list of `Response` objects from
the history of the Request. Any redirect responses will end
up here. The list is sorted from the oldest to the most recent request.
#### *property* is_permanent_redirect
True if this Response one of the permanent versions of redirect.
#### *property* is_redirect
True if this Response is a well-formed HTTP redirect that could have
been processed automatically (by `Session.resolve_redirects()`).
#### iter_content(chunk_size=1, decode_unicode=False)
Iterates over the response data. When stream=True is set on the
request, this avoids reading the content at once into memory for
large responses. The chunk size is the number of bytes it should
read into memory. This is not necessarily the length of each item
returned as decoding can take place.
chunk_size must be of type int or None. A value of None will
function differently depending on the value of stream.
stream=True will read data as it arrives in whatever size the
chunks are received. If stream=False, data is returned as
a single chunk.
If decode_unicode is True, content will be decoded using encoding
information from the response. If no encoding information is available,
bytes will be returned. This can be bypassed by manually setting
encoding on the response.
#### iter_lines(chunk_size=512, decode_unicode=False, delimiter=None)
Iterates over the response data, one line at a time. When
stream=True is set on the request, this avoids reading the
content at once into memory for large responses.
The decode_unicode param works the same as in iter_content, with the
same caveats.
#### NOTE
This method is not reentrant safe.
#### json(\*\*kwargs)
Decodes the JSON response body (if any) as a Python object.
This may return a dictionary, list, etc. depending on what is in the response.
* **Parameters:**
**\*\*kwargs** – Optional arguments that `json.loads` takes.
* **Raises:**
**requests.exceptions.JSONDecodeError** – If the response body does not
contain valid json.
#### *property* links
Returns the parsed header links of the response, if any.
#### *property* next
Returns a PreparedRequest for the next request in a redirect chain, if there is one.
#### *property* ok
Returns True if `status_code` is less than 400, False if not.
This attribute checks if the status code of the response is between
400 and 600 to see if there was a client error or a server error. If
the status code is between 200 and 400, this will return True. This
is **not** a check to see if the response code is `200 OK`.
#### raise_for_status()
Raises `HTTPError`, if one occurred.
#### raw
File-like object representation of response (for advanced usage).
Use of `raw` requires that `stream=True` be set on the request.
This requirement does not apply for use internally to Requests.
#### reason
Textual reason of responded HTTP Status, e.g. “Not Found” or “OK”.
#### request
The `PreparedRequest` object to which this
is a response.
#### status_code
Integer Code of responded HTTP Status, e.g. 404 or 200.
#### *property* text
Content of the response, in unicode.
If Response.encoding is None, encoding will be guessed using
`charset_normalizer` or `chardet`.
The encoding of the response content is determined based solely on HTTP
headers, following RFC 2616 to the letter. If you can take advantage of
non-HTTP knowledge to make a better guess at the encoding, you should
set `r.encoding` appropriately before accessing this property.
#### url
Final URL location of Response.
## ResponseContextManager class
### *class* ResponseContextManager(error)
A Response class that also acts as a context manager that provides the ability to manually
control if an HTTP request should be marked as successful or a failure in Locust’s statistics
This class is a subclass of `Response` with two additional
methods: `success` and
`failure`.
#### failure(exc)
Report the response as a failure.
if exc is anything other than a python exception (like a string) it will
be wrapped inside a CatchResponseError.
Example:
```
with self.client.get("/", catch_response=True) as response:
if response.content == b"":
response.failure("No data")
```
#### success()
Report the response as successful
Example:
```
with self.client.get("/does/not/exist", catch_response=True) as response:
if response.status_code == 404:
response.success()
```
## Exceptions
### *exception* InterruptTaskSet(reschedule=True)
Exception that will interrupt a User when thrown inside a task
### *exception* RescheduleTask
When raised in a task it’s equivalent of a return statement.
Also used internally by TaskSet. When raised within the task control flow of a TaskSet,
but not inside a task, the execution should be handed over to the parent TaskSet.
### *exception* RescheduleTaskImmediately
When raised in a User task, another User task will be rescheduled immediately (without calling wait_time first)
## Environment class
### *class* Environment(\*, user_classes=None, shape_class=None, tags=None, locustfile=None, exclude_tags=None, events=None, host=None, reset_stats=False, stop_timeout=None, catch_exceptions=True, parsed_options=None, parsed_locustfiles=None, available_user_classes=None, available_shape_classes=None, available_user_tasks=None, dispatcher_class=, profile=None)
#### assign_equal_weights()
Update the user classes such that each user runs their specified tasks with equal
probability.
#### available_shape_classes
List of the available Shape Classes to pick from in the ShapeClass Picker
#### available_user_classes
List of the available User Classes to pick from in the UserClass Picker
#### available_user_tasks
List of the available Tasks per User Classes to pick from in the Task Picker
#### catch_exceptions
If True exceptions that happen within running users will be caught (and reported in UI/console).
If False, exceptions will be raised.
#### create_local_runner()
Create a `LocalRunner` instance for this Environment
#### create_master_runner(master_bind_host='\*', master_bind_port=5557)
Create a `MasterRunner` instance for this Environment
* **Parameters:**
* **master_bind_host** – Interface/host that the master should use for incoming worker connections.
Defaults to “\*” which means all interfaces.
* **master_bind_port** – Port that the master should listen for incoming worker connections on
#### create_web_ui(host='', port=8089, web_base_path=None, web_login=False, tls_cert=None, tls_key=None, stats_csv_writer=None, delayed_start=False, userclass_picker_is_active=False, build_path=None)
Creates a `WebUI` instance for this Environment and start running the web server
* **Parameters:**
* **host** – Host/interface that the web server should accept connections to. Defaults to “”
which means all interfaces
* **port** – Port that the web server should listen to
* **web_login** – If provided, an authentication page will protect the app
* **tls_cert** – An optional path (str) to a TLS cert. If this is provided the web UI will be
served over HTTPS
* **tls_key** – An optional path (str) to a TLS private key. If this is provided the web UI will be
served over HTTPS
* **stats_csv_writer** – StatsCSV instance.
* **delayed_start** – Whether or not to delay starting web UI until start() is called. Delaying web UI start
allows for adding Flask routes or Blueprints before accepting requests, avoiding errors.
#### create_worker_runner(master_host, master_port)
Create a `WorkerRunner` instance for this Environment
* **Parameters:**
* **master_host** – Host/IP of a running master node
* **master_port** – Port on master node to connect to
#### dispatcher_class
A user dispatcher class that decides how users are spawned, default `UsersDispatcher`
#### events
Event hooks used by Locust internally, as well as to extend Locust’s functionality
See events for available events.
#### exclude_tags
If set, only tasks that aren’t tagged by tags in this list will be executed. Leave this as None to use the one from parsed_options
#### host
Base URL of the target system
#### locustfile
Filename (not path) of locustfile
#### parsed_locustfiles
A list of all locustfiles for the test
#### parsed_options
Reference to the parsed command line options (used to pre-populate fields in Web UI). When using Locust as a library, this should either be None or an object created by argument_parser.parse_args()
#### process_exit_code
If set it’ll be the exit code of the Locust process
#### profile
Profile name for the test run
#### reset_stats
Determines if stats should be reset once all simulated users have been spawned
#### runner
Reference to the `Runner` instance
#### shape_class
A shape class to control the shape of the load test
#### stats
Reference to RequestStats instance
#### tags
If set, only tasks that are tagged by tags in this list will be executed. Leave this as None to use the one from parsed_options
#### user_classes
User classes that the runner will run
#### web_ui
Reference to the WebUI instance
#### worker_logs
Captured logs from all connected workers
## Event hooks
Locust provides event hooks that can be used to extend Locust in various ways.
The following event hooks are available under `Environment.events`,
and there’s also a reference to these events under `locust.events` that can be used at the module level
of locust scripts (since the Environment instance hasn’t been created when the locustfile is imported).
### *class* Events
#### cpu_warning
Fired when the CPU usage exceeds runners.CPU_WARNING_THRESHOLD (90% by default)
Event arguments:
* **Parameters:**
* **environment** – Environment instance
* **cpu_usage** – Current CPU usage in percent
#### heartbeat_received
Fired when a heartbeat is received by a worker from master.
Event arguments:
* **Parameters:**
* **client_id** – worker client id
* **timestamp** – time in seconds since the epoch (float) when the event occurred
#### heartbeat_sent
Fired when a heartbeat is sent by master to a worker.
Event arguments:
* **Parameters:**
* **client_id** – worker client id
* **timestamp** – time in seconds since the epoch (float) when the event occurred
#### init
Fired when Locust is started, once the Environment instance and locust runner instance
have been created. This hook can be used by end-users’ code to run code that requires access to
the Environment. For example to register listeners to other events.
Event arguments:
* **Parameters:**
**environment** – Environment instance
#### init_command_line_parser
Event that can be used to add command line options to Locust
Event arguments:
* **Parameters:**
**parser** – ArgumentParser instance
#### quit
Fired after quitting events, just before process is exited.
Event arguments:
* **Parameters:**
**exit_code** – Exit code for process
#### quitting
Fired when the locust process is exiting.
Event arguments:
* **Parameters:**
**environment** – Environment instance
#### report_to_master
Used when Locust is running in –worker mode. It can be used to attach
data to the dicts that are regularly sent to the master. It’s fired regularly when a report
is to be sent to the master server.
Note that the keys “stats” and “errors” are used by Locust and shouldn’t be overridden.
Event arguments:
* **Parameters:**
* **client_id** – The client id of the running locust process.
* **data** – Data dict that can be modified in order to attach data that should be sent to the master.
#### request
Fired when a request in completed.
Event arguments:
* **Parameters:**
* **request_type** – Request type method used
* **name** – Path to the URL that was called (or override name if it was used in the call to the client)
* **response_time** – Time in milliseconds until exception was thrown
* **response_length** – Content-length of the response
* **response** – Response object (e.g. a `requests.Response`)
* **context** – User/request context
* **exception** – Exception instance that was thrown. None if request was successful.
If you want to simplify a custom client, you can have Locust measure the time for you by using `measure()`
#### reset_stats
Fired when the Reset Stats button is clicked in the web UI.
#### spawning_complete
Fired when all simulated users has been spawned. The event is fired on master first, and then distributed to workers.
Event arguments:
* **Parameters:**
**user_count** – Number of users that were spawned (in total, not per-worker)
#### test_start
Fired on each node when a new load test is started. It’s not fired again if the number of
users change during a test.
Event arguments:
* **Parameters:**
**environment** – Environment instance
#### test_stop
Fired on each node when a load test is stopped.
Event arguments:
* **Parameters:**
**environment** – Environment instance
#### test_stopping
Fired on each node when a load test is about to stop - before stopping users.
Event arguments:
* **Parameters:**
**environment** – Environment instance
#### usage_monitor
Fired every runners.CPU_MONITOR_INTERVAL (5.0 seconds by default) with information about
current CPU and memory usage.
Event arguments:
* **Parameters:**
* **environment** – locust environment
* **cpu_usage** – current CPU usage in percent
* **memory_usage** – current memory usage (RSS) in bytes
#### user_error
Fired when an exception occurs inside the execution of a User class.
Event arguments:
* **Parameters:**
* **user_instance** – User class instance where the exception occurred
* **exception** – Exception that was thrown
* **tb** – Traceback object (from e._\_traceback_\_)
#### worker_connect
Fired on master when a new worker connects. Note that is fired immediately after the connection is established, so init event may not yet have finished on worker.
Event arguments:
* **Parameters:**
**client_id** – Client id of the connected worker
#### worker_report
Used when Locust is running in –master mode and is fired when the master
server receives a report from a Locust worker server.
This event can be used to aggregate data from the locust worker servers.
Event arguments:
* **Parameters:**
* **client_id** – Client id of the reporting worker
* **data** – Data dict with the data from the worker node
#### NOTE
It’s highly recommended that you add a wildcard keyword argument in your event listeners
to prevent your code from breaking if new arguments are added in a future version.
### EventHook class
The event hooks are instances of the **locust.events.EventHook** class:
### *class* EventHook
Simple event class used to provide hooks for different types of events in Locust.
Here’s how to use the EventHook class:
```
my_event = EventHook()
def on_my_event(a, b, **kw):
print("Event was fired with arguments: %s, %s" % (a, b))
my_event.add_listener(on_my_event)
my_event.fire(a="foo", b="bar")
```
If reverse is True, then the handlers will run in the reverse order
that they were inserted
#### measure(request_type, name, response_length=0, context=None)
Convenience method for firing the event with automatically calculated response time and automatically marking the request as failed if an exception is raised (this is really only useful for the *request* event)
Example usage (in a task):
```python
with self.environment.events.request.measure("requestType", "requestName") as request_meta:
# do the stuff you want to measure
```
You can optionally add/overwrite entries in the request_meta dict and they will be passed to the request event.
Experimental.
## Runner classes
### *class* Runner(environment)
Orchestrates the load test by starting and stopping the users.
Use one of the `create_local_runner`,
`create_master_runner` or
`create_worker_runner` methods on
the `Environment` instance to create a runner of the
desired type.
#### quit()
Stop any running load test and kill all greenlets for the runner
#### stop()
Stop a running load test by stopping all running users
#### *property* user_count
* **Returns:**
Number of currently running users
### *class* LocalRunner(environment)
Runner for running single process load test
### *class* MasterRunner(environment, master_bind_host, master_bind_port)
Runner used to run distributed load tests across multiple processes and/or machines.
MasterRunner doesn’t spawn any user greenlets itself. Instead it expects
`WorkerRunners` to connect to it, which it will then direct
to start and stop user greenlets. Stats sent back from the
`WorkerRunners` will aggregated.
#### register_message(msg_type, listener, concurrent=False)
Register a listener for a custom message from another node
* **Parameters:**
* **msg_type** – The type of the message to listen for
* **listener** – The function to execute when the message is received
#### send_message(msg_type, data=None, client_id=None)
Sends a message to attached worker node(s)
* **Parameters:**
* **msg_type** – The type of the message to send
* **data** – Optional data to send
* **client_id** – Optional id of the target worker node.
If None, will send to all attached workers
### *class* WorkerRunner(environment, master_host, master_port)
Runner used to run distributed load tests across multiple processes and/or machines.
WorkerRunner connects to a `MasterRunner` from which it’ll receive
instructions to start and stop user greenlets. The WorkerRunner will periodically
take the stats generated by the running users and send back to the `MasterRunner`.
#### register_message(msg_type, listener, concurrent=False)
Register a listener for a custom message from another node
* **Parameters:**
* **msg_type** – The type of the message to listen for
* **listener** – The function to execute when the message is received
#### send_message(msg_type, data=None, client_id=None)
Sends a message to master node
* **Parameters:**
* **msg_type** – The type of the message to send
* **data** – Optional data to send
* **client_id** – (unused)
## Web UI class
### *class* WebUI(environment, host, port, web_base_path=None, web_login=False, tls_cert=None, tls_key=None, stats_csv_writer=None, delayed_start=False, userclass_picker_is_active=False, build_path=None)
Sets up and runs a Flask web app that can start and stop load tests using the
`environment.runner` as well as show the load test statistics
in `environment.stats`
#### app *= None*
Reference to the `flask.Flask` app. Can be used to add additional web routes and customize
the Flask app in other various ways. Example:
```
from flask import request
@web_ui.app.route("/my_custom_route")
def my_custom_route():
return "your IP is: %s" % request.remote_addr
```
#### auth_args
Arguments used to render auth.html for the web UI auth page. Must be used when configuring auth
#### auth_required_if_enabled(view_func)
Decorator that can be used on custom route methods that will turn on Flask Login
authentication if the `--web-login` flag is used. Example:
```
@web_ui.app.route("/my_custom_route")
@web_ui.auth_required_if_enabled
def my_custom_route():
return "custom response"
```
#### greenlet *= None*
Greenlet of the running web server
#### server *= None*
Reference to the `pyqsgi.WSGIServer` instance
#### stop()
Stop the running web server
#### template_args
Arguments used to render index.html for the web UI. Must be used with custom templates
extending index.html.
## Other
### *class* LoadTestShape
Base class for custom load shapes.
#### get_current_user_count()
Returns current actual number of users from the runner
#### get_run_time()
Calculates run time in seconds of the load test
#### reset_time()
Resets start time back to 0
#### runner *= None*
Reference to the `Runner` instance
#### *abstract* tick()
Returns a tuple with 2 elements to control the running load test:
> user_count – Total user count
> spawn_rate – Number of users to start/stop per second when changing number of users
> user_classes – None or a List of userclasses to be spawned in it tick
If None is returned then the running load test will be stopped.
### *class* RequestStats(use_response_times_cache=True)
Class that holds the request statistics. Accessible in a User from self.environment.stats
#### get(name, method)
Retrieve a StatsEntry instance by name and method
### *class* StatsEntry(stats, name, method, use_response_times_cache=False)
Represents a single stats entry (name and method)
### run_single_user(user_class, include_length=False, include_time=False, include_context=False, include_payload=False, loglevel='WARNING')
Runs a single User. Useful when you want to run a debugger.
It creates in a new locust `Environment` and triggers any `init` or `test_start` events as normal.
It does **not** trigger `test_stop` or `quit` when you quit the debugger.
It prints some info about every request to stdout, and you can get additional info using the include_\* flags
It also initiates logging on WARNING level (not INFO, because it could interfere with the printing of requests),
but you can change that by passing a log level (or disabling logging entirely by passing None)
## CsvRequestLogger class
### *class* CsvRequestLogger(filepath, \*, flush_interval=1)
Listens to Locust’s `request` event and writes one CSV row per request.
### Parameters
filepath:
: Path to the output CSV file. Parent directories must already exist.
If the file exists it will be **overwritten** at the start of each run.
flush_interval:
: Number of rows to buffer before flushing to disk. Use `1` for
immediate write-through (safest for crash recovery), or a larger value
for better performance on high-RPS tests. Defaults to `1`.
#### close()
Flush and close the underlying file handle.
Safe to call multiple times.
#### register(environment)
Attach this logger to *environment*’s event hooks.
Must be called once, typically inside an `@events.init` listener.