Getting Started with Performance Testing Using Locust: A Comprehensive Guide

Performance testing is crucial for ensuring that applications and systems deliver a seamless user experience, even under high traffic or resource constraints. In this blog, we will introduce the fundamentals of performance testing and how to leverage Locust, an open-source Python-based tool, to simulate user traffic, identify bottlenecks, and optimize system performance. Whether you are a seasoned developer or new to Python, this guide will help you understand and use Locust effectively.

What is Performance Testing?

Performance testing evaluates how a system behaves under specific conditions, such as varying loads, user traffic, or limited resources. The primary goal is to identify and address bottlenecks, optimize performance, and ensure stability.

Related read: Mastering Performance Testing with Apache JMeter: A Comprehensive Guide

Key Metrics

➡️ Response Time: The time taken for a request to be processed and a response to be received.
Example: If a user requests a webpage, the time taken for the page to load is the response time.

➡️ Throughput: The number of transactions or requests processed per unit of time.
Example: A website handling 500 requests per second.

➡️ Latency: The delay between a request being sent and the first byte of the response being received.
Example: A delay caused by network issues.

➡️ Error Rate: The percentage of failed requests.
Example: If 2 out of 100 requests fail, the error rate is 2%.

➡️ Concurrency: The number of simultaneous users or threads accessing the system.
Example: A live-streaming platform handling 10,000 concurrent viewers.

Types of Performance Testing

  1. Load Testing: Evaluates system behavior under expected user loads.
  2. Stress Testing: Pushes the system beyond its capacity to find breaking points.
  3. Soak Testing: Runs the system over an extended period to check for memory leaks or stability issues.
  4. Spike Testing: Assesses the system’s ability to handle sudden traffic surges.

Why is Performance Testing Important?

Performance testing helps:

●  Identify System Bottlenecks: Highlight areas where the application slows down or fails.
●  Optimize Resource Utilization: Ensure efficient use of CPU, memory, and network resources.
●  Validate Scalability: Confirm the system can handle increased user traffic.
●  Improve User Experience: Ensure low latency and fast response times.
●  Prevent Crashes: Guarantee stability during peak loads.

What is Locust?

Locust is an open-source performance testing tool written in Python. It allows developers to simulate millions of concurrent users and test the scalability of web applications, APIs, and other systems.

Why Use Locust?

  1. Python-Based: Write test scripts using Python, making it beginner-friendly and highly customizable.
  2. Distributed Testing: Scale tests horizontally by running them on multiple machines.
  3. Real-Time Monitoring: Get metrics like response times, error rates, and throughput during tests.
  4. Customizable Scenarios: Simulate user behavior with complex workflows, delays, and authentication flows.
  5. Lightweight: Minimal resource usage compared to other tools like JMeter.
  6. Headless Mode: Run tests in CI/CD pipelines for automated performance testing.

Fundamentals of Locust

Locust is designed to simulate user behavior by defining workflows in Python. Let’s explore the key components and features of Locust:

1. HttpUser Class

The HttpUser class represents a simulated user that makes HTTP requests. All user tasks in Locust are defined as methods within this class.

Example:

from locust import HttpUser, task, between

class WebsiteUser(HttpUser):
    wait_time = between(1, 2) # Simulate user think time

    @task
    def browse_home(self):
    self.client.get("/") # Simulates a GET request to the homepage

    @task
    def browse_products(self):
        self.client.get("/products") # Simulates a GET request to the products page

2. TaskSet Class

The TaskSet class groups tasks that a user performs randomly. It helps create user workflows.

Example:

from locust import HttpUser, TaskSet, task

class UserBehavior(TaskSet):
    @task
    def view_item(self):
    self.client.get("/item")

    @task
    def add_to_cart(self):
        self.client.post("/cart", json={"item_id": 1})

class WebsiteUser(HttpUser):
    tasks = [UserBehavior]

3. SequentialTaskSet Class

The SequentialTaskSet class is used when tasks need to be executed in a specific order.

Example:

from locust import HttpUser, SequentialTaskSet, task

class CheckoutProcess(SequentialTaskSet):
    @task
    def browse_items(self):
        self.client.get("/items")

    @task
    def add_to_cart(self):
        self.client.post("/cart", json={"item_id": 2})

    @task
    def checkout(self):
        self.client.post("/checkout")

class WebsiteUser(HttpUser):
    tasks = [CheckoutProcess]

4. wait_time

In Locust, the wait_time attribute defines the delay between the execution of consecutive tasks for a simulated user. It helps mimic real-world user behavior, as users don’t perform actions instantly. Locust provides several built-in wait time functions to simulate realistic delays.

➡️ between(min_time, max_time)

The between function introduces a random delay between tasks, with the delay duration lying within the range of min_time and max_time (in seconds).

Example:

from locust import HttpUser, between, task

class WebsiteUser(HttpUser):
    wait_time = between(2, 5) # Delay between 2 to 5 seconds randomly

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

Explanation:

● After the browse task, there will be a random delay of 2 to 5 seconds before the next task is executed.

➡️ constant(wait_time)

The constant function introduces a fixed delay between tasks.

Example:

from locust import HttpUser, constant, task

class WebsiteUser(HttpUser):
    wait_time = constant(3) # Delay of 3 seconds between tasks

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

