Custom Shapes allow you to precisely control how users ramp up and down during your tests. This is useful for advanced scenarios where a fixed or linear user load is insufficient.

Tip: The default LoadForge test settings work well for most use cases. Custom Shapes should be used when you have a specific load profile requirement.

Basic Custom Shape

This shape gradually increases users in blocks of 100 over a 10-minute test duration:

from locust import HttpUser, task, between
from locust import LoadTestShape

class QuickstartUser(HttpUser):
    wait_time = between(5, 9)

    @task(1)
    def index_page(self):
        self.client.get("/")

class MyCustomShape(LoadTestShape):
    time_limit = 600
    spawn_rate = 20

    def tick(self):
        run_time = self.get_run_time()

        if run_time < self.time_limit:
            user_count = round(run_time, -2)  # Round to nearest 100
            return (user_count, self.spawn_rate)
        return None

Double Wave Custom Shape

Simulates two peaks of user traffic, useful for scenarios like meal times or daily traffic fluctuations.

import math
from locust import HttpUser, task, between
from locust import LoadTestShape

class QuickstartUser(HttpUser):
    wait_time = between(5, 9)

    @task(1)
    def index_page(self):
        self.client.get("/")

class DoubleWave(LoadTestShape):
    min_users = 20
    peak_one_users = 60
    peak_two_users = 40
    time_limit = 300

    def tick(self):
        run_time = round(self.get_run_time())

        if run_time < self.time_limit:
            user_count = (
                (self.peak_one_users - self.min_users) * math.exp(-(((run_time / (self.time_limit / 10 * 2 / 3)) - 5) ** 2))
                + (self.peak_two_users - self.min_users) * math.exp(-(((run_time / (self.time_limit / 10 * 2 / 3)) - 10) ** 2))
                + self.min_users
            )
            return (round(user_count), round(user_count))
        return None

Stages Custom Shape

Define exact user counts at specific times.

from locust import HttpUser, task, between
from locust import LoadTestShape

class QuickstartUser(HttpUser):
    wait_time = between(5, 9)

    @task(1)
    def index_page(self):
        self.client.get("/")

class StagesShape(LoadTestShape):
    stages = [
        {"duration": 60, "users": 10, "spawn_rate": 10},
        {"duration": 100, "users": 50, "spawn_rate": 10},
        {"duration": 180, "users": 100, "spawn_rate": 10},
        {"duration": 220, "users": 30, "spawn_rate": 10},
        {"duration": 240, "users": 1, "spawn_rate": 1},
    ]

    def tick(self):
        run_time = self.get_run_time()
        for stage in self.stages:
            if run_time < stage["duration"]:
                return (stage["users"], stage["spawn_rate"])
        return None

Step Load Shape

Increases users in steps and waits at each step before increasing further.

import math
from locust import HttpUser, task, between
from locust import LoadTestShape

class QuickstartUser(HttpUser):
    wait_time = between(5, 9)

    @task(1)
    def index_page(self):
        self.client.get("/")

class StepLoadShape(LoadTestShape):
    step_time = 30
    step_load = 10
    spawn_rate = 10
    time_limit = 600

    def tick(self):
        run_time = self.get_run_time()
        if run_time > self.time_limit:
            return None
        current_step = math.floor(run_time / self.step_time) + 1
        return (current_step * self.step_load, self.spawn_rate)

When to Use Custom Shapes

Consider using a custom shape if:

  • You need gradual user ramp-ups rather than sudden traffic spikes.
  • You are simulating real-world traffic patterns, such as lunch-hour surges.
  • Your application has specific scalability concerns requiring controlled increases.

Otherwise, the default LoadForge load shape is recommended for general testing.

By implementing Custom Shapes, you can fine-tune your LoadForge tests to reflect realistic user behavior and performance bottlenecks more accurately.