HTTP load-testing CLI with multi-step YAML scenarios.
A single static binary that benchmarks one endpoint or drives a full virtual-user flow — login, extract a token, fan out parallel requests — and reports it all in your terminal, JSON, and a standalone HTML page.
$ goload run --url https://example.com/api --duration 30s --concurrency 50
reqs: 8431 | rps: 702.00 | p95: 180ms | errors: 0.14%
- Why goload
- Install
- Quick start
- Scenarios
- Reports
- How it compares
- Flags
- Architecture
- Development
- License
Most load testers hammer a single URL. Real traffic doesn't look like that — a user
logs in, gets a token, then makes several authenticated calls, some of them at once.
goload models that directly.
- Multi-step YAML scenarios — sequential steps with a
parallelblock for concurrent requests inside one virtual-user session. - Pass data between steps — extract a value from one response (
json:$.pathorheader:Name) and template it into the next request with{{var}}. - Two limit modes — bound a run by total requests (
-n) or wall-clock duration (-d), with optional RPS throttling. - Live terminal output — RPS, p95, and error rate update in place while the run is active.
- Three report formats — terminal summary, machine-readable JSON, and a standalone HTML page with a latency chart, status-code table, and per-step breakdown.
- One dependency-light binary — stdlib
net/httpunder the hood, no runtime needed.
go install github.com/egordushenko/goload/cmd/goload@latestOr build from source:
git clone https://github.com/egordushenko/goload
cd goload
go build -o goload ./cmd/goload# Fixed number of requests at a set concurrency
goload run --url https://example.com/api --requests 5000 --concurrency 50
# Run for a duration, throttled to 200 requests/sec
goload run --url https://example.com/api --duration 30s --concurrency 100 --rps 200
# Save machine and human reports
goload run --url https://example.com/api --requests 1000 --json results.json --report report.htmlLive output updates in place while the run is active:
reqs: 8431 | rps: 702.00 | p95: 180ms | errors: 0.14%
Scenario mode runs one virtual-user session per worker job. Steps are sequential by
default; a parallel block runs its child requests concurrently inside the same session.
name: "User checkout flow"
variables:
base_url: "https://api.example.com"
api_key: "${API_KEY}" # ${VAR} expands from the environment
steps:
- name: "login"
request:
method: POST
url: "{{base_url}}/auth/login"
headers:
Content-Type: application/json
body: |
{"user": "test", "pass": "test"}
extract:
token: "json:$.access_token" # capture for later steps
expect:
status: 200
- name: "get_profile"
request:
method: GET
url: "{{base_url}}/profile"
headers:
Authorization: "Bearer {{token}}" # use the captured token
expect:
status: 200
- name: "parallel_widgets"
parallel:
- name: "widget_a"
request: { method: GET, url: "{{base_url}}/widgets/a" }
- name: "widget_b"
request: { method: GET, url: "{{base_url}}/widgets/b" }Run it:
goload run --scenario examples/checkout.yaml --duration 60s --concurrency 20 --report report.htmlExtraction expressions:
| Expression | Reads |
|---|---|
json:$.path |
a value from the JSON response body |
header:Name |
a response header |
By default a failed expect.status records an error and the virtual session
continues. Use --stop-on-error to halt the current session after the first failed step.
JSON includes config, the overall summary, status-code distribution, a time-series of samples, and per-step summaries. Durations are encoded as Go duration nanoseconds.
goload run --url https://example.com --requests 100 --json results.jsonHTML is a single standalone file — inline styles, embedded data, a p95-over-time chart, status-code table, and step breakdown. Nothing external to load.
goload run --url https://example.com --requests 100 --report report.html| Feature | goload | hey | vegeta |
|---|---|---|---|
| Single endpoint load test | ✅ | ✅ | ✅ |
| Duration and request-count limits | ✅ | ✅ | ✅ |
| RPS throttling | ✅ | limited | ✅ |
| JSON output | ✅ | ❌ | ✅ |
| HTML report | ✅ | ❌ | plot |
| Multi-step YAML scenarios | ✅ | ❌ | ❌ |
| Extract response data into later steps | ✅ | ❌ | ❌ |
| Parallel requests inside a user flow | ✅ | ❌ | ❌ |
| Flag | Default | Description |
|---|---|---|
--url |
Target URL for simple mode | |
--scenario |
YAML scenario path | |
--method |
GET |
HTTP method for simple mode |
--header, -H |
Repeatable request header, Name: value |
|
--body |
Request body, or @file.json to read from disk |
|
--requests, -n |
0 |
Total jobs; 0 means unlimited until duration/cancel |
--duration, -d |
0 |
Run duration |
--concurrency, -c |
10 |
Worker count |
--rps |
0 |
Request/session jobs per second; 0 disables throttling |
--timeout |
30s |
Per-request timeout |
--report |
HTML report path | |
--json |
JSON report path | |
--insecure |
false |
Skip TLS certificate verification |
--stop-on-error |
false |
Stop current scenario session after a failed step |
Exactly one of --url or --scenario is required. At least one of --requests or
--duration is required.
flowchart LR
producer["producer"] --> jobs["job channel"]
jobs --> workers["worker pool"]
workers --> results["result channel"]
results --> collector["collector"]
collector --> metrics["metrics summary"]
metrics --> reports["terminal / JSON / HTML"]
A producer feeds jobs into a channel, a pool of concurrency workers drains it, and
every result flows back through a single collector goroutine — the only path that
mutates metrics, so the hot path needs no locks on the aggregate. Cancellation and the
--duration / RPS limits are driven by context.Context.
Scenario workers execute one virtual session at a time; each request step emits a result
tagged with its StepName, so summaries carry both overall and per-step metrics.
The code is split into focused internal packages:
| Package | Responsibility |
|---|---|
cli |
Flag parsing, validation, signal handling, terminal output |
runner |
Producer, worker pool, result collection, cancellation |
httpclient |
Request execution and transport setup |
scenario |
YAML parsing, {{var}} templating, extraction, sequential/parallel execution |
metrics |
Single-path collector, percentiles, time series |
report |
JSON and standalone HTML rendering |
go test -race ./... # run the test suite with the race detector
go vet ./... # static checks
go build ./cmd/goload # build the binaryCI runs go vet, golangci-lint, and the race-enabled test suite, plus a build matrix
across Linux, macOS, and Windows.
MIT © Egor Dushenko