A lightweight, fast CLI load and stress testing tool written in Rust.
Adrenaline sends concurrent HTTP requests to API endpoints and reports latency percentiles, throughput, error rates, and status code distribution — from your terminal or CI pipeline.
adrenaline hit https://example.com -n 1000 -c 50
adrenaline ramp https://example.com -n 1000 --start-concurrency 5 --end-concurrency 100
adrenaline scenario examples/scenario.yaml| Feature | Description |
|---|---|
| hit | Fixed concurrency load test |
| ramp | Gradually increase concurrency across steps |
| spike | Baseline load followed by a sudden traffic burst |
| find-limit | Step up concurrency until error rate exceeds threshold |
| compare | Compare two baseline JSON reports for regressions |
| scenario | Run multi-step YAML test plans |
| HTTP methods | GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS |
| Headers & body | Custom headers and request body from file |
| JSON output | --json for machine-readable results |
| HTML reports | --html report.html |
| Baseline tracking | --save-baseline / --baseline for CI regression checks |
| GitHub Actions | CI workflow with smoke load test |
Pre-built release: no extra dependencies — just download and run.
Build from source: Rust 1.85+ (2024 edition)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source "$HOME/.cargo/env"- Open GitHub Releases
- Download the archive for your platform (see table below)
- Extract it and put
adrenalineon yourPATH(platform steps below) - Run
adrenaline --versionto confirm
| Platform | Download |
|---|---|
| Linux x86_64 | adrenaline-*-linux-x86_64.tar.gz |
| Linux ARM64 | adrenaline-*-linux-aarch64.tar.gz |
| macOS Apple Silicon | adrenaline-*-macos-aarch64.tar.gz |
| macOS Intel | adrenaline-*-macos-x86_64.tar.gz |
| Windows x86_64 | adrenaline-*-windows-x86_64.zip |
Replace * with the release version (e.g. 0.1.2).
cd ~/Downloads
# pick the file you downloaded, e.g.:
tar -xzf adrenaline-0.1.2-macos-aarch64.tar.gz
# install globally (requires password)
sudo install -m 755 adrenaline-0.1.2-macos-aarch64/adrenaline /usr/local/bin/adrenaline
# or install for your user only (no sudo)
mkdir -p ~/.local/bin
install -m 755 adrenaline-0.1.2-macos-aarch64/adrenaline ~/.local/bin/adrenaline
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc
source ~/.zshrc
adrenaline --version
adrenaline hit https://example.com -n 10 -c 2If macOS blocks the binary (“cannot be opened”), remove the quarantine flag once:
xattr -d com.apple.quarantine "$(which adrenaline)"cd ~/Downloads
# example: linux x86_64
tar -xzf adrenaline-0.1.2-linux-x86_64.tar.gz
sudo install -m 755 adrenaline-0.1.2-linux-x86_64/adrenaline /usr/local/bin/adrenaline
# user-only install (no sudo)
mkdir -p ~/.local/bin
install -m 755 adrenaline-0.1.2-linux-x86_64/adrenaline ~/.local/bin/adrenaline
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
adrenaline --version
adrenaline hit https://example.com -n 10 -c 2cd $env:USERPROFILE\Downloads
# example file name — adjust version if needed
Expand-Archive adrenaline-0.1.2-windows-x86_64.zip -DestinationPath .
# add to user PATH for this session
$bin = "$env:USERPROFILE\Downloads\adrenaline-0.1.2-windows-x86_64"
$env:Path = "$bin;$env:Path"
# persist PATH (open a new terminal after)
[Environment]::SetEnvironmentVariable(
"Path",
[Environment]::GetEnvironmentVariable("Path", "User") + ";$bin",
"User"
)
adrenaline --version
adrenaline hit https://example.com -n 10 -c 2Each release includes SHA256SUMS.txt. On macOS/Linux:
shasum -a 256 -c SHA256SUMS.txtOn Windows (PowerShell), compare the hash manually:
Get-FileHash adrenaline-0.1.2-windows-x86_64.zip -Algorithm SHA256
# match the line for your file in SHA256SUMS.txtYou should see OK (Unix) or matching hashes (Windows) for the archive you downloaded.
adrenaline --help
adrenaline hit https://example.com -n 100 -c 10git clone https://github.com/ege-ayan/adrenaline.git
cd adrenaline
cargo build --release
# run without installing: ./target/release/adrenaline hit ...make installThis installs adrenaline to ~/.cargo/bin/. If the command is not found, add Rust’s bin dir to your shell:
echo 'export PATH="$HOME/.cargo/bin:$PATH"' >> ~/.zshrc
source ~/.zshrc
adrenaline --versionLocal dev only (no global install):
make build
source scripts/env.sh # adds ./target/release to PATH for this shell
adrenaline --versionIf you use direnv, .envrc does the same when you cd into the repo.
make release
# creates dist/adrenaline-<version>-<target>.tar.gz (or .zip on Windows)adrenaline hit https://api.example.com/health \
-n 1000 \
-c 50 \
--method GET \
--header "Authorization: Bearer token" \
--body payload.json \
--timeout 10 \
--json \
--html report.html \
--save-baseline baseline.json| Flag | Default | Description |
|---|---|---|
-n, --requests |
1000 | Total requests |
-c, --concurrency |
50 | Max in-flight requests |
--rps |
— | Cap request rate (requests per second) |
--method |
GET | HTTP method |
--header |
— | Repeatable Key: Value header |
--body |
— | Request body file (POST/PUT) |
--timeout |
10 | Per-request timeout (seconds) |
--json |
false | JSON output |
--html |
— | Write HTML report |
--baseline |
— | Compare against saved baseline |
--save-baseline |
— | Save results as baseline JSON |
adrenaline ramp https://api.example.com -n 1000 \
--start-concurrency 5 \
--end-concurrency 100 \
--steps 10Distributes total requests across --steps, linearly increasing concurrency from start to end.
adrenaline spike https://api.example.com \
--baseline-requests 100 --baseline-concurrency 10 \
--spike-requests 500 --spike-concurrency 200Runs baseline phase first, then a high-concurrency spike, and reports combined metrics.
adrenaline find-limit https://api.example.com \
--requests-per-step 100 \
--start-concurrency 10 \
--max-concurrency 500 \
--step 20 \
--max-error-rate 5.0Steps concurrency until error rate exceeds --max-error-rate. Reports the last passing concurrency as limit found.
adrenaline compare baseline.json current.json \
--p99-threshold 10 \
--error-rate-threshold 1Exits with code 1 if regressions are detected (useful in CI).
Compared metrics: error rate, p99 latency, p50 latency, requests/sec.
adrenaline scenario examples/scenario.yaml --jsonExample scenario (examples/scenario.yaml):
name: api smoke test
defaults:
timeout: 10
method: GET
steps:
- type: hit
url: https://example.com
requests: 50
concurrency: 5
- type: ramp
url: https://example.com
requests: 200
start_concurrency: 5
end_concurrency: 50
steps: 5
- type: spike
url: https://example.com
baseline_requests: 50
baseline_concurrency: 5
spike_requests: 200
spike_concurrency: 100
- type: find-limit
url: https://example.com
requests_per_step: 50
start_concurrency: 10
max_concurrency: 200
step: 20
max_error_rate: 5.0Adrenaline hit
Target: https://example.com
Method: GET
Requests: 1000
Concurrency: 50
Timeout: 10s
Summary
-------
Total time: 2.41s
Requests/sec: 414.93
Completed: 1000
Successful: 998
Failed: 2
Error rate: 0.20%
Latency
-------
min: 12ms
p50: 34ms
p90: 81ms
p95: 120ms
p99: 310ms
max: 590ms
Status codes
------------
200: 998
500: 2
JSON output (--json) serializes the same data as ReportSnapshot for tooling and baselines.
src/
├── main.rs # Binary entry point
├── lib.rs # Library + command dispatch
├── cli.rs # clap structs and validation
├── request.rs # HTTP method, headers, send_request
├── runner.rs # Fixed worker pool load execution
├── stats.rs # Worker-local stats, histogram merge, snapshots
├── output.rs # Text, JSON, HTML formatting
├── baseline.rs # Baseline comparison logic
├── report.rs # Output orchestration (baseline, HTML)
├── hit.rs # hit command
├── ramp.rs # ramp command
├── spike.rs # spike command
├── find_limit.rs # find-limit command
├── compare.rs # compare command
└── scenario.rs # YAML scenario runner
tests/
└── cli_integration.rs # End-to-end CLI tests (wiremock)
examples/
└── scenario.yaml
.github/workflows/
├── ci.yml # fmt, clippy, test, smoke load test
└── release.yml # multi-platform release binaries
cargo build
make test # runs all tests + prints total summary
./scripts/test.sh # same as make test
cargo test # plain cargo (no total line at the end)
cargo fmt
cargo clippy --all-targets -- -D warnings
cargo run -- hit https://example.com -n 100 -c 10make test ends with a summary like Total: 41 tests — 41 passed, 0 ignored.
Plain cargo test runs the same tests but prints per-crate blocks without a grand total.
Compare Adrenaline against Locust locally (requires Docker + Python 3):
make bench-setup # once: nginx container + locust venv
make bench # max throughput
make bench-rps # fixed RPS / latency overhead
make bench-clean # remove results and stop containerSee bench/README.md for methodology and sample numbers.
| Area | Tests |
|---|---|
| CLI validation | Zero requests/concurrency, invalid URL |
| HTTP layer | Methods, headers, body file, timeout classification |
| Stats | Success/failure, histogram merge, JSON snapshots |
| Output | Text, JSON, HTML rendering |
| Baseline | Regression/improvement detection |
| Commands | hit, ramp, spike, find-limit via wiremock |
| Scenario | YAML parsing and execution |
| Integration | Full CLI via assert_cmd |
The included workflow (.github/workflows/ci.yml) runs:
cargo fmt --checkcargo clippycargo test- Release smoke test against
https://example.com - Baseline artifact upload
Use in your own pipeline:
- name: Load test
run: |
cargo run --release -- hit ${{ env.API_URL }} -n 200 -c 20 --json --save-baseline baseline.json
- name: Compare against baseline
run: |
cargo run --release -- compare previous-baseline.json baseline.json| Crate | Purpose |
|---|---|
| clap | CLI parsing |
| tokio | Async runtime |
| reqwest | HTTP client (rustls) |
| hdrhistogram | Latency percentiles |
| serde / serde_json / serde_yaml | JSON & YAML |
| anyhow | Error handling |
GPL-3.0 — see LICENSE.