Rust · single binary · zero dependencies

Break things
on purpose.

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.

$ cargo install loadr
loadr run checkout.yaml
  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)
7
load executors
6
protocols built in
1
binary, zero deps
p99.9
HDR-exact, fleet-wide

Why loadr

The k6 model. The JMeter arsenal. None of the baggage.

Honest load, honest numbers

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.

Tests you can code-review

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.

A platform, not just a CLI

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

Real recordings. Real runs. No mockups.

Every clip is the actual loadr binary executing against a live server.

View all 7 demos →

The live web UI, mid-run

Live charts, threshold pills, run controls, editor and fleet view — a real browser session.

0:40

The quickstart 0:24

A distributed fleet, live 0:34

Everything, in the box

The exhaustive feature list

If it's listed, it ships in the binary — and every item links to its documentation.

All 7 k6 executors

constant-vus, ramping-vus, constant-arrival-rate, ramping-arrival-rate, per-vu-iterations, shared-iterations, externally-controlled — identical semantics, open and closed models.

Concurrent scenarios

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.

Thresholds as CI gates

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.

Checks + JMeter assertions

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.

Correlation & extraction

JSONPath, regex capture groups, XPath 1.0, CSS selectors, boundary extractors and headers — extracted values flow into later requests as ${name} and into JS.

Data-driven testing

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.

Embedded JavaScript

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.

Timers & pacing

Constant, uniform-random and gaussian think time; constant-throughput pacing (the JMeter timer, done right); per-scenario and global defaults.

Phase-level HTTP timings

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.

Cookies, redirects, bodies

Automatic per-VU RFC 6265 cookie jars with manual override, redirect policies, JSON/form/multipart/file bodies, query params — everything interpolated.

TLS, mTLS, proxies, HTTP/2+

Custom CAs, client certificates, SNI override, insecure mode for staging, HTTP/HTTPS proxies (CONNECT), ALPN negotiation with version forcing — all rustls, no OpenSSL.

Environment overlays

One file, many targets: loadr run -e staging deep-merges named overlays — gentler CI load, staging URLs, relaxed thresholds, without copy-paste.

6 metric exporters

JSON lines, CSV, Prometheus (scrape + remote-write), InfluxDB line protocol, OpenTelemetry OTLP (gRPC & HTTP), StatsD — plus a pre-built Grafana dashboard in the repo.

WASM + native plugins

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.

Distributed by design

Controller + agents over gRPC with optional mTLS: load partitioning, synchronized starts, data-file shipping, heartbeats, reconnection, agent-loss policies — and central HDR merging.

Built-in web UI

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.

JMeter & k6 importers

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.

Reports & tooling

k6-style console summaries, JSON export, self-contained HTML reports (loadr report), shell completions, JSON Schema output, structured logs, --quiet/-v spectrum.

Show me

From smoke test to fleet-scale in the same file

checkout.yamldeclarative, schema-validated
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

Six protocols. Full metrics on every one.

Every protocol reports DNS, connect, TLS, TTFB, duration, and bytes sent/received — and works with the same extract/assert/check blocks.

HTTP/1.1 + 2

- 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.

WebSocket

- request:
    url: wss://chat.example.com/ws
    ws:
      send: [ '{"type":"hello"}' ]
      receive_until: '"ack"'
      session_duration: 10s

Subprotocols, binary frames, message counters, session metrics.

gRPC

- 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.

GraphQL

- 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.

TCP

- 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.

UDP

- 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

Most tools average percentiles across nodes. That number is wrong.

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.

  • VU counts and arrival rates partitioned exactly across agents — global ramps stay precise
  • Synchronized start barrier, heartbeats, jittered reconnection, agent-loss policies
  • Test definitions and CSV/proto/JS files shipped to agents automatically
  • One bidirectional gRPC stream per agent, plaintext or mTLS
  • Docker Compose stack and Helm chart in the repo: agents.replicas=10 and go
controller
partition · merge · thresholds · UI
│ gRPC · mTLS │
agent-1
200 rps
agent-2
200 rps
agent-3
200 rps
// verified by the test suite:
agents see 1–1000ms and 1001–2000ms
merged p99 = 1980ms ✓ (true union p99)
averaged p99 = 1485ms ✗ (what others report)

Management UI

RabbitMQ-style management, for load tests

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.

loadr · overview
live · run 4588f1bf
Requests / s
612.4
Active VUs
300
p95 latency
187 ms
Error rate
0.02%
✓ p(95)<400 — 187.2 ✓ rate<0.01 — 0.0002 ✓ checks rate>0.99 — 0.998 ⏸ Pause ■ Stop

Test library & editor

Save, edit and validate YAML in the browser — diagnostics jump to the line.

Run history

Every run's full summary persisted: trends, checks, thresholds, pass/fail.

Fleet view

Agent health, active VUs, cores, labels, last heartbeat — at a glance.

Auth built in

HTTP Basic and bearer tokens; loopback-only by default. JSON API for everything.

The honest comparison

loadr vs k6 vs JMeter

k6 JMeter loadr
Test formatJavaScriptXML via GUIYAML + JS, JSON-Schema validated
Open-model load (arrival rate)plugin✓ all 7 executors
Protocols built inHTTP, WS, gRPCmanyHTTP/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 executionpaid cloudRMI, fiddly✓ built-in, gRPC + mTLS
Fleet-wide percentilescloud onlyaveraged ⚠✓ exact (HDR histogram merge)
Live management UIpaid cloud✓ embedded
Per-phase timings (DNS/TLS/TTFB)partial✓ on every protocol
Runtime footprintGo binaryJVM + tuningone Rust binary, distroless image
Migration path in✓ imports .jmx and k6 scripts

Install

Running in under a minute

Binary (Linux / macOS / Windows)
$ 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
Cargo
$ 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
$ docker run --rm -v $PWD:/work \
    ghcr.io/reaandrew/loadr \
    run /work/test.yaml

# distroless, non-root, tiny
First test
$ 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.

The documentation is exhaustive.

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.