docs: the reseq probe's readings, and the invariants resequencing changes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Martino FerrariandClaude Sonnet 4.6 committed 2026-10-08 23:14:38 +02:00
1 parent 4549c622fc
commit eacb3f5efc
11 files changed
+258 -23

No files matched your search

+63 -11
View File
@@ -201,6 +201,59 @@ as distinct from the fault injection below, which exercises ingest:
- `--quiet-at S` freezes `sine` at its held value from stream second `S` onwards, so it stops
crossing zero: the dropout trigger's input.
- `UDPSCOPE_PROBE_RESEQ_MS` — one mid-run reading of the resequencing path. Four cases,
**run each as its own process** — four back to back starve the mock past its 2 s silence
timeout and every case then reads a reconnect instead of what it was testing. Common
conditions for all four: the mock at `--rate 10000 --batch 100 --freq 5`, udpscope with
no extra options, under `xvfb-run` with
`UDPSCOPE_EXIT_AFTER_MS=8000 UDPSCOPE_ENABLE_ALL_MS=600 UDPSCOPE_EXIT_DUMP=1
UDPSCOPE_PROBE_RESEQ_MS=5000`.
| case | mock extra flags | key reading |
| --- | --- | --- |
| **base** | *(none)* | every counter 0; proves the machinery costs nothing |
| **reorder** | `--reorder-every 10` | `reseq=51` (mid-run) / `102` (dump); `holes=0`; resequencing inside the window leaves no reservation |
| **late** | `--reorder-every 10 --reorder-delay-ms 50` | `holes=42 bands=42 bands_repaired=42 nan_slots=0`; every band is the faint, repaired tier |
| **mixed** | `--drop-every 20 --reorder-every 10 --reorder-delay-ms 50` | `bands=42 bands_repaired=20 nan_slots=2300`; all three band states at once |
Full measured readings (probe line first, then exit-dump; `bands_repaired` was added after the
original probe and any earlier reading without it is obsolete):
```
=== base mock: none
[reseq-probe] reseq=0 late=0 stale=0 holes=0 hole_slots=0 backfilled=0 open_holes=0 bands=0
bands_repaired=0 nan_slots=0 scanned=44000 trigger_in_hole=0
[udpscope] frames=1024 packets=1025 gaps=0 channels=4 span=838.861s stride=1 reseq=0 late=0
stale=0 holes=0 hole_slots=0 backfilled=0
[udpscope] channel 0 'sine' stored=95200 published=95000
=== reorder mock: --reorder-every 10
[reseq-probe] reseq=51 late=0 stale=0 holes=0 hole_slots=0 backfilled=0 open_holes=0 bands=0
bands_repaired=0 nan_slots=0 scanned=44000 trigger_in_hole=0
[udpscope] frames=1028 packets=1029 gaps=0 channels=4 span=838.861s stride=1 reseq=102 late=0
stale=0 holes=0 hole_slots=0 backfilled=0
[udpscope] channel 0 'sine' stored=95400 published=95100
=== late mock: --reorder-every 10 --reorder-delay-ms 50
[reseq-probe] reseq=142 late=48 stale=0 holes=42 hole_slots=4200 backfilled=16800 open_holes=42
bands=42 bands_repaired=42 nan_slots=0 scanned=44000 trigger_in_hole=0
[udpscope] frames=1026 packets=1027 gaps=100 channels=4 span=838.861s stride=1 reseq=288
late=100 stale=0 holes=94 hole_slots=9400 backfilled=37600
[udpscope] channel 0 'sine' stored=95700 published=95500
=== mixed mock: --drop-every 20 --reorder-every 10 --reorder-delay-ms 50
[reseq-probe] reseq=163 late=24 stale=0 holes=43 hole_slots=4300 backfilled=8000 open_holes=43
bands=42 bands_repaired=20 nan_slots=2300 scanned=44100 trigger_in_hole=0
[udpscope] frames=974 packets=975 gaps=101 channels=4 span=838.861s stride=1 reseq=314 late=50
stale=0 holes=94 hole_slots=9400 backfilled=18400
[udpscope] channel 0 'sine' stored=95100 published=94900
```
The `mixed` case's `open_holes=43` against `bands=42` is correct: the 43rd hole has scrolled
out of the visible window, and `build_invalid_spans()` drops a span outside the window rather
than pinning it to the edge. The per-field reference is in
[probe-readings.md](probe-readings.md).
The mock also carries three **fault injections** that no probe schedules, because what they exercise
is the ingest rather than the UI. The first: `--time-glitch-at <stream seconds> --time-glitch-by <seconds>` makes
**one** packet's time signal jump ahead and then carries on from where the true timeline was, which
@@ -229,12 +282,12 @@ session for that port; the figures above are the default 2 s window (50 % pre),
`~/.config/udpscope/<host>_<port>.toml` aside to reproduce them.
The other two are **packet loss** (`--drop-every N`, `--drop-burst K`) and **wire inversion**
(`--reorder-every N`). Both withhold a datagram the counter and the stream clock have already
advanced over, which is what makes them loss and reordering rather than a slower producer: the
client orders the stream by the header's counter alone (`udps_client.c:504`), counting a forward
jump as `counter_gaps` and rejecting anything that does not advance it as `stale_packets`. There is
**no resequencing and no interpolation** anywhere below the ring — an inverted packet's samples are
discarded and the hole they leave is a jump in x, which is the behaviour these options are for.
(`--reorder-every N`). Loss withholds a datagram the counter and the stream clock have already
advanced over; the client counts the forward jump as `counter_gaps`. An inversion withholds a
packet until the one behind it is sent, so the receiver sees a later counter first; a packet that
arrives within the 20 ms reorder window is resequenced with no hole, and one that arrives past it
leaves a committed hole that it then backfills (`UDPSCOPE_PROBE_RESEQ_MS` exercises both paths).
There is **no interpolation** anywhere below the ring.
All of the readings below are off `UDPSCOPE_EXIT_DUMP`'s `[udpscope] frames= packets= gaps=` line,
over an 8 s run against `--rate 10000 --batch 100 --freq 5` (100 packets a second) except where the
@@ -247,11 +300,10 @@ including the clean one, then read a reconnect instead of what it was testing:
- `--drop-every 10`: `frames=925 packets=926 gaps=102`. Every tenth packet never reaches the wire,
so `packets` falls with `frames` and `gaps` is the count of them. No warning: a one-packet hole is
a one-packet jump in the stamps, far inside the glitch guard.
- `--reorder-every 10`: `frames=925 packets=1028 gaps=102`. This is the signature to look for —
`packets` is the clean figure (every datagram arrived) while `frames` is the dropped one, because
the overtaken packet is decoded far enough to be read as stale and then thrown away. One inversion
costs one `gaps` (for the packet that overtook it) **and** one `stale_packets`, which udpscope does
not currently display; the ~104 missing frames are the only place it shows.
- `--reorder-every 10`: `frames=1028 packets=1029 gaps=0`. The inverted packet lands inside the
20 ms reorder window and is resequenced: every datagram arrives and `gaps` is 0. The pre-resequencing
signature was `frames=925 packets=1028 gaps=102`; that is now the reading for
`--reorder-every 10 --reorder-delay-ms 50`, which forces a late arrival past the window.
- `--drop-every 1000 --drop-burst 200` at `--rate 100000 --batch 100` (1000 packets a second):
`frames=7000 packets=7001 gaps=1600`, eight bursts of 200, and still no warning.
- `--drop-every 1000 --drop-burst 300`, same stream: `frames=6203 packets=6204 gaps=2400` **and the