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.

Want structured learning?

Take the full Locust course →