Examples

48 runnable examples

Every one is a complete, valid test you can run with loadr run <file>. They ship in the examples/ folder of every download, or grab them here.

01-quickstart.yaml
quickstart

The smallest useful loadr test: 10 virtual users hammering one endpoint for 30 seconds, with checks and a pass/fail threshold. loadr run examples/01-quickstart.yaml

02-ramping-load.yaml
ramping-load

Classic ramp test: climb to 50 VUs, hold, ramp down — with think time between requests so VUs behave like humans.

03-arrival-rate.yaml
arrival-rate

Open-model load: start exactly 100 iterations per second regardless of how slowly the target responds. Watch `dropped_iterations` to spot saturation.

04-spike-test.yaml
spike-test

Spike test with a ramping arrival rate: calm baseline, sudden 10x spike, recovery. The abort threshold kills the run if errors explode.

05-data-driven.yaml
data-driven-login

Data-driven login: each iteration takes the next row from users.csv (shared cursor across all VUs, wrapping at EOF).

06-correlation.yaml
correlation

Correlation: extract values from earlier responses (regex, CSS selector, JSONPath, headers, boundary) and reuse them in later requests.

07-scenarios-and-groups.yaml
mixed-workload

Several scenarios running concurrently with independent executors, start times and tags, plus groups and a custom metric.

08-javascript.yaml
javascript-lifecycle

Full JavaScript lifecycle: setup() fetches an auth token once, every iteration runs an exported function, beforeRequest/afterRequest hooks fire around YAML requests, teardown() cleans up.

09-websocket.yaml
websocket

WebSocket load: connect, exchange messages, assert on the conversation.

10-grpc.yaml
grpc

gRPC load: compile .proto files in-process (no protoc needed) or use server reflection, then call unary and streaming methods dynamically.

11-graphql.yaml
graphql

GraphQL over HTTP with variables; GraphQL `errors` are detected and the request is marked failed when no data comes back.

12-tcp-udp.yaml
raw-sockets

Raw socket load: TCP request/response and UDP datagrams, with hex payloads.

13-environments.yaml
environments

One test, many environments: `loadr run -e staging examples/13-environments.yaml` deep-merges the env block over the file. Secrets come from the process environment and are never printed.

14-outputs-and-soak.yaml
soak

Long soak test wired into observability: Prometheus scrape endpoint, JSONL archive, and StatsD — plus an abort threshold as a circuit breaker.

15-distributed.yaml
distributed

Distributed run: submit this to a controller and the load is partitioned across every connected agent (VUs split, arrival rates divided, percentiles merged centrally from HDR histograms — never averaged). loadr controller --bind 0.0.0.0:7625 & loadr agent --join controller-host:7625 --name agent-1 & loadr run --controller controller-host:6464 examples/15-distributed.yaml

16-flow-control.yaml
flow-control

Flow control: repeat, while, if/else and weighted random branches — the control flow from Gatling (repeat/during/doIf/randomSwitch) and the weighted-task model from Locust, in declarative YAML.

17-feeders-and-throttle.yaml
feeders-and-throttle

Feeder strategies (Gatling: sequential / random / shuffle, plus JSON feeders) and a global request-rate ceiling (Gatling throttle / reachRps).

18-sse.yaml
server-sent-events

Server-Sent Events load: open an event stream, read until a condition, assert on the last event.

20-browser.yaml
browser

Real-browser load test: drive headless Chrome over CDP and capture Navigation Timing + Web Vitals (FCP, LCP, DCL, load) for a page. Unlike the protocol-level `http` client, the `browser` protocol renders the page in a real Chrome tab — running JavaScript, fetching subresources, and firing the load/DOMContentLoaded events — so the timings reflect what a user actually experiences. Each VU keeps its own warm tab for the whole run. Requires headless Chrome/Chromium installed on the runner; it is launched lazily on the first browser request.

21-http-advanced.yaml
http-advanced

