test: a probe reads the resequencing path's state, not only its counters
This commit is contained in:
1 parent
ee55ac84b7
commit
4666b807fc
1 file changed
+71
+71
@@ -10496,6 +10496,74 @@ gboolean color_probe(gpointer user) {
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
/// `UDPSCOPE_PROBE_RESEQ_MS`: one reading of the resequencing path, mid-run.
|
||||
///
|
||||
/// The counters are in the exit dump too; what is only here is `nan_slots`, `trigger_in_hole` and
|
||||
/// `bands`. The first is the state of the *data* rather than of a counter: a reservation writes
|
||||
/// placeholders and a backfill repairs them, and the two counters agreeing proves only that the
|
||||
/// same number of slots was written and read, not that the right ones were. The second asserts the
|
||||
/// trigger never latched inside a reserved span. The third asserts the band reached the plot.
|
||||
gboolean reseq_probe(gpointer user) {
|
||||
App* a = static_cast<App*>(user);
|
||||
if (a->window == nullptr) return G_SOURCE_REMOVE;
|
||||
ScopeModel& m = *a->model;
|
||||
std::uint64_t nan_slots = 0;
|
||||
std::uint64_t scanned = 0;
|
||||
std::size_t open_holes = 0;
|
||||
int trigger_in_hole = 0;
|
||||
{
|
||||
// The hole table and the live store are the producer's, so both are read under the one mutex
|
||||
// -- the same lock the frame's bind takes (`src/ui.cpp:6263`).
|
||||
std::lock_guard<std::mutex> lock(m.mutex());
|
||||
// The first non-math channel's retained window. One channel and not all of them: every
|
||||
// non-math channel is reserved and repaired by the same loop over the same ticks, so a second
|
||||
// channel would restate the first's answer at the cost of another pass over the ring.
|
||||
for (std::size_t i = 0; i < m.channel_count(); ++i) {
|
||||
if (m.channels()[i].math) continue;
|
||||
const plot::data::RingBuffer<f32>& v = m.live().values(i);
|
||||
// `operator[]` is window-relative: index 0 is the slot at `base()`.
|
||||
scanned = v.produced() - v.base();
|
||||
for (u64 k = 0; k < scanned; ++k) {
|
||||
if (!std::isfinite(v[k])) ++nan_slots;
|
||||
}
|
||||
break;
|
||||
}
|
||||
open_holes = m.holes().size();
|
||||
// A reserved span's samples are never fed to the trigger engine, so a latch stamped inside one
|
||||
// would mean they had been. `Hole::x_begin`/`x_end` and `trigger_time()` are both in stream
|
||||
// seconds, so they compare directly with no epoch in between.
|
||||
if (m.trigger_time_valid().load()) {
|
||||
const f64 t = m.trigger_time().load();
|
||||
for (const Hole& h : m.holes()) {
|
||||
if (t >= h.x_begin && t <= h.x_end) {
|
||||
trigger_in_hole = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// The shading as the plot actually holds it, read off row 0 after the frame published it. Every
|
||||
// row carries the same spans, so one row is the whole answer.
|
||||
std::size_t bands = 0;
|
||||
if (a->rows->row_count() > 0 && a->rows->row(0).plot != nullptr) {
|
||||
bands = a->rows->row(0).plot->invalid_spans().size();
|
||||
}
|
||||
std::fprintf(stderr,
|
||||
"[reseq-probe] reseq=%llu late=%llu stale=%llu holes=%llu hole_slots=%llu "
|
||||
"backfilled=%llu open_holes=%zu bands=%zu nan_slots=%llu scanned=%llu "
|
||||
"trigger_in_hole=%d\n",
|
||||
static_cast<unsigned long long>(m.stats().resequenced.load()),
|
||||
static_cast<unsigned long long>(m.stats().late.load()),
|
||||
static_cast<unsigned long long>(m.stats().stale.load()),
|
||||
static_cast<unsigned long long>(m.stats().holes.load()),
|
||||
static_cast<unsigned long long>(m.stats().hole_slots.load()),
|
||||
static_cast<unsigned long long>(m.stats().backfilled.load()), open_holes, bands,
|
||||
static_cast<unsigned long long>(nan_slots),
|
||||
static_cast<unsigned long long>(scanned), trigger_in_hole);
|
||||
std::fflush(stderr);
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
/// Fires a representative accel from each group and reports the state it changed, then
|
||||
/// checks the GtkEditable refusal. It goes through run_app_action() for the actions and
|
||||
/// through the real handler for the refusal, because the refusal lives in the handler.
|
||||
@@ -10717,6 +10785,9 @@ int run_ui(int argc, char** argv, const Config& cfg, ScopeModel& model, Acquisit
|
||||
if (const char* env = g_getenv("UDPSCOPE_PROBE_COLOR_MS")) {
|
||||
g_timeout_add(static_cast<guint>(std::atoi(env)), color_probe, &app);
|
||||
}
|
||||
if (const char* env = g_getenv("UDPSCOPE_PROBE_RESEQ_MS")) {
|
||||
g_timeout_add(static_cast<guint>(std::atoi(env)), reseq_probe, &app);
|
||||
}
|
||||
|
||||
// Our own options were consumed by parse_args(); GTK would reject them, so only
|
||||
// the program name is handed to g_application_run().
|
||||
|
||||
Reference in new issue
Block a user