Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow tasks to be declared directly under Locust classes #1304

Merged
merged 13 commits into from
Apr 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ Locust class
============

.. autoclass:: locust.core.Locust
:members: wait_time, task_set, weight
:members: wait_time, tasks, weight, abstract

HttpLocust class
================

.. autoclass:: locust.core.HttpLocust
:members: wait_time, task_set, client
:members: wait_time, tasks, client, abstract


TaskSet class
Expand Down
29 changes: 13 additions & 16 deletions docs/increase-performance.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,15 @@ How to use FastHttpLocust

Subclass FastHttpLocust instead of HttpLocust::

from locust import TaskSet, task, between
from locust import task, between
from locust.contrib.fasthttp import FastHttpLocust

class MyTaskSet(TaskSet):
class MyLocust(FastHttpLocust):
wait_time = between(1, 60)

@task
def index(self):
response = self.client.get("/")

class MyLocust(FastHttpLocust):
task_set = MyTaskSet
wait_time = between(1, 60)


.. note::
Expand All @@ -43,21 +41,20 @@ Subclass FastHttpLocust instead of HttpLocust::
the default HttpLocust that uses python-requests. Therefore FastHttpLocust might not work as a
drop-in replacement for HttpLocust, depending on how the HttpClient is used.

.. note::

You can set the following properties on your FastHttpLocust subclass to alter its behaviour:

| network_timeout (default 60.0)
| connection_timeout (default 60.0)
| max_redirects (default 5, meaning 4 redirects)
| max_retries (default 1, meaning zero retries)
| insecure (default True, meaning ignore ssl failures)

API
===


FastHttpLocust class
--------------------

.. autoclass:: locust.contrib.fasthttp.FastHttpLocust
:members: network_timeout, connection_timeout, max_redirects, max_retries, insecure


FastHttpSession class
=====================
---------------------

.. autoclass:: locust.contrib.fasthttp.FastHttpSession
:members: request, get, post, delete, put, head, options, patch
Expand Down
128 changes: 69 additions & 59 deletions docs/quickstart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,86 +5,96 @@ Quick start
Example locustfile.py
=====================

Below is a quick little example of a simple **locustfile.py**:

When using Locust you define the behaviour of users in Python code, and then you have the ability to
simulate any number of those users while gathering request statistic. The entrypoint for defining the
user behaviour is the `locustfile.py`.

.. code-block:: python
.. note::

from locust import HttpLocust, TaskSet, between
The ``locustfile.py`` is a normal Python file that will get imported by Locust. Within it you
can import modules just as you would in any python code.

The file can be named something else and specified with the `-f` flag to the ``locust`` command.

def login(l):
l.client.post("/login", {"username":"ellen_key", "password":"education"})
Below is a quick little example of a simple **locustfile.py**:

def index(l):
l.client.get("/")
.. code-block:: python

import random
from locust import HttpLocust, task, between

class WebsiteUser(HttpLocust):
wait_time = between(5, 9)

@task(2)
def index(self):
self.client.get("/")

@task(1)
def view_post(self):
post_id = random.randint(1, 10000)
self.client.get("/post?id=%i" % post_id, name="/post?id=[post-id]")

def on_start(self):
""" on_start is called when a Locust start before any task is scheduled """
self.login()

def login(self):
self.client.post("/login", {"username":"ellen_key", "password":"education"})

def profile(l):
l.client.get("/profile")

class UserBehavior(TaskSet):
tasks = {index: 2, profile: 1}
Let's break it down:
--------------------

def on_start(self):
login(self)
.. code-block:: python

class WebsiteUser(HttpLocust):
task_set = UserBehavior
wait_time = between(5.0, 9.0)

Here we define a class for the users that we will be simulating. It inherits from
:py:class:`HttpLocust <locust.core.HttpLocust>` which gives each user a ``client`` attribute,
which is an instance of :py:class:`HttpSession <locust.clients.HttpSession>`, that
can be used to make HTTP requests to the target system that we want to load test. When a test starts
locust will create an instance of this class for every user that it simulates, and each of these
users will start running within their own green gevent thread.

Here we define a number of Locust tasks, which are normal Python callables that take one argument
(a :py:class:`Locust <locust.core.Locust>` class instance). These tasks are gathered under a
:py:class:`TaskSet <locust.core.TaskSet>` class in the *tasks* attribute. Then we have a
:py:class:`HttpLocust <locust.core.HttpLocust>` class which represents a user, where we define how
long a simulated user should wait between executing tasks, as well as what
:py:class:`TaskSet <locust.core.TaskSet>` class should define the user's \"behaviour\".
:py:class:`TaskSet <locust.core.TaskSet>` classes can be nested.
.. code-block:: python