Advanced HTTP defaults: the "power features" of the HTTP client, all in one test. Exercises: - cache: true per-VU browser-style HTTP cache (fresh hits + ETag/Last-Modified revalidation) - hosts: pin a hostname to a fixed address (curl --resolve) - discard_response_bodies drop bodies after reading them (high throughput) - tracing: true inject a W3C `traceparent` header per request - tls.min_version/max_version pin the negotiated TLS version to 1.2..1.3 Run it, optionally dumping every request/response to the console: loadr run examples/21-http-advanced.yaml loadr run examples/21-http-advanced.yaml --http-debug

22-lifecycle.yaml
lifecycle-hooks

Scenario lifecycle hooks + a custom summary report. Each VU logs in once via the scenario `on_start` hook, runs its `exec` function every iteration, then logs out once via `on_stop` when it retires. After the run, the exported handleSummary(data) function prints a custom end-of-run report instead of loadr's default console summary.

23-tags.yaml
tag-selection

Tagged scenarios for selective runs. Each scenario carries `tags` (a map of name → value); `loadr run` filters scenarios by their tag *values*. loadr run examples/23-tags.yaml # all four scenarios loadr run --tags smoke examples/23-tags.yaml # only smoke scenarios loadr run --tags read,write examples/23-tags.yaml # read OR write (any-match) loadr run --tags full --exclude-tags write examples/23-tags.yaml # full, but not writes --tags keeps a scenario when it carries at least one of the listed values. --exclude-tags drops a scenario when it carries any of the listed values, and always wins over --tags.

24-timeseries-report.yaml
timeseries-report

Time-series report demo: a ramp-up / spike / ramp-down profile that produces an interesting shape over time. Run it, export the summary, then render the self-contained HTML report — every metric is charted against elapsed time (throughput, latency p50/p95/p99, active VUs, error rate), not just shown as an end-of-run average. loadr run --summary-export results.json examples/24-timeseries-report.yaml loadr report results.json -o report.html open report.html # hover the charts to read off any point in the run The summary JSON carries a top-level `timeline` array (one point per snapshot interval) that drives the charts; the aggregate tables remain exact.

25-check-chains.yaml
check-chains

Fused check-chains: extract → coerce type → transform → validate → save, all in one declarative `extract:` step (Gatling-style). Chains live happily next to the classic `type:`-tagged extractors — mix them freely. Each `chain:` is the variable name to save. Pick exactly one source (jmespath / jsonpath / regex / header / css / xpath / left+right boundary), then optionally `as:` a type, run a `transform:` pipeline, `check:` the value and fall back to a `default:`.

26-failure-breakdown.yaml
failure-breakdown

Deliberately produces a MIX of failures so the web UI's failure breakdown panel has something to group: a healthy endpoint, 4xx and 5xx responses, a check that fails, and a JS step that throws an uncaught exception. Run it with the management UI and watch the "Failure breakdown" panel: docker compose -f examples/harness/docker-compose.yml up -d --build loadr run --ui examples/26-failure-breakdown.yaml (go-httpbin's /status/{code} endpoint returns the requested status code.)

27-postgres.yaml
postgres

PostgreSQL load test, driven by the `loadr-plugin-postgres` native protocol plugin. PostgreSQL support is NOT built into loadr core — it is a runtime-loadable plugin that ships the heavy `sqlx` Rust driver on its own (with ONLY the `postgres` feature, so it carries no `rsa` advisory). The plugin declares the `postgres://`/`postgresql://` URL schemes, so once it is installed a request to one of those URLs routes straight to it; no explicit `protocol:` needed. Each request runs one configured query as the "request"; loadr records query latency in `postgres_req_duration`, the number of rows returned (SELECT) or affected (INSERT/UPDATE/DELETE) in `postgres_rows`, and any database error fails the request (status 1 = ok, status 0 = DB/transport error). Positional parameters are bound safely by the driver with `$1, $2, ...` placeholders. Build + install the plugin, then run: cargo build -p loadr-plugin-postgres --release mkdir -p dist && cp plugins/loadr-plugin-postgres/plugin.toml dist/ \ && cp target/release/libloadr_plugin_postgres.so dist/ loadr plugin install dist loadr run examples/27-postgres.yaml Or point the plan's `plugins:` entry at the built artifact directly (below).

28-mongo.yaml
mongo

