diff --git a/src/app.cpp b/src/app.cpp index ed495ea..8a0b2b0 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -966,7 +966,7 @@ void App::update() checkAutoLock(); } - // Mine-when-idle check (runs every frame, internally rate-limited by idle detection) + // Mine-when-idle check (cheap on most frames: it gates itself to 1 Hz) checkIdleMining(); // P8: Dedup rebuildAddressList — only rebuild once per frame diff --git a/src/app.h b/src/app.h index 1c9af2e..8d3b753 100644 --- a/src/app.h +++ b/src/app.h @@ -1604,6 +1604,9 @@ private: // Mine-when-idle: auto-start/stop mining based on system idle state bool idle_mining_active_ = false; // true when mining was auto-started by idle detection + std::chrono::steady_clock::time_point idle_mining_next_check_{}; // checkIdleMining runs at 1 Hz + std::chrono::steady_clock::time_point idle_gpu_next_sample_{}; // GPU probe at most every 5 s + bool idle_gpu_busy_ = false; // last GPU-aware verdict (with hysteresis) bool idle_scaled_to_idle_ = false; // true when threads have been scaled up for idle // Private methods - rendering diff --git a/src/app_security.cpp b/src/app_security.cpp index 5fabe73..e68800c 100644 --- a/src/app_security.cpp +++ b/src/app_security.cpp @@ -752,6 +752,17 @@ void App::checkIdleMining() { // Skip idle mining adjustments while thread benchmark is running if (ui::IsMiningBenchmarkActive()) return; + // The decisions below work on a seconds scale (the idle delay is tens of seconds or more), so check + // at 1 Hz. This used to run every frame: an X11 idle query and, with GPU-aware idle, an nvidia-smi + // launch on the UI thread per frame. Steady clock rather than ImGui::GetTime(), whose clock stops + // while minimized (update() still runs then). + const auto now = std::chrono::steady_clock::now(); + // ...except while a miner this feature started (or scaled up) is running: it must yield on the first + // frame that sees the user back, and frames can be seconds apart when the app idles, so a gated frame + // may be the only one carrying that input. The idle query is cheap; the GPU probe has its own cache. + if (!(idle_mining_active_ || idle_scaled_to_idle_) && now < idle_mining_next_check_) return; + idle_mining_next_check_ = now + std::chrono::seconds(1); + int idleSec = util::Platform::getSystemIdleSeconds(); int delay = settings_->getMineIdleDelay(); bool isPool = settings_->getPoolMode() || !supportsSoloMining(); @@ -759,26 +770,40 @@ void App::checkIdleMining() { bool threadScaling = settings_->getIdleThreadScaling(); int maxThreads = std::max(1, (int)std::thread::hardware_concurrency()); - // GPU-aware idle detection: if enabled, treat GPU utilization >= 10% - // as "user active" (e.g. watching a video). Disabled = unrestricted - // mode that only looks at keyboard/mouse input. - bool gpuBusy = false; - if (settings_->getIdleGpuAware()) { - int gpuUtil = util::Platform::getGpuUtilization(); - gpuBusy = (gpuUtil >= 10); - } - bool systemIdle = (idleSec >= delay) && !gpuBusy; - // Check if mining is already running (manually started by user) bool miningActive = isPool ? (xmrig_manager_ && xmrig_manager_->isRunning()) : state_.mining.generate; + // Thread scaling only adjusts a miner the user started: with none running there is nothing to + // decide, or to probe for. + if (threadScaling && (!miningActive || mining_toggle_in_progress_.load())) return; + + // GPU-aware idle detection: if enabled, treat GPU utilization >= 10% as "user active" (e.g. watching + // a video). Disabled = unrestricted mode that only looks at keyboard/mouse input. The probe can only + // matter once input is idle and a decision can change (start/stop mode leaves a miner the user + // started alone), so it runs only then, at most every 5 s, with hysteresis (busy at >= 10%, clear + // below 5%) so a GPU hovering near 10% can't keep restarting the miner. Unknown (-1) reads as not busy. + const bool inputIdle = idleSec >= delay; + const bool decisionPossible = threadScaling || !miningActive || idle_mining_active_; + bool gpuBusy = false; + if (inputIdle && decisionPossible && settings_->getIdleGpuAware()) { + if (now >= idle_gpu_next_sample_) { + const int gpuUtil = util::Platform::getGpuUtilization(); + if (gpuUtil >= 10) idle_gpu_busy_ = true; + else if (gpuUtil < 5) idle_gpu_busy_ = false; + idle_gpu_next_sample_ = now + std::chrono::seconds(5); + } + gpuBusy = idle_gpu_busy_; + } else if (!inputIdle) { + idle_gpu_busy_ = false; // input activity decides now; sample afresh once idle again + idle_gpu_next_sample_ = {}; + } + bool systemIdle = inputIdle && !gpuBusy; + if (threadScaling) { // --- Thread scaling mode --- - // Mining must already be running (started by user). We just adjust threads. - if (!miningActive || mining_toggle_in_progress_.load()) return; - + // Mining is already running (started by user; checked above). We just adjust threads. int activeThreads = settings_->getIdleThreadsActive(); int idleThreads = settings_->getIdleThreadsIdle(); // Resolve auto values: active defaults to half, idle defaults to all