loadr is the load testing platform that ends the k6-vs-JMeter trade-off: declarative YAML tests, embedded JavaScript, six protocols, plugins, a built-in live web UI, and distributed execution with mathematically exact percentiles. All in one binary.
live-demo — 1 scenario(s), 8.1s
checks........................: 100.00% — ✓ 752 ✗ 0
✓ status is 200 (376 / 376)
✓ under 200ms (376 / 376)
http_req_duration.............: avg=4.96ms med=3.02ms p(95)=10.73ms p(99)=49.38ms
http_req_tls_handshaking......: avg=0µs (connections reused per VU)
http_req_waiting..............: avg=4.85ms med=2.90ms p(95)=10.57ms
http_reqs.....................: 376 (46.66/s)
iterations....................: 376 (46.66/s)
vus...........................: value=5 min=4 max=5
thresholds:
✓ http_req_duration: p(95)<500 (observed: 10.73)
✓ checks: rate>0.95 (observed: 1.00)
✓ http_req_failed: rate<0.01 (observed: 0.00)
Why loadr
Open-model arrival-rate executors keep the offered load constant even when your
system slows down — saturation shows up as dropped_iterations,
not silently lower RPS. Every latency is an HDR histogram: p(99.9) is exact, never estimated, never averaged.
Declarative YAML with a generated JSON Schema — your editor autocompletes it,
loadr validate lints it with line numbers and
did-you-mean fixes, and the diff in your PR actually means something.
Drop into JavaScript exactly where logic demands it.
Built-in management web UI (think RabbitMQ for load tests), distributed
controller/agent mode over mTLS gRPC, six metric exporters, WASM & native plugins,
and importers that eat your existing .jmx plans and k6 scripts.
See it in action
Every clip is the actual loadr binary executing against a live server.
Live charts, threshold pills, run controls, editor and fleet view — a real browser session.
Everything, in the box
If it's listed, it ships in the binary — and every item links to its documentation.
constant-vus, ramping-vus, constant-arrival-rate, ramping-arrival-rate, per-vu-iterations, shared-iterations, externally-controlled — identical semantics, open and closed models.
Any number of named scenarios per test with independent executors, stages, start times, graceful stop and ramp-down — browsers, API clients and batch jobs in one run.
p(95)<400, rate>0.99, any percentile, tag-filtered selectors, abort_on_fail circuit breakers with warm-up delay. Exit code 99 on failure — k6-compatible.
k6-style checks that never fail requests, and JMeter-style assertions that do: status, body contains/regex, JSONPath, XPath, duration, size, headers, JS expressions — with abort actions.
JSONPath, regex capture groups, XPath 1.0, CSS selectors, boundary extractors and headers — extracted values flow into later requests as ${name} and into JS.
CSV data sets (shared or per-VU cursors, recycle or stop-at-EOF), inline rows, environment variables, and secrets from env/file that never reach logs or reports.
QuickJS per VU with k6-style imports (k6/http, check, sleep, metrics), setup()/teardown(), scenario functions, beforeRequest/afterRequest hooks, inline ${js: …} — sandboxed with time & memory limits.
Constant, uniform-random and gaussian think time; constant-throughput pacing (the JMeter timer, done right); per-scenario and global defaults.
DNS, connect, TLS handshake, send, TTFB and receive measured per request on a hand-built hyper stack — plus exact wire-level byte counts. No averaged guesses.
Automatic per-VU RFC 6265 cookie jars with manual override, redirect policies, JSON/form/multipart/file bodies, query params — everything interpolated.
Custom CAs, client certificates, SNI override, insecure mode for staging, HTTP/HTTPS proxies (CONNECT), ALPN negotiation with version forcing — all rustls, no OpenSSL.
One file, many targets: loadr run -e staging deep-merges named overlays — gentler CI load, staging URLs, relaxed thresholds, without copy-paste.
JSON lines, CSV, Prometheus (scrape + remote-write), InfluxDB line protocol, OpenTelemetry OTLP (gRPC & HTTP), StatsD — plus a pre-built Grafana dashboard in the repo.
Five plugin types (protocol, output, extractor, assertion, service) over two mechanisms: sandboxed WASM components with a WIT interface, and abi_stable native libraries. No rebuilds, ever.
Controller + agents over gRPC with optional mTLS: load partitioning, synchronized starts, data-file shipping, heartbeats, reconnection, agent-loss policies — and central HDR merging.
Live dashboards over SSE, a test editor with one-click validation, run history, pause/stop/scale controls, agent fleet view, log tail — embedded in the binary, dark mode native.
loadr convert plan.jmx translates thread groups, samplers, timers, assertions, extractors and CSV configs; the k6 importer maps options, scenarios, checks and http calls — with clear warnings for the rest.
k6-style console summaries, JSON export, self-contained HTML reports (loadr report), shell completions, JSON Schema output, structured logs, --quiet/-v spectrum.
Show me
name: checkout-under-load
defaults:
http: { base_url: https://shop.example.com }
data:
users: { type: csv, path: users.csv, mode: shared, on_eof: recycle }
scenarios:
shoppers:
executor: ramping-vus
stages: [ { duration: 2m, target: 100 }, { duration: 5m, target: 100 } ]
think_time: { type: uniform, min: 1s, max: 3s }
flow:
- request:
url: /login
method: POST
body: { form: { user: "${data.users.username}", pass: "${data.users.password}" } }
extract:
- { type: css, name: csrf, expression: "input[name=csrf]", attribute: value }
assert:
- { type: status, equals: 200 }
- request:
method: POST
url: /cart
body: { form: { sku: W-1, csrf: "${csrf}" } }
checks:
- { type: status, equals: 201 }
- { type: duration, name: fast checkout, max: 300ms }
thresholds:
http_req_duration: [ "p(95)<400", "p(99.9)<1500" ]
http_req_failed: [ { threshold: "rate<0.01", abort_on_fail: true } ]
checks: [ "rate>0.99" ]
Protocols
Every protocol reports DNS, connect, TLS, TTFB, duration, and bytes sent/received — and works with the same extract/assert/check blocks.
- request:
method: POST
url: /orders
body: { json: { sku: W-1, qty: 2 } }
checks: [ { type: status, equals: 201 } ]
ALPN, prior-knowledge h2, keep-alive tuning, per-VU pools.
- request:
url: wss://chat.example.com/ws
ws:
send: [ '{"type":"hello"}' ]
receive_until: '"ack"'
session_duration: 10s
Subprotocols, binary frames, message counters, session metrics.
- request:
url: grpc://svc:50051
grpc:
reflection: true # or proto_files
service: helloworld.Greeter
method: SayHello
message: { name: "vu-${vu}" }
Unary + all streaming shapes, in-process proto compile — no protoc.
- request:
url: /graphql
protocol: graphql
graphql:
query: "query($t:String!){ search(t:$t){ id } }"
variables: { t: widget }
GraphQL error semantics, partial-error awareness, own metric family.
- request:
url: tcp://gateway:7000
socket:
send_text: "PING\r\n"
read_bytes: 64
checks: [ { type: body_contains, value: PONG } ]
Exact byte accounting; regex/boundary extraction over raw payloads.
- request:
url: udp://stats:8125
socket:
send_hex: "deadbeef 0102"
read_timeout: 500ms
Datagram round trips with hex payloads and loss-aware timeouts.
Need MQTT, Kafka, or your in-house protocol? Write a protocol plugin — no fork, no rebuild.
Distributed
If agent A's p99 is 100 ms and agent B's is 1000 ms, the fleet's true p99 is not 550 ms. loadr agents stream HDR histogram deltas every second; the controller merges the histograms — a lossless operation — and computes percentiles only after the merge. Thresholds evaluate centrally against fleet-wide truth.
agents.replicas=10 and goManagement UI
Embedded in the binary. loadr run --ui for a single run, or the full fleet console on the controller. Edit and validate tests in the browser, watch live percentiles, pause, stop, or turn the VU dial mid-run.
Save, edit and validate YAML in the browser — diagnostics jump to the line.
Every run's full summary persisted: trends, checks, thresholds, pass/fail.
Agent health, active VUs, cores, labels, last heartbeat — at a glance.
HTTP Basic and bearer tokens; loopback-only by default. JSON API for everything.
The honest comparison
| k6 | JMeter | loadr | |
|---|---|---|---|
| Test format | JavaScript | XML via GUI | YAML + JS, JSON-Schema validated |
| Open-model load (arrival rate) | ✓ | plugin | ✓ all 7 executors |
| Protocols built in | HTTP, WS, gRPC | many | HTTP/1.1+2, WS, gRPC + reflection, GraphQL, TCP, UDP |
| JMeter-style assertions / extractors / timers | — | ✓ | ✓ JSONPath, XPath, CSS, regex, boundary; 3 timers + pacing |
| Extensions without rebuilding | — (xk6 recompile) | jars | ✓ WASM (sandboxed) + native plugins |
| Distributed execution | paid cloud | RMI, fiddly | ✓ built-in, gRPC + mTLS |
| Fleet-wide percentiles | cloud only | averaged ⚠ | ✓ exact (HDR histogram merge) |
| Live management UI | paid cloud | — | ✓ embedded |
| Per-phase timings (DNS/TLS/TTFB) | ✓ | partial | ✓ on every protocol |
| Runtime footprint | Go binary | JVM + tuning | one Rust binary, distroless image |
| Migration path in | — | — | ✓ imports .jmx and k6 scripts |
Install
$ curl -sSL https://github.com/reaandrew/loadr.io/releases/latest/download/loadr-x86_64-unknown-linux-gnu.tar.gz | tar xz
$ sudo mv loadr-*/loadr /usr/local/bin/
$ loadr version
$ git clone https://github.com/reaandrew/loadr.io
$ cargo install --path loadr.io/crates/loadr-cli
# no system deps: no protoc,
# no OpenSSL, no JVM, no node
$ docker run --rm -v $PWD:/work \
ghcr.io/reaandrew/loadr \
run /work/test.yaml
# distroless, non-root, tiny
$ loadr validate examples/01-quickstart.yaml # line-numbered diagnostics, did-you-mean fixes
$ loadr run examples/01-quickstart.yaml # exit 0 = thresholds passed, 99 = failed
$ loadr run --ui examples/02-ramping-load.yaml # live dashboard at http://127.0.0.1:6464
$ loadr report results.json -o report.html # self-contained HTML report
15 runnable examples ship in the repo — ramp tests, spike tests, soak tests, data-driven logins, WebSocket chat, gRPC streaming, GraphQL, raw sockets, environment overlays and a distributed fleet test.
Forty-plus pages: a getting-started path, the complete YAML reference, the full JS API, every protocol, distributed operations, plugin development with worked examples, k6 and JMeter migration guides, and the architecture decision records explaining why loadr is built the way it is.