perf(mining): sample daemon memory off the UI thread

The daemon's RSS was read on the UI thread: a scan of every /proc entry
on Linux (1-7 ms), a forked ps on macOS (up to ~30 ms). The mining poll
probed every 1-5 s on every tab, and the Mining tab's memory bar probed
again every 1.5 s.

util::BackgroundSampler now runs the probe on one lazily started thread
and caches the result. getDaemonMemoryUsageMB() returns the last sample
and asks for a new one at most every 3 s, and only while the Mining tab
memory bar or the Peers tab Memory cell is drawn: nothing on other tabs
or while minimized. EmbeddedDaemon::memoryProbe() hands the sampler a
pid snapshot (on Windows the value read from the owned process handle),
so nothing off-thread touches daemon state. The mining poll does no I/O
any more; it carries the last sample. Both shutdown paths stop the
sampler.

Fixed along the way: an unreadable /proc made the directory iterator
throw on the UI thread; the scan now just stops.

User-visible:
- the Peers tab Memory cell keeps updating during sync and shows an
  external daemon's memory (it showed the mining poll's value, which
  pauses during sync and covers only the embedded daemon);
- the Mining tab memory bar refreshes every ~3 s (was 1.5 s).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-09 21:09:48 -05:00
co-authored by Claude Opus 5.5
parent 2618744e59
commit 296c04386c
11 changed files with 312 additions and 50 deletions
+6 -4
View File
@@ -38,6 +38,7 @@
#include "services/wallet_security_controller.h"
#include "services/wallet_security_workflow.h"
#include "util/async_task_manager.h"
#include "util/background_sampler.h"
#include "util/coalescing_writer.h"
#include "util/fetch_backoff.h"
#include "util/pool_stats_service.h"
@@ -957,10 +958,11 @@ private:
std::int64_t sb_unconf_key_ts_ = -1;
std::size_t sb_unconf_key_n_ = 0;
int sb_unconf_count_ = 0;
// Daemon-memory probe is expensive (/proc scan on Linux, popen on macOS); throttle it to ~1.5s so the
// Mining tab's per-frame read doesn't hammer the OS. See App::getDaemonMemoryUsageMB().
mutable double daemon_mem_cached_mb_ = 0.0;
mutable double daemon_mem_probe_at_ = 0.0;
// Daemon-memory probe is expensive (/proc scan on Linux, popen on macOS): it runs on this sampler's own
// thread, at most once per 3 s and only while a reader asks (Mining tab memory bar, Peers tab Memory cell).
// See App::getDaemonMemoryUsageMB(). Mutable: that accessor is const. Probes capture values only, so any
// member order is safe; shutdown() stops it.
mutable util::BackgroundSampler daemon_mem_sampler_;
// ── Chat note buffer (BOTH variants) ────────────────────────────────────────────────────────
// Each chat message is a shielded tx that spends a note; its change needs a few confirmations before