Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion doc/bin/relay/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,11 @@ its linger restarts its counters under a new epoch without an announce. The
node's totals count everything since its epoch began, so billing reads those;
a group's per-path traffic and per-label sessions and HTTP requests are for
dashboards. Session counters report how close sessions come to the
per-session limits, past which one is closed with `TOO_MANY_REQUESTS`.
per-session limits, past which one is closed with `TOO_MANY_REQUESTS`. The
totals and prefix tracks also carry how far viewers are behind
([`lag`](/concept/stats#traffic)) and what was dropped before they acknowledged
it, sampled once per `interval`: while the broadcast produces, a viewer that
acknowledges nothing for a whole interval reads at least that far behind.
[Stats](/concept/stats) describes the paths, tracks, and encodings; read them
with the [`moq-stats`](https://docs.rs/moq-stats) crate.

Expand Down
17 changes: 16 additions & 1 deletion doc/concept/stats.md
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,8 @@ reader that misses every frame from the group's last change until it is
unlisted loses that instance's tail, so a meter reads the node's totals.

`paths` holds each broadcast path's egress (`publisher`) and ingress
(`subscriber`) [Traffic](#traffic). A path appears from its first nonzero
(`subscriber`) [Traffic](#traffic), without `lag` and `dropped`: the group's
own totals, the node's totals, and the prefix tracks carry those. A path appears from its first nonzero
counter until the relay drops its counters, even while idle: the last viewer
can leave while the publisher still holds the path, and a returning viewer
resumes the same counters. So a path missing from a frame had no counters, and
Expand Down Expand Up @@ -367,6 +368,8 @@ stale.
"bytes": 1048576, "frames": 900, "groups": 30, "datagrams": 0,
"inflated": 0,
"stale": { "bytes": 0, "frames": 0, "groups": 0, "datagrams": 0 },
"lag": { "50ms": 1040000, "1s": 8576 },
"dropped": { "bytes": 1500, "frames": 15, "groups": 1, "datagrams": 0 },
"announced": 1, "announced_closed": 0,
"broadcasts": 3, "broadcasts_closed": 1,
"subscriptions": 6, "subscriptions_closed": 2
Expand All @@ -385,6 +388,18 @@ stale.
| `datagrams` | Groups delivered as an unreliable datagram. A subset of `groups`. |
| `inflated` | Bytes inflated from compressed groups for plain readers, counted once per group however many share the copy, plus received compressed datagrams, which inflate on arrival. Not part of `bytes`, which counts payload as stored or sent. |
| `stale` | Payload skipped because it aged past a subscriber's latency budget, with the same four fields. Disjoint from the top-level payload counters. |
| `lag` | Egress only: how far each subscription's peer is behind, weighted by bytes. Every stats interval, the bytes a subscription's track produced since the last are counted in the bucket of its lag then. Keys are each bucket's exclusive upper edge (`50ms`, `100ms`, `250ms`, `500ms`, `1s`, `2s`, `5s`, then `inf`), and an empty bucket is left out. |
| `dropped` | Egress only: content written on group streams that ended before the peer acknowledged them (a skip, an expiry, or a closed session), with the same four fields as `stale`. `groups` counts those that carried a frame, `frames` the frames they carried, and `bytes` the payload the transport accepted on them. A subset of the top-level payload counters, which count it as handed over. `datagrams` stays zero, since a datagram has no acknowledgement to miss. |

A subscription's lag is the newest media its track produced minus the newest
the peer acknowledged, counting a group stream as acknowledged once its FIN
is. It starts level with the newest media when the subscription opens, so a
backlog it starts behind is not lag. A peer that acknowledges nothing for a
whole interval in which the track produced reads at least that interval behind,
and every interval after adds to it until an acknowledgement arrives, so a
media clock that crawls or stops cannot hide a stalled viewer. A subscription
that closes counts its last bytes at its lag when it closed. Diff two frames
for one interval's distribution, and sum nodes bucket by bucket.

The last six fields are legacy spellings of the `*_started` and `*_ended`
counters, still written so an older consumer reads a newer relay. A reader
Expand Down
1 change: 0 additions & 1 deletion quest/m2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ lite-07; bitrate enforcement is not part of this release.
- [Generated C bindings](/quest/m2/c/README.md) - C generated from moq-ffi ships as `moq-c` 0.8.0 and replaces the hand-written crate
- [Benchmark sources](/quest/m2/bench-sources.md) - every measuring nightly benchmark feeds the regression table, and none is a smoke run
- [Poll-native async](/quest/m2/ffi-poll-migrate.md) - moq-ffi's pure-kio async exports poll their state directly instead of going through `ffi::Task`
- [QoS](/quest/m2/qos/README.md) - the relay's egress `lag` histogram and `dropped` counters on the stats totals and prefix tracks
- [Lazy retained HLS instances](/quest/m2/hls-lazy-instances.md) - a replaced HLS instance starts its watchers only when a request reaches it
- [HLS and DASH browser playback](/quest/m2/hls-player-test.md) - hls.js and dash.js play, reload, switch, and survive reconfiguration nightly
- [moq-hls export on kio](/quest/m2/hls-export-kio.md) - export watchers and the init build are owned kio tasks instead of tokio::spawn
Expand Down
1 change: 0 additions & 1 deletion quest/m2/latency-ledger.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,4 @@ probes, which was the right trade to get it running. This quest promotes them.

## Related

- [QoS](/quest/m2/qos/README.md) - relay-side health, the same idea from the other end
- [Media stats](/quest/m2/stats/README.md) - the hang stats and `.echo` snapshots these stages join
18 changes: 6 additions & 12 deletions quest/m2/qos-lag-dashboard.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,17 @@

The demo stats dashboard (`demo/web/src/stats.ts`) shows the relay's viewer
lag and dropped media: percentiles of the egress `lag` histogram over the
chart window, and `dropped` duration, bytes, and groups as rates, cluster-wide
chart window, and `dropped` bytes, frames, and groups as rates, cluster-wide
and per node like the existing counters.

## Plan

- Read both from the stats totals and prefix tracks
(`<path>/prefix.json`) rather than the group tracks' per-path rows they sit on today (decided in the
2026-10-06 audit; the rows stay for existing readers, 2026-10-12); [viewer lag histogram](/quest/m2/qos/lag-histogram.md)
lands them on the egress side of its per-group totals and prefix tracks
(decided 2026-10-08). `lag` is a cumulative byte count per bucket keyed
- Read both from the egress side of the stats totals, a group's totals, and
the prefix tracks (`<path>/prefix.json`); a group's per-path `paths` rows
leave them out (decided in the 2026-10-06 audit). `lag` is a cumulative byte count per bucket keyed
by its upper edge (`"50ms"` to `"5s"`, then `"inf"`, empty buckets
omitted); `dropped` is `{ duration, bytes, groups }` with the duration in
fractional milliseconds. `doc/concept/stats.md` documents them. Diff two
omitted); `dropped` has the same four fields as `stale` (`bytes`, `frames`,
`groups`, `datagrams`). `doc/concept/stats.md` documents them. Diff two
samples within one subscription for an interval's distribution, as the
dashboard already does to turn cumulative bytes into rates, and sum nodes
bucket by bucket.
Expand All @@ -30,7 +28,3 @@ and per node like the existing counters.
package is planned.

Public API: none. Wire: none.

## Required

- [Viewer lag histogram](/quest/m2/qos/lag-histogram.md) - the `lag` histogram and `dropped` counters the dashboard reads
9 changes: 2 additions & 7 deletions quest/m2/qos-publisher-timeliness.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@ cooperation.

## Plan

This is the ingress mirror of the egress `lag` histogram ([viewer lag
histogram](/quest/m2/qos/lag-histogram.md), which lands first so this reuses
its histogram type, decided 2026-10-08) and needs no acknowledgments: the relay is the receiver, and a frame is measured
This is the ingress mirror of the egress `lag` histogram (reuse its
`moq_net::stats::Histogram` type, decided 2026-10-08) and needs no acknowledgments: the relay is the receiver, and a frame is measured
when its last byte arrives, because a partial frame is not useful.

Frame timestamps are relative and jittered with no epoch, so measure each
Expand Down Expand Up @@ -51,7 +50,3 @@ simulated hour; a paused publisher records a
stall and no drift for frames it never sent; a regression across groups is
counted while intra-group reordering is not; a track without a timescale is
excluded.

## Required

- [Viewer lag histogram](/quest/m2/qos/lag-histogram.md) - the egress histogram whose type and edges this reuses
67 changes: 0 additions & 67 deletions quest/m2/qos/README.md

This file was deleted.

58 changes: 0 additions & 58 deletions quest/m2/qos/lag-histogram.md

This file was deleted.

22 changes: 8 additions & 14 deletions quest/m2/starvation-frames.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ pending frame and retire everything at or below the acknowledged offset.
Each acknowledged frame moves the subscription's frontier to that frame's
timestamp, so the interval samples from the lag histogram read a frontier that
is at most one frame stale instead of one group. When a stream
is reset before a frame is acknowledged, `dropped_duration` now grows by the
span from the newest acknowledged frame to the newest written one, which is
the exact media the viewer lost.
is reset before a frame is acknowledged, `dropped` now counts only the frames
written after the newest acknowledged one, which is the exact media the viewer
lost.

Add a second byte-weighted cumulative histogram of delivery delay:
`acked_at - written_at` per frame, where `acked_at` is when the serve task is
Expand All @@ -42,25 +42,19 @@ Backends that return unsupported from `poll_acked` keep the group-granularity
sampling from the lag histogram, so the histogram never disappears over qmux or
a browser transport; document which resolution a node offers.

Deferred to m2 with the parent QoS sampler. Group-granularity lag lands
first after the next release; frame-granularity sampling then adds the ACK
hook without making either resolution a release gate.

Decided 2026-10-08: the group-granularity sampler is the
[viewer lag histogram](/quest/m2/qos/lag-histogram.md), not yet on main (in
progress as [moq-dev/moq#6791](https://github.com/moq-dev/moq/pull/6791)), so
that quest is Required.
The group-granularity sampler is `Registry::report` in
`rs/moq-net/src/stats.rs`, reading the frontier that `stats::Delivery`
advances on each acknowledged group FIN. Frame-granularity sampling adds the
ACK hook without making either resolution a release gate.

Tests: the frontier tracking frame ends under a peer that acknowledges in
bursts, with interval samples landing one bucket lower than group-granularity
tracking of the same run; a reset mid-group attributing only the
unacknowledged span to `dropped_duration`; delivery delay staying inside one bucket of the true RTT plus the peer's
unacknowledged frames to `dropped`; delivery delay staying inside one bucket of the true RTT plus the peer's
`max_ack_delay`; and fallback to group-granularity frontier tracking when the hook is
unsupported.

## Required

- [poll_acked on moq-net's send stream](/quest/m2/quic-ack-hook.md) - the
hook this samples through
- [Egress lag histogram](/quest/m2/qos/lag-histogram.md) - the
group-granularity sampler and starvation histogram this refines
7 changes: 2 additions & 5 deletions quest/m2/stats/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,8 @@ kind.
removes `.z` siblings; follow the delta policy it retains on the single track. An unsubscribed stats track never leaves the process.
Gauges are carried but never summed.
- **On main.** Every change is additive: optional sections on
`#[non_exhaustive]` catalog types, and new types. The line left the
[QoS](/quest/m2/qos/README.md) line, which keeps the relay's moq-stats
changes.
`#[non_exhaustive]` catalog types, and new types. The relay's moq-stats
changes stay out of this line.
- **No `@moq/stats` package.** Media types live in `@moq/hang`, and the
demo dashboard's relay-stats reader stays where it is.
- Docs stay inline: `doc/concept/hang.md` documents both catalog sections,
Expand Down Expand Up @@ -121,8 +120,6 @@ connection view and the dashboard flow.

## Related

- [QoS](/quest/m2/qos/README.md) - the relay's delivery counters; the
combined per-broadcast verdict reading both lives downstream
- [Encoder feedback](/quest/m3/stats-encoder-feedback.md) - a Rust encoder
adapts its bitrate to what its viewers report
- [Latency ledger](/quest/m2/latency-ledger.md) - adds the viewer's latency
Expand Down
1 change: 0 additions & 1 deletion quest/m3/cut-through/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,5 +75,4 @@ stats field, which its quest documents inline.

## Related

- [QoS](/quest/m2/qos/README.md) - the loss-delay counter follows its per-broadcast ingress row conventions
- [Frame slot accounting](https://github.com/moq-dev/moq/pull/5239) - landed: the cache charges frame slot growth, which covers the in-flight frames a cut-through build would hold
4 changes: 0 additions & 4 deletions quest/m3/cut-through/loss-delay.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,3 @@ an RTT counts nothing.
Public API: an additive trait accessor. Wire: a new `moq-stats` field,
additive on `main` since `Traffic` is `#[non_exhaustive]` and readers default
missing fields.

## Related

- [QoS](/quest/m2/qos/README.md) - the per-broadcast ingress conventions this follows
Loading