working on todo.md features
This commit is contained in:
1 parent
210d124814
commit
8f54841c2c
110 files changed
+15932
-6576
No files matched your search
@@ -220,6 +220,159 @@ as distinct from the fault injection below, which exercises ingest:
|
||||
| **re-plan** | `--rate 100000` with the `mixed` flags, and `UDPSCOPE_PROBE_WINDOW_MS=4000 UDPSCOPE_PROBE_RESEQ_MS=6000 UDPSCOPE_EXIT_AFTER_MS=9000` | four `buffer rebuilt` lines before the reading, then `bands=64 bands_repaired=16 nan_slots=7800`: **a band is still only marked repaired when its own slots hold values**. A `bands_repaired == bands` beside a non-zero `nan_slots` here is the hole table and the producer's `tick_` surviving a re-plan that reset `produced()` — a band marked settled over samples that are all still NaN. The non-zero `overflows` is this case too: 100 kS/s with a 50 ms reorder delay keeps about 50 packets in flight against 64 slots |
|
||||
|
||||
The re-plan case needs `--rate 100000`, not the other four's 10 000: at 10 kS/s the ring
|
||||
|
||||
- `UDPSCOPE_PROBE_CAPTURE_MS` — toggles the packet capture **through the same handler the
|
||||
status-bar button uses**, waits, then reads the file back off disk and prints one line:
|
||||
`enabled= path= slots= stored= resident= cursor= torn= truncated= header_ok= first_seq=
|
||||
last_seq= payload= file= open_ms= t0=`. `stored` is the datagrams-since-open counter and must
|
||||
equal the dump's datagram count from the toggle's instant; `resident` is what the file holds,
|
||||
which is the slot count once the ring has wrapped; `file` is the reserved size
|
||||
`64 + slot_count * slot_bytes` and is a constant for a given N, so a mismatch against the file
|
||||
on disk is a reading. Two cases, **run each as its own process**:
|
||||
|
||||
1. **enable at runtime** — the mock at `--rate 20000 --batch 200`, udpscope with
|
||||
`UDPSCOPE_ENABLE_ALL_MS=700 UDPSCOPE_PROBE_CAPTURE_MS=1500 UDPSCOPE_EXIT_AFTER_MS=9000`
|
||||
and no capture flag: `enabled=1 header_ok=1 torn=0 truncated=0 first_seq=1`, `stored` equal
|
||||
to the datagrams since the toggle, `open_ms` a few ms.
|
||||
2. **wrap** — the same with `--capture-packets=16` and
|
||||
`UDPSCOPE_PROBE_CAPTURE_MS=7000 UDPSCOPE_EXIT_AFTER_MS=10000`: `stored` (870 in the
|
||||
reference run) exceeds `slot_count`, `resident=16`, `last_seq - first_seq == 15`,
|
||||
`torn=0`, and `cursor` agrees with `seq` (counter mod slot count). A wrap that loses
|
||||
ordering, or a `cursor` that disagrees, shows here and nowhere else.
|
||||
|
||||
Then the converter over the wrap case's file: `tools/capture_to_pcap.py <capture> out.pcap`
|
||||
must exit 0, print a record count equal to `slot_count` with `torn` 0, and the pcap's first
|
||||
and last record timestamps must fall inside the run's wall-clock interval — both checked by a
|
||||
`struct.unpack` of the pcap headers, nothing outside the standard library.
|
||||
|
||||
- **A gap must not kill a filtered trace.** `--relative-time` with the mock dropping
|
||||
(`--drop-every 20`) and `UDPSCOPE_PROBE_MATH_MS=2500 UDPSCOPE_ENABLE_ALL_MS=600
|
||||
UDPSCOPE_EXIT_AFTER_MS=9000`: every derived channel's dump line
|
||||
`math '<name>': finite=N/4000` must read about 3800 — the tail minus the reserved holes —
|
||||
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.
|
||||
- `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
|
||||
round trip must carry the note **and** the startup settings —
|
||||
`comment='written by the configs probe' buffer_mb=8 relative=0 capture=0`. A `buffer_mb=0` there
|
||||
is the per-source buffer size not being written, which is the one setting a user notices only
|
||||
after a capture has been sized wrong. Run it with a throwaway `XDG_CONFIG_HOME`; the probe
|
||||
deletes its own configuration at the end (`after forget names=…`).
|
||||
- `UDPSCOPE_PROBE_ROWMENU_MS` — gives row 0 a trace, opens the plot's context menu with
|
||||
`open_row_menu()` (the one entry point `on_right_click()` wraps) and walks its two views one step
|
||||
at a time (`main` → click **Remove signal** → `remove` → click **Back** → `after-back` → click
|
||||
**Remove signal** again → `second-open` → click a trace), measuring each view's rows:
|
||||
`[rowmenu] <view> N '<label>' h=18 label_x=26 accel_right=R/W ok=1` per row and one
|
||||
`rows=/bad=/labels=` line per view, then `[rowmenu] summary main_rows=5 main_bad=0 remove_rows=6
|
||||
remove_bad=0 after_back_rows=5 after_back_bad=0 second_rows=6 second_bad=0 remove_row_in_main=1
|
||||
remove_row_gone=1 can_open_again=1` and `[rowmenu] remove-clicked 'sine' trace_freed=1`.
|
||||
**Every `bad` must be 0** (the same compact row style as the app's own menu: one label column, a
|
||||
shortcut flush right, 18 px rows — the **Back** row's icon indents its label, which every menu
|
||||
does, so its `label_x` is 52 against the others' 26) and the three booleans must all read 1: the
|
||||
remove view *replaces* the main one (`remove_row_gone`, `remove_row_in_main`), and it can be
|
||||
entered again after leaving it (`can_open_again`) — that last one is the case a
|
||||
`GtkMenuButton`-owned submenu failed, its row going dead on the second click. Each view is
|
||||
measured a step after the click that swapped it in, because a popover allocates its new child on
|
||||
the next layout pass.
|
||||
- `UDPSCOPE_PROBE_DEFAULTS_MS` — opens **Calibrate** and reads every spin button in it and in the
|
||||
trigger panel: `[defaults] calibrate: 1 0 model=1 0 | trigger: 0.001 0.002 0 0.001 0 50 0.01`. A
|
||||
`GtkSpinButton` starts at its range's *minimum*, so a `-1e12` here is an unseeded button the
|
||||
dialog's own reader would then apply — which is exactly what the calibration offset did. It then
|
||||
calibrates signal 0 through `set_calibration()` and reopens the dialog:
|
||||
`[defaults] calibrated: 2.5 -1.25 model=2.5 -1.25` — **the dialog must show the signal's stored
|
||||
calibration**, and the two halves must match.
|
||||
- The **chooser**, display-free: `UDPSCOPE_PROBE_SOURCES=1` prints `[sources] N known:` with one
|
||||
entry per configuration (`host:port/name … buffer= relative= note='…'`), and
|
||||
`UDPSCOPE_PROBE_PICK_MS=800` opens the chooser, picks the first row and prints
|
||||
`[sources] picked host:port … buffer=64 relative=0 capture=0 config=… note='…'` — the settings the
|
||||
configuration carried, into the `Config` the model is built from. A legacy file
|
||||
(`<host>_<port>.toml`) produces `layout loaded from …/<host>_<port>/default.toml`, an `active`
|
||||
holding `default`, and the old file renamed `.imported`.
|
||||
- **The event cache round-trips, and only where it is available.** `UDPSCOPE_PROBE_EVENTS_MS` reads
|
||||
the pane's availability first, in the state the app starts in -- `[events] live: button_visible=0
|
||||
pane=0 recording=1`, **no pane and no button without trigger mode** -- then turns trigger mode on
|
||||
through the real toggle (`[events] after trigger on: button_visible=1 pane=0`: the button appears,
|
||||
the pane stays collapsed) and clicks the button, waits for a latch, then reads the cache and replays
|
||||
the newest event through the pane's own row:
|
||||
`[events] dir=… n=1 newest: dur=2.000000 pre=1.000000 post=1.000000 edge=… signals=4 samples=20001
|
||||
rate=10000 seq=1 thumb_bytes=230454 recorded=1 pane=1`, then
|
||||
`[events] activated row0: replaying=1 thumbnail=1 thumb_size=108x36 thumb_visible=1` (the size is
|
||||
what says the picture is *on screen*: a paint-able alone is not), `[events] replayed: channels=4
|
||||
samples=20001 replaying=1 waveform=1` and `[events] live again: replaying=0 waveform=0`. It ends on
|
||||
the pane's other precondition, through the trigger panel's switch: `[events] recording off, trigger
|
||||
off` and `[events] recording off, trigger on` must **both** read `button_visible=0 pane=0
|
||||
recording=0` -- the pane goes with its switch, in either trigger state. **`samples` must equal the
|
||||
export's row count for the same window** (`20001` for a 2 s event at 10 kS/s), `thumb_bytes` must be
|
||||
a real BMP (`54 + 480*160*3`-ish, i.e. hundreds of kB, not 0), `thumbnail` must be 1 (the row loaded
|
||||
the picture) and the last line must read `replaying=0` -- a replay with no way back is a one-way
|
||||
door. It then **waits for a second latch** (bounded: twelve retries) and drives the two group layouts
|
||||
through the buttons: `[events] compare buttons: cached=2 selected=2 compare=1 accumulate=1` (the
|
||||
buttons read the selection), `[events] halve`… `[events] halves a_first=20001 a_second=0 b_first=0
|
||||
b_second=20001` -- **the two halves of the ring, and the reading that says a comparison's gap is a
|
||||
gap: the first event's channel is finite only in the first half and the second's only in the second**
|
||||
-- `[events] compare: kind=1 rows=4 channels=8 samples0=40002 traces0=2 traces1=2 window=4.000
|
||||
recording=1 pane=1` (`samples0` must be exactly the stamps pushed, **not more**: a live producer left
|
||||
running pushes its own four signals into the group's eight channels), `[events] compare plots:
|
||||
area_b=1 rows_a=1 rows_b=1 x_a=[-2.000,0.000] x_b=[-2.000,0.000] same_x=1` (the second plot exists, is
|
||||
on screen, mirrors the row count, and both are on one x window), then the sync -- the probe sets a y
|
||||
range nobody would arrive at by accident on the *first* plot and reads the second a few frames later:
|
||||
`[events] compare sync: y_a=-3.500 y_b=-3.500 mirrored=1` -- and `[events] accumulate: kind=2 rows=1
|
||||
channels=8 samples0=33045 selected=2 traces0=8 window=2.000` (`traces0=8`: the four signals the layout
|
||||
holds plus one layer each for the second event, **in the same row**, which is what an accumulation
|
||||
means). Run it with a throwaway
|
||||
`XDG_CACHE_HOME` and `UDPSCOPE_EXIT_AFTER_MS` long enough for the second latch (20 s).
|
||||
- **A waveform round-trips.** Export from a live run (`UDPSCOPE_EXPORT_MS` with
|
||||
`UDPSCOPE_EXPORT_PATH`, against the mock at 10 kS/s), then open that file with `--open` and an
|
||||
exit dump. The load must print
|
||||
`[udpscope] waveform loaded: … signals=4 samples=20001 span=2.000000s rate=10000 time=[-2.000000..0.000000]`
|
||||
— **the same row count, column count, span and time range the export probe reported**
|
||||
(`rows=20001 cols=4 time=[-2.000000..0.000000]`) — and the dump must show `channels=4`,
|
||||
`frames=0 packets=0` (no acquisition) and, per channel,
|
||||
`20001 samples vs 20001 time slots … y=[-1.0000, 1.0000]`. A file that loads with fewer samples
|
||||
than it was written with, or with `NaN` slots, is the reader mis-slicing its records.
|
||||
- **A loaded file can be plotted.** `UDPSCOPE_PROBE_DROP_MS` drives the drop handler the way a drag
|
||||
from the signal panel does, on a file opened with `--open`: it must read
|
||||
`[drop-probe] signals=4 channels=4 rows=4 slots0=[sine|||]` — **`rows` equal to the column count**,
|
||||
one trace per row — and each drop must return `1` and add its trace (`dropped signal 1 -> 1
|
||||
slots=[sine|sine|square|]`). `rows=1 slots0=[sine|square|ramp|noise]` with every drop returning `0`
|
||||
is the defect this exists for: rows sized four signals to one row fill the only row, so the app
|
||||
reads as one that cannot plot anything. A file-mode run must also write **no session** for the
|
||||
default `127.0.0.1:8000` (`App::source_port` is 0), or it overwrites that source's saved layout.
|
||||
- **The chooser's Waveforms page**: `UDPSCOPE_PROBE_WAVEFORMS=1` prints
|
||||
`[sources] waveforms: N recent <path>(ok|missing) … page=1`, and `UDPSCOPE_PROBE_WAVE=<path>` sets
|
||||
the field the file dialog's callback sets and prints `[sources] picked waveform <path>` — then the
|
||||
load line above, with no socket involved.
|
||||
- **A `--server` launch reads the stored configuration.** With a throwaway `XDG_CONFIG_HOME`
|
||||
holding `127.0.0.1_8101/bench.toml` (`buffer_mb = 64`, `relative_time = true`,
|
||||
`capture_packets = 500`, `active` = `bench`), `./build/udpscope --server 127.0.0.1 --port 8101`
|
||||
must print `[sources] settings from configuration 'bench': buffer=64 relative=1 capture=500`, the
|
||||
`relative time:` line, and a `span=` of about 209 s (64 MB at 10 kS/s) — **not** the 200 MB
|
||||
default's ~655 s. The same command with `--buffer-mb 8` must read `buffer=8` and `span=26.214s`:
|
||||
**an explicit flag wins over the file**. Without this, the settings a host was edited to have were
|
||||
ignored whenever the address came from the command line.
|
||||
- The chooser's **form**, with `UDPSCOPE_PROBE_PICK_MS` scheduling it:
|
||||
`UDPSCOPE_PROBE_NEWHOST=host:port` fills it and prints
|
||||
`[sources] form: name='probe-host' note='…' buffer=32 relative=1 connect_sensitive=1`, then
|
||||
`[sources] configuration created: host:port/probe-host … buffer=32 relative=1` and a
|
||||
`picked … config=probe-host`; a following `UDPSCOPE_PROBE_SOURCES=1` must list it
|
||||
(`buffer=32 relative=1 note='…'`) — a configuration the form wrote that the chooser does not
|
||||
list cannot be edited next time. `UDPSCOPE_PROBE_EDITHOST=host:port` opens the same form on that
|
||||
configuration and prints `[sources] edit form: name='…' note='…' buffer=… relative=… button='Save'`
|
||||
— **the pre-fill is the assertion**: an edit form that opens empty cannot edit anything — then
|
||||
saves and reports the new values. `UDPSCOPE_PROBE_EDITNAME=new-name` renames it as well: one
|
||||
`known:` row afterwards, with the old file gone.
|
||||
- `UDPSCOPE_PROBE_RESEQ_MS`'s neighbour for **reconnect**: the mock gains an EMI case,
|
||||
`--blackout-at S --blackout D` — silent from second S for D seconds (D must exceed the
|
||||
client's 2 s silence timeout), resuming from *now* so the link's backlog is what the link
|
||||
ate. One run (`--blackout-at 4 --blackout 3.5`, `UDPSCOPE_EXIT_AFTER_MS=15000`) has to read:
|
||||
every reconnect's `CONFIG:` line followed by `CONFIG unchanged: keeping the buffer and its
|
||||
history` (a reconnect that re-plans wipes history, which is the defect this case exists for),
|
||||
no `buffer rebuilt` line after the first CONFIG, `frames` climbing again after the blackout
|
||||
with `gaps=0`, and a trigger armed *after* recovery (`UDPSCOPE_PROBE_KEYS_MS` past the
|
||||
blackout) latching — the dump's `CAPTURING`/`EVENT HELD` and `publish cost: N events` with
|
||||
N ≥ 1 are that proof.
|
||||
already covers 838 s, so no window the control can reach moves `time_stride`,
|
||||
`set_target_span()` returns without rebuilding, and the window probe reports `decim=1`
|
||||
throughout — it re-plans nothing. At 100 kS/s the ring covers 83.9 s and the probe's
|
||||
|
||||
Reference in new issue
Block a user