Skip to content
Merged
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions doc/bin/relay/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,15 @@ and auth root, split by a **tier** label chosen by the auth server's grant or
possible. [Stats](/concept/stats) describes the paths, tracks, and encodings;
read them with the [`moq-stats`](https://docs.rs/moq-stats) crate.

`publisher.json` also reports how far behind each broadcast's viewers are. Every
`interval` the relay samples each subscription's lag, the newest frame produced
minus the newest frame the viewer acknowledged, into a byte-weighted `lag`
histogram with buckets at 50 ms, 100 ms, 250 ms, 500 ms, 1 s, 2 s, 5 s, and above.
`dropped` counts the duration, bytes, and groups of media written to a
viewer on a group stream that ended before the viewer acknowledged it. Both
resolve one group at a time, so they overstate lag and drops for long groups;
see [Lag](/concept/stats#lag).

## \[iroh]

```toml
Expand Down
40 changes: 38 additions & 2 deletions doc/concept/stats.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ return later as a new broadcast.
"fetches": 0,
"bytes": 1048576, "frames": 900, "groups": 30, "datagrams": 0,
"stale": { "bytes": 0, "frames": 0, "groups": 0, "datagrams": 0 },
"lag": { "50ms": 1040000, "1s": 8576 },
"dropped": { "duration": 966.5, "bytes": 8576, "groups": 1 },
"announced": 1, "announced_closed": 0,
"broadcasts": 3, "broadcasts_closed": 1,
"subscriptions": 6, "subscriptions_closed": 2
Expand All @@ -101,11 +103,44 @@ return later as a new broadcast.
| `bytes` / `frames` / `groups` | Payload delivered. |
| `datagrams` | Groups delivered as an unreliable datagram. A subset of `groups`. |
| `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` | `publisher.json` only: how far behind the viewers' acknowledged media is, as bytes per bucket of media time. See [Lag](#lag). Omitted while empty. |
| `dropped` | `publisher.json` only: media written to a viewer on a group stream that was reset or abandoned before the viewer acknowledged it, as `duration` (fractional milliseconds), `bytes`, and `groups`. Omitted while empty. |

The last six fields are legacy spellings of the `*_started` and `*_ended`
The last six fields in the example are legacy spellings of the `*_started` and `*_ended`
counters, still written so an older consumer reads a newer relay. A reader
should prefer the canonical name and fall back to the legacy one.

### Lag

Each track subscription has an acknowledged **frontier**: the newest frame
timestamp the viewer is known to have received. It starts at the track's newest
frame when the subscription opens, so a backlog the viewer deliberately starts
behind never counts, and advances when the viewer acknowledges the end (FIN) of
a group stream. Every stats interval the relay samples each subscription's lag,
the newest frame the track has produced minus the frontier, or the wall-clock
time the frontier has stood still while newer media exists if that is larger.
It adds the bytes the track produced during the interval to the bucket that lag
falls in, and the broadcast's subscriptions sum into one entry.

| Bucket | Lag |
| --- | --- |
| `50ms` | under 50 ms |
| `100ms` | 50 ms to 100 ms |
| `250ms` | 100 ms to 250 ms |
| `500ms` | 250 ms to 500 ms |
| `1s` | 500 ms to 1 s |
| `2s` | 1 s to 2 s |
| `5s` | 2 s to 5 s |
| `inf` | 5 s and above |

Each bucket is a cumulative byte counter, so the difference of two frames is
the byte-weighted lag distribution over that interval, from which a dashboard
reads percentiles or a mean. A paused broadcast produces nothing and adds
nothing. The frontier moves once per group, so a healthy viewer of a stream
with two-second groups still spreads across the buckets below `2s`; the dropped
counters likewise count a whole group's written span, including frames the
viewer did receive.

### Presence

```json
Expand All @@ -119,7 +154,8 @@ ends on the old one and starts on the new.

### Counters

Every counter is a cumulative, monotonic unsigned integer. A rate is the
Every counter is cumulative and monotonic: an unsigned integer, apart from
`dropped.duration` in fractional milliseconds. A rate is the
difference between two frames divided by the time between them, and a live
count is started minus ended. A frame never shows ended above started.

Expand Down
3 changes: 0 additions & 3 deletions quest/m1/qos/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,6 @@ preflight, consumes them downstream.

## Quests

- [Starvation](/quest/m1/qos/starvation.md) - per broadcast, how far behind
the acknowledged frontier of its subscriptions is, in media time, plus the
media dropped before it was acknowledged
- [Starvation at frame granularity](/quest/m1/qos/starvation-frames.md) - the
acknowledged frontier moves at every frame boundary through `poll_acked`,
with a delivery-delay histogram for jitter
Expand Down
11 changes: 5 additions & 6 deletions quest/m1/qos/publisher-timeliness.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ cooperation.

## Plan

This is the ingress mirror of [starvation](/quest/m1/qos/starvation.md) and
needs no acknowledgments: the relay is the receiver, and a frame is measured
This is the ingress mirror of the egress `lag` histogram on `stats::Traffic`
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 All @@ -27,8 +27,9 @@ milliseconds, and the re-anchor happens on its own as old minima expire.
Document the window length beside the bucket edges.

Aggregate as a byte-weighted cumulative histogram of drift on the
`Role::Subscriber` (ingress) rows, with the same bucket edges and the same
monotonic contract as the starvation histogram. Beside it keep two cumulative
`Role::Subscriber` (ingress) rows, reusing `stats::Histogram` (its
`EDGES` and cumulative contract) and its crate-private atomic backing, as the
egress `lag` field does. Beside it keep two cumulative
counters: `timestamp_regressions`, frames whose timestamp is below the
previous group's newest timestamp, and `stalls`, gaps between complete frames
longer than a documented threshold. Compare only across group boundaries for
Expand All @@ -51,7 +52,5 @@ excluded.

## Related

- [Starvation](/quest/m1/qos/starvation.md) - the egress half, same
histogram shape
- [Client stats](/quest/m1/qos/stats/schema.md) - the publisher's own view
of the same uplink, in the transport section of its stats broadcast
26 changes: 13 additions & 13 deletions quest/m1/qos/starvation-frames.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,19 @@
## Goal

Each subscription's acknowledged frontier moves at every frame boundary
instead of once per group, so the starvation histogram and dropped-media
counters from the group slice read a frontier at most one frame stale, and a
second histogram reports per-frame delivery delay so viewer jitter is visible
per broadcast. The wire shape from the group slice is unchanged apart from
the new histogram.
instead of once per group, so the `lag` histogram and `dropped` counters
on `stats::Traffic` read a frontier at most one frame stale, and a second
histogram reports per-frame delivery delay so viewer jitter is visible per
broadcast. The wire shape is unchanged apart from the new histogram.

## Plan

A frame is atomic to the viewer: it is useful only once fully delivered. The
sampler from the parent quest is unchanged; what changes is how often the
frontier it reads moves. `Writer` in `rs/moq-net/src/coding/writer.rs` gains
sampler in `Registry::report` (`rs/moq-net/src/stats.rs`) is unchanged; what
changes is how often the frontier it reads moves. Today each group serve task
holds a `stats::Delivery` that advances the frontier when `Writer::poll_close`
reports the FIN acknowledged, and counts the group's whole written span as
dropped otherwise. `Writer` in `rs/moq-net/src/coding/writer.rs` gains
a monotonic written-offset counter, since every write funnels through it, so
at each frame end the per-group serve task knows the stream offset and the
frame timestamp. Record `(offset, timestamp, bytes, written_at)` and await
Expand All @@ -24,9 +26,9 @@ acknowledged in order for the purpose of this metric, so poll the oldest
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 parent quest read a frontier that
timestamp, so the interval samples 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
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.

Expand All @@ -38,19 +40,17 @@ the return one-way delay and that a backend without the hook leaves the
histogram absent, never zero.

Backends that return unsupported from `poll_acked` keep the group-granularity
sampling from the parent quest, so the histogram never disappears over qmux or
`Delivery`, so the histogram never disappears over qmux or
a browser transport; document which resolution a node offers.

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 under an injected ACK delay staying inside one bucket of the
unacknowledged span to `dropped.duration`; delivery delay under an injected ACK delay staying inside one bucket of the
true RTT; and fallback to group-granularity frontier tracking when the hook is
unsupported.

## Required

- [Starvation](/quest/m1/qos/starvation.md) - fixes the wire shape and the
group-granularity fallback
- [poll_acked in web-transport](/quest/m1/quic/ack-hook.md) - the released
hook this samples through
89 changes: 0 additions & 89 deletions quest/m1/qos/starvation.md

This file was deleted.

5 changes: 0 additions & 5 deletions quest/m1/qos/stats/schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,3 @@ what a subscriber received and played, per audio and video. The relay is
`Traffic` and `Presence` serializers once a moq-stats release carrying both
has shipped and the demo dashboard (and any moq.pro consumer) reads the
`*_started` / `*_ended` names. Deserialize keeps accepting them. Wire only.

## Related

- [Starvation](/quest/m1/qos/starvation.md) - the relay's delivery counters,
the other half of a health verdict
2 changes: 0 additions & 2 deletions quest/m1/quic/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,6 @@ This is a transport API change, not a MoQ wire change.

- [Scope track priority](/quest/m1/track-priority-scope.md) - the
per-broadcast fairness policy on cluster sessions
- [Starvation](/quest/m1/qos/starvation.md) - the first consumer of ACK
progress: how far behind viewers are, from the relay's point of view
- [Receive timestamps](/quest/m2/quic-receive-ts.md) - per-packet arrival
times for GCC and deadlines
- [GCC egress experiment](/quest/m2/quic-gcc.md) - a measured verdict on
Expand Down
5 changes: 0 additions & 5 deletions quest/m1/track-priority-scope.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,3 @@ change.

- [Hierarchical stream scheduling](/quest/m1/quic/scheduler.md) - the fair
tier the per-broadcast send groups ride on

## Related

- [Starvation](/quest/m1/qos/starvation.md) - the relay-side signal that
shows a starved subscription
1 change: 1 addition & 0 deletions rs/moq-net/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ moq-pattern = { workspace = true, features = ["serde"] }
num_enum = { workspace = true }
rand = { workspace = true }
serde = { workspace = true }
serde_with = { workspace = true }
slab = { workspace = true }
thiserror = { workspace = true }
tracing = { workspace = true }
Expand Down
Loading
Loading