fix: a recompute that renumbered its ring, and readouts frozen under a zoom
Three defects, all of them a window describing data that is not there. A math edit refilled the derived ring with `clear()`, which renumbers it from 0, leaving `produced()` short by the inputs' retained base. Since `live_window_locked()` clamps against every channel's `produced()`, that one lagging channel answered `n = 0` for the live view, every pause view and every trigger capture until a whole ring of fresh data had gone by -- the reported freeze, invisible until the ring first wraps, hence its intermittence. It now restarts with `clear_to(base)`, and `ChannelBuffer::republish_from(base)` puts the published window back: `acquire()` is incremental, so a rewrite in place published nothing and the pyramid went on describing the samples the edit replaced. The trigger's channel is an index, re-resolved by name after a renumbering -- but only in `rebuild_enabled_locked()`, so a math add or delete went through `set_math_channels()` with no restore, the index was clamped to -1, and `process()` returned at once while the panel still named the channel. The restore moves into `rebuild_channels()`, which *is* the renumbering, so no caller can forget it. And `update_measures()` suppressed its "the window moved" test while an event was held -- the one mode where a gesture is the only thing that can move it. Zooming a latched event left the pane describing the whole captured event. A pinned window needs no exemption: it lands on the range it already measured and fails the 2 % test by itself. The checks that missed all three: `UDPSCOPE_PROBE_MATHWRAP_MS` edits a graph whose ring has already wrapped, which `math_probe` structurally cannot reach; `[measure-trace]` now prints the mode, the window on screen and the window the numbers describe; and the dump's `marks_in_window` counts against the window on screen instead of against the one the marks were computed over, where it was a tautology. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
074cb611bc
commit
d91957a2fe
15 files changed
+374
-45
No files matched your search
+35
-12
@@ -96,11 +96,20 @@ precedent, not an exception.
|
||||
overshoot, kept by the extremes and excluded by the levels. The per-type values, the reason for
|
||||
each, and the cost of the histogram are in
|
||||
[invariants-measurements.md](invariants-measurements.md).
|
||||
`[measure-trace] view=[..] <type>=<value>... marks=N level:V/time:V...` is a repeating line
|
||||
(250 ms) from the probe: the pane's numbers beside the *current* window and the marks actually
|
||||
placed, which is the only way a stale result or a mark that should not be drawn shows up as a
|
||||
number. It must never print `marks=0` while measurements with `show_on_plot` are on and the
|
||||
data is there -- that was the rulers blinking off at a mode change.
|
||||
`[measure-trace] mode=M view=[..] s0=S swin=[..] for=[..] passes=N <type>=<value>... marks=N
|
||||
level:V/time:V...` is a repeating line (250 ms) from the probe: the pane's numbers beside the
|
||||
*current* window and the marks actually placed, which is the only way a stale result or a mark
|
||||
that should not be drawn shows up as a number. `swin` is `view` moved onto the stream's clock by
|
||||
`s0` (`stream_at_x0()`), and `for` is the window the printed numbers were computed over, in the
|
||||
same clock -- so a reading that moved while `view` stood still is a change of `s0` (a new
|
||||
snapshot under the same mode) and not a stale pass, and only these three fields tell the two
|
||||
apart. `passes` beside them says whether a recompute answered the move. **While `mode=held`,
|
||||
`for` must equal `swin` exactly**, through any number of zooms and pans: a latched event does
|
||||
not scroll, so there is nothing for a recompute to be late for. That reading is where
|
||||
the measurements standing still under a gesture was found -- `for=[0.47..2.47]` against a
|
||||
`swin=[1.417..1.467]` with `passes` frozen, the pane describing the whole 2 s event while a 50 ms
|
||||
slice of it was on screen. The line must also never print `marks=0` while measurements with
|
||||
`show_on_plot` are on and the data is there -- that was the rulers blinking off at a mode change.
|
||||
`measure cost: N recomputes, X ms total, Y ms each, data D ms (P%), covered=C buckets=B
|
||||
raw=R (F%), N ns/covered sample` follows it: **read it against a run with
|
||||
`UDPSCOPE_PROBE_MEASURE_MS` unset**, because a frame mean cannot attribute a cost. The last
|
||||
@@ -283,9 +292,14 @@ as distinct from the fault injection below, which exercises ingest:
|
||||
conditions for the first 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`. Delete `~/.config/udpscope/127.0.0.1_8000.toml` first: a
|
||||
session left by an earlier run restores its math channel, and five channels instead of four
|
||||
change `channels=`, `span=` and every count derived from them.
|
||||
UDPSCOPE_PROBE_RESEQ_MS=5000`. Move `~/.config/udpscope/127.0.0.1_8000/` aside first (the
|
||||
per-endpoint session is a *directory* now -- `active` plus one `.toml` per named configuration;
|
||||
a bare `127.0.0.1_8000.toml` is a pre-migration leftover): a session left by an earlier run
|
||||
restores its math channel, and five channels instead of four change `channels=`, `span=` and
|
||||
every count derived from them. It restores the **window and the measurements** too, which is
|
||||
the subtler trap: a run after a zoom-sweep probe starts at `window_s = 0.05` with the previous
|
||||
runs' measurements still in the pane, so `measures window:` then reports a short-window
|
||||
sawtooth (144 %, marks off screen) on a run that asked for none of it.
|
||||
|
||||
| case | mock extra flags | key reading |
|
||||
| --- | --- | --- |
|
||||
@@ -328,6 +342,14 @@ as distinct from the fault injection below, which exercises ingest:
|
||||
and **`finite=0` beside a non-zero `hole_slots` is the defect** (a biquad that integrated the
|
||||
hole's NaN, permanently). The filters' `sd` must stay near the no-drop value; run the same
|
||||
command without `--drop-every` as the control, where every `finite` reads 4000.
|
||||
- **A math edit on a ring that has already wrapped.** `UDPSCOPE_PROBE_MATHWRAP_MS` — installs one
|
||||
`Gain` graph over the mock's sine, waits for the ring to **saturate**, and only then edits the gain,
|
||||
which is the one state `UDPSCOPE_PROBE_MATH_MS` cannot reach (each of its edits follows a structural
|
||||
change, and a replan restarts every ring at absolute 0). It therefore needs a **small buffer and a long
|
||||
run**: `--buffer-mb 1` against `--rate 20000 --batch 200`, with
|
||||
`UDPSCOPE_PROBE_MATHWRAP_MS=1500 UDPSCOPE_EXIT_AFTER_MS=25000`. `base > 0` on its second line is the
|
||||
precondition — **`base=0` means the run tested nothing** — and the third line's `produced == slots` and
|
||||
`y=±0.5` are the two assertions; both are in [probe-readings.md](probe-readings.md).
|
||||
- `UDPSCOPE_PROBE_CONFIGS_MS` — saves the running session as a configuration named `probe` with a
|
||||
note, through `save_configuration_as()` (the same entry point the dialog's button uses), then
|
||||
reads it back off disk. Reading: `save-as=1`; `names=` gains `probe`; `active=probe`; and the
|
||||
@@ -615,7 +637,7 @@ Four readings, all off the once-a-second `[udpscope] trig engine:` line:
|
||||
Before the fix the same run read a fallback warning, `history` collapsing 4.03 → 0.95, `now_t`
|
||||
jumping 5.040 → 16.040, and two latches **1.2 s** apart. Note the window here comes from the saved
|
||||
session for that port; the figures above are the default 2 s window (50 % pre), so move
|
||||
`~/.config/udpscope/<host>_<port>.toml` aside to reproduce them.
|
||||
`~/.config/udpscope/<host>_<port>/` aside to reproduce them.
|
||||
|
||||
The other two are **packet loss** (`--drop-every N`, `--drop-burst K`) and **wire inversion**
|
||||
(`--reorder-every N`). Loss withholds a datagram the counter and the stream clock have already
|
||||
@@ -759,9 +781,10 @@ have run. `--spike-every`/`--spike-amp`/
|
||||
The three lines and what each must read are in `probe-readings.md`.
|
||||
|
||||
`UDPSCOPE_PROBE_NOISE_MS`, `UDPSCOPE_PROBE_MEASURE_MS`, `UDPSCOPE_PROBE_MEASURES_MS`,
|
||||
`UDPSCOPE_PROBE_BANDAVG_MS` and `UDPSCOPE_PROBE_MATH_MS` must **not** be scheduled
|
||||
in the same run. The noise, measurement, measurements and band-average probes all push onto
|
||||
`UDPSCOPE_PROBE_BANDAVG_MS`, `UDPSCOPE_PROBE_MATH_MS` and `UDPSCOPE_PROBE_MATHWRAP_MS` must **not** be
|
||||
scheduled in the same run. The noise, measurement, measurements and band-average probes all push onto
|
||||
`app.measures`, and the later one would read
|
||||
the earlier one's rows as its own; the noise and math probes both write `app.math`, so the later one's graph
|
||||
the earlier one's rows as its own; the noise, math and math-wrap probes all write `app.math`, so the later
|
||||
one's graph
|
||||
lands on top of the earlier one's and both sets of readings describe a state neither probe set up. The noise probe carries
|
||||
its own measurement legs (see `probe-readings.md`), so nothing is lost by keeping them apart.
|
||||
Reference in new issue
Block a user