locust Documentation

Write scalable load tests in plain Python ๐Ÿš—๐Ÿ’จ

RAW Doc

Docker & Self-Hosted Deployment Verified OCI

Deploy locust on any server, homelab, or cloud container platform.

1-Click Terminal Command
bash (docker run)
docker run -d --name locust -p 8089:8089 --restart always locustio/locust
Configured Services & Ports
โš™๏ธ master
image: locustio/locust
๐Ÿ”Œ Ports: 8089:8089
โš™๏ธ worker
image: locustio/locust
docker-compose.yml
yaml (docker-compose.yml)
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

File: README.md

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 get started right away, head over to the documentation.

Features

Write user test scenarios in plain old Python

If you want your users to loop, perform some conditional behaviour 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)

python
from locust import HttpUser, task, between

class QuickstartUser(HttpUser):
    wait_time = between(1, 2)

    def on_start(self):
        self.client.post("/login", json={"username":"foo", "password":"bar"})

    @task
    def hello_world(self):
        self.client.get("/hello")
        self.client.get("/world")

    @task(3)
    def view_item(self):
        for item_id in range(10):
            self.client.get(f"/item?id={item_id}", name="/item")

Distributed & Scalable - supports hundreds of thousands of users

Locust makes it easy to run load tests distributed over multiple machines. It is event-based (using gevent), 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 web sites/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.

Hackable

Locust's code base is intentionally kept small and doesn't solve everything out of the box. Instead, we try to make it easy to adapt to any situation you may come across, using regular Python code. There is nothing stopping you from:

VS Code extension

Microsoft has built an excellent VS Code extension that uses Copilot to help you create and run Locust tests. Here's a video to get you started:

Azure Load Testing

Azure offers a managed load-testing service that lets you run and scale Locust tests easily.

  • Scalable and flexible, with no need to host your own infrastructure
  • If your application is hosted on Azure, it can combine test results with detailed resource metrics to help pinpoint bottlenecks and optimize performance.
  • Integrates well with Azure DevOps and GitHub for CI/CD pipelines, enabling automated performance testing as part of your development workflow.

Authors

Special thanks

Microsoft contributes to and sponsors the continued maintenance of Locust โค๏ธ

License

Open source licensed under the MIT license (see LICENSE file for details).


File: docs/api.rst

API

User class

.. autoclass:: locust.User
:members: wait_time, tasks, weight, fixed_count, abstract, on_start, on_stop, wait, context, environment

HttpUser class

.. autoclass:: locust.HttpUser
:members: wait_time, tasks, client, abstract

HttpSession class

.. autoclass:: locust.clients.HttpSession
:members: init, request, get, post, delete, put, head, options, patch

FastHttpUser class

.. autoclass:: locust.contrib.fasthttp.FastHttpUser
:members: wait_time, tasks, client, abstract, rest
:noindex:

MqttUser class

.. autoclass:: locust.contrib.mqtt.MqttUser
:members: init, host, port, transport, ws_path, tls_context, client_cls, client_id, username, password, protocol

.. _socketio:

SocketIOUser class

.. autoclass:: locust.contrib.socketio.SocketIOUser
:members: connect, call, emit, send, on_message, client, options
:noindex:

FastHttpSession class

.. autoclass:: locust.contrib.fasthttp.FastHttpSession
:members: init, request, get, post, delete, put, head, options, patch, iter_lines

PostgresUser class

.. autoclass:: locust.contrib.postgres.PostgresUser
:members: wait_time, tasks, client, abstract
:noindex:

MongoDBUser class

.. autoclass:: locust.contrib.mongodb.MongoDBUser
:members: wait_time, tasks, client, abstract
:noindex:

MilvusUser class

.. autoclass:: locust.contrib.milvus.MilvusUser
:members: wait_time, tasks, client, abstract
:noindex:

QdrantUser class

.. autoclass:: locust.contrib.qdrant.QdrantUser
:members: wait_time, tasks, client, abstract
:noindex:

DNSUser class

.. autoclass:: locust.contrib.dns.DNSUser
:members: client
:noindex:

TaskSet class

