load generator rust
lorust is a Rust load generator for HTTP APIs. It supports simple CLI-driven
HTTP load tests and TOML flow files with RustPython scripting between actions.
cargo build --release
target/release/lorust --helpFor development, replace target/release/lorust with:
cargo run --Run 100 total requests at 10 requests per second:
target/release/lorust http https://example.com \
-n 100 \
-r 10 \
--output-path metrics.jsonRun for 60 seconds at 10 requests per second:
target/release/lorust http https://example.com \
--duration 60s \
-r 10 \
--output-path metrics.jsonSend a POST request with headers and a raw body:
target/release/lorust http https://api.example.com/users \
-n 50 \
-r 5 \
-m POST \
-H 'Content-Type: application/json' \
-d '{"name":"Ada"}' \
--timeout 10 \
--output-path metrics.jsonFail the process when thresholds are missed:
target/release/lorust http https://example.com \
--duration 30s \
-r 20 \
--max-error-rate 1 \
--max-p95-ms 500 \
--min-rps 18 \
--output-path metrics.jsonlorust has two primary modes:
http: run a simple HTTP load test without writing a flow file.run: run a TOML or JSON flow definition.
Legacy top-level --flow and --flow-path flags are still supported.
These flags work with both http and run.
| Option | Description | Default |
|---|---|---|
--output-path <path> |
Write raw HTTP metrics JSON to this path. | metrics_output |
--run-id <id> |
Logical run ID attached to every emitted metric. | generated |
--worker-id <id> |
Worker ID attached to every emitted metric. | local |
Example:
target/release/lorust \
--run-id local-check \
--worker-id laptop \
http https://example.com -n 10 -r 2Usage:
target/release/lorust http [OPTIONS] <URL>| Option | Description | Default |
|---|---|---|
<URL> |
URL to request. | required |
-n, --requests <n> |
Total number of requests to execute. | 1 when --duration is absent |
-r, --rate <n> |
Requests to start per second. | 1 |
--duration <duration> |
Start requests for a duration. Supports plain seconds, s, m, and h. |
unset |
-m, --method <method> |
HTTP method. Defaults to POST when --body is provided, otherwise GET. |
GET or POST |
-H, --header <header> |
Header in Name: value format. Can be passed multiple times. |
none |
-d, --body <body> |
Raw request body. | none |
--timeout <seconds> |
Per-request timeout in seconds. | 30 |
--redirect-limit <n> |
Maximum redirects to follow. | 5 |
--max-response-body-bytes <n> |
Maximum failed-response body bytes to store in metrics. | 4096 |
--max-error-rate <percent> |
Fail if error rate is above this percentage. | unset |
--max-p95-ms <ms> |
Fail if p95 latency is above this many milliseconds. | unset |
--max-p99-ms <ms> |
Fail if p99 latency is above this many milliseconds. | unset |
--min-rps <rps> |
Fail if achieved requests/sec is below this value. | unset |
--requests and --duration are mutually exclusive. Use request-count mode for
fixed-size tests and duration mode for time-window tests.
lorust prints a human-readable summary to stdout:
=== Load test complete ===
TOTAL TASKS: 100
PASSED: 100
FAILED: 0
Collected metrics array size: 100
=== HTTP metric summary ===
REQUESTS: 100
HTTP 2XX: 100
HTTP NON-2XX/ERROR: 0
ERROR RATE: 0.00%
REQUESTS/SEC: 9.98
AVG LATENCY MS: 42.10
P50 LATENCY MS: 40
P95 LATENCY MS: 75
P99 LATENCY MS: 91
Saving collected metrics to: "metrics.json"
It also writes raw per-request metrics to --output-path as a JSON array.
Each HTTP metric includes:
run_idworker_idtask_idsequenceurlhttp_verbstatus_coderesponse_body_sizetime_stampstarted_at_nanosresponse_bodyresponse_body_truncated- upload/download totals and speeds
- DNS/connect/TLS/start-transfer/elapsed/redirect timings in milliseconds
Metrics are sorted by:
started_at_nanosworker_idtask_idsequence
This makes local output easier to inspect and prepares the format for future multi-worker aggregation.
The process exits successfully when the load test passes and all configured thresholds pass.
The process exits non-zero when:
- any task fails
- HTTP failures make tasks fail
- a configured threshold fails
- a flow or CLI argument is invalid
Even when a threshold fails, lorust still writes the metrics file before
returning the error.
Use flow files for multi-step scenarios, scripting, or variable interpolation. TOML is the preferred authoring format because multiline Python code can be written as literal strings. JSON flow files are still supported for compatibility.
Run a flow file:
target/release/lorust run --flow-path flow.toml --output-path metrics.jsonRun an inline JSON flow:
target/release/lorust run \
--flow '{"functions":[{"LoadGen":{"max_tasks":1,"spawn_rate":"1","timeout":5,"functions_to_execute":[{"Sleep":{"duration":"1"}}]}}]}'Legacy form:
target/release/lorust --flow-path flow.toml --output-path metrics.jsonFlow files are parsed by extension. Use .toml for the readable flow syntax and
.json for the legacy enum-shaped JSON syntax.
Each [[loadgen]] table schedules virtual-user tasks.
Common fields:
| Field | Description |
|---|---|
spawn_rate |
Python expression returning tasks to start per second. TICK is available. |
timeout |
Per virtual-user timeout in seconds. |
max_tasks |
Total tasks to start. |
duration |
Seconds to start tasks for. |
run_id |
Optional run ID. CLI --run-id overrides this. |
worker_id |
Optional worker ID. CLI --worker-id overrides this. |
thresholds |
Optional summary threshold object. |
step |
Ordered [[loadgen.step]] tables each virtual user executes. |
max_tasks and duration can both be omitted only if the CLI path supplies a
default. In flow files, provide at least one.
Request-count example:
[[loadgen]]
max_tasks = 100
spawn_rate = "10"
timeout = 30
[[loadgen.step]]
type = "http"
url = "https://example.com"
timeout = 10Duration example:
[[loadgen]]
duration = 60
spawn_rate = "10"
timeout = 30
[[loadgen.step]]
type = "http"
url = "https://example.com"
timeout = 10Threshold example:
[[loadgen]]
duration = 30
spawn_rate = "20"
timeout = 30
[loadgen.thresholds]
max_error_rate = 1.0
max_p95_latency_ms = 500
max_p99_latency_ms = 1000
min_requests_per_sec = 18.0
[[loadgen.step]]
type = "http"
url = "https://example.com"type = "http" performs one HTTP request.
Fields:
| Field | Description | Default |
|---|---|---|
url |
Request URL. | required |
method |
HTTP method. | GET |
headers |
Inline table of HTTP header names to values. | {} |
body |
Raw request body string. | empty |
form_data |
Inline table of multipart form field names to string values or file tables. | unset |
form_urlencoded |
Inline table of form field names to values. | unset |
timeout |
Request timeout in seconds. | 60 |
redirect_limit |
Maximum redirects to follow. | 5 |
max_response_body_bytes |
Maximum failed-response body bytes to store in metrics. | 4096 |
GET example:
[[loadgen.step]]
type = "http"
url = "https://example.com"
timeout = 10POST with raw JSON body:
[[loadgen.step]]
type = "http"
method = "POST"
url = "https://api.example.com/users"
headers = { "Content-Type" = "application/json" }
body = '''
{"name":"Ada"}
'''
timeout = 10
max_response_body_bytes = 4096Only one body style can be used on an HTTP step:
- omit
body,form_data, andform_urlencodedfor an empty body body = '''...'''for a raw request bodyform_data = { name = "Ada" }for multipart form fieldsform_data = { avatar = { file_path = "avatar.png", content_type = "image/png" } }for multipart filesform_urlencoded = { name = "Ada" }for URL-encoded form bodies
BinaryOctetFilePath exists in the type but is not implemented yet.
type = "python" runs RustPython code against the current virtual-user local
scope.
Values assigned in Python are written back to the virtual-user scope when they can be serialized to JSON.
Example:
[[loadgen.step]]
type = "python"
code = '''
user_id = http_response["data"][0]["id"]
'''Strings can interpolate Python expressions with %|...|%.
Example:
[[loadgen.step]]
type = "http"
url = "https://api.example.com/users/%|user_id|%"The expression is evaluated against the virtual-user local scope.
type = "sleep" pauses the virtual user for a number of seconds.
[[loadgen.step]]
type = "sleep"
duration = "1"[[loadgen]]
max_tasks = 2
spawn_rate = "1"
timeout = 300
[[loadgen.step]]
type = "http"
url = "https://reqres.in/api/users?page=1"
timeout = 300
[[loadgen.step]]
type = "python"
code = '''
user_id = http_response["data"][0]["id"]
print(f"Picked user_id: {user_id}")
'''
[[loadgen.step]]
type = "http"
url = "https://reqres.in/api/users/%|user_id|%"
timeout = 300
[[loadgen.step]]
type = "python"
code = '''
data = http_response["data"]
print(data["first_name"] + " " + data["last_name"])
'''lorust is still a single-node load generator today. The metric format already
includes run_id, worker_id, task_id, and sequence so future
supervisor/worker aggregation can merge outputs safely.
The distributed implementation plan lives in:
docs/distributed-load-testing-plan.md