MongoDB load test, driven by the `loadr-plugin-mongo` native protocol plugin. MongoDB support is NOT built into loadr core — it is a runtime-loadable plugin that ships the heavy `mongodb` Rust driver on its own. The plugin declares the `mongodb://` (and `mongo://`) URL scheme, so once it is installed a request to a `mongodb://` URL routes straight to it; no explicit `protocol:` is needed. Each request runs one operation (insert/find/update/delete/aggregate/command) from its `plugin:` block. loadr records the operation latency in `mongo_req_duration`, the count of documents inserted/matched/modified/returned in `mongo_docs`, and any driver error fails the request (status 0 = error, status 1 = ok). Build + install the plugin, then run: cargo build -p loadr-plugin-mongo --release mkdir -p dist && cp plugins/loadr-plugin-mongo/plugin.toml dist/ \ && cp target/release/libloadr_plugin_mongo.so dist/ loadr plugin install dist loadr run examples/28-mongo.yaml Or point the plan's `plugins:` entry at the built artifact directly (below).

29-mysql.yaml
mysql

MySQL load test, driven by the `loadr-plugin-mysql` native protocol plugin. MySQL support is NOT built into loadr core — it is a runtime-loadable plugin that ships the heavy `sqlx` Rust driver on its own (with ONLY the `mysql` feature). The plugin declares the `mysql://` URL scheme, so once it is installed a request to such a URL routes straight to it; no explicit `protocol:` is needed. Each request runs one configured query as the "request"; loadr records query latency in `mysql_req_duration`, the number of rows returned (SELECT) or affected (INSERT/UPDATE/DELETE) in `mysql_rows`, and any database error fails the request (status 1 = ok, status 0 = DB/transport error). Positional parameters are bound safely by the driver with `?` placeholders. Build + install the plugin, then run: cargo build -p loadr-plugin-mysql --release mkdir -p dist && cp plugins/loadr-plugin-mysql/plugin.toml dist/ \ && cp target/release/libloadr_plugin_mysql.so dist/ loadr plugin install dist loadr run examples/29-mysql.yaml Or point the plan's `plugins:` entry at the built artifact directly (below).

30-redis.yaml
redis

Redis (RESP) load test, driven by the `loadr-plugin-redis` native protocol plugin. Redis is NOT built into loadr core — it is a runtime-loadable plugin that speaks RESP directly over TCP (no `redis` crate, no OpenSSL). The plugin declares the `redis://` (and `rediss://`) URL scheme, so once it is installed a request to a `redis://` URL routes straight to it; no explicit `protocol:` is needed. Each request runs one command from its `plugin.command` array (argv form). loadr records the command latency in `redis_req_duration` and counts requests in `redis_reqs`. A successful reply is status 0; a RESP error reply is status 1 and fails the request. Build + install the plugin, then run: cargo build -p loadr-plugin-redis --release mkdir -p dist && cp plugins/loadr-plugin-redis/plugin.toml dist/ \ && cp target/release/libloadr_plugin_redis.so dist/ loadr plugin install dist loadr run examples/30-redis.yaml Or point the plan's `plugins:` entry at the built artifact directly (below).

31-kafka.yaml
kafka

Apache Kafka load test, driven by the `loadr-plugin-kafka` native protocol plugin. Kafka support is NOT built into loadr core — it is a runtime-loadable plugin that ships its own Kafka client. That client is `rskafka`, a PURE-RUST client (no librdkafka / C toolchain), so the plugin cross-compiles to every release target. The plugin declares the `kafka://` URL scheme, so once it is installed a request to a `kafka://` URL routes straight to it; no explicit `protocol:` is needed. The broker is the URL authority and the topic is the URL path (`kafka://broker:9092/topic`). Each request runs one operation (produce/fetch) from its `plugin:` block. loadr records the operation latency in `kafka_req_duration`, the count of messages produced/fetched in `kafka_msgs`, and any client error fails the request (status 1 = ok, status 0 = client/transport error). Build + install the plugin, then run: cargo build -p loadr-plugin-kafka --release mkdir -p dist && cp plugins/loadr-plugin-kafka/plugin.toml dist/ \ && cp target/release/libloadr_plugin_kafka.so dist/ loadr plugin install dist loadr run examples/31-kafka.yaml Or point the plan's `plugins:` entry at the built artifact directly (below).

