mirror of https://github.com/locustio/locust.git
178 lines
6.2 KiB
ReStructuredText
178 lines
6.2 KiB
ReStructuredText
:orphan:
|
|
.. _tasksets:
|
|
|
|
TaskSet class
|
|
=============
|
|
|
|
If you are performance testing a website that is structured in a hierarchical way, with
|
|
sections and sub-sections, it may be useful to structure your load test the same way.
|
|
For this purpose, Locust provides the TaskSet class. It is a collection of tasks that will
|
|
be executed much like the ones declared directly on a User class.
|
|
|
|
.. note::
|
|
|
|
TaskSets are an advanced feature and only rarely useful. Most of the time, you're better off
|
|
using regular Python loops and control statements to achieve the same thing. There are a few
|
|
gotchas as well, the most frequent one being forgetting to call self.interrupt()
|
|
|
|
.. code-block:: python
|
|
|
|
from locust import User, TaskSet, constant
|
|
|
|
class ForumSection(TaskSet):
|
|
wait_time = constant(1)
|
|
|
|
@task(10)
|
|
def view_thread(self):
|
|
pass
|
|
|
|
@task
|
|
def create_thread(self):
|
|
pass
|
|
|
|
@task
|
|
def stop(self):
|
|
self.interrupt()
|
|
|
|
class LoggedInUser(User):
|
|
wait_time = constant(5)
|
|
tasks = {ForumSection:2}
|
|
|
|
@task
|
|
def my_task(self):
|
|
pass
|
|
|
|
A TaskSet can also be inlined directly under a User/TaskSet class using the @task decorator:
|
|
|
|
.. code-block:: python
|
|
|
|
class MyUser(User):
|
|
@task
|
|
class MyTaskSet(TaskSet):
|
|
...
|
|
|
|
The tasks of a TaskSet class can be other TaskSet classes, allowing them to be nested any number
|
|
of levels. This allows us to define a behaviour that simulates users in a more realistic way.
|
|
|
|
For example we could define TaskSets with the following structure::
|
|
|
|
- Main user behaviour
|
|
- Index page
|
|
- Forum page
|
|
- Read thread
|
|
- Reply
|
|
- New thread
|
|
- View next page
|
|
- Browse categories
|
|
- Watch movie
|
|
- Filter movies
|
|
- About page
|
|
|
|
When a running User thread picks a TaskSet class for execution an instance of this class will
|
|
be created and execution will then go into this TaskSet. What happens then is that one of the
|
|
TaskSet's tasks will be picked and executed, and then the thread will sleep for a duration specified
|
|
by the User's wait_time function (unless a ``wait_time`` function has been declared directly on
|
|
the TaskSet class, in which case it'll use that function instead), then pick a new task from the
|
|
TaskSet's tasks, wait again, and so on.
|
|
|
|
The TaskSet instance contains a reference to the User - ``self.user``. It also has a shortcut to its
|
|
User's client attribute. So you can make a request using ``self.client.request()``,
|
|
just like if your task was defined directly on an HttpUser.
|
|
|
|
|
|
Interrupting a TaskSet
|
|
----------------------
|
|
|
|
One important thing to know about TaskSets is that they will never stop executing their tasks, and
|
|
hand over execution back to their parent User/TaskSet, by themselves. This has to be done by the
|
|
developer by calling the :py:meth:`TaskSet.interrupt() <locust.TaskSet.interrupt>` method.
|
|
|
|
.. autofunction:: locust.TaskSet.interrupt
|
|
:noindex:
|
|
|
|
In the following example, if we didn't have the stop task that calls ``self.interrupt()``, the
|
|
simulated user would never stop running tasks from the Forum taskset once it has went into it:
|
|
|
|
.. code-block:: python
|
|
|
|
class RegisteredUser(User):
|
|
@task
|
|
class Forum(TaskSet):
|
|
@task(5)
|
|
def view_thread(self):
|
|
pass
|
|
|
|
@task(1)
|
|
def stop(self):
|
|
self.interrupt()
|
|
|
|
@task
|
|
def frontpage(self):
|
|
pass
|
|
|
|
Using the interrupt function, we can - together with task weighting - define how likely it
|
|
is that a simulated user leaves the forum.
|
|
|
|
|
|
Differences between tasks in TaskSet and User classes
|
|
-------------------------------------------------------
|
|
|
|
One difference for tasks residing under a TaskSet, compared to tasks residing directly under a User,
|
|
is that the argument that they are passed when executed (``self`` for tasks declared as methods with
|
|
the :py:func:`@task <locust.task>` decorator) is a reference to the TaskSet instance, instead of
|
|
the User instance. The User instance can be accessed from within a TaskSet instance through the
|
|
:py:attr:`TaskSet.user <locust.TaskSet.user>`. TaskSets also contains a convenience
|
|
:py:attr:`client <locust.TaskSet.client>` attribute that refers to the client attribute on the
|
|
User instance.
|
|
|
|
|
|
Referencing the User instance, or the parent TaskSet instance
|
|
---------------------------------------------------------------
|
|
|
|
A TaskSet instance will have the attribute :py:attr:`user <locust.TaskSet.user>` point to
|
|
its User instance, and the attribute :py:attr:`parent <locust.TaskSet.parent>` point to its
|
|
parent TaskSet instance.
|
|
|
|
|
|
Tags and TaskSets
|
|
------------------
|
|
You can tag TaskSets using the :py:func:`@tag <locust.tag>` decorator in a similar way to normal tasks,
|
|
but there are some nuances worth mentioning. Tagging a TaskSet will automatically apply the tag(s) to
|
|
all of the TaskSet's tasks. Furthermore, if you tag a task within a nested TaskSet, Locust will execute
|
|
that task even if the TaskSet isn't tagged.
|
|
|
|
|
|
.. _sequential-taskset:
|
|
|
|
SequentialTaskSet class
|
|
=======================
|
|
|
|
:py:class:`SequentialTaskSet <locust.SequentialTaskSet>` is a TaskSet whose tasks will be executed
|
|
in the order that they are declared. If the *<Task : int>* dictionary is used, each task will be repeated by the amount
|
|
given by the integer at the point of declaration. It is possible to nest SequentialTaskSets
|
|
within a TaskSet and vice versa.
|
|
|
|
For example, the following code will request URLs /1-/4 in order, and then repeat.
|
|
|
|
.. code-block:: python
|
|
|
|
def function_task(taskset):
|
|
taskset.client.get("/3")
|
|
|
|
class SequenceOfTasks(SequentialTaskSet):
|
|
@task
|
|
def first_task(self):
|
|
self.client.get("/1")
|
|
self.client.get("/2")
|
|
|
|
# you can still use the tasks attribute to specify a list or dict of tasks
|
|
tasks = [function_task]
|
|
# tasks = {function_task: 1}
|
|
|
|
@task
|
|
def last_task(self):
|
|
self.client.get("/4")
|
|
|
|
Note that you dont need SequentialTaskSets to just do some requests in order. It is often easier to
|
|
just do a whole user flow in a single task.
|