open-standards/wai/wai-metrics/README.md
Transaction Science 0838cbceef joule-moq-relay: a track's receipts track found by the catalog that names it
The relay found a media track's receipts track by a configured suffix alone. It now reads every
catalog it forwards on a subscription or a PUBLISH (the track named `catalog`, MSF §5: each
Group's first object independent, the objects after it delta updates, an earlier Group's update
ignored) and pairs each track with the receipts track its `wai_receipts_track` names, in its
namespace (the packaging draft §5.3.3: "a receiver finds a track's receipts chain and receipts
track by them"; WAI moq-streaming-format §4.1). A catalog's naming overrides the suffix for the
tracks it names; the door, the cache's and the DVR store's pairing and a receipts track's
exemption from delivery timeouts follow it, and a subscription made before its catalog arrived
is changed when it does. A naming the texts make unusable (not a non-empty string, a namespace
that is not MOQT §8.8's rendering, the track's own name, a name the catalog lists no track of
packaging `wai-receipts` under, one receipts track two tracks name) is refused, logged and
counted (`wai_relay_catalog_receipts_refused_total{why}`), and the track is paired as one no
catalog names, by the suffix; an alias two tracks state is neither's. The catalog's
`wai_receipt_alias` is kept for each track.

WAI decision D75 records this reading and the relay's delivery-gap claims, which follow.

Tests: `receipts_names`'s unit tests (independent and delta catalogs, each refusal);
`tests/receipts_catalog.rs` (a door passes a track on the receipts track its catalog names, not
its suffix-named one; a receipts track two tracks name is refused and the suffix pairs them).
Planted faults, each caught: the suffix used though a catalog names another track, a receipts
track two tracks name taken, an unlisted one taken.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-12 05:04:31 -05:00

76 lines
6.4 KiB
Markdown

# 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.
```rust
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_door_running_total{outcome}`, `wai_relay_door_admitted_on_run_total`, `wai_relay_door_runs_contradicted_total`, `wai_relay_catalog_receipts_refused_total{why}`, `wai_relay_delivery_gap_claims_total{outcome}`, `wai_relay_requests_refused_total{why}`, `wai_relay_age_withheld_total`, `wai_relay_subscriptions_too_far_behind_total`, `wai_relay_objects_dropped_too_far_behind_total`, `wai_relay_probes_total{direction}`, `wai_relay_probes_refused_total`, `wai_relay_probes_ended_for_shape_total`, `wai_relay_probe_bytes_total{direction}`, `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_lent_turns_total{pool,event}`, `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
```sh
cargo test --locked
```