32-rabbitmq.yaml
rabbitmq

RabbitMQ load test, driven by the `loadr-plugin-rabbitmq` native protocol plugin. RabbitMQ support is NOT built into loadr core — it is a runtime-loadable plugin that ships the pure-Rust `lapin` AMQP 0.9.1 client on its own. The plugin declares the `amqp://` / `amqps://` (and `rabbitmq://`) URL schemes, so once it is installed a request to one of those URLs routes straight to it; no explicit `protocol:` is needed. Each request runs one operation (publish/get) from its `plugin:` block. loadr records the operation latency in `rabbitmq_req_duration`, the count of messages published/consumed in `rabbitmq_msgs`, and any broker error fails the request (status 1 = ok, status 0 = AMQP/transport error). Build + install the plugin, then run: cargo build -p loadr-plugin-rabbitmq --release mkdir -p dist && cp plugins/loadr-plugin-rabbitmq/plugin.toml dist/ \ && cp target/release/libloadr_plugin_rabbitmq.so dist/ loadr plugin install dist loadr run examples/32-rabbitmq.yaml Or point the plan's `plugins:` entry at the built artifact directly (below).

33-elasticsearch.yaml
elasticsearch

Elasticsearch load test, driven by the `loadr-plugin-elasticsearch` native protocol plugin. Elasticsearch support is NOT built into loadr core — it is a runtime-loadable plugin. Elasticsearch's API is plain HTTP/JSON, so the plugin talks to it over loadr's own hyper + hyper-rustls stack (pure-Rust TLS, no system OpenSSL); it does not pull in the heavy official `elasticsearch` crate. The plugin declares the `elasticsearch://` (and `es://`) URL schemes — both mapped onto `http://` internally — so once it is installed a request to one of those URLs routes straight to it; no explicit `protocol:` is needed. A plain `http(s)://` URL works too. Each request runs one operation (index/get/search/bulk) from its `plugin:` block. loadr records the operation latency in `elasticsearch_req_duration`, the count of documents written (index = 1, bulk = items succeeded) in `elasticsearch_docs`, and any HTTP/cluster error fails the request (status 1 = ok, status 0 = error). Build + install the plugin, then run: cargo build -p loadr-plugin-elasticsearch --release mkdir -p dist && cp plugins/loadr-plugin-elasticsearch/plugin.toml dist/ \ && cp target/release/libloadr_plugin_elasticsearch.so dist/ loadr plugin install dist loadr run examples/33-elasticsearch.yaml Or point the plan's `plugins:` entry at the built artifact directly (below).

34-c-echo.yaml
c-echo

A protocol plugin written in plain C, loaded over loadr's frozen C ABI. Unlike the Rust native plugins (mongo/redis/...), `c-echo` is a C shared library exporting four `extern "C"` symbols — proving loadr plugins can be authored in non-Rust languages. The host auto-detects the C ABI at load time. It serves the `cecho://` scheme and echoes each request body back, status 200. Build + install the plugin, then run: make -C examples/plugins/c-echo mkdir -p dist && cp examples/plugins/c-echo/plugin.toml dist/ \ && cp examples/plugins/c-echo/libloadr_plugin_cecho.so dist/ loadr plugin install dist loadr run examples/34-c-echo.yaml Or point the plan's `plugins:` entry at the built artifact directly: plugins: - { name: cecho, path: examples/plugins/c-echo/libloadr_plugin_cecho.so }

35-go-echo.yaml
go-echo

A protocol plugin written in Go, loaded over loadr's frozen C ABI. Like the c-echo example, go-echo is NOT a Rust plugin: it's a Go shared library (`go build -buildmode=c-shared`) exporting the four `extern "C"` symbols of loadr's C ABI — proving loadr plugins can be authored in Go. The host auto-detects the C ABI at load time. It serves the `goecho://` scheme and echoes each request body back, status 200. Build + install the plugin, then run: make -C examples/plugins/go-echo mkdir -p dist && cp examples/plugins/go-echo/plugin.toml dist/ \ && cp examples/plugins/go-echo/libloadr_plugin_goecho.so dist/ loadr plugin install dist loadr run examples/35-go-echo.yaml Or point the plan's `plugins:` entry at the built artifact directly: plugins: - { name: goecho, path: examples/plugins/go-echo/libloadr_plugin_goecho.so }

