From 62fa37bb7e6b71ac31cb013d22a310b50daa9693 Mon Sep 17 00:00:00 2001 From: Lars Holmberg Date: Tue, 18 Aug 2020 00:05:35 +0200 Subject: [PATCH] Lots of updates/tweaks to quickstart and writing-a-locustfile documentation. --- docs/quickstart.rst | 71 ++++++++++++++++++----------------- docs/writing-a-locustfile.rst | 15 ++++---- examples/locustfile.py | 17 +++++---- 3 files changed, 54 insertions(+), 49 deletions(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 93dad3595a..4387785270 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -4,19 +4,15 @@ Quick start ============= -In Locust you define your user behaviour in Python code. You then use the ``locust`` command and (optionally) its web interface to spawn and simulate a number of those users while gathering request statistics. +A Locust performance test is specified in a plain python file: - - -Example locustfile.py -===================== .. code-block:: python - import random + import time from locust import HttpUser, task, between class QuickstartUser(HttpUser): - wait_time = between(5, 9) + wait_time = between(1, 2) @task def index_page(self): @@ -25,18 +21,19 @@ Example locustfile.py @task(3) def view_item(self): - item_id = random.randint(1, 10000) - self.client.get(f"/item?id={item_id}", name="/item") + for item_id in range(10): + self.client.get(f"/item?id={item_id}", name="/item") + time.sleep(1) def on_start(self): - self.client.post("/login", {"username":"foo", "password":"bar"}) + self.client.post("/login", json={"username":"foo", "password":"bar"}) .. rubric:: Let's break it down .. code-block:: python - import random + import time from locust import HttpUser, task, between A locust file is just a normal Python module, it can import code from other files or packages. @@ -45,56 +42,57 @@ A locust file is just a normal Python module, it can import code from other file class QuickstartUser(HttpUser): -Here we define a class for the users that we will be simulating. It inherits from -:py:class:`HttpUser ` which gives each user a ``client`` attribute, -which is an instance of :py:class:`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. +The behaviour of a simulated user is represented by a class in your locust file. When you start a test run, Locust will create an instance of the class for each concurrent user. .. code-block:: python - wait_time = between(5, 9) + wait_time = between(1, 2) -Our class defines a ``wait_time`` function that will make the simulated users wait between 5 and 9 seconds after each task +The class defines a ``wait_time`` that will make the simulated users wait between 1 and 2 seconds after each task (see below) is executed. For more info see :ref:`wait-time`. +.. code-block:: python + + @task + def index_page(self): + ... + +Methods declared with the ``@task`` attribute are the core of your locust file. For every running user, Locust creates a greenlet (micro-thread), that will call those methods. + .. code-block:: python @task def index_page(self): self.client.get("/hello") self.client.get("/world") - + +The self.client attribute makes it possible to make HTTP calls that will be logged by Locust. `See more details on how to make requests, validate the response, etc `_. + +.. code-block:: python + @task(3) def view_item(self): ... -We've also declared two tasks by decorating two methods with ``@task``, one of which has been given a higher weight (3). -When a User of this type runs it'll pick one of either ``index_page`` or ``view_item`` - with three times the chance of picking -``view_item`` - 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. +Tasks are picked at random, but you can give them different weighting. The above configuration will make Locust three times likelier to pick ``view_item`` than ``index_page``. .. code-block:: python - :emphasize-lines: 4,4 - + @task(3) def view_item(self): - item_id = random.randint(1, 10000) - self.client.get(f"/item?id={item_id}", name="/item") + for item_id in range(10) + self.client.get(f"/item?id={item_id}", name="/item") + time.sleep(1) -In the ``view_item`` 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 separate entries in Locust's statistics - since the stats is grouped on the URL - we use +In the ``view_item`` task we load 10 different URLs by using a query parameter based on a variable. In order to not get 10 separate entries in Locust's statistics - since the stats is grouped on the URL - we use the :ref:`name parameter ` to group all those requests under an entry named ``"/item"`` instead. -Note that only methods decorated with ``@task`` will be called, so you can define your own internal helper methods any way you like. - .. code-block:: python def on_start(self): + self.client.post("/login", json={"username":"foo", "password":"bar"}) -Additionally we've declared an `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`. +If you declare a method called `on_start`, it will be called once for each user. For more info see :ref:`on-start-on-stop`. Start Locust ============ @@ -142,3 +140,8 @@ To start tests directly, without using the web interface, use ``--headless``. Parameters can also be set through :ref:`environment variables `, or in a :ref:`config file `. + +How to write a *real* locust file? +================================== + +The above example was just the bare minimum, see :ref:`writing-a-locustfile` for more info. diff --git a/docs/writing-a-locustfile.rst b/docs/writing-a-locustfile.rst index dd47f1ca9a..e235ec584f 100644 --- a/docs/writing-a-locustfile.rst +++ b/docs/writing-a-locustfile.rst @@ -1,16 +1,17 @@ +.. _writing-a-locustfile: + ====================== Writing a locustfile ====================== -A locustfile is a normal python file. The only requirement is that it declares at least one class - -let's call it the user class - that inherits from the class :py:class:`User `. +A locustfile is a normal python file. The only requirement is that it declares at least one class that inherits from the class :py:class:`User `. User class ========== -A user class represents one user (or a swarming locust if you will). Locust will spawn (hatch) one -instance of the User class for each user that is being simulated. There are a few attributes that -a User class should typically define. +A user class represents one user (or a swarming locust if you will). Locust will spawn one +instance of the User class for each user that is being simulated. There are some common attributes that +a User class may define. .. _wait-time: @@ -43,8 +44,8 @@ With the following locustfile, each user would wait between 5 and 15 seconds bet The wait_time method should return a number of seconds (or fraction of a second) and can also be declared on a TaskSet class, in which case it will only be used for that TaskSet. -It's also possible to declare your own wait_time method directly on a User or TaskSet class. The -following User class would start sleeping for one second and then one, two, three, etc. +It's also possible to declare your own wait_time method directly on a User or TaskSet class. +For example, the following User class would sleep for one second, then two, then three, etc. .. code-block:: python diff --git a/examples/locustfile.py b/examples/locustfile.py index 8fb78ef206..a592a21db7 100644 --- a/examples/locustfile.py +++ b/examples/locustfile.py @@ -1,18 +1,19 @@ -import random +import time from locust import HttpUser, task, between -class MyUser(HttpUser): +class QuickstartUser(HttpUser): + wait_time = between(1, 2) + @task - def index(self): + def index_page(self): self.client.get("/hello") self.client.get("/world") @task(3) def view_item(self): - item_id = random.randint(1, 10000) - self.client.get(f"/item?id={item_id}", name="/item") + for item_id in range(10): + self.client.get(f"/item?id={item_id}", name="/item") + time.sleep(1) def on_start(self): - self.client.post("/login", {"username":"foo", "password":"bar"}) - - wait_time = between(5, 9) + self.client.post("/login", json={"username":"foo", "password":"bar"}) \ No newline at end of file