Explanation:

● After the browse task, there will always be a fixed delay of 3 seconds before the next task.

➡️ constant_pacing(wait_time)

The constant_pacing function ensures that the total time taken for a task (execution time + wait time) remains constant. If the task is completed quickly, the remaining time is used as the wait time.

Example:

from locust import HttpUser, constant_pacing, task

class WebsiteUser(HttpUser):
    wait_time = constant_pacing(5) # Total time for task + wait = 5 seconds

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

Explanation:

● If the browse task takes 1 second to execute, the wait time will be 4 seconds.
● This method ensures consistent pacing, especially for time-sensitive tests.

5. tasks=[] List

The tasks attribute defines the list of tasks a user can perform.

Example:

class WebsiteUser(HttpUser):
    tasks = [task1, task2] # Tasks can be defined as separate functions

6. @task Decorator

The @task decorator marks methods as user tasks.

Example:

from locust import HttpUser, task

class WebsiteUser(HttpUser):
   @task
   def load_home(self):
       self.client.get("/")

7. @tag Decorator

The @tag decorator allows you to mark specific tasks and execute them selectively.

Example:

from locust import HttpUser, task, tag

class WebsiteUser(HttpUser):
    @tag("homepage")
    @task
    def home(self):
        self.client.get("/")

    @tag("product")
    @task
    def products(self):
        self.client.get("/products")

Run specific tasks using the –tags flag:

locust -f locustfile.py --tags homepage

8. Task Weight

Tasks can be assigned weights to control their frequency.

Example:

from locust import HttpUser, task

class WebsiteUser(HttpUser):
    @task(3) # Runs three times more often than the next task
    def browse(self):
        self.client.get("/browse")

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

9. on_start and on_stop

These methods define setup and teardown logic for each user.

Example:

from locust import HttpUser, task

class WebsiteUser(HttpUser):
    def on_start(self):
        self.client.post("/login", json={"username": "test", "password": "test"})

    def on_stop(self):
        self.client.post("/logout")

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

Transform Your Web App's Performance with Effective Load Testing Using Locust!

How to Run Locust Using UI and Headless?

➡️ Running Locust with Web UI

locust -f locustfile.py

● Open http://localhost:8089 in a browser.
● Specify:

  1. Number of Users: Total concurrent users.
  2. Spawn Rate: Users spawned per second.
  3. Host: Base URL of the system.

➡️ Running Locust in Headless Mode

locust -f locustfile.py --headless -u 100 -r 10 --run-time 10m --host https://example.com --csv=results

●  -u 100: 100 concurrent users.
●  -r 10: Spawn 10 users per second.
●  –run-time 10m: Run for 10 minutes.
●  –csv=results: Generate CSV reports.

Running Locust for Different Types of Tests

Locust allows you to simulate various performance testing scenarios. Here’s how to configure and execute them effectively:

1. Load Testing

Load testing evaluates system performance under expected user loads. It helps determine if the system can handle the anticipated traffic without degradation.

Example Command:

locust -f locustfile.py --headless --users 100 --spawn-rate 10 --run-time 5m

Explanation:

–headless: Runs Locust without the web UI.
–users 100: Simulates 100 concurrent users.
–spawn-rate 10: Adds 10 users per second until the total reaches 100.
–run-time 5m: Runs the test for 5 minutes.

2. Stress Testing

Stress testing pushes the system beyond its capacity to identify breaking points. It’s useful for testing system resilience and error handling under extreme conditions.

Example Command:

locust -f locustfile.py --headless --users 1000 --spawn-rate 50 --run-time 10m

Here, 1,000 users are gradually added at a rate of 50 per second to observe how the system behaves under high load.

3. Soak Testing

Soak testing evaluates system performance over an extended period to identify memory leaks, performance degradation, or resource exhaustion.

Example Command:

locust -f locustfile.py --headless --users 200 --spawn-rate 5 --run-time 2h

This command simulates a sustained load of 200 users for 2 hours, mimicking long-term usage.

4. Spike Testing

Spike testing examines how the system handles sudden traffic surges.

Example Command:

locust -f locustfile.py --headless --users 500 --spawn-rate 500 --run-time 5m

In this case, 500 users are added instantly (high spawn rate), creating a traffic spike.

Generating Reports

Locust provides built-in mechanisms to generate detailed performance reports during and after test execution. These reports include key metrics like response time, throughput, error rates, and more.

➡️ Generate Reports in CSV Format

Use the –csv flag to export results:

locust -f locustfile.py --headless -u 100 -r 10 --run-time 10m --csv=results

This generates files like:

● results_stats.csv
● results_failures.csv

➡️ Generate HTML Reports

Use the –html flag:

locust -f locustfile.py --headless -u 100 -r 10 --run-time 10m --html=report.html
coma

Conclusion

Locust is a powerful and flexible tool for performance testing, suitable for a wide range of scenarios. By combining its Python-based scripting capabilities with real-time monitoring and reporting, you can gain valuable insights into your system’s performance and ensure its readiness for production traffic. Whether you’re running load tests, stress tests, or spike tests, Locust equips you with everything needed to optimize application performance.

Keep Reading

Keep Reading

  • Service
  • Career
  • Let's create something together!

  • We’re looking for the best. Are you in?