## File: README.md

# Locust

[](https://pypi.org/project/locust/)
[](https://pypi.org/project/locust/)
[](https://pepy.tech/projects/locust)
[](https://github.com/locustio/locust/graphs/contributors)
[](https://github.com/support-ukraine/support-ukraine)
[](https://inspect.software/software/locustio/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](http://docs.locust.io/en/stable/installation.html).

## 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](http://www.gevent.org/)), which makes it possible for a single process to handle many thousands concurrent users. While there may be other tools that are capable of doing more requests per second on a given hardware, the low overhead of each Locust user makes it very suitable for testing highly concurrent workloads.

#### Web-based UI

Locust has a user friendly web interface that shows the progress of your test in real-time. You can even change the load while the test is running. It can also be run without the UI, making it easy to use for CI/CD testing.






















#### Can test any system

Even though Locust primarily works with web sites/services, it can be used to test almost any system or protocol. Just [write a client](https://docs.locust.io/en/latest/testing-other-systems.html#testing-other-systems) for what you want to test, or [explore some created by the community](https://github.com/SvenskaSpel/locust-plugins#users).

## 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](https://github.com/SvenskaSpel/locust-plugins/blob/master/locust_plugins/dashboards/README.md)
* [Wrap calls to handle the peculiarities of your REST API](https://github.com/SvenskaSpel/locust-plugins/blob/8af21862d8129a5c3b17559677fe92192e312d8f/examples/rest_ex.py#L87) 
* [Use a totally custom load shape/profile](https://docs.locust.io/en/latest/custom-load-shape.html#custom-load-shape)
* [...](https://github.com/locustio/locust/wiki/Extensions)

## VS Code extension

Microsoft has built an excellent [VS Code extension](https://learn.microsoft.com/azure/app-testing/load-testing/quickstart-create-run-load-tests-from-visual-studio-code) that uses [Copilot](https://github.com/features/copilot) to help you create and run Locust tests. Here's a video to get you started:

[](https://youtu.be/J4pIBwocmvE)

## Azure Load Testing

Azure offers [a managed load-testing service](https://learn.microsoft.com/azure/app-testing/load-testing/quickstart-create-and-run-load-test) 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](https://docs.locust.io)
* Support/Questions: [StackOverflow](https://stackoverflow.com/questions/tagged/locust)
* Chat/discussion: [Discord](https://discord.gg/faeXQY82Zs)
* Blog: [medium.com](https://medium.com/locust)

## Authors

* Maintainer: [Lars Holmberg](https://github.com/cyberw)
* UI: [Andrew Baldwin](https://github.com/andrewbaldwin44)
* Original creator: [Jonatan Heyman](https://github.com/heyman)
* Massive thanks to [all of our contributors](https://github.com/locustio/locust/graphs/contributors)

## Special thanks

 [Microsoft](https://learn.microsoft.com/azure/app-testing/load-testing/quickstart-create-and-run-load-test) 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::

    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 locust

On Windows:

.. code-block::

    > 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

    $ locust --config custom_config.conf

Here'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 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::

    ├── 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.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

    $ 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

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

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

When using this way to configure your users, you can set any attribute.

.. note::

    ``--config-users`` is a somewhat experimental feature and the json format may change even between minor Locust revisions.

Configuring Profiles
====================

Profiles are an advanced feature that allow for grouping and filtering testruns. A profile may be set using the ``--profile`` argument
or by inputting a value in the advanced options from the web ui.

It may be useful to see a list of existing profiles as options in the form. If you have such a list, you may set it in your locustfile:

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

    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

    $ 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

    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

    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

    $ 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

    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

    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

    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

    # 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

    $ 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/test

Individual test:

.. code-block:: console

    $ 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

    $ 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_docs
    
View your generated documentation by opening ``docs/_build/index.html`` or running `make serve_docs`


Making changes to Locust's Web UI
=================================

The Web UI is built using React and Typescript

Setup
-----

Node
````

Install node using nvm to easily switch between node version

- Copy and run the install line from `nvm <https://github.com/nvm-sh/nvm>`_ (starts with curl/wget ...)

- Verify nvm was installed correctly

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

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

    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 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)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

   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)