The :py:class:`HttpLocust <locust.core.HttpLocust>` class inherits from the
:py:class:`Locust <locust.core.Locust>` class, and it adds a client attribute which is an instance of
:py:class:`HttpSession <locust.clients.HttpSession>` that can be used to make HTTP requests.
wait_time = between(5, 9)

Another way we could declare tasks, which is usually more convenient, is to use the
``@task`` decorator. The following code is equivalent to the above:
Our class defines a ``wait_time`` function that will make the simulated users wait between 5 and 9 seconds after each task
is executed.

.. code-block:: python

from locust import HttpLocust, TaskSet, task, between

class UserBehaviour(TaskSet):
def on_start(self):
""" on_start is called when a Locust start before any task is scheduled """
self.login()

def login(self):
self.client.post("/login", {"username":"ellen_key", "password":"education"})

@task(2)
def index(self):
self.client.get("/")

@task(1)
def profile(self):
self.client.get("/profile")
@task(2)
def index(self):
self.client.get("/")

class WebsiteUser(HttpLocust):
task_set = UserBehaviour
wait_time = between(5, 9)
@task(1)
def view_post(self):
...

The :py:class:`Locust <locust.core.Locust>` class (as well as :py:class:`HttpLocust <locust.core.HttpLocust>`
since it's a subclass) also allows one to specify the wait time between the execution of tasks
(:code:`wait_time = between(5, 9)`) as well as other user behaviours.
With the between function the time is randomly chosen uniformly between the specified min and max values,
but any user-defined time distributions can be used by setting *wait_time* to any arbitrary function.
For example, for an exponentially distributed wait time with average of 1 second:
We've also declared two tasks by decorating two methods with ``@task`` and given them
different weights (2 and 1). When a simulated user of this type runs it'll pick one of either ``index``
or ``view_post`` - with twice the chance of picking ``index`` - call that method and then pick a duration
uniformly between 5 and 9 and just sleep for that duration. After it's wait time it'll pick a new task
and keep repeating that.

.. code-block:: python

import random

class WebsiteUser(HttpLocust):
task_set = UserBehaviour
wait_time = lambda self: random.expovariate(1)
post_id = random.randint(1, 10000)
self.client.get("/post?id=%i" % post_id, name="/post?id=[post-id]")

In the ``view_post`` task we load a dynamic URL by using a query parameter that is a number picked at random between
1 and 10000. In order to not get 10k entries in Locust's statistics - since the stats is grouped on the URL - we use
the :ref:`name parameter <name-parameter>` to group all those requests under an entry named ``"/post?id=[post-id]"`` instead.

.. code-block:: python

def on_start(self):

Additionally we've declared a `on_start` method. A method with this name will be called for each simulated
user when they start. For more info see :ref:`on-start-on-stop`.


Start Locust
Expand Down
4 changes: 2 additions & 2 deletions docs/running-locust-distributed.rst
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ Sets locust in master mode. The web interface will run on this node.


``--worker``
-----------
------------

Sets locust in worker mode.

Expand Down Expand Up @@ -82,7 +82,7 @@ Optionally used together with ``--master``. Determines what network ports that t
listen to. Defaults to 5557.

``--expect-workers=X``
---------------------
----------------------

Used when starting the master node with ``--no-web``. The master node will then wait until X worker
nodes has connected before the test is started.
Expand Down
12 changes: 8 additions & 4 deletions docs/testing-other-systems.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,14 @@ Here is an example of a Locust class, **XmlRpcLocust**, which provides an XML-RP

.. literalinclude:: ../examples/custom_xmlrpc_client/xmlrpc_locustfile.py

If you've written Locust tests before, you'll recognize the class called *ApiUser* which is a normal
Locust class that has a *TaskSet* class with *tasks* in its *task_set* attribute. However, the *ApiUser*
inherits from *XmlRpcLocust* that you can see right above ApiUser. The *XmlRpcLocust* class provides an
instance of XmlRpcClient under the *client* attribute. The *XmlRpcClient* is a wrapper around the standard
If you've written Locust tests before, you'll recognize the class called ``ApiUser`` which is a normal
Locust class that has a couple of tasks declared. However, the ``ApiUser`` inherits from
``XmlRpcLocust`` that you can see right above ``ApiUser``. The ``XmlRpcLocust`` is marked as abstract
using ``abstract = True`` which means that Locust till not try to create simulated users from that class
(only of classes that extends it). ``XmlRpcLocust`` provides an instance of XmlRpcClient under the
``client`` attribute.

The ``XmlRpcClient`` is a wrapper around the standard
library's :py:class:`xmlrpc.client.ServerProxy`. It basically just proxies the function calls, but with the
important addition of firing :py:attr:`locust.event.Events.request_success` and :py:attr:`locust.event.Events.request_failure`
events, which will record all calls in Locust's statistics.
Expand Down
Loading