Spike Testing with Locust’s LoadTestShape Class

Keith Miller
4 min readDec 30, 2022

In an earlier article I discussed the benefits of performance testing and went through a small walkthrough of setting up a bare-bones performance tester application using Faker. Since then, I have come across a few more mature applications for performance testing. One of my favorite is Locust.

Locust is really easy to use but also allows you to be as granular as you desire in defining user actions and testing. Simply hitting endpoints multiple times doesn’t really give you a true indication of actual users interfacing with your application. Locust allows you to define the user with the User class . You can specify a user that uses Chrome, Safari, mobile, etc. Within the User class you can use a task decorator to emulate actions the user would take.

A quick way to emulate granular tasks is using a tool called har2locust. This tool allows you to record sessions with DevTools and export a .har file that can be used to generate a locust file that mirrors the user and tasks that you recorded. If you do use this tool in your work environment, my recommendation would be to make sure you are using a user with limited access (using the least privilege rule), in addition make sure to remove all authorization tokens from the generated file.Here is a snippet of a generated file from one of my recorded sessions:

class myhost_com(FastHttpUser):
host = "https://myhost.com"
default_headers = {
"accept-encoding": "gzip, deflate, br",
"accept-language": "en-US,en;q=0.9",
"cache-control": "no-cache",
"pragma": "no-cache",
"sec-ch-ua": '"Not?A_Brand";v="8", "Chromium";v="108", "Google Chrome";v="108"',
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": '"macOS"',
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36",
}

@task
def t(self):
with self.rest(
"POST",
"/api/v1/users/sign_in",
headers={
"accept": "application/json, text/plain, */*",
"origin": "https://myhost.com",
"referer": "https://myhost.com/",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-site",
},
json={"email": "keith@myhost.com", "password": "<my_password>"},
) as resp:
pass
with self.rest(
"PATCH",
"/api/v1/users/<user_id>",
headers={
"accept": "application/json, text/plain, */*",
"authorization": "Bearer <some-token>",
"origin": "https://myhost.com",
"referer": "https://myhost.com/",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-site",
},
json={"favorite_sites": "0"},
) as resp:
pass
with self.rest(
"GET",
"/api/v1/somepage/users/<user_id>",
headers={
"accept": "application/json, text/plain, */*",
"authorization": "Bearer <some-token>",
"origin": "https://myhost.com",
"referer": "https://myhost.com/",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-site",
},
) as resp:
pass

Types of Performance Tests

There are four major types of Performance Tests:

  • Load — Simulate the number of virtual users
  • Stress — Identify a breaking point (usually going beyond expected load)
  • Spike — Number of users change suddenly
  • Volume — Requests that are data intensive

There are a number of ways that you can use the GUI to handle the first two types by simply adding the amount of users and spawn rate. In the image listed below I simply selected the amount of users I wanted to reach and the rate which to create them. Each of these users are logging in and poking around the site.

Spike Test Example

Spike testing requires a little more fine tuning that the GUI does not provide. How would I create a spike test where the amount of users could vary throughout the test run? Enter the LoadTestShape class. I would also recommend this class for load and stress type tests since it allows you to control users and spawn_rate based on other conditions as well. But for this article, we’ll focus on how I used this for spike testing.

from locust import LoadTestShape
import random

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

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

if run_time < self.time_limit:
user_count = random.randrange(13288)
return (user_count, self.spawn_rate)

return None

I’ll admit that this snippet could use some finesse in handling when and how often the number of users is created and maybe also randomizing the spawn rate as well. But for our purposes the code block is simple enough to explain and also show the granularity possible with Locust. Lets start by breaking down the snippet.

The time_limit and spawn_rate are self explanatory. The time_limit obviously indicates how long the test will run in seconds and the spawn_rate is how often a user starts a session.

The tick() method occurs every second and within each tick the code within that method is executed. At each tick a random amount of users are created from 0 to 13288. This will go on until runtime is no longer less than self.time_limt . Pretty simple!!!

Below is the graphical representation of the spike test:

--

--