36-auth-tokens.yaml
auth-tokens

Per-VU auth tokens with automatic refresh — Artillery's `generating-vu-tokens` + `refresh-auth-token` in one place. Each VU mints its own token the first time it needs one (scripts/auth.js, beforeRequest hook), attaches it to every request, and silently re-mints it just before it expires. A custom `token_refreshes` counter tracks how often that happens. Swap the synthetic mint() for a real call to your auth server. loadr run examples/36-auth-tokens.yaml

37-file-uploads.yaml
file-uploads

Multipart file uploads — Artillery's `http-file-uploads`. A POST with a multipart/form-data body that mixes literal fields with a file part read from disk. `file:` streams the file contents; `filename` and `content_type` set the part headers. Values support `${...}` interpolation. loadr run examples/37-file-uploads.yaml

38-cookies-session.yaml
cookies-session

Cookie-based sessions — Artillery's `using-cookies`. loadr keeps a per-VU cookie jar (on by default). `Set-Cookie` from the login response is stored and replayed automatically on every later request in that VU's flow, so a server-side session "just works". You can also read/write/clear cookies from JS (session.cookieGet / cookieSet / cookiesClear). loadr run examples/38-cookies-session.yaml

39-custom-metrics.yaml
custom-metrics

User-defined metrics — Artillery's `track-custom-metrics`. Declare your own metrics under `metrics:` and emit to them from JS: counter -> session.counterAdd(name, n) (monotonic totals, e.g. £ booked) rate -> session.rateAdd(name, bool) (proportion true, e.g. cache hits) trend -> session.trendAdd(name, value) (distribution, e.g. a custom latency) gauge -> session.gaugeSet(name, value) (last value, e.g. queue depth) Tag requests by endpoint so the built-in HTTP metrics break down per route too. loadr run examples/39-custom-metrics.yaml

40-scenario-weights.yaml
scenario-weights

Weighted traffic mix — Artillery's `scenario-weights` (Locust @task weights / Gatling randomSwitch). A single scenario whose flow picks one weighted branch per iteration: 70% just browse, 20% search, 10% complete a checkout. Each branch is tagged (`branch`) so per-path metrics fall out automatically. Tune the weights to model real traffic shape without spinning up separate scenarios. loadr run examples/40-scenario-weights.yaml

41-functional-test.yaml
functional-test

Functional / smoke testing — Artillery's `functional-testing-with-expect` + `table-driven-functional-tests`. loadr doubles as a functional tester: make one pass over a table of cases (data feeder with `on_eof: stop`), assert each response, and let the run exit non-zero if a single check fails (`checks: rate>=1.0`). Drop it straight into CI as a contract/smoke gate — no separate test framework needed. loadr run examples/41-functional-test.yaml # exit code 99 on any failed check

42-soap.yaml
soap

SOAP / XML web services — Artillery's `soap-with-custom-function`, natively. loadr sends the raw SOAP envelope as a string body and pulls values back out of the XML response with XPath extractors and checks (no custom function needed). `local-name()` keeps the XPath namespace-agnostic. loadr run examples/42-soap.yaml

43-twirp.yaml
twirp

Twirp RPC — Artillery's `rpc-twirp-with-custom-function`, natively. Twirp speaks plain HTTP: POST to /twirp/<pkg.Service>/<Method> with a JSON (or protobuf) body. loadr's JSON body sets `Content-Type: application/json`, which is exactly Twirp's JSON mode — so a Twirp call is just an HTTP request. (For protobuf mode, send `body: { file: req.bin }` with a protobuf Content-Type.) loadr run examples/43-twirp.yaml

44-compare-and-gate.yaml
compare-and-gate

