$ goload run --url https://example.com/api --duration 30s --concurrency 50
reqs: 8431 | rps: 702.00 | p95: 180ms | errors: 0.14%
Contents
Why goload
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
parallel block for
concurrent requests inside one virtual-user session.
- Pass data between steps — extract a value from one response (
json:$.path
or header: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/http under the hood, no runtime needed.
Install
go install github.com/egordushenko/goload/cmd/goload@latest
Or build from source:
git clone https://github.com/egordushenko/goload
cd goload
go build -o goload ./cmd/goload
Quick start
# 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.html
Live output updates in place while the run is active:
reqs: 8431 | rps: 702.00 | p95: 180ms | errors: 0.14%
Scenarios
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.html
Extraction 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.
Reports
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.json
HTML 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
How it compares
| 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 |
✅ |
❌ |
❌ |
Flags
| 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.
Architecture
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 |
Development
go test -race ./... # run the test suite with the race detector
go vet ./... # static checks
go build ./cmd/goload # build the binary
CI runs go vet, golangci-lint, and the race-enabled test suite, plus a build matrix
across Linux, macOS, and Windows.
License
MIT © Egor Dushenko