.. autoclass:: locust.TaskSet
:members: user, parent, wait_time, client, tasks, interrupt, schedule_task, on_start, on_stop, wait

task decorator

.. autofunction:: locust.task

tag decorator

.. autofunction:: locust.tag

SequentialTaskSet class

.. autoclass:: locust.SequentialTaskSet
:members: user, parent, wait_time, client, tasks, interrupt, schedule_task, on_start, on_stop

.. _wait_time_functions:

Built in wait_time functions

.. automodule:: locust.wait_time
:members: between, constant, constant_pacing, constant_throughput

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
:py:class:Response <requests.Response> class at the
requests documentation <https://requests.readthedocs.io/>_.

.. autoclass:: requests.Response
:inherited-members:
:noindex:

ResponseContextManager class

.. autoclass:: locust.clients.ResponseContextManager
:members: success, failure

.. _exceptions:

Exceptions

.. autoexception:: locust.exception.InterruptTaskSet

.. autoexception:: locust.exception.RescheduleTask

.. autoexception:: locust.exception.RescheduleTaskImmediately

Environment class

.. autoclass:: locust.env.Environment
:members:

.. _events:

Event hooks

Locust provides event hooks that can be used to extend Locust in various ways.

The following event hooks are available under :py:attr:Environment.events <locust.env.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).

.. autoclass:: locust.event.Events
:members:

.. note::

text
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:

.. autoclass:: locust.event.EventHook
:members:

Runner classes

.. autoclass:: locust.runners.Runner
:members: start, stop, quit, user_count

.. autoclass:: locust.runners.LocalRunner

.. autoclass:: locust.runners.MasterRunner
:members: register_message, send_message

.. autoclass:: locust.runners.WorkerRunner
:members: register_message, send_message, client_id, worker_index

Web UI class

.. autoclass:: locust.web.WebUI
:members:

Other

.. autoclass:: locust.shape.LoadTestShape
:members:

.. autoclass:: locust.stats.RequestStats
:members: get

.. autoclass:: locust.stats.StatsEntry

.. autofunction:: locust.debug.run_single_user

CsvRequestLogger class

.. autoclass:: locust.contrib.csv_request_logger.CsvRequestLogger
:members: register, close


File: docs/configuration.rst

.. _configuration:

============= Configuration

Command Line Options

Locust is configured mainly through command line arguments.

.. code-block:: console

text
$ locust --help

.. literalinclude:: cli-help-output.txt
:language: console

.. _environment-variables:

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:

.. code-block::

text
$ LOCUST_LOCUSTFILE=custom_locustfile.py locust

On Windows:

.. code-block::

text
> set LOCUST_LOCUSTFILE=custom_locustfile.py
> locust

.. _configuration-file:

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.

.. code-block:: console

text
$ locust --config custom_config.conf

Here's an example:

locust.conf

.. code-block:: ini

text
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.

.. code-block:: toml

