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:
+23
-19
@@ -6178,26 +6178,28 @@ bool App::stopDaemonForBootstrap()
|
||||
|
||||
double App::getDaemonMemoryUsageMB() const
|
||||
{
|
||||
// The Mining tab reads this every frame, but the probe is expensive (Linux embedded/external paths
|
||||
// scan /proc; macOS forks a shell). Throttle to ~1.5s so a 60Hz caller doesn't hammer the OS.
|
||||
const double now = ImGui::GetTime();
|
||||
if (daemon_mem_probe_at_ > 0.0 && now - daemon_mem_probe_at_ < 1.5)
|
||||
return daemon_mem_cached_mb_;
|
||||
daemon_mem_probe_at_ = now;
|
||||
|
||||
double result = 0.0;
|
||||
// If we have an embedded daemon with a tracked process handle, use it
|
||||
// directly — more reliable than a process scan since we own the handle.
|
||||
if (daemon_controller_ && daemon_controller_->isRunning()) {
|
||||
double mb = daemon_controller_->memoryUsageMB();
|
||||
daemon_mem_diag_ = "embedded";
|
||||
result = (mb > 0.0) ? mb : util::Platform::getDaemonMemoryUsageMB();
|
||||
} else {
|
||||
daemon_mem_diag_ = "process scan";
|
||||
result = util::Platform::getDaemonMemoryUsageMB(); // external daemon
|
||||
// Never probes on the calling (UI) thread. The probe (a /proc scan on Linux, a forked `ps` on macOS, a
|
||||
// process snapshot for an external daemon) runs on daemon_mem_sampler_'s thread. This returns the last
|
||||
// completed sample (0 until the first lands, tens of ms after the first ask) and asks for a fresh one
|
||||
// when the last request is older than 3 s.
|
||||
constexpr auto kMaxAge = std::chrono::milliseconds(3000);
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
if (daemon_mem_sampler_.due(now, kMaxAge)) {
|
||||
util::BackgroundSampler::Probe probe;
|
||||
if (daemon_controller_ && daemon_controller_->isRunning()) {
|
||||
// The embedded daemon we own: its tracked process group first, a process scan if that reads 0.
|
||||
daemon_mem_diag_ = "embedded";
|
||||
probe = [own = daemon_controller_->memoryProbe()]() {
|
||||
const double mb = own ? own() : 0.0;
|
||||
return mb > 0.0 ? mb : util::Platform::getDaemonMemoryUsageMB();
|
||||
};
|
||||
} else {
|
||||
daemon_mem_diag_ = "process scan";
|
||||
probe = []() { return util::Platform::getDaemonMemoryUsageMB(); }; // external daemon
|
||||
}
|
||||
daemon_mem_sampler_.requestIfStale(std::move(probe), now, kMaxAge);
|
||||
}
|
||||
daemon_mem_cached_mb_ = result;
|
||||
return result;
|
||||
return daemon_mem_sampler_.value();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@@ -7401,6 +7403,7 @@ void App::shutdown()
|
||||
if (settings_) {
|
||||
settings_->save();
|
||||
}
|
||||
daemon_mem_sampler_.stop();
|
||||
if (daemon_controller_) {
|
||||
stopEmbeddedDaemon(StopMode::Fallback); // on the UI thread: bounded, never killed
|
||||
}
|
||||
@@ -7428,6 +7431,7 @@ void App::shutdown()
|
||||
if (http_worker_) {
|
||||
http_worker_->stop(); // http_cancel_ was set in beginShutdown(): an in-flight GET aborts within ~1 s
|
||||
}
|
||||
daemon_mem_sampler_.stop(); // joins a probe in flight (tens of ms at most)
|
||||
// Disconnect RPC after worker is fully stopped (safe — no curl in flight)
|
||||
if (rpc_) {
|
||||
rpc_->disconnect();
|
||||
|
||||
Reference in New Issue
Block a user