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
+10
View File
@@ -128,8 +128,18 @@ public:
/**
* @brief Get daemon process memory usage (RSS) in MB
* @return Memory usage in MB, or 0 if unavailable
*
* Synchronous (Linux scans /proc, macOS forks `ps`): UI code uses memoryProbe() through a
* util::BackgroundSampler instead.
*/
double getMemoryUsageMB() const;
/**
* @brief A self-contained RSS probe (MB) for another thread. Snapshots what it needs on the calling
* thread (the pid; on Windows the value itself: one cheap call on the owned handle); the callable
* touches no EmbeddedDaemon state. Empty when there is no process.
*/
std::function<double()> memoryProbe() const;
/**
* @brief Get last error message