Locust’s default linear ramp-up is a blunt instrument; real-world traffic is often spiky and irregular.
Let’s see how we can model that. Imagine we have a typical e-commerce site. Traffic surges during flash sales, dips overnight, and has small, consistent bumps during lunch hours. A simple linear ramp-up doesn’t capture this.
Here’s a Locust file that models this kind of traffic:
from locust import HttpUser, task, between
from locust_plugins.connection_hacks import SeekBar
import random
import time
class EcommerceUser(HttpUser):
wait_time = between(0.5, 2) # Default wait time between user actions
host = "http://localhost:8089"
# Define custom load shape
def on_start(self):
self.load_shape = EcommerceLoadShape(self.environment)
self.environment.events.request.add_listener(self.load_shape.on_request)
@task
def index(self):
self.client.get("/")
@task(2) # Higher weight for product pages
def product_page(self):
self.client.get(f"/products/{random.randint(1, 100)}")
class EcommerceLoadShape:
def __init__(self, environment):
self.environment = environment
self.start_time = time.time()
self.user_count = 0
self.requests_per_second = [] # Store RPS for logging/debugging
def on_request(self, request_type, name, response_time, response_length, **kwargs):
self.requests_per_second.append(time.time())
def tick(self):
current_time = time.time()
elapsed_time = current_time - self.start_time
# Calculate current RPS based on recent requests
# Keep only requests from the last 60 seconds for a rolling average
self.requests_per_second = [t for t in self.requests_per_second if current_time - t < 60]
current_rps = len(self.requests_per_second)
# --- Define our custom load pattern ---
target_users = 0
# Overnight low traffic
if 2 <= (elapsed_time % 86400) // 3600 < 7: # 2 AM to 7 AM
target_users = 50
# Morning build-up
elif 7 <= (elapsed_time % 86400) // 3600 < 10: # 7 AM to 10 AM
target_users = 150
# Peak morning hours
elif 10 <= (elapsed_time % 86400) // 3600 < 12: # 10 AM to 12 PM
target_users = 300
# Lunchtime dip
elif 12 <= (elapsed_time % 86400) // 3600 < 14: # 12 PM to 2 PM
target_users = 200
# Afternoon
elif 14 <= (elapsed_time % 86400) // 3600 < 17: # 2 PM to 5 PM
target_users = 250
# Evening / Flash Sale potential
elif 17 <= (elapsed_time % 86400) // 3600 < 21: # 5 PM to 9 PM
# Introduce a spike for a flash sale every few days
if int(elapsed_time) % (86400 * 3) < 3600: # Spike for 1 hour, every 3 days
target_users = 500
else:
target_users = 200
# Late evening
elif 21 <= (elapsed_time % 86400) // 3600 < 23: # 9 PM to 11 PM
target_users = 100
# Late night
else: # 11 PM to 2 AM
target_users = 70
# Adjust user count gradually
current_user_count = self.environment.runner.user_count
if current_user_count < target_users:
self.environment.runner.scale_users(1)
elif current_user_count > target_users:
self.environment.runner.scale_users(-1)
self.requests_per_second = [] # Reset for the next interval
This EcommerceLoadShape class intercepts requests and uses the tick method to dynamically adjust the number of simulated users. The tick method is called by Locust every second by default. Inside tick, we calculate the elapsed time within a 24-hour cycle (elapsed_time % 86400) and then use a series of if/elif statements to determine the target_users based on the hour of the day. We also include a periodic spike for a flash sale.
The core idea is to translate your understanding of real-world traffic into a series of target user counts for different time periods. Locust then attempts to match this target by scaling users up or down by one at a time in each tick interval.
The most powerful lever you have when modeling custom load shapes is the granularity of your time-based conditions. You can go down to minutes or even seconds if your traffic patterns are that precise.
The next step is to integrate this with external data sources.