Skip to content

feat(mux): report TS health and the export's release clock - #5784

Closed
t0ms wants to merge 2 commits into
moq-dev:quest/m2/stats/schemafrom
t0ms:quest/m2/ts-health-stats
Closed

t0ms wants to merge 2 commits into
moq-dev:quest/m2/stats/schemafrom
t0ms:quest/m2/ts-health-stats

Conversation

@t0ms

@t0ms t0ms commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #5643 (base quest/m2/stats/schema) and on the Rust reporters PR, #5783; until that merges, this diff shows both commits. Rebased onto #5643's head 8cadf9f4, which takes main: #5713's trimmed and gaps sit under release beside dropped, and the passthrough counts its gaps too. That is the re-timing section the decision recorded on #5643 describes.

Problem

ts::stats counts the TR 101 290 checks and each elementary stream's delivery, but only logs them.
The TS health stats quest
asks for them in the media reports. On #5643, the maintainer asked for the export's re-timing stats
(frames dropped at the release deadline, the release margin, and the output clock's offset from the
source) to sit in their own section beside mpegts.

Approach

  • serde on ts::stats. Snapshot, Stream, and Class are camelCase, with every field
    defaulted. Counters are omitted at zero, as hang's are. quiet is whole milliseconds. An unknown
    class reads as data. PID keys go through DisplayFromStr: flattened into a snapshot, a section
    loses serde_json's integer-key parsing, and hang::stats::Snapshot<Report> would not read back
    ("invalid type: string "256", expected u16").

  • ts::stats::Report { mpegts, release } is the E of both hang::stats::Snapshot and
    hang::echo::Snapshot. An import fills mpegts from Import::stats(). A TS export fills it with
    its rows and adds release. A passthrough export reports release alone.

  • ts::stats::Release: dropped, margin, offset, drift (ppm), and outOfTolerance.
    stats::Export becomes { streams, release }, and ts::Release::stats() gives the passthrough's.
    The jitter buffer measures the two new gauges:

    • margin: the least time any frame arrived before its deadline over each whole second of our
      clock, negative when one missed.
    • offset: the gap between the slack of the track sent latest and the delay, which the clock pulls
      shut, as last measured. Like drift, it is absent until the rate is measured.

    Both are integer milliseconds.

  • CLI: the import's decode loop sets mpegts at each one-second sample and after the final
    drain. The TS export and the passthrough set theirs as they sample. The release-clock log line
    gains margin_ms and offset_ms.

  • Docs: a doc/concept/stats.md § MPEG-TS sections covers:

    • each counter's TR 101 290 check;
    • the monitoring point (an import grades what arrived; an export runs no checks; neither grades the
      downstream wire);
    • the PCR checks graded on values;
    • PID_error covered by units and quiet;
    • the release fields.

    Also a paragraph in doc/bin/cli.md. The quest file is removed, with its links.

Decisions a reviewer may want to revisit:

  • The section name release, and trimmed and gaps in it: they are the
    export's own counts, not a row's, though gaps is about the source rather than the clock.
  • margin over one second (a report's interval) rather than since the last sample, so two readers do
    not interfere.
  • Integer milliseconds for margin and offset.
  • Moving Class::Data last, which #[serde(other)] requires.

Impact

  • Breaking for readers of ts::stats::Export: dropped, trimmed, gaps, drift, and
    out_of_tolerance move under release.
  • Reports gain sections only for TS. The .z deltas stay small, since counters only climb.

Results

just fix and just check pass: 3,243 and 436 tests. On macOS, play's link step trips
build.warnings on the __eh_frame linker note; with warnings allowed it passes 206 of 206, that note
being its only warning.

Test What it shows Result
a_report_flattens_beside_the_media_counters the wire shape (camelCase, PID keys, quiet in ms, zeros omitted) and a full read-back through hang::stats::Snapshot<Report>; an unknown class reads as data new, pass; fails on the read-back without DisplayFromStr
the_margin_is_the_least_spare_over_a_second a frame 20 ms late gives a −20 ms margin once the second closes, held until the next new, pass
the_clock_follows_a_drifting_source_for_a_day now also: offset within the run's existing bound at ±25 and ±30 ppm, and margin equals the delay plus offset to 1 ms pass
a_slow_source_clock_keeps_the_delay (export) now also: a positive margin and a measured offset pass
import_stats_count_what_each_rendition_wrote (CLI) now also: mpegts present, every TR 101 290 counter zero on the clean clip, every PID's units advancing and ending equal to an in-process import's, and no release at import pass
import_stats_report_a_silent_video_pid (CLI) the fault arm offered on #5643: the video PES suppressed 2.5 to 4.5 s with its PCR and continuity legal. Video quiet peaks (the import logged 1.24 s) while audio stays under 500 ms; video counts again after; no priority 1 check fires new, pass
export_echo_reports_what_the_export_reads (CLI) now also: the export's rows advance and stay clean, and release carries a margin (2,737 ms on a 4 s delay, nothing dropped) pass

(Written by Claude Opus 5.5)

t0ms and others added 2 commits October 17, 2026 04:28
`moq import --stats` names a stats track in the catalog the import builds and
fills it with what each rendition has written: frames, payload bytes, and video
keyframes, with the connection's transport. `moq export --echo <name>`
publishes the feedback a catalog solicits, counting what the export reads and
its newest arrival. `moq export stats` and `moq export echo` print either as
JSON lines.

moq-mux counts at the container producer and the source, so every container
import and export shares the counters. Capture encoders, gateways, `archive`,
and `ts --program all` refuse `--stats`; MSF catalogs refuse `--echo`.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A TS import's stats and a TS export's feedback now carry an `mpegts` section:
each elementary stream's access units and quiet time, and the TR 101 290
counters. An export adds `release`, how its output clock keeps up with the
source: frames dropped at the deadline, the least margin frames arrived with
over the last second, the clock's offset and drift from the source, and
measurements past the 30 ppm a system clock may run off.

`ts::stats` gains serde and `Report`, the extension both snapshots flatten.
`stats::Export` moves its clock fields under `release`, and `ts::Release`
reports the same for a passthrough export.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
@t0ms
t0ms force-pushed the quest/m2/ts-health-stats branch from 70aebba to a0b877c Compare October 17, 2026 04:00
@kixelated
kixelated deleted the branch moq-dev:quest/m2/stats/schema October 17, 2026 04:00
@kixelated kixelated closed this Oct 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants