load

js/cli/load.md

fino load runs bounded HTTP or scripted stateful load through Fino's public protocol clients. Static workloads support explicit HTTP/1.1, HTTP/2, and HTTP/3 and fail when the target cannot speak the requested protocol instead of silently changing the workload. H2 and H3 targets must use https:; h2c is not supported.

fino load --connections 100 --duration 30s http://127.0.0.1:3000/
fino load -p h2 -c 20 -m 10 --warmup 5s -d 30s https://localhost:3000/
fino load --rate 1000 --rate-to 5000 -d 30s http://127.0.0.1:3000/
fino load --scenario ./chat.load.ts --users 100 --duration 30s

The default response policy is consume: chunks are counted and immediately dropped, so complete bodies are never retained and connections remain reusable. --response cancel stops after final headers. That closes an HTTP/1.1 connection, sends RST_STREAM for HTTP/2, or cancels the HTTP/3 request stream. Because cancellation can reduce server work, output always labels it as headers-only.

Load model

Without --rate, the command is closed-loop. HTTP/1.1 runs one operation per connection; HTTP/2 and HTTP/3 run connections * streams operations. A worker starts its next request after the previous response has been consumed or cancelled.

--rate selects open-loop arrivals per second. --rate-to adds a linear ramp over the measured duration or exact request count. Arrivals retain their intended monotonic timestamp while waiting, so queue and total latency include generator delay rather than hiding coordinated omission. If --max-queued-operations is full, excess arrivals are counted as dropped.

Use either --duration or --requests. With neither, measurement lasts 10 seconds. --warmup runs unmeasured work first and carries the same client and surviving connections into measurement.

Static HTTP mode repeats one normalized request without target selection or generated data in its hot path. Put multi-endpoint, stateful, or varying request behavior in a TypeScript scenario, where the workload contract is explicit.

--expect-body compares an exact UTF-8 value while streaming and dropping the response. It never buffers the received body and cannot be combined with --response cancel. Bailout thresholds stop scheduling after status/body failures or timeout/transport errors reach the configured count.

TypeScript scenarios

Use --scenario for protocols whose load behavior is not defined by a URL and concurrency alone. The module default-exports a LoadScenario. Each virtual user receives controlled HTTP, SSE, WebSocket, WebTransport, and raw QUIC constructors plus cancellation, counters, and bounded custom metrics. Resources opened through the controlled client are closed when the hook returns or throws.

import type { LoadScenario } from 'fino:load';

export default {
  protocol: 'websocket',
  async session(client, context) {
    const socket = await client.websocket('wss://localhost:3000/chat');
    const response = new Promise<MessageEvent>((resolve) => {
      socket.addEventListener('message', (event) => resolve(event as MessageEvent), {
        once: true,
      });
    });
    const start = performance.now();
    await socket.send(`ping:${context.sequence}`);
    context.messages('sent');
    const message = await response;
    context.messages('received');
    context.bytes('received', String(message.data).length);
    context.metric('round_trip_ms', performance.now() - start);
    await socket.close();
  },
} satisfies LoadScenario;

SSE scenarios define event predicates and completion. WebSocket scenarios define message correlation. WebTransport and raw QUIC scenarios define stream or datagram framing, delivery expectations, and whether 0-RTT is valid. The runner does not invent those application contracts or combine 0-RTT and 1-RTT measurements.

Command reference

Name Value Description
url URL Static HTTP target unless --scenario is used; H2/H3 require https:.
--scenario path TypeScript module default-exporting a LoadScenario.
--users integer Concurrent virtual users for a scenario.
--sessions integer Exact scenario-session count; mutually exclusive with duration.
-p, --protocol h1, h2, h3 Required HTTP wire protocol; defaults to h1.
-c, --connections integer Physical HTTP connections; defaults to 10.
-m, --streams integer Streams per H2/H3 connection; must be 1 for H1.
-d, --duration duration Measured duration (ms, s, m, h); defaults to 10s.
-n, --requests integer Exact HTTP operation count; mutually exclusive with duration.
--warmup duration Unmeasured HTTP warmup; defaults to 0ms.
--rate number Fixed arrivals/second or ramp start.
--rate-to number Linear arrival-rate ramp endpoint.
--max-queued-operations integer Bound waiting HTTP operations or scenario sessions.
--reconnect-after integer Recreate pooled sessions after this many starts.
-X, --method string HTTP method; defaults to GET.
-H, --header name:value Repeatable request header.
--body string Static body replayed for every request.
--body-file path Body read once and replayed; mutually exclusive with --body.
--response consume, cancel Stream/drop bodies or stop after final headers.
--expect-status status Repeatable successful status; defaults to 200-399.
--expect-body string Exact UTF-8 body matched incrementally.
--bailout-failures integer Stop after status/body failures reach this count.
--bailout-errors integer Stop after timeout/transport errors reach this count.
--timeout duration Total per-request timeout; defaults to 30s.
--connect-timeout duration DNS plus new-connection timeout.
--headers-timeout duration Local queue plus final-headers timeout.
--body-idle-timeout duration Maximum gap between response chunks.
--max-pending-requests integer Bound requests waiting inside HttpClient.
--max-buffered-response-bytes integer Bound unread H2/H3 bytes per response; defaults to 1 MiB.
--retry integer Total safe replay attempts; defaults to 1.
--no-decompress flag Count encoded rather than decoded body bytes.
--redirect follow, error, manual Redirect policy; defaults to follow.
--ca, --cert, --key path TLS trust and client-identity PEM files.
--insecure flag Disable TLS peer verification for local development.
--title string Run label included in text and JSON.
-q, --quiet flag Suppress text output; explicit JSON is still emitted.
--json flag Emit the versioned machine-readable result.

Output contract

HTTP text and JSON come from the schema-version-2 result. Counters distinguish offered, started, completed, scheduler-dropped, status/body-failed, timed-out, cancelled, transport-failed, and headers-only work. Results include response bytes, status/protocol distributions, connection reuse, bailout reason, and bounded histograms for queue, final-header (TTFB), download, and total latency. Machine output records header names and non-secret TLS configuration, never header values, certificate paths, or key material.

Scenario output has a separate versioned result: session outcomes, peak active sessions, application bytes/messages reported by the script, bounded error classes, and fixed-size logarithmic histograms for custom metrics. Metric names are limited to 64 safe ASCII characters. Defaults cap distinct metrics at 64 and diagnostic log calls at 1000.

For comparable runs, keep the build mode, machine, target, TLS policy, protocol, response policy, warmup, and content-decoding policy stable.