Budget the sample buffer in samples per signal, under a hard memory limit
--buffer-samples (k/M/G) replaces --buffer-mb: the request is rounded down to a power of two for every signal and the shared time ring. A separate memory limit (--memory-limit-mb, a Preferences row, default 8 GiB) halves the plan until the real ring bytes fit and says so on stderr and in the status line. The per-source key buffer_mb becomes buffer_samples; an old buffer_mb is ignored. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
616cde5b13
commit
034c82f7d7
20 files changed
+516
-134
No files matched your search
+50
-3
@@ -4397,7 +4397,16 @@ gboolean tick(gpointer user) {
|
||||
snap.channels = channel_count;
|
||||
snap.plot1_traces = bound;
|
||||
snap.plot1_samples = samples;
|
||||
snap.buffer_mb = double(model.config().buffer_mb);
|
||||
{
|
||||
// The planned buffer when there is one; before the first CONFIG there is no plan, and the
|
||||
// request (rounded as the ring will round it) is the honest figure, with no bytes to quote.
|
||||
const Partition& part = model.partition();
|
||||
snap.buffer_requested = model.config().buffer_samples;
|
||||
snap.buffer_samples = part.time_capacity > 0 ? part.time_capacity : buffer_ticks(snap.buffer_requested);
|
||||
snap.buffer_bytes = part.bytes();
|
||||
snap.memory_limit_bytes = model.memory_limit_bytes();
|
||||
snap.memory_limited = part.memory_limited;
|
||||
}
|
||||
snap.span_s = span_s;
|
||||
snap.time_stride = time_stride;
|
||||
snap.have_event = app->last_latch_us != 0;
|
||||
@@ -5281,8 +5290,9 @@ void on_activate(GtkApplication* application, gpointer user) {
|
||||
if (app->waveform_name.empty()) {
|
||||
const Config& cfg = app->model->config();
|
||||
char buf[192];
|
||||
std::snprintf(buf, sizeof(buf), "connecting to %s:%u -- planning the %.0f MB buffer",
|
||||
cfg.server.c_str(), unsigned(cfg.port), cfg.buffer_mb);
|
||||
std::snprintf(buf, sizeof(buf), "connecting to %s:%u -- planning the buffer: %s samples per signal",
|
||||
cfg.server.c_str(), unsigned(cfg.port),
|
||||
format_samples(buffer_ticks(cfg.buffer_samples)).c_str());
|
||||
set_notice(*app, buf, 6);
|
||||
}
|
||||
}
|
||||
@@ -6081,6 +6091,43 @@ int run_ui(int argc, char** argv, const Config& cfg, ScopeModel& model, Acquisit
|
||||
static_cast<unsigned long long>(model.live().values(i).produced()),
|
||||
static_cast<unsigned long long>(model.live().channel(i).size()));
|
||||
}
|
||||
// The sample budget as this run planned it, and the planner on fixed inputs. The first line is
|
||||
// the live buffer: `used` is the request rounded down to a power of two and clipped by the memory
|
||||
// limit, `bytes` the rings' real size. The second feeds `plan_partition()` three channels and a
|
||||
// 100 M request three ways -- room to spare, a 1 GiB limit that must clip, a limit too small for
|
||||
// even 128 slots -- so a change to the rounding, the halving or the byte formula reads here even
|
||||
// when the run's own plan happens not to be clipped.
|
||||
std::printf("[udpscope] buffer: requested=%llu used=%llu bytes=%llu limit=%llu limited=%d\n",
|
||||
static_cast<unsigned long long>(model.config().buffer_samples),
|
||||
static_cast<unsigned long long>(part.time_capacity),
|
||||
static_cast<unsigned long long>(part.bytes()),
|
||||
static_cast<unsigned long long>(model.memory_limit_bytes()),
|
||||
part.memory_limited ? 1 : 0);
|
||||
{
|
||||
const std::vector<double> dts(3, 1.0e-4);
|
||||
const auto pow2 = [](unsigned long long v) { return v != 0 && (v & (v - 1)) == 0; };
|
||||
const Partition open = plan_partition(dts, 100000000ull, 16ull << 30, 0.0);
|
||||
constexpr unsigned long long kGiB = 1ull << 30;
|
||||
const Partition clip = plan_partition(dts, 100000000ull, kGiB, 0.0);
|
||||
const Partition clip_rel = plan_partition(dts, 100000000ull, kGiB, 0.0, true);
|
||||
bool thrown = false;
|
||||
try {
|
||||
(void)plan_partition(dts, 100000000ull, 1000, 0.0);
|
||||
} catch (const std::exception&) {
|
||||
thrown = true;
|
||||
}
|
||||
std::printf("[udpscope] planner: unclipped got=%llu pow2=%d limited=%d | clipped(1GiB) "
|
||||
"got=%llu bytes=%llu fits=%d pow2=%d limited=%d | clipped_rel got=%llu "
|
||||
"bytes=%llu fits=%d | too_small_throws=%d\n",
|
||||
static_cast<unsigned long long>(open.time_capacity),
|
||||
pow2(open.time_capacity) ? 1 : 0, open.memory_limited ? 1 : 0,
|
||||
static_cast<unsigned long long>(clip.time_capacity),
|
||||
static_cast<unsigned long long>(clip.bytes()), clip.bytes() <= kGiB ? 1 : 0,
|
||||
pow2(clip.time_capacity) ? 1 : 0, clip.memory_limited ? 1 : 0,
|
||||
static_cast<unsigned long long>(clip_rel.time_capacity),
|
||||
static_cast<unsigned long long>(clip_rel.bytes()),
|
||||
clip_rel.bytes() <= kGiB ? 1 : 0, thrown ? 1 : 0);
|
||||
}
|
||||
// `frames_painted` is the area's, read from the snapshot taken before the window was
|
||||
// destroyed (the PlotArea wrapper dies with its widget): one figure, one render per
|
||||
// frame, so it is the same count for every row.
|
||||
|
||||
Reference in new issue
Block a user