fix: a claimed gap must be corroborated before it is believed, and its hole must fit
The user's real host reported "connected and live" with one missing-data band over the whole window
and no trace in it. The counter is the only thing that claims a packet existed, and a reservation
was booked on its word alone: a source whose DATA counter counts samples, or frames of an inner
layer, advances by more than 1 per datagram, the client cannot check that claim (it has no clock of
its own to compare against), so it holds each packet, saturates its reorder ring, delivers it in
order and reports the skipped counters as lost. Every frame therefore reported a phantom gap, and
`reserve_hole()` wrote `lost * batch_` NaN samples with no bound but the 256-PACKET cap -- the
sample form of that rule ("the number to weigh against the ring is 256 * batch_, not the 256") was
never implemented. With relative time on -- which that session carries -- each phantom gap is
materialised as NaN slots by construction: the window filled with reservations, there was never
anything missing to repair them, and the gaps also stretched the relative clock, which is why the
status bar read live at ten times the true rate.
`Acquisition::gap_plausible()` now gates every reservation on two independent tests: the arrival
clock must corroborate the claim (that many missing packets would take that many frame intervals;
deliberately permissive, because a false refusal costs a shading and never data), and the ring must
be able to hold the hole. The verdict is taken before the stamps, and relative mode's slot advance
is skipped with it, since the advance and the reservation are the same claim. Refusals are counted
in `StreamStats::gaps_refused` and said once per connection with the numbers, naming the wire shape
they describe.
tools/mock_udpstreamer.py gains `--counter-step N`: a counter that is not a packet counter on an
intact link, which is the one shape the mock could not produce and the shape the report came from.
Verified in both directions. A counter stepping 1000 per datagram reads `holes=0 hole_slots=0
backfilled=0` with the traces present (3 traces, 1.4 M samples) against 7.9 M claimed gaps, and the
warning above names the cause. Every other packet withheld -- a real loss -- still reads
`holes=3579`, so genuine gaps are still reserved: a fix that stopped believing real losses would
have traded one wrong picture for another. The user's control read as "the math channel is the
problem" and was confounded -- disabling it is a structural change, which rebuilds the buffer and
clears the accumulated NaN.
This commit is contained in:
1 parent
22e7a8efcc
commit
27c9b34277
9 files changed
+249
-15
No files matched your search
@@ -590,7 +590,7 @@ as distinct from the fault injection below, which exercises ingest:
|
||||
at 64. The `mixed` case's `open_holes=43` against `bands=43` has no such spread because
|
||||
nothing re-planned. 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
|
||||
The mock also carries four **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
|
||||
is what a device whose clock glitches puts on the wire. Run it with the trigger armed, e.g.
|
||||
@@ -625,6 +625,41 @@ arrives within the 20 ms reorder window is resequenced with no hole, and one tha
|
||||
leaves a committed hole that it then backfills (`UDPSCOPE_PROBE_RESEQ_MS` exercises both paths).
|
||||
There is **no interpolation** anywhere below the ring.
|
||||
|
||||
The fourth is the counter itself: **`--counter-step N`** advances the header counter by `N` per
|
||||
datagram instead of one -- a device whose counter counts *samples*, or frames of an inner layer,
|
||||
rather than packets. Nothing is withheld and every datagram goes out on schedule, so the link is
|
||||
intact and the status bar is right to say `live`; what is wrong is the counter's claim, which the
|
||||
client cannot check (it has no clock) and only the app can (it has the arrival times). It is the
|
||||
one wire shape the mock could not otherwise produce, and the run that reads it is the reseq probe
|
||||
with relative time on (the mode that frames a counter gap as NaN by construction):
|
||||
|
||||
```sh
|
||||
# mock: --counter-step 10
|
||||
xvfb-run -a env UDPSCOPE_EXIT_AFTER_MS=8000 UDPSCOPE_ENABLE_ALL_MS=600 \
|
||||
UDPSCOPE_PROBE_RESEQ_MS=5000 UDPSCOPE_EXIT_DUMP=1 \
|
||||
./build/udpscope --server 127.0.0.1 --port 8000 --relative-time
|
||||
```
|
||||
|
||||
```
|
||||
# fixed: [reseq-probe] … holes=1 hole_slots=900 backfilled=0 gaps_refused=419 open_holes=0
|
||||
# bands=0 bands_repaired=0 nan_slots=0 scanned=21700 trigger_in_hole=0
|
||||
# status bar: glance=[● live · 9.97 kS/s · 2 s window]
|
||||
# broken: [reseq-probe] … holes=420 hole_slots=378000 backfilled=0 open_holes=64 bands=64
|
||||
# bands_repaired=0 nan_slots=198000 scanned=220000 trigger_in_hole=0
|
||||
# status bar: glance=[● live · 101 kS/s · 2 s window]
|
||||
```
|
||||
|
||||
**`gaps_refused` above 0 with `nan_slots` at 0 is the assertion** — the gap was refused and the
|
||||
samples were kept. The broken reading is `nan_slots` at 90% of `scanned` with `backfilled=0`: a
|
||||
reserved hole per datagram, `relative_time` framing each one as NaN, and nothing ever missing to
|
||||
backfill it, i.e. one missing-data band over the whole window with no trace in it, under a status
|
||||
bar reading `live` at ten times the stream's real rate. The same field
|
||||
must be **0** on every honest shape above (`--drop-every`, `--drop-burst 200`,
|
||||
`--reorder-delay-ms 50`, at 10 kS/s: `holes=210 hole_slots=21000 nan_slots=11000 scanned=22000
|
||||
gaps_refused=0` for `--drop-every 2`, `holes=3 hole_slots=60000 nan_slots=20000` for the 200-packet
|
||||
burst at 100 kS/s) — a non-zero reading there means the arrival test is refusing real loss, whose
|
||||
only cost is a gap with no shading.
|
||||
|
||||
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
|
||||
burst needs the faster stream. **`gaps` is the exact reading** and repeats to the packet; `frames`
|
||||
|
||||
Reference in new issue
Block a user