strest
⚠️ Warning: Only use strest for testing infrastructure you own or have explicit permission to test. Unauthorized use may be illegal.
strest is a command-line tool for stress testing web servers by sending a large number of HTTP requests. It provides insights into server performance by measuring average response times, reporting observed requests per minute (RPM), and other relevant metrics.
Screenshot Overview
These screenshots showcase key metrics and real-time statistics from strest’s stress testing, including response time, error rate, request count, latency percentiles (all vs ok), timeouts, status distribution, and throughput.
Latency
Throughput
Errors
Features
- Send HTTP requests to a specified URL for a specified duration.
- Customize the HTTP method, headers, and request payload data.
- Measure the average response time of successful requests.
- Report the observed requests per minute (RPM) metric.
- Display real-time statistics and progress in the terminal.
- UI shows timeouts, transport errors, non-expected status, and ok vs all percentiles.
- UI chart window length is configurable via
--ui-window-ms(default: 10000). - Optional non-interactive summary output for long-running tests.
- Streams run metrics to disk while aggregating summary and chart data during the run.
- Optional rate limiting for controlled load generation.
- Optional CSV/JSON exports for pipeline integration.
- Scenario scripts with multi-step flows, dynamic templates, and per-step asserts.
- Experimental WASM scripting to generate scenarios programmatically.
- Warm-up period support to exclude early metrics from summaries and charts.
- TLS/HTTP/2 controls (TLS min/max, HTTP/2 toggle, ALPN selection).
- HDR histogram percentiles for accurate end-of-run latency stats.
- Pluggable output sinks (Prometheus textfile, OTel JSON, Influx line protocol).
- Distributed mode with controller/agent coordination and weighted load splits.
- Distributed streaming summaries for live aggregation and sink updates.
- Manual controller mode with HTTP start/stop control and scenario registry.
- Agent standby mode with automatic reconnects between runs.
- Experimental HTTP/3 support (build flag required).
- Linux systemd install/uninstall helpers for controller/agent services.
Who It's For
- Engineers who want a config-first, CLI-driven load test tool.
- Teams who need multi-step scenarios with assertions, not full JS runtimes.
- CI and lab users who want reproducible runs and exportable metrics.
- Distributed testing setups with controller/agent coordination.
Not a Fit For
- k6 users looking for JavaScript scripting or k6-compatible workflows.
- GUI-first users who want a hosted dashboard-first experience today.
Prerequisites
- Make sure you have Rust and Cargo installed on your system. You can install Rust from rustup.rs.
Installation
From crates.io (recommended)
Prebuilt binaries
Prebuilt binaries are attached to GitHub Releases for tagged versions (Linux, macOS, Windows).
From source
To use strest from source, follow these installation instructions:
-
Clone the repository to your local machine:
-
Change to the project directory:
-
Build the project:
-
Once the build is complete, you can find the executable binary in the
/target/release/directory. -
Copy the binary to a directory in your system's PATH to make it globally accessible:
Alternatively, install from the local path using Cargo:
Getting Started
Quick smoke test:
Scenario quickstart:
# strest.toml
[]
= "http://localhost:3000"
[[]]
= "get"
= "/health"
= 200
Usage
strest is used via the command line. Here's a basic example of how to use it:
This command sends GET requests to http://localhost:3000 for 60 seconds.
For long-running or CI runs, disable the UI and print a summary:
For more options and customization, use the --help flag to see the available command-line options and their descriptions.
Logging
Use --verbose to enable debug logging (useful for distributed controller/agent handshakes). You can also override the log level via STREST_LOG or RUST_LOG.
Presets
Smoke (quick validation, low load):
Steady (sustained load, CI-friendly):
Ramp (gradual increase):
# ramp.toml
= "http://localhost:3000"
= 300
[]
= 100
[[]]
= "60s"
= 300
[[]]
= "120s"
= 800
Charts
By default charts are stored in ~/.strest/charts (or %USERPROFILE%\\.strest\\charts on Windows). You can change the location via --charts-path (-c).
To disable charts use the --no-charts flag.
Charts produced:
average_response_time.pngcumulative_successful_requests.pngcumulative_error_rate.pngcumulative_total_requests.pngrequests_per_second.pnglatency_percentiles_P50.png(all vs ok overlay)latency_percentiles_P90.png(all vs ok overlay)latency_percentiles_P99.png(all vs ok overlay)timeouts_per_second.pngerror_rate_breakdown.png(timeouts vs transport vs non-expected)status_code_distribution.pnginflight_requests.png
UI Metrics
The UI highlights:
- Total requests, success count, and error breakdown (timeouts, transport errors, non-expected status).
- All vs ok latency percentiles (P50/P90/P99).
- Live RPS and RPM.
Temp Data
Run data is logged to a temporary file during the test while summary and chart data are aggregated during the run. This keeps the request pipeline from blocking on metrics in long runs. By default this lives in ~/.strest/tmp (or %USERPROFILE%\\.strest\\tmp on Windows). You can change the location via --tmp-path. Temporary data is deleted after the run unless --keep-tmp is set.
Charts collection can be bounded for long runs:
--metrics-rangelimits chart collection to a time window (e.g.,10-30seconds).--metrics-maxcaps the total number of metrics kept for charts (default:1000000).
Common Options
--method(-X) sets the HTTP method.--url(-u) sets the target URL.--headers(-H) adds request headers (repeatable,Key: Value).--data(-d) sets the request body data (POST/PUT/PATCH).--duration(-t) sets the test duration in seconds.--no-uidisables the interactive UI and shows a progress bar in the terminal (summary output is printed automatically).--ui-window-mssets the UI chart window length in milliseconds (default:10000).--summaryprints an end-of-run summary.--status(-s) sets the expected HTTP status code.--timeoutsets the request timeout (supportsms,s,m,h).--warmupignores the first N seconds for summary/charts/exports (supportsms,s,m,h).--proxy(-p) sets a proxy URL.--max-tasks(-m) limits concurrent request tasks (--concurrencyalias).--spawn-rate(-r) and--spawn-interval(-i) control how quickly tasks are spawned.--ratesets a global requests-per-second limit.--controller-listenstarts a distributed controller (e.g.,0.0.0.0:9009).--controller-modeselects controller mode (autoormanual).--control-listensets the manual control-plane HTTP listen address.--control-auth-tokensets the control-plane Bearer token.--agent-joinjoins a distributed controller as an agent.--auth-tokensets a shared token for controller/agent authentication.--agent-weightsets an agent weight for load distribution.--agent-idsets an explicit agent id.--min-agentssets how many agents the controller waits for before starting.--agent-wait-timeout-mssets a max wait time for min agents (auto mode; manual start honors this too).--agent-standbykeeps agents connected between distributed runs.--agent-reconnect-mssets the standby reconnect interval.--agent-heartbeat-interval-mssets the agent heartbeat interval.--agent-heartbeat-timeout-mssets the controller heartbeat timeout.--stream-interval-mssets the stream snapshot interval for distributed mode.--scriptruns a WASM script that produces a scenario (requires--features wasmbuild).--tls-minand--tls-maxset the TLS version floor/ceiling.--http2enables HTTP/2 (adaptive).--http3enables HTTP/3 (requires--features http3andRUSTFLAGS=--cfg reqwest_unstable).--alpnsets the advertised protocols (repeatable, e.g.--alpn h2).--tmp-pathsets where temporary run data is written.--keep-tmpkeeps temporary run data after completion.--log-shardscontrols the number of log writers (default1).--export-csvwrites metrics to a CSV file (bounded by--metrics-rangeand--metrics-max).--export-jsonwrites summary and metrics to a JSON file (bounded by--metrics-rangeand--metrics-max).--install-serviceinstalls a Linux systemd service for controller/agent.--uninstall-serviceremoves a Linux systemd service for controller/agent.--service-nameoverrides the systemd service name.
HTTP/3 is experimental and requires building with --features http3 plus
RUSTFLAGS="--cfg reqwest_unstable" (reqwest requirement):
RUSTFLAGS="--cfg reqwest_unstable"
Configuration File
You can provide a config file with --config path. If no config is specified, strest will look for ./strest.toml or ./strest.json (TOML is preferred if both exist). CLI flags override config values.
Example strest.toml:
= "http://localhost:3000"
= "get"
= 60
= "10s"
= "5s"
= 200
= true
= 10000
= true
= true
= "1.2"
= "1.3"
= true
= ["h2"]
= [
"Content-Type: application/json",
"X-Env: local",
]
= "10-30"
= 1000000
[]
= 1000
[[]]
= "10s"
= 500
[[]]
= "20s"
= 1500
Load profiles are optional. load.rate is the initial RPS, and each stage linearly ramps to its target RPS over the stage duration. You can use rpm instead of rate/target for RPM-based control.
Example strest.json:
Scenario Scripts
Scenario scripts model multi-step flows with per-step asserts and templated payloads. If scenario.base_url is set you can omit the top-level url. Templates use {{var}} placeholders from scenario.vars, step.vars, and built-ins: seq, step, timestamp_ms, timestamp_s.
think_time adds a delay after a step completes before the next step starts (supports ms, s, m, h).
Example strest.toml:
[]
= "http://localhost:3000"
= { = "demo" }
[[]]
= "login"
= "post"
= "/login"
= ["Content-Type: application/json"]
= "{\"user\":\"{{user}}\",\"seq\":\"{{seq}}\"}"
= 200
= "token"
= "500ms"
[[]]
= "profile"
= "get"
= "/profile"
= ["Authorization: Bearer {{seq}}"]
Example strest.json:
WASM Scripts (Experimental)
You can generate scenarios from a WASM module and run them with --script. This is useful when you want programmable test setup while still using strest’s scenario engine.
Build with the optional feature:
Run with a WASM script:
Example WASM script (prebuilt in this repo):
# Optional: regenerate from WAT
# Run the example scenario
Note: the example scenario targets http://localhost:8887 and expects /health, /login,
/search, /items/{id}, and /checkout endpoints to exist. Update the WAT if your server differs.
WASM contract
Your module must export:
memoryscenario_ptr() -> i32(pointer to a UTF-8 JSON buffer)scenario_len() -> i32(length of that buffer in bytes)
The JSON payload must match the scenario config schema (same as strest.toml / strest.json). It must include schema_version: 1. Size is capped at 1MB.
Sandboxing policy (enforced):
- No imports are allowed.
- The exported
memorymust declare a maximum and be <= 128 pages. - The module size is capped at 4MB.
- The scenario payload is capped at 1MB.
scenario_ptrandscenario_lenmust return constanti32values.memory64and shared memory are not allowed.
Minimal Rust example (wasm32-unknown-unknown):
pub extern "C"
pub extern "C"
static SCENARIO: &str = r#"{
"schema_version": 1,
"base_url": "http://localhost:3000",
"steps": [
{ "method": "get", "path": "/health", "assert_status": 200 }
]
}"#;
Notes:
- If you pass
--url, it becomes the default base URL when the scenario omitsbase_url. --scriptcannot be combined with an explicitscenarioconfig section.
Output Sinks
Configure output sinks in the config file to emit summary metrics periodically during the run and once after the run completes. The default update interval is 1000ms.
Example strest.toml:
[]
# Optional. Controls periodic updates (defaults to 1000ms).
= 1000
[]
= "./out/strest.prom"
[]
= "./out/strest.otel.json"
[]
= "./out/strest.influx"
Distributed Mode
Run a controller and one or more agents. If you configure sinks on agents, they write per-agent
files when stream summaries are off. If you configure sinks on the controller, it writes an
aggregated sink report at the end of the run (or periodically when streaming).
If distributed.stream_summaries = true, agents stream
periodic summaries to the controller; the controller updates sinks during the run and agents
skip local sink writes. Stream cadence is controlled by distributed.stream_interval_ms
(default 1000ms) and sink update cadence by sinks.update_interval_ms. When UI rendering is
enabled, the controller aggregates streamed metrics into the UI.
Use distributed.agent_wait_timeout_ms (or --agent-wait-timeout-ms) to bound how long the
controller waits for min_agents before starting.
The controller does not generate load; it only orchestrates and aggregates. Agents are the
ones that send requests.
Agents send periodic heartbeats; the controller marks agents unhealthy if no heartbeat is
seen within --agent-heartbeat-timeout-ms (default 3000ms).
Aggregated charts are available in distributed mode when --stream-summaries is enabled and
--no-charts is not set (charts are written by the controller). Per-agent exports are still
disabled during distributed runs.
CLI equivalents: --stream-summaries and --stream-interval-ms 1000.
Kubernetes (Basic Manifests)
The kubernetes/ folder ships minimal manifests for a controller and scalable agents.
Apply:
Scale agents:
Start a run (manual controller mode; port-forward the control plane):
Agents discover the controller via the service DNS name
strest-controller.<namespace>.svc.cluster.local (the manifests use
strest-controller:9009).
Scaling notes:
- There is no hard agent limit; practical limits come from OS file descriptors, CPU, and memory.
- Streaming summaries add controller load (histogram decode + merge). Increase
--stream-interval-msto reduce overhead as agent counts grow. - Wire messages are capped at 4MB; very large histograms can exceed this limit.
Controller:
Agent:
Agent standby (keeps the agent connected and auto-reconnects between runs):
Example strest.toml controller config:
[]
= "controller"
= "0.0.0.0:9009"
= "secret"
= 2
= 30000
= 3000
= true
= 1000
Example strest.toml agent config:
[]
= "agent"
= "10.0.0.5:9009"
= "secret"
= "agent-1"
= 2
= 1000
Manual controller mode (HTTP control plane):
Manual controller config example:
[]
= "controller"
= "manual"
= "0.0.0.0:9009"
= "127.0.0.1:9010"
= "control-secret"
Start and stop via HTTP:
The /start payload can include scenario_name (from the registry) and/or an inline
scenario (same schema as the config file). If you pass a scenario without a name,
it runs once and is not stored. If you pass both scenario and scenario_name,
the controller stores/updates that named scenario and runs it. You can also pass
start_after_ms to delay the run and agent_wait_timeout_ms to wait for enough
agents before starting. If omitted, the controller runs the default scenario or
--url configured on startup.
Scenario registry (preload multiple named scenarios):
[]
= "http://localhost:3000"
[[]]
= "get"
= "/health"
= 200
[]
= "http://localhost:3000"
[[]]
= "post"
= "/login"
= "{\"user\":\"demo\"}"
= 200
Linux Systemd Service
Install a controller service (requires sudo):
Install an agent service:
Uninstall a service:
Systemd install/uninstall writes to /etc/systemd/system and runs systemctl, so it must be executed with sudo.
Reproducible Builds
Use --locked to ensure the build uses the exact dependency versions in Cargo.lock:
Testing
Run the full test suite with nextest:
Run the WASM end-to-end test:
Formatting
Check formatting with:
Auto-format with:
Contributions
If you'd like to contribute, please start with CONTRIBUTING.md for the exact workflow and checks.
I'm a solo maintainer, so response times may vary. I review contributions as time allows and will respond when I can.
This project is licensed under the GNU AGPL v3.0 - see the LICENSE file for details.
Motivation
strest was born to provide performance insight for stexs and the infrastructure behind it.