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;
|
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
|
/// 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
|
/// 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.
|
/// 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")) {
|
if (const char* env = g_getenv("UDPSCOPE_PROBE_COLOR_MS")) {
|
||||||
g_timeout_add(static_cast<guint>(std::atoi(env)), color_probe, &app);
|
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
|
// Our own options were consumed by parse_args(); GTK would reject them, so only
|
||||||
// the program name is handed to g_application_run().
|
// the program name is handed to g_application_run().
|
||||||
|
|||||||
Reference in new issue
Block a user