text
[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::

text
Configuration values are read (and overridden) in the following order:

.. code-block:: 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:

.. include:: config-options.rst

Running without the web UI

See :ref:running-without-web-ui

Using multiple Locustfiles at once

-f/--locustfile accepts multiple, comma-separated locustfiles.

Example:

With the following file structure:

.. code-block::

text
โ”œโ”€โ”€ locustfiles/
โ”‚   โ”œโ”€โ”€ locustfile1.py
โ”‚   โ”œโ”€โ”€ locustfile2.py
โ”‚   โ””โ”€โ”€ more_files/
โ”‚       โ”œโ”€โ”€ locustfile3.py
โ”‚       โ”œโ”€โ”€ _ignoreme.py

.. code-block:: console

text
$ 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:

.. code-block:: console

text
$ 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:

.. code-block:: console

text
$ locust -f https://raw.githubusercontent.com/locustio/locust/master/examples/basic.py

.. _class-picker:

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:

.. code-block::

text
โ”œโ”€โ”€ src/
โ”‚   โ”œโ”€โ”€ some_file.py
โ”œโ”€โ”€ locustfiles/
โ”‚   โ”œโ”€โ”€ locustfile1.py
โ”‚   โ”œโ”€โ”€ locustfile2.py
โ”‚   โ””โ”€โ”€ more_files/
โ”‚       โ”œโ”€โ”€ locustfile3.py
โ”‚       โ”œโ”€โ”€ _ignoreme.py
โ”‚   โ””โ”€โ”€ shape_classes/
โ”‚       โ”œโ”€โ”€ DoubleWaveShape.py
โ”‚       โ”œโ”€โ”€ StagesShape.py

.. code-block:: console

text
$ locust -f locustfiles --class-picker

The Web UI will display:

.. image:: images/userclass_picker_example.png

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:

.. code-block:: python

text
class Example(HttpUser):
    some_custom_arg: str = "example"

    @task
    def example_task(self):
        self.client.get(f"/example/{self.some_custom_arg}")

    @classmethod
    def json(cls):
        return {
            "host": cls.host,
            "some_custom_arg": cls.some_custom_arg
        }

.. note::

text
The ``json`` classmethod is only used to populate the class picker UI (the gear icon next to each User class).
It does not automatically set the attribute values on the User class. The default values must be defined as
class-level attributes (e.g. ``some_custom_arg: str = "example"``). To apply changes made in the UI, click the
save button for each User class.

Configure Users from command line

You can update User class attributes from the command line too, using the --config-users argument:

.. code-block:: console

text
$ 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.

.. code-block:: console

text
$ 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::

text
``--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:

.. code-block:: python

text
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-shape:

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:

.. code-block:: python

text
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:

.. code-block:: python

text
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 :ref:---class-picker <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:

.. code-block:: console

text
$ 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:

.. code-block:: python

text
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.

.. _use-common-options:

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:

.. code-block:: python

text
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:

.. code-block:: console

text
$ 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:

.. code-block:: python

text
import locust.stats
locust.stats.CSV_STATS_INTERVAL_SEC = 5 # default is 1 second

Custom arguments

See :ref: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:

.. code-block:: python

text
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 | 10 |
| | time percentile | |
+-----------------------------------------+----------------------------------------------------------------------------------+----------------------------------------------------------------------+
| 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:

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:

.. code-block:: python

text
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 |
+-------------------------------------------+--------------------------------------------------------------------------------------+


File: docs/developing-locust.rst

.. _developing-locust:

================================= 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

.. code-block:: sh

text
# clone the repo:
$ git clone git://github.com/<YourName>/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:

.. code-block:: console

text
$ hatch test
...
py39: commands[1]> pytest locust/test
...

You can also run these tests against a specific Python version

.. code-block:: console

text
$ hatch test -py=3.14
...
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:

.. code-block:: console

text
$ pytest locust/test

Individual test:

.. code-block:: console

text
$ pytest locust/test/test_main.py::DistributedIntegrationTests::test_distributed_tags

Debugging

See: :ref: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:

.. code-block:: console

text
$ ruff check --fix <file_or_folder_to_be_formatted>
$ ruff format <file_or_folder_to_be_formatted>

You can validate the whole project using hatch:

.. code-block:: console

text
$ 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

#. Install the documentation requirements:

text
.. code-block:: console

    $ uv sync --all-groups

#. Build the documentation locally:

text
.. code-block:: 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

text
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

.. code-block:: console

    $ nvm --version

- Install the proper Node version according to engines in the ``locust/webui/package.json``

.. code-block:: 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

.. code-block:: console

text
$ yarn --version
  • Next, install all dependencies

.. code-block:: console

text
$ 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:

.. code-block:: console

text
$ 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.


File: docs/extending-locust.rst

.. _extending_locust:

=========== 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::

text
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::

text
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 :py:attr:runner <locust.env.Environment.runner>::

text
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 :ref:events.

.. _request_context:

Request context

The :py:attr:request event <locust.event.Events.request> 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::

text
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::

text
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 :ref:catch_response <catch-response>::

text
with self.client.get("/", catch_response=True) as resp:
    resp.request_meta["context"]["requestId"] = resp.json()["requestId"]

.. note::

text
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 :py:attr:init <locust.event.Events.init> event, we can retrieve a reference
to the Flask app instance and use that to set up a new route::

text
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. 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, ...

.. image:: images/extend_modern_web_ui_cache_stats.png

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 provided
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:

.. code-block:: python

text
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:

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:

.. code-block:: bash

text
MY_FUNKY_VAR=42 locust ...

On windows:

.. code-block:: bash

text
SET MY_FUNKY_VAR=42
locust ...

... and then access them in your locustfile.

.. code-block:: python

text
import os
print(os.environ['MY_FUNKY_VAR'])

.. _custom-arguments:

Custom arguments

You can add your own command line arguments to Locust, using the :py:attr:init_command_line_parser <locust.event.Events.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.

.. literalinclude:: ../examples/add_command_line_argument.py
:language: python

When running Locust :ref:distributed <running-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/testdata_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 :class:~locust.contrib.csv_request_logger.CsvRequestLogger:

.. code-block:: python

text
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 :mod:locust.contrib.csv_request_logger for full API documentation.

More examples

See locust-plugins <https://github.com/SvenskaSpel/locust-plugins#listeners>_


File: docs/extensions.rst

.. _extensions:

====================== 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

File: docs/faq.rst

.. _faq:

=== FAQ

How do Iโ€ฆ

Resolve errors that occur during load (error 5xx, Connection aborted, Connection reset by peer, etc)

text
Check your server logs. If it works at low load then it is almost
   certainly not a Locust issue, but an issue with the system you are
   testing.

Clear cookies, to make the next task iteration seem like a new user to my system under test
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

   Call ``self.client.cookies.clear()`` at the end of your task.

Control headers or other things about my HTTP requests
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

   Locustโ€™s client in ``HttpUser`` inherits from
   `requests <https://requests.readthedocs.io/en/master/>`__ and the
   vast majority of parameters and methods for requests should just work
   with Locust. Check out the docs and Stack Overflow answers for
   requests first and then ask on Stack Overflow for additional Locust
   specific help if necessary.


Basic auth (Authorization header) does not work after redirection
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   `requests <https://requests.readthedocs.io/en/master/>`__ has a security mechanism that
   drops the authorization header on domain change. It could occur when testing a SSO,
   which is typically on a different domain and use multiple redirections (30x).

   Since ``allow_redirects=True`` is the default ``request`` behavior you'll have to turn it off,
   handle manually the redirections and inject again the authorization header, ex:


.. code-block:: python

    response = self.client.post(
             allow_redirects=False,
             url=...)

    while "location" in response.headers:
        response = self.client.get(
        allow_redirects=False,
        url=response.headers['location'],
        headers={
          'Authorization': 'XXX'
        }
    )


Create a Locust file based on a recorded browser session
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

   Try using `har2locust <https://github.com/SvenskaSpel/har2locust>`__

How to run a Docker container of Locust in Windows Subsystem for Linux (Windows 10 users)?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

   If you use WSL on a Windows computer, then you need one extra step
   than the `โ€œdocker run โ€ฆโ€
   command <https://docs.locust.io/en/stable/running-locust-docker.html>`__:
   copy your locusttest1.py to a folder in a Windows path on your WSL
   and mount that folder instead of your normal WSL folder:

::

   $ mkdir /c/Users/[YOUR_Windows_USER]/Documents/Locust/
   $ cp ~/path/to/locusttest1.py /c/Users/[YOUR_Windows_USER]/Documents/Locust/
   $ docker run -p 8089:8089 -v /c/Users/[YOUR_Windows_USER]/Documents/Locust/:/mnt/locust locustio/locust:1.3.1 -f /mnt/locust/locusttest1.py

How to run locust on custom endpoint
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

   Prefix the endpoint to all ``@app.route`` definitions in
   ``locust/web.py`` file & also change as follows (where ``/locust`` is
   new endpoint)

``app = Flask(__name__, static_url_path='/locust')``

   Change the entries of static content location in file
   ``locust/templates/index.html``.

Eg:
``<link rel="shortcut icon" href="{{ url_for('static', filename='img/favicon.ico') }}" type="image/x-icon"/>``

Locust web UI doesnโ€™t show my tasks running, says 0 RPS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Locust only knows what youโ€™re doing when you tell it. There are `Event
Hooks <https://docs.locust.io/en/stable/api.html#events>`__ that you use
to tell Locust whatโ€™s going on in your code. If you use Locustโ€™s
``HttpUser`` and then use ``self.client`` to make http calls, the proper
events are normally fired for you automatically, making less work for
you unless you want to override the default events.

If you use the plain ``User`` or use ``HttpUser`` and youโ€™re not using
``self.client`` to make http calls, Locust will not fire events for you.
You will have to fire events yourself. See `the Locust
docs <https://docs.locust.io/en/stable/testing-other-systems.html>`__
for examples.

HTML Report is filled up with failed requests for long running tests
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

https://github.com/locustio/locust/issues/2328

Other questions and issues
~~~~~~~~~~~~~~~~~~~~~~~~~~

Ask on `Locust's Discord <https://discord.gg/faeXQY82Zs>`__.

`Check the list of issues (a lot of questions/misunderstandings are
filed as
issues) <https://github.com/locustio/locust/issues?q=is%3Aissue%20>`__

Add things that you ran into and solved here! Anyone with a GitHub
account can contribute!

If you have questions about Locust that are not answered here, please
check
`StackOverflow <https://stackoverflow.com/questions/tagged/locust>`__,
or post your question there.

---

## File: docs/further-reading.rst

===============================
Further reading / knowledgebase
===============================

- :ref:`extensions`.

- An list of Locust `articles, blog posts, reviews, and walkthroughs <https://github.com/orgs/locustio/discussions/2752>`_.

- :ref:`Common questions and answers about Locust <faq>`.

You'll also find a lot of answers on `stackoverflow <https://stackoverflow.com/questions/tagged/locust?sort=MostVotes>`_, for example:

- `BadStatusLine Errors <https://stackoverflow.com/questions/31600508/locust-io-load-testing-getting-connection-aborted-badstatusline-errors>`_

- `Passing HTTP Headers <https://stackoverflow.com/questions/52007848/locust-passing-headers>`_

- `POST file upload <https://stackoverflow.com/questions/36163355/proper-post-file-upload-load-testing-with-locust>`_

- `How often does Locust perform dns queries? <https://stackoverflow.com/questions/36087637/how-often-does-python-requests-perform-dns-queries>`_

In other words, just google whatever problem or error message you're getting and you are likely to find good answers out there.

If you think Locust is missing some obvious feature (like stopping after a certain number of iterations/requests, setting goals/thresholds for when to fail a test run, support for additional User types like Kafka, Selenium/WebDriver etc), chances are it is implemented in `locust-plugins <https://github.com/SvenskaSpel/locust-plugins/>`_.

---

## File: docs/history.rst

:orphan:
.. _history:

===============================
The history of Locust
===============================

Locust was created because we were fed up with existing solutions. None of them are solving the 
right problem and to me, they are missing the point. We've tried both Apache JMeter and Tsung. 
Both tools are quite OK to use; we've used the former many times benchmarking stuff at work.
JMeter comes with a UI, which you might think for a second is a good thing. But you soon realize it's
a PITA to "code" your testing scenarios through some point-and-click interface. Secondly, JMeter 
is thread-bound. This means for every user you want to simulate, you need a separate thread. 
Needless to say, benchmarking thousands of users on a single machine just isn't feasible.

Tsung, on the other hand, does not have these thread issues as it's written in Erlang. It can make 
use of the light-weight processes offered by BEAM itself and happily scale up. But when it comes to 
defining the test scenarios, Tsung is as limited as JMeter. It offers an XML-based DSL to define how 
a user should behave when testing. I guess you can imagine the horror of "coding" this. Displaying 
any sorts of graphs or reports when completed requires you to post-process the log files generated from
the test. Only then can you get an understanding of how the test went.

Anyway, we've tried to address these issues when creating Locust. Hopefully none of the above 
pain points should exist.

I guess you could say we're really just trying to scratch our own itch here. We hope others will 
find it as useful as we do.

- `Jonatan Heyman <http://heyman.info>`_ (`@jonatanheyman <https://twitter.com/jonatanheyman>`_ on Twitter)