Skip to main content

Locust Load Shapes

· 5 min read
Avi Stramer

Locust.io is a popular Python based open-source load testing tool that allows developers to simulate user traffic on their websites or applications.

Locust comes with several parameters to let you shape the amount of load it produces. It also gives you the ability to customize load shapes, which enables developers to create more realistic test scenarios for many use cases.

In this post, we will explore all of these options and look at some examples using tests run with Locust on the Testable platform.

Basic Load Shapes

Locust provides two parameters out of the box that are often sufficient to define your load shape:

  1. # of Users (--users): Peak number of concurrent Locust users.
  2. Spawn Rate (--spawn-rate): Rate to spawn users at per second. Once the peak number of users is reached, it will stop spawning additional users.
  3. Run time (--run-time): Stop after the specified amount of time.

These two basic parameters give you the ability to create two load shapes out of the box:

  1. Constant Load: In this load shape, the number of users remains constant throughout the duration of the test. Example: --users 100 --spawn-rate 100 --run-time 10m -- spawn 100 users the first second of the test and then hold for 10 minutes.

  2. Ramp Up Load: In this load shape, the number of users is gradually increased until reaching the peak number at which point we hold for the remaining time. Example: --users 120 --spawn-rate 1 --run-time 5m -- ramp up to 120 users over 2 minutes (1 per second) and then hold for another 3 minutes (5 minutes total).

Custom Load Shapes

Sometimes the above basic load shapes are not sufficient to simulate the traffic patterns you require for your website or application. For this Locust provides the ability to define a custom load shape.

Custom Load Shape API

class LoadTestShape:

# Returns how long the test has been running for in seconds
def get_run_time()

# Must be implemented by the user. Locust will call this method
# expecting the following information:
# * How many users should be running right now
# * At what rate to spawn users if we have not reached that number of users
# * [Optional] Which user classes to run right now
# Response: (user_count, spawn_rate[, user_classes]) or None
# if the test should be stopped immediately.
def tick(self)

Let's look at a simple example that the Locust team provides to start.

Example: Step Function Load Shape

Let's look at this example with comments added to explain each line.

class MyCustomShape(LoadTestShape):
step_time = 30 # how long to hold each step in seconds
step_load = 10 # how many users to add each step as well as how many users to start with
spawn_rate = 10 # what rate to spawn the users per second
time_limit = 600 # how long to run the test in seconds

def tick(self):
# Returns how long the test has been running for in seconds
run_time = self.get_run_time()

# If the run time exceeds our time limit,
# return None which stops the test immediately
if run_time > self.time_limit:
return None

# Figure out which step we are currently on.
# In this example the first 30 seconds are the first step, etc.
current_step = math.floor(run_time / self.step_time) + 1
# How many users should be running right now,
# and how fast to spawn users if we need to add additional users.
# By making the step_load == spawn_rate the users for each step
# will be spawned immediately once required
return (current_step * self.step_load, self.spawn_rate)

And here is a graph of what this looks like from a Testable run:

Step Load Shape

Example: Stages Load Shape

In this example we want to have different stages in our test each with a unique load profile.

class StagesShape(LoadTestShape):
# At each stage we specify the duration (in seconds)at which that stage should end
# and the number of users we want during that stage.
stages = [
{"duration": 60, "users": 10}, # first 60 seconds = 10 users
{"duration": 100, "users": 50}, # next 40 seconds = 50 users
{"duration": 180, "users": 100}, # next 80 seconds = 100 users
{"duration": 220, "users": 30}, # next 40 seconds = 30 users
{"duration": 240, "users": 10}, # next 10 seconds = 10 users
{"duration": 260, "users": 1} # next 20 seconds = 1 user (4m20s total duration)
]

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

for stage in self.stages:
if run_time < stage["duration"]:
# arbitrary high spawn rate to get to users as quickly as possible
tick_data = (stage["users"], 100)
return tick_data

return None

And here is a graph of what this looks like from a Testable run:

Step Load Shae

Hopefully this gives you a good idea of how custom load shapes work. Check out more examples that the Locust team provided at https://github.com/locustio/locust/tree/master/examples/custom_shape.

Conclusion

In conclusion, Locust.io provides developers with a range of options to generate different load shapes. This can help simulate real-world traffic patterns, identify potential performance bottlenecks, and ensure that an application can handle the expected load.

Consider using Testable.io to run your distributed Locust tests across over 100 cloud regions as well as in your own data centers. The real-time reports are customizable and give you metrics, analysis, and traces not available out of the box with Locust.