feat: the status bar and the exit dump report resequencing, holes and stale packets

This commit is contained in:
Martino Ferrari committed 2026-10-08 18:58:06 +02:00
1 parent d38ab3f9c9
commit fc81e661d0
5 files changed
+59 -2

No files matched your search

+3
View File
@@ -91,6 +91,9 @@ void Acquisition::run() {
out.packets.store(s.packets_received, std::memory_order_relaxed);
out.frames.store(s.frames_delivered, std::memory_order_relaxed);
out.gaps.store(s.counter_gaps, std::memory_order_relaxed);
out.resequenced.store(s.late_resequenced, std::memory_order_relaxed);
out.late.store(s.late_delivered, std::memory_order_relaxed);
out.stale.store(s.stale_packets, std::memory_order_relaxed);
out.reconnects.store(s.reconnects, std::memory_order_relaxed);
out.connected.store(udps_client_is_connected(client_) != 0, std::memory_order_relaxed);
}
+15
View File
@@ -195,6 +195,21 @@ struct StreamStats {
std::atomic<bool> have_config{false};
std::atomic<std::uint64_t> holes{0}; ///< reserved invalidated spans
std::atomic<std::uint64_t> hole_slots{0}; ///< stored slots those spans cover
/// Out-of-order packets put back in order inside the reorder window: no hole was ever
/// seen for these.
std::atomic<std::uint64_t> resequenced{0};
/// Out-of-order packets delivered behind the sequence, past the window.
std::atomic<std::uint64_t> late{0};
/// Packets the client still refused outright (a duplicate, or any late packet while
/// resequencing is disabled). Counted since the beginning and displayed nowhere until
/// now, which is why an inversion had to be inferred from `packets` and `frames`
/// diverging.
std::atomic<std::uint64_t> stale{0};
/// Stored slots a late packet repaired. Declared here rather than with the backfill that
/// writes it, because the display segment below is built once and reads it from the start:
/// a reading of 0 beside a non-zero `holes` is exactly the honest answer until the
/// backfill lands.
std::atomic<std::uint64_t> backfilled{0};
};
/// What the plot is showing, which is one decision made once per frame: the bind, the
+15
View File
@@ -79,6 +79,21 @@ std::string format_detail(const StatusSnapshot& s) {
s.plot1_traces, s.plot1_samples, s.buffer_mb, s.span_s,
static_cast<unsigned long long>(s.time_stride));
std::string out = buf;
// Nothing about resequencing is said at all while all six read zero, which is a stream that
// never lost ordering: the field would otherwise be six zeros on every clean run, and this
// string is read by eye out of the exit dump.
if (s.resequenced || s.late || s.stale || s.holes || s.hole_slots || s.backfilled) {
char note[160];
std::snprintf(note, sizeof(note),
" | reseq %llu late %llu stale %llu holes %llu (%llu slots, %llu filled)",
static_cast<unsigned long long>(s.resequenced),
static_cast<unsigned long long>(s.late),
static_cast<unsigned long long>(s.stale),
static_cast<unsigned long long>(s.holes),
static_cast<unsigned long long>(s.hole_slots),
static_cast<unsigned long long>(s.backfilled));
out += note;
}
if (s.paused) out += " | paused for " + format_elapsed(s.paused_for_s);
if (s.trigger_on) {
out += s.have_event ? " | last event " + format_elapsed(s.since_event_s) + " ago"
+10
View File
@@ -56,6 +56,16 @@ struct StatusSnapshot {
/// the plot.
bool paused = false;
double paused_for_s = 0.0;
/// The resequencing counters, gathered with `packets`/`frames`/`gaps` so the detail tier and
/// the dump quote one consistent set. All six zero means the stream never lost ordering, and
/// the detail tier then says nothing about it at all.
std::uint64_t resequenced = 0;
std::uint64_t late = 0;
std::uint64_t stale = 0;
std::uint64_t holes = 0;
std::uint64_t hole_slots = 0;
std::uint64_t backfilled = 0;
};
/// The bar: "live · 10 kS/s · 2 s window", plus " · event 4.2 s ago" in trigger mode, " · for
+16 -2
View File
@@ -6898,6 +6898,12 @@ gboolean tick(gpointer user) {
snap.packets = st.packets.load();
snap.frames = st.frames.load();
snap.gaps = st.gaps.load();
snap.resequenced = st.resequenced.load();
snap.late = st.late.load();
snap.stale = st.stale.load();
snap.holes = st.holes.load();
snap.hole_slots = st.hole_slots.load();
snap.backfilled = st.backfilled.load();
snap.signals = signal_count;
snap.channels = channel_count;
snap.plot1_traces = bound;
@@ -10718,12 +10724,20 @@ int run_ui(int argc, char** argv, const Config& cfg, ScopeModel& model, Acquisit
if (exit_after_ms > 0.0) {
const Partition& part = model.partition();
std::printf("[udpscope] frames=%llu packets=%llu gaps=%llu channels=%zu span=%.3fs "
"stride=%llu\n",
"stride=%llu"
" reseq=%llu late=%llu stale=%llu holes=%llu hole_slots=%llu backfilled=%llu"
"\n",
static_cast<unsigned long long>(model.stats().frames.load()),
static_cast<unsigned long long>(model.stats().packets.load()),
static_cast<unsigned long long>(model.stats().gaps.load()),
model.channel_count(), part.span_s,
static_cast<unsigned long long>(part.time_stride));
static_cast<unsigned long long>(part.time_stride),
static_cast<unsigned long long>(model.stats().resequenced.load()),
static_cast<unsigned long long>(model.stats().late.load()),
static_cast<unsigned long long>(model.stats().stale.load()),
static_cast<unsigned long long>(model.stats().holes.load()),
static_cast<unsigned long long>(model.stats().hole_slots.load()),
static_cast<unsigned long long>(model.stats().backfilled.load()));
for (std::size_t i = 0; i < model.channel_count(); ++i) {
std::printf("[udpscope] channel %zu '%s' stored=%llu published=%llu\n", i,
model.channels()[i].name.c_str(),