Locust runs on gevent — every "user" is a greenlet, not an OS thread. That lets a single process drive thousands of virtual users on commodity hardware. The two HTTP clients are HttpUser (uses requests) and FastHttpUser (uses geventhttpclient, 3–10× faster). For more than a few thousand users, run distributed (--master / --worker) or single-host multi-core with --processes.
Install · runSetup
bash
# Install
pip install "locust>=2.30"
# Optional: gevent-friendly faster HTTP (already bundled, but you may pin)
pip install "geventhttpclient>=2"
# Project layout
# locustfile.py # auto-discovered when running `locust`
# tests/load_*.py # alternative — pass with -f
# shapes.py # custom LoadTestShape (optional)
# Hello world (locustfile.py)
cat > locustfile.py <<'EOF'
from locust import HttpUser, task, between
class WebsiteUser(HttpUser):
wait_time = between(1, 3)
@task
def index(self):
self.client.get("/")
EOF
# Run — web UI on http://localhost:8089
locust -f locustfile.py --host https://stg.example.com
# Headless (CI mode)
locust -f locustfile.py --host https://stg.example.com \
--users 200 --spawn-rate 50 --run-time 2m \
--headless --print-stats --html report.html --csv results
# Distributed (separate terminals / hosts)
locust -f locustfile.py --master # control plane
locust -f locustfile.py --worker --master-host=10.0.0.1
# Or scale workers with --processes (single-host multi-core)
locust -f locustfile.py --processes 4 --headless --users 1000 --spawn-rate 100 --run-time 5m
Where things liveCommon imports
from locust import HttpUser, FastHttpUser, User
Base classes. FastHttpUser for high throughput.
from locust import task, tag, constant, between, constant_pacing
Task decorator + wait-time helpers.
from locust import TaskSet, SequentialTaskSet
Reusable task groups. Sequential runs in order.
from locust import events
Event hooks — start, stop, request, quitting…
from locust import LoadTestShape
Programmatic ramp shapes.
from locust.runners import MasterRunner, WorkerRunner, LocalRunner
Detect distributed mode in event handlers.
from locust.contrib.fasthttp import FastHttpUser # legacy path, still works
Older import path — new code uses top-level.
HttpUser · FastHttpUserUser classes
class WebUser(HttpUser): wait_time = between(1, 3)
Wait between tasks. requests-based.
class WebUser(FastHttpUser): ...
Faster Gevent HTTP — 3–10× throughput.
host = "https://example.com"
Default base URL. Override with --host.
weight = 3
Spawn ratio across user classes. 3:1 means 3× more of this user.
fixed_count = 2
Pin exactly N of this user, ignoring weight.
def on_start(self): ...
Per-VU setup — runs once when the VU spawns. Log in here.
Single-host scale Auto-spawn N worker processes on one machine.
--tags smoke / --exclude-tags slow
Filter by @tag(...).
--stop-timeout 30
Seconds to let users finish their current task before exit.
--config locust.conf
Read flags from a config file.
master · workersDistributed mode
locust -f f.py --master
Coordinator — serves the web UI, no traffic itself.
locust -f f.py --worker --master-host=10.0.0.1
Connect a worker.
locust -f f.py --processes -1
One worker per CPU core (single host).
--expect-workers N
Master waits until N workers connect before starting.
User count is divided across workers
Spawn rate per worker = total / worker_count.
isinstance(env.runner, MasterRunner)
Gate logic that should only run on the master.
@events.report_to_master / @events.worker_report
Push custom metrics across the boundary.
Locust scales horizontally — the master’s CPU caps total throughput. If the master pegs at 100%, add more workers or shed UI usage.
login → browse → checkoutEnd-to-end · full flow
FastHttpUser shopper, weighted browse vs. checkout, per-VU login in on_start, and a quitting event that turns p95 + error-rate into a CI exit code.
python
# locustfile.py — login, browse, checkout with events & SLO gating
import os, json, logging
from locust import FastHttpUser, task, between, events
from locust.runners import MasterRunner
BASE = os.getenv("API_URL", "https://stg.example.com")
with open("users.json") as f: # loaded once per worker process
USERS = json.load(f)
class Shopper(FastHttpUser):
host = BASE
wait_time = between(0.5, 2)
def on_start(self):
u = USERS[self.environment.runner.user_count % len(USERS)]
r = self.client.post("/api/login", json=u, name="POST /api/login")
self.token = r.json()["token"]
self.client.headers["Authorization"] = f"Bearer {self.token}"
@task(4)
def browse(self):
self.client.get("/api/items", name="GET /api/items")
@task(1)
def checkout(self):
with self.client.post("/api/orders", json={}, catch_response=True,
name="POST /api/orders") as r:
if r.status_code != 201:
r.failure(f"status={r.status_code}")
# Fail the run when SLOs miss
@events.quitting.add_listener
def _check_slo(environment, **kw):
s = environment.stats.total
if s.fail_ratio > 0.01:
environment.process_exit_code = 1
logging.error(f"fail ratio {s.fail_ratio:.2%} > 1%")
if s.get_response_time_percentile(0.95) > 500:
environment.process_exit_code = 1
logging.error(f"p95 {s.get_response_time_percentile(0.95)}ms > 500ms")
@events.test_start.add_listener
def _on_start(environment, **kw):
if isinstance(environment.runner, MasterRunner):
logging.info(f"Starting against {BASE}")
Best practiceGood to know
Group dynamic URLs with name="...".
Without it, /users/123 and /users/124 become separate rows in the stats table — one per real user.
Reach for FastHttpUser when throughput matters.HttpUser uses requests, which holds GIL-fighting locks — ~3–10× slower per VU. Switch when one node can’t generate the load.
Locust doesn’t fail CI by default.
A run with 100% errors still exits 0 unless you set environment.process_exit_code in @events.quitting. Make this a habit.
Common trapsWatch out for
Don’t mix requests with HttpUser sessions.
Calling requests.get(url) bypasses Locust’s instrumentation entirely — the request runs but isn’t measured. Always use self.client.
Blocking sync code blocks the whole worker.
Greenlets are cooperative — a CPU-bound NumPy call or a time.sleep() pauses every other user on that process. Use gevent.sleep().
One node has a ceiling.
A single Python process tops out around 1–3k VUs depending on workload. Hit the wall → --processes or distributed mode.
Locust is an open-source Python load testing tool. You write user behavior as plain Python code (not XML or YAML), then Locust spawns thousands of concurrent users to hammer your service and records response times, error rates, and throughput in real time via a web UI or headless mode.
What is the difference between HttpUser and FastHttpUser in Locust?
HttpUser uses the requests library under the hood — full feature parity but slower due to the synchronous I/O. FastHttpUser uses geventhttpclient, which is significantly faster for high-concurrency scenarios. Use FastHttpUser when you need to simulate thousands of users per machine; use HttpUser when you need requests-specific features like session or auth adapters.
How do I run Locust in headless mode?
Add --headless to skip the web UI: locust --headless -u 200 -r 20 -t 5m sets 200 users, a spawn rate of 20 users/second, and a 5-minute test duration. Pair with --csv=results to write output files, and hook events.quitting to set the exit code based on SLO thresholds.
What is a custom load shape in Locust?
A custom load shape lets you vary user count and spawn rate over time in arbitrary patterns — ramps, spikes, steps, or real-traffic replays. Subclass LoadTestShape and implement tick() to return (user_count, spawn_rate) tuples. Return None to stop the test. Shapes override the --users and --spawn-rate CLI flags.
How does distributed load testing work in Locust?
Run one master (locust --master) and any number of workers (locust --worker --master-host=master). Workers connect to the master, receive task instructions, and send results back for aggregation. Each worker can simulate hundreds of users, letting you scale to millions of requests per second across a cluster.
Is Locust free and open source?
Yes. Locust is MIT-licensed and free to use. It runs on any machine with Python installed. For large distributed tests you pay only for the infrastructure (VMs, containers) you run the workers on.