Regression gating — `loadr compare` diffs two summary exports and fails CI when the current run is worse than the baseline. The workflow: run the SAME plan twice (baseline vs candidate), export each summary, then diff: loadr run examples/44-compare-and-gate.yaml --summary-export base.json # ...deploy the candidate build... loadr run examples/44-compare-and-gate.yaml --summary-export current.json loadr compare base.json current.json p95, p99, error_rate and the checks pass_rate gate at a 5% default tolerance; every other field is informational. Tune gates per field or per metric, and make regressions fail the pipeline (exit 99) with --assert: loadr compare base.json current.json --assert \ --max-regression p95=10% \ --max-regression http_req_duration.p99=25 \ --max-regression error_rate=0.5 \ --max-regression rps=5% Percent limits are relative to the baseline; bare numbers are absolute in the field's display unit (milliseconds for latency, percentage points for rates). PR-comment markdown and machine-readable JSON come for free: loadr compare base.json current.json --markdown compare.md --output compare.json

45-sweep.yaml
sweep

Capacity sweep — `loadr sweep` runs ONE plan across a parameter matrix and tabulates p50/p95/p99, error rate and RPS side by side, one row per combo: loadr sweep examples/45-sweep.yaml --var vus=10,25,50 --duration 30s Repeated --var axes multiply into a cartesian matrix. Every value is exported to the plan's environment as LOADR_SWEEP_<NAME> (and `vus` / `duration` additionally map onto the run overrides), so custom knobs are plain ${env.*} / __ENV reads: loadr sweep examples/45-sweep.yaml \ --var vus=10,50 --var page_size=25,100 \ --out-dir sweep-results --markdown sweep.md Per-combo summary exports land in --out-dir (default: loadr-sweep/), so any two cells of the matrix can be diffed later with `loadr compare`.

46-slo-thresholds.yaml
slo-thresholds

SLO-style thresholds: `slo(N%) < bound` reads as "N% of requests finish within the bound" — the way SLOs are written in the reliability doc, with no percentile translation in your head. slo(99%) < 300ms == p(99) < 300. Supported objectives match the fixed points the histogram summary carries: 50, 90, 95, 99, 99.9 (the % sign is optional). Bounds accept duration units (300ms, 1.5s) or plain milliseconds, and the classic `p(95)<500` syntax mixes in freely. loadr run examples/46-slo-thresholds.yaml

47-system-observe.yaml
system-observe

Was it the target — or your load generator? An `observe` collector samples the local host's CPU / memory / disk / network from /proc every second during the run (Linux; other platforms log a warning and skip) and overlays the series on the run timeline — in the HTML report, the web UI and the summary export. Observed series are ordinary metrics: system_cpu, system_memory, system_disk_io, system_network (rename the family with `as_prefix`). CPU and memory are 0..1 ratios; disk I/O and network are bytes per second. They are valid threshold targets, so a saturated generator FAILS the run instead of silently skewing the latency numbers. loadr run examples/47-system-observe.yaml

48-chaos.yaml
chaos

Chaos drill — what does the client-side picture look like when the backend misbehaves? Steady traffic runs its normal flow while a second scenario keeps forcing server errors and multi-second stalls (go-httpbin's /status and /delay endpoints stand in for a flaky dependency). Scenario-scoped thresholds judge the blast radius on the steady traffic only, and the abort threshold is the circuit breaker: if errors explode, the run stops instead of burning the target. docker compose -f examples/harness/docker-compose.yml up -d --build loadr run examples/48-chaos.yaml Coordinated fault injection (network partitions, node kill, added latency, clock skew) is the `loadr chaos` RFC — see docs/design/concurrency-consistency-testing.md. The planned schedule shape, for when the nemesis lands: nemesis: seed: 1234 # reproducible fault schedule faults: - { at: 20s, do: partition, groups: [[node-1], [node-2, node-3]], for: 25s } - { at: 60s, do: kill, node: primary, for: 15s } - { every: 30s, do: latency, node: any, ms: 200, jitter: 50, for: 10s } heal_at_end: true

49-payload-complexity.yaml
payload-complexity-probe

Algorithmic-complexity (DoS) probe. The request body is a generated adversarial payload whose depth is the swept `depth` axis (exported to the run as LOADR_SWEEP_DEPTH). Run it under `loadr sweep` with `--complexity depth` to fit response-time vs depth and `--max-exponent 1.2` to fail CI when a parser scales worse than quasilinear: loadr sweep examples/49-payload-complexity.yaml \ --var depth=4000,8000,16000,32000,64000 \ --complexity depth --max-exponent 1.2 See `loadr payload --list` for every payload kind.