open-standards/wai/wai-metrics
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Transaction Science a5b1d10a39 wai-relay answers /metrics on its control listener, and wai-quantum-service on its API
The relay's operator's surface (the loopback listener and the Unix socket of its own user)
answers a connection that opens with an HTTP/1 request line as HTTP, once, after the same
check of its peer as every control request: GET /metrics (HEAD without the text) gives its
counters in the text exposition format through wai/wai-metrics: sessions open and opened,
tracks held and the downstream subscriptions across them, what its schedulers hold, objects in,
fetches by source, the door's refusals and drops, requests refused by why, upstream requests by
outcome, ratings held, going away, the unmetered energy marker, and the surface's own requests
by op and refusals by why. Another path, or a request through a front, is 404; another method
405. Every other connection keeps the JSON-lines protocol. Relay::fan_out gives the tracks and
subscriptions.

The quantum service answers GET /metrics in its outermost layer, before the API key and the
client's limits, outside its OpenAPI document and MCP endpoint: requests by route pattern,
method and status, jobs held by status, the memory running jobs hold, what the store held at
start, and the unmetered energy marker; through a front it is 404.

Each has a test that reads its answer strictly, moves its series with real sessions or a real
job, refuses each front field and finds no label value naming a party; the relay's socket test
holds a peer stated as another user to the refusal before /metrics is answered. The locks that
build the relay by path, and the service's, gain wai-metrics; the workflows that build either
run on a change to wai/wai-metrics.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-09 20:06:07 -05:00
..
src wai-metrics' reader finds the routes a router's source declares, and says a front that sets no field is configured not to pass the path on 2026-11-07 01:50:41 -05:00
Cargo.lock Six WAI services answer GET /metrics, and wai-audit builds as a library 2026-11-07 01:50:40 -05:00
Cargo.toml Six WAI services answer GET /metrics, and wai-audit builds as a library 2026-11-07 01:50:40 -05:00
README.md wai-relay answers /metrics on its control listener, and wai-quantum-service on its API 2026-11-09 20:06:07 -05:00

wai-metrics

The text exposition format (version 0.0.4) that a WAI service answers GET /metrics with, in a crate of its own with no dependencies. The services beside wai-rs (wai-audit, wai-ledger, wai-fleet, wai-key-proxy, wai-quantum-service) and the room service, ingest gateway and relay of joule-code depend on it by path.

  • Exposition writes counters and gauges family by family: each family's # HELP and # TYPE lines, then its samples. Names are checked against the format's grammars, a family is written once, label values are escaped (\, " and a line feed), help text likewise (\ and a line feed), and values are written as the format reads them (+Inf, -Inf, NaN).
  • Every label is fixed in the code. A label's name and value are &'static str: a value that exists only at run time (a room's id, a principal, an account, a client's address) cannot become a label. The one value written at run time is the code of Requests, a request's status, which is a number. A service's series are the ones its code names, and their number is bounded by it.
  • The scrape is the operator's. through_a_front tells a request that came through a front by the fields a front sets (Forwarded, Via, X-Forwarded-For, X-Forwarded-Host, X-Forwarded-Proto, X-Real-IP, CF-Connecting-IP). A service answers such a request by any method as it does a path it does not have, so /metrics is read at the service's own address. A front that sets none of these fields is configured not to pass the path on.
  • Counts counts by labels fixed in the code (an outcome, a role, a protocol), each series written from the start at 0 when the service names it so.
  • Requests counts a service's requests by route, method and status. The route is the pattern a path matched (/v1/channels/{name}/report), never the path; a method outside HTTP's own is other.
  • read reads the format for a service's tests. It reads every text the format's grammar allows: blanks and tabs between tokens and around a line, comments, and every spelling of a value (Inf, Infinity and NaN in any case). It refuses a text that breaks one of the rules it holds, with the line and the reason: names, quoting and escapes, one # HELP and one # TYPE a family, each before the first sample, a family's lines together, no series twice, the text ended by a line feed. It also refuses a counter that is negative or NaN, and a family named as a counter is without its _total, which are a service's rules rather than the grammar's. read::identifier_like tells a label value that looks like a key, a digest, an address or an account, which a service's tests refuse in its exposition. read::declared_routes reads the routes a router's source declares and read::path_of makes a path of each, so that a service's tests ask every route it has and a route left out of its labels is seen.
let requests = wai_metrics::Requests::default();
requests.count("/v1/health", wai_metrics::method("GET"), 200);

let mut e = wai_metrics::Exposition::new();
e.gauge("wai_example_sessions", "Sessions open now.", 2u64);
e.family("wai_example_refused_total", wai_metrics::Kind::Counter, "Requests refused, by why.")
    .sample(&[("why", "full")], 1u64);
requests.write(&mut e, "wai_example_requests_total", "Requests answered.");
let body = e.finish(); // answered as wai_metrics::CONTENT_TYPE

What each service exposes

Every service's counters start from 0 when it starts. Every family below is in every answer, with no sample where nothing was counted yet. Each service answers HEAD /metrics as it does GET, without the text.

service families
wai-room wai_room_requests_total, wai_room_grants_total{role}, wai_room_present, wai_room_publishers, wai_room_audience, wai_room_viewer_grants, wai_room_rooms_total, wai_room_relay_connected
wai-ingest wai_ingest_requests_total, wai_ingest_sessions{protocol}, wai_ingest_sessions_opened_total{protocol}, wai_ingest_refused_total, wai_ingest_records_total, wai_ingest_energy_unmetered
wai-audit wai_audit_requests_total, wai_audit_sessions_total{outcome}, wai_audit_cmcd_batches_total{outcome}, wai_audit_reports_total{outcome}, wai_audit_channels
wai-ledger wai_ledger_requests_total, wai_ledger_tariffs, wai_ledger_sources, wai_ledger_invoices, wai_ledger_verifications
wai-fleet wai_fleet_requests_total, wai_fleet_enrolments, wai_fleet_records_held, wai_fleet_reports, wai_fleet_records_total{outcome}, wai_fleet_verifications
wai-key-proxy wai_key_proxy_requests_total, wai_key_proxy_receipts_total{decision,delivery}, wai_key_proxy_receipts_held, wai_key_proxy_forwarding, wai_key_proxy_proofs_in_flight, wai_key_proxy_upstream_total{class}
wai-relay (on its control listener) wai_relay_sessions, wai_relay_sessions_opened_total, wai_relay_tracks, wai_relay_subscriptions, wai_relay_queued, wai_relay_objects_in_total, wai_relay_fetches_served_total{from}, wai_relay_door_refused_total, wai_relay_door_groups_dropped_total, wai_relay_requests_refused_total{why}, wai_relay_age_withheld_total, wai_relay_upstream_total{outcome}, wai_relay_ratings_held, wai_relay_going_away, wai_relay_energy_unmetered, wai_relay_control_requests_total{op}, wai_relay_control_refused_total{why}
wai-quantum-service wai_quantum_requests_total, wai_quantum_jobs{status}, wai_quantum_memory_held_bytes, wai_quantum_restored_jobs{outcome}, wai_quantum_energy_unmetered

Each *_requests_total is labelled route, method and code. The relay answers on its control listener (the operator's surface, loopback or a Unix socket of its own user), not on its media ports, and so has no *_requests_total. Each service's README says what its families count.

Checks

cargo test --locked