feat: a repaired hole keeps a fainter band, so a report is not a false accusation
This commit is contained in:
1 parent
4666b807fc
commit
3859d2ca79
5 files changed
+48
-11
No files matched your search
+8
-2
@@ -875,8 +875,8 @@ void Acquisition::backfill_from(const udps_frame_t* frame, const Partition& part
|
|||||||
// and resequencing are one design. A stamp could not answer it -- the packet's own stamps
|
// and resequencing are one design. A stamp could not answer it -- the packet's own stamps
|
||||||
// are a claim about a timeline the ring has moved past -- but the counter indexes the
|
// are a claim about a timeline the ring has moved past -- but the counter indexes the
|
||||||
// reservation table directly.
|
// reservation table directly.
|
||||||
const Hole* hole = nullptr;
|
Hole* hole = nullptr;
|
||||||
for (const Hole& h : model_.holes()) {
|
for (Hole& h : model_.holes()) {
|
||||||
if (frame->counter >= h.counter_first && frame->counter < h.counter_first + h.packets) {
|
if (frame->counter >= h.counter_first && frame->counter < h.counter_first + h.packets) {
|
||||||
hole = &h;
|
hole = &h;
|
||||||
break;
|
break;
|
||||||
@@ -928,6 +928,12 @@ void Acquisition::backfill_from(const udps_frame_t* frame, const Partition& part
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// This packet's share of the hole has been delivered, whatever the ring still retains of it.
|
||||||
|
// Marked on *arrival* and not on `filled`: a decimation coarse enough that no whole slot lies
|
||||||
|
// inside one packet repairs no slot at all, and keying off `filled` would leave such a hole
|
||||||
|
// permanently unrepairable -- a solid band over data that is all there, which is the exact
|
||||||
|
// defect this task closes.
|
||||||
|
++hole->filled_packets;
|
||||||
model_.stats().backfilled.fetch_add(filled, std::memory_order_relaxed);
|
model_.stats().backfilled.fetch_add(filled, std::memory_order_relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -183,6 +183,22 @@ struct Hole {
|
|||||||
u64 tick_begin = 0; ///< absolute base tick of the hole's first raw sample
|
u64 tick_begin = 0; ///< absolute base tick of the hole's first raw sample
|
||||||
std::uint32_t counter_first = 0; ///< counter of the first missing packet
|
std::uint32_t counter_first = 0; ///< counter of the first missing packet
|
||||||
std::uint32_t packets = 0; ///< packets the hole is worth
|
std::uint32_t packets = 0; ///< packets the hole is worth
|
||||||
|
/// How many of those packets have since arrived late and been written in. The band's two tints
|
||||||
|
/// are this field against `packets`: a hole still missing data is shaded solidly and a fully
|
||||||
|
/// repaired one faintly, so a reader can tell "there is no data here" from "there was none when
|
||||||
|
/// this window was first drawn".
|
||||||
|
///
|
||||||
|
/// Counted in *packets* and not in slots, because a packet is the unit the reservation was made
|
||||||
|
/// in: the hole is worth exactly `packets` of them, each late arrival delivers exactly one, and
|
||||||
|
/// the client refuses a duplicate -- so this count is exact. A slot count would not be: the
|
||||||
|
/// channels have different strides, so "how many slots did this hole want" has one answer per
|
||||||
|
/// channel and the repaired test would have to pick one.
|
||||||
|
std::uint32_t filled_packets = 0;
|
||||||
|
|
||||||
|
/// Every packet this hole was reserved for has arrived. A hole worth no packets is never
|
||||||
|
/// repaired -- there is nothing for a late packet to deliver -- which keeps a degenerate
|
||||||
|
/// reservation shaded rather than silently downgraded.
|
||||||
|
bool repaired() const noexcept { return packets > 0 && filled_packets >= packets; }
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Live connection and counter state, readable from the UI thread.
|
/// Live connection and counter state, readable from the UI thread.
|
||||||
@@ -342,6 +358,10 @@ class ScopeModel {
|
|||||||
static constexpr std::size_t kMaxHoles = 64;
|
static constexpr std::size_t kMaxHoles = 64;
|
||||||
void note_hole(const Hole& h);
|
void note_hole(const Hole& h);
|
||||||
const std::vector<Hole>& holes() const noexcept { return holes_; }
|
const std::vector<Hole>& holes() const noexcept { return holes_; }
|
||||||
|
/// The producer's view of the same table. `Acquisition::backfill_from()` marks a hole's packet
|
||||||
|
/// filled as it repairs it, and that mark is what the band's fainter tint reads. Caller holds
|
||||||
|
/// `mutex()` -- the same requirement `note_hole()` above states, and the producer already does.
|
||||||
|
std::vector<Hole>& holes() noexcept { return holes_; }
|
||||||
/// Drops holes whose time-ring slots the ring has already discarded.
|
/// Drops holes whose time-ring slots the ring has already discarded.
|
||||||
void forget_holes_before(u64 slot);
|
void forget_holes_before(u64 slot);
|
||||||
|
|
||||||
|
|||||||
+3
-1
@@ -377,7 +377,9 @@ void RowSet::set_invalid_spans(ScopeModel& model, DisplayMode mode) {
|
|||||||
// `Hole::x_begin`/`x_end` are stream seconds; the plots' x is `stream_t + epoch`.
|
// `Hole::x_begin`/`x_end` are stream seconds; the plots' x is `stream_t + epoch`.
|
||||||
const f64 epoch = model.display_epoch();
|
const f64 epoch = model.display_epoch();
|
||||||
for (const Hole& h : model.holes()) {
|
for (const Hole& h : model.holes()) {
|
||||||
spans_.emplace_back(h.x_begin + epoch, h.x_end + epoch);
|
// `repaired()` and not a second list: the band keeps reporting a repaired hole, in the
|
||||||
|
// fainter tint, until its slots age out of the ring and `forget_holes_before()` drops it.
|
||||||
|
spans_.push_back(plot::InvalidSpan{h.x_begin + epoch, h.x_end + epoch, h.repaired()});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (int i = 0; i < rows_; ++i) {
|
for (int i = 0; i < rows_; ++i) {
|
||||||
|
|||||||
+1
-2
@@ -12,7 +12,6 @@
|
|||||||
#include <map>
|
#include <map>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <utility>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace udpscope {
|
namespace udpscope {
|
||||||
@@ -502,7 +501,7 @@ class RowSet {
|
|||||||
/// coloured without anything having to seed them.
|
/// coloured without anything having to seed them.
|
||||||
std::map<std::string, plot::u32> signal_colors_;
|
std::map<std::string, plot::u32> signal_colors_;
|
||||||
/// Reused across frames by `set_invalid_spans()`: see there.
|
/// Reused across frames by `set_invalid_spans()`: see there.
|
||||||
std::vector<std::pair<f64, f64>> spans_;
|
std::vector<plot::InvalidSpan> spans_;
|
||||||
CursorMode cursor_mode_ = CursorMode::Vertical;
|
CursorMode cursor_mode_ = CursorMode::Vertical;
|
||||||
bool cursors_on_ = false;
|
bool cursors_on_ = false;
|
||||||
bool relative_x_ = false;
|
bool relative_x_ = false;
|
||||||
|
|||||||
+16
-6
@@ -10502,7 +10502,10 @@ gboolean color_probe(gpointer user) {
|
|||||||
/// `bands`. The first is the state of the *data* rather than of a counter: a reservation writes
|
/// `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
|
/// 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
|
/// 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.
|
/// trigger never latched inside a reserved span. The third asserts the band reached the plot, and
|
||||||
|
/// splits it by state: a hole whose late packets have all arrived keeps its band in the fainter
|
||||||
|
/// tint, so `bands_repaired` is how a reading tells a window that is *missing* data from one that
|
||||||
|
/// merely *was*.
|
||||||
gboolean reseq_probe(gpointer user) {
|
gboolean reseq_probe(gpointer user) {
|
||||||
App* a = static_cast<App*>(user);
|
App* a = static_cast<App*>(user);
|
||||||
if (a->window == nullptr) return G_SOURCE_REMOVE;
|
if (a->window == nullptr) return G_SOURCE_REMOVE;
|
||||||
@@ -10543,22 +10546,29 @@ gboolean reseq_probe(gpointer user) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// The shading as the plot actually holds it, read off row 0 after the frame published it. Every
|
// 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.
|
// row carries the same spans, so one row is the whole answer. Split by state because the two
|
||||||
|
// mean different things: `bands` is what the window is reporting and `bands_repaired` how much
|
||||||
|
// of that report is historical, so `bands == bands_repaired` with `nan_slots=0` is a healthy
|
||||||
|
// reading and not the contradiction it would be if the band had one state.
|
||||||
std::size_t bands = 0;
|
std::size_t bands = 0;
|
||||||
|
std::size_t bands_repaired = 0;
|
||||||
if (a->rows->row_count() > 0 && a->rows->row(0).plot != nullptr) {
|
if (a->rows->row_count() > 0 && a->rows->row(0).plot != nullptr) {
|
||||||
bands = a->rows->row(0).plot->invalid_spans().size();
|
for (const plot::InvalidSpan& span : a->rows->row(0).plot->invalid_spans()) {
|
||||||
|
++bands;
|
||||||
|
if (span.repaired) ++bands_repaired;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
std::fprintf(stderr,
|
std::fprintf(stderr,
|
||||||
"[reseq-probe] reseq=%llu late=%llu stale=%llu holes=%llu hole_slots=%llu "
|
"[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 "
|
"backfilled=%llu open_holes=%zu bands=%zu bands_repaired=%zu nan_slots=%llu "
|
||||||
"trigger_in_hole=%d\n",
|
"scanned=%llu trigger_in_hole=%d\n",
|
||||||
static_cast<unsigned long long>(m.stats().resequenced.load()),
|
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().late.load()),
|
||||||
static_cast<unsigned long long>(m.stats().stale.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().holes.load()),
|
||||||
static_cast<unsigned long long>(m.stats().hole_slots.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>(m.stats().backfilled.load()), open_holes, bands,
|
||||||
static_cast<unsigned long long>(nan_slots),
|
bands_repaired, static_cast<unsigned long long>(nan_slots),
|
||||||
static_cast<unsigned long long>(scanned), trigger_in_hole);
|
static_cast<unsigned long long>(scanned), trigger_in_hole);
|
||||||
std::fflush(stderr);
|
std::fflush(stderr);
|
||||||
return G_SOURCE_REMOVE;
|
return G_SOURCE_REMOVE;
|
||||||
|
|||||||
Reference in new issue
Block a user