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)
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:
- Send real time reporting data to TimescaleDB and visualize it in Grafana
- Wrap calls to handle the peculiarities of your REST API
- Use a totally custom load shape/profile
- ...
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.
Links
- Documentation: docs.locust.io
- Support/Questions: StackOverflow
- Chat/discussion: Discord
- Blog: medium.com
Authors
- Maintainer: Lars Holmberg
- UI: Andrew Baldwin
- Original creator: Jonatan Heyman
- Massive thanks to all of our contributors
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 therequests 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::
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
$ 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::
$ LOCUST_LOCUSTFILE=custom_locustfile.py locustOn Windows:
.. code-block::
> set LOCUST_LOCUSTFILE=custom_locustfile.py
> locust.. _configuration-file:
Configuration File
Options can also be set in a configuration file in theconfig 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
$ locust --config custom_config.confHere's an example:
locust.conf
.. code-block:: 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.
.. code-block:: 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:
.. code-block:: console
./pyproject.toml -> ./locust.conf -> (file specified using --config) -> env vars -> cmd argsAll 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::
โโโ locustfiles/
โ โโโ locustfile1.py
โ โโโ locustfile2.py
โ โโโ more_files/
โ โโโ locustfile3.py
โ โโโ _ignoreme.py.. code-block:: console
$ locust -f locustfiles/locustfile1.py,locustfiles/locustfile2.py,locustfiles/more_files/locustfile3.pyLocust 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
$ locust -f locustfilesLocust 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
$ 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::
โโโ src/
โ โโโ some_file.py
โโโ locustfiles/
โ โโโ locustfile1.py
โ โโโ locustfile2.py
โ โโโ more_files/
โ โโโ locustfile3.py
โ โโโ _ignoreme.py
โ โโโ shape_classes/
โ โโโ DoubleWaveShape.py
โ โโโ StagesShape.py.. code-block:: console
$ locust -f locustfiles --class-pickerThe 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
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::
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
$ 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
$ 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.jsonWhen 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:
.. code-block:: 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-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
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 NoneThis 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
class MyBaseShape(LoadTestShape):
abstract = True
def tick(self):
# Something reusable but needing inheritance
return NoneCombining 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
$ locust -f locustfile.py,low_load.py
$ locust -f locustfile.py,high_load.pyRestricting 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
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 NoneThis 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
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 NoneSave 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
$ locust -f examples/basic.py --csv example --headless -t10mThe 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
import locust.stats
locust.stats.CSV_STATS_INTERVAL_SEC = 5 # default is 1 secondCustom 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
import locust.stats
locust.stats.CONSOLE_STATS_INTERVAL_SEC = 15It 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 theresponse_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
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_functionThe 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
# 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 syncNow 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
$ hatch test
...
py39: commands[1]> pytest locust/test
...You can also run these tests against a specific Python version
.. code-block:: console
$ 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
$ pytest locust/testIndividual test:
.. code-block:: console
$ pytest locust/test/test_main.py::DistributedIntegrationTests::test_distributed_tagsDebugging
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
$ 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
$ 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:
.. code-block:: console
$ uv sync --all-groups#. Build the documentation locally:
.. code-block:: console
$ make build_docsView 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
.. 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
$ yarn --version- Next, install all dependencies
.. code-block:: console
$ cd locust/webui
$ yarnDeveloping
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
$ make frontend_buildLinting
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::
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 :py:attr:runner <locust.env.Environment.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 :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::
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 :ref:catch_response <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 :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::
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
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
MY_FUNKY_VAR=42 locust ...On windows:
.. code-block:: bash
SET MY_FUNKY_VAR=42
locust ...... and then access them in your locustfile.
.. code-block:: python
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
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 exchangingmsgpack <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/>__ - GoLocust4j <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)
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)