Locust provides two special methods, on_start and on_stop, to handle setup and teardown actions for individual users. These methods allow you to execute specific code when a simulated user starts or stops, making it easier to simulate real-world scenarios like login/logout or initialization tasks.
In this blog, weβll cover,
What on_start and on_stop do.
Why they are important.
Practical examples of using these methods.
Running and testing Locust scripts.
What Are on_start and on_stop?
on_start: This method is executed once when a new simulated user starts. Itβs commonly used for tasks like logging in or setting up the environment.
on_stop: This method is executed once when a simulated user stops. Itβs often used for cleanup tasks like logging out.
These methods are executed only once per user during the lifecycle of a test, as opposed to tasks that are run repeatedly.
Why Use on_start and on_stop?
Simulating Real User Behavior: Real users often start a session with an action (e.g., login) and end it with another (e.g., logout).
Initial Setup: Some tasks require initializing data or setting up user state before performing other actions.
Cleanup: Ensure that actions like logout are performed to leave the system in a clean state.
Examples
Basic Usage of on_start and on_stop
In this example, we just print on start and `on stop` for each user while running a task.
from locust import User, task, between, constant, constant_pacing
from datetime import datetime
class MyUser(User):
wait_time = between(1, 5)
def on_start(self):
print("on start")
def on_stop(self):
print("on stop")
@task
def print_datetime(self):
print(datetime.now())
Locust allows you to define multiple user types in your load tests, enabling you to simulate different user behaviors and traffic patterns. This is particularly useful when your application serves diverse client types, such as web and mobile users, each with unique interaction patterns.
In this blog, we will
Discuss the concept of multiple user types in Locust.
Explore how to implement multiple user classes with weights.
Run and analyze the test results.
Why Use Multiple User Types?
In real-world applications, different user groups interact with your system differently. For example,
Web Users might spend more time browsing through the UI.
Mobile Users could make faster but more frequent requests.
By simulating distinct user types with varying behaviors, you can identify performance bottlenecks across all client groups.
Understanding User Classes and Weights
Locust provides the ability to define user classes by extending the User or HttpUser base class. Each user class can,
Have a unique set of tasks.
Define its own wait times.
Be assigned a weight, which determines the proportion of that user type in the simulation.
For example, if WebUser has a weight of 1 and MobileUser has a weight of 2, the simulation will spawn 1 web user for every 2 mobile users.
Example: Simulating Web and Mobile Users
Below is an example Locust test with two user types
from locust import User, task, between
# Define a user class for web users
class MyWebUser(User):
wait_time = between(1, 3) # Web users wait between 1 and 3 seconds between tasks
weight = 1 # Web users are less frequent
@task
def login_url(self):
print("I am logging in as a Web User")
# Define a user class for mobile users
class MyMobileUser(User):
wait_time = between(1, 3) # Mobile users wait between 1 and 3 seconds
weight = 2 # Mobile users are more frequent
@task
def login_url(self):
print("I am logging in as a Mobile User")
How Locust Uses Weights
With the above configuration
For every 3 users spawned, 1 will be a Web User, and 2 will be Mobile Users (based on their weights: 1 and 2).
Locust automatically handles spawning these users in the specified ratio.
Running the Locust Test
Save the Code Save the above code in a file named locustfile.py.
Start Locust Open your terminal and run `locust -f locustfile.py`
Host: If you are testing an actual API or website, specify its URL (e.g., http://localhost:8000).
Analyze Results
Observe how Locust spawns the users according to their weights and tracks metrics like request counts and response times.
After running the test:
Check the distribution of requests to ensure it matches the weight ratio (e.g., for every 1 web user request, there should be ~3 mobile user requests).
Use the metrics (response time, failure rate) to evaluate performance for each user type.
Locust is an excellent load testing tool, enabling developers to simulate concurrent user traffic on their applications. One of its powerful features is wait times, which simulate the realistic user think time between consecutive tasks. By customizing wait times, you can emulate user behavior more effectively, making your tests reflect actual usage patterns.
In this blog, weβll cover,
What wait times are in Locust.
Built-in wait time options.
Creating custom wait times.
A full example with instructions to run the test.
What Are Wait Times in Locust?
In real-world scenarios, users donβt interact with applications continuously. After performing an action (e.g., submitting a form), they often pause before the next action. This pause is called a wait time in Locust, and it plays a crucial role in mimicking real-life user behavior.
Locust provides several ways to define these wait times within your test scenarios.
FastAPI App Overview
Hereβs the FastAPI app that weβll test,
from fastapi import FastAPI
# Create a FastAPI app instance
app = FastAPI()
# Define a route with a GET method
@app.get("/")
def read_root():
return {"message": "Welcome to FastAPI!"}
@app.get("/items/{item_id}")
def read_item(item_id: int, q: str = None):
return {"item_id": item_id, "q": q}
Locust Examples for FastAPI
1. Constant Wait Time Example
Here, weβll simulate constant pauses between user requests
from locust import HttpUser, task, constant
class FastAPIUser(HttpUser):
wait_time = constant(2) # Wait for 2 seconds between requests
@task
def get_root(self):
self.client.get("/") # Simulates a GET request to the root endpoint
@task
def get_item(self):
self.client.get("/items/42?q=test") # Simulates a GET request with path and query parameters
2. Between wait time Example
Simulating random pauses between requests.
from locust import HttpUser, task, between
class FastAPIUser(HttpUser):
wait_time = between(1, 5) # Random wait time between 1 and 5 seconds
@task(3) # Weighted task: this runs 3 times more often
def get_root(self):
self.client.get("/")
@task(1)
def get_item(self):
self.client.get("/items/10?q=locust")
3. Custom Wait Time Example
Using a custom wait time function to introduce more complex user behavior
import random
from locust import HttpUser, task
def custom_wait():
return max(1, random.normalvariate(3, 1)) # Normal distribution (mean: 3s, stddev: 1s)
class FastAPIUser(HttpUser):
wait_time = custom_wait
@task
def get_root(self):
self.client.get("/")
@task
def get_item(self):
self.client.get("/items/99?q=custom")
Full Test Example
Combining all the above elements, hereβs a complete Locust test for your FastAPI app.
from locust import HttpUser, task, between
import random
# Custom wait time function
def custom_wait():
return max(1, random.uniform(1, 3)) # Random wait time between 1 and 3 seconds
class FastAPIUser(HttpUser):
wait_time = custom_wait # Use the custom wait time
@task(3)
def browse_homepage(self):
"""Simulates browsing the root endpoint."""
self.client.get("/")
@task(1)
def browse_item(self):
"""Simulates fetching an item with ID and query parameter."""
item_id = random.randint(1, 100)
self.client.get(f"/items/{item_id}?q=test")
Running Locust for FastAPI
Run Your FastAPI App Save the FastAPI app code in a file (e.g., main.py) and start the server
uvicorn main:app --reload
By default, the app will run on http://127.0.0.1:8000.
2. Run Locust Save the Locust file as locustfile.py and start Locust.