Skip to content

GPU usage monitoring with selectable animation source (CPU/GPU/Both) - #41

Merged
bearice merged 9 commits into
masterfrom
gpu-monitoring
Sep 11, 2026
Merged

GPU usage monitoring with selectable animation source (CPU/GPU/Both)#41
bearice merged 9 commits into
masterfrom
gpu-monitoring

Conversation

@bearice

@bearice bearice commented Sep 11, 2026

Copy link
Copy Markdown
Owner

What

RustCat can now animate off GPU usage, not just CPU. A new "Usage Source" tray menu item (CPU / GPU / CPU + GPU) selects the source; the choice is persisted per platform (Windows registry / macOS defaults / Linux settings.conf) and defaults to CPU, so existing installs are unaffected. The tooltip shows the selected source's usage, and the sleep/idle logic now follows the effective usage of the selected source.

How

New GpuMonitor trait (fn get_gpu_usage() -> io::Result<f64>) with per-platform implementations:

  • Windows — PDH counter \GPU Engine(*)\Utilization Percentage (Win10 1803+, all vendors). Max across engine instances, matching Task Manager's "GPU busy" semantics (summing would double-count parallel engines).
  • macOS — IORegistry PerformanceStatistics"Device Utilization %" (the same data powermetrics reads, no root). IOKit takes ownership of the IOServiceMatching dictionary (CFReleasing it afterwards segfaults — bisected on real hardware), so the looked-up service handle is cached for the process lifetime, with a one-shot re-lookup if a property read fails (stale service, e.g. GPU hot-plug).
  • Linux/sys/class/drm/card*/device/gpu_busy_percent (amdgpu/i915) and nvidia-smi, max of available sources.

Build fixes along the way:

  • build.rs: winres import gated on the build host and resource embedding additionally gated on the target (winres is a target.'cfg(windows)' build-dependency, which cargo resolves against the host — the runtime-only gate broke nix build on Linux, and the original host-cfg gate ran winres when cross-compiling to macOS).

Verified

  • Windows (RTX 4090): all three sources live in the tray; GPU baseline 16–33% at desktop load drives the animation speed correctly (speed 30–62).
  • Linux (NixOS, RTX 3080, headless): nix build .#rustcat passes; GPU smoke test passes, reading nvidia-smi without an X server.
  • macOS (real hardware): cargo build clean (no new warnings); GPU smoke test passes with real IORegistry readings.

Smoke tests are deliberately lenient (pass on machines without a GPU), so cargo test stays green everywhere.

…Both)

- New 'Usage Source' tray menu (CPU / GPU / CPU + GPU), persisted per
  platform (Windows registry / macOS defaults / Linux settings.conf),
  default CPU so existing installs are unaffected. Tooltip shows the
  selected source's usage; sleep/idle logic follows effective usage.
- New GpuMonitor trait (fn get_gpu_usage() -> io::Result<f64>) with
  per-platform implementations:
  - Windows: PDH \GPU Engine(*)\Utilization Percentage (Win10 1803+,
    all vendors); max across engine instances (Task Manager semantics).
  - macOS: IORegistry PerformanceStatistics 'Device Utilization %'
    (powermetrics data source, no root). IOKit owns the
    IOServiceMatching dictionary (CFReleasing it segfaults - bisected
    on real hardware), so the looked-up service handle is cached for
    the process lifetime with a one-shot re-lookup on failed reads.
  - Linux: /sys/class/drm/card*/device/gpu_busy_percent + nvidia-smi,
    max of available sources.
- build.rs: winres import host-gated, resource embedding target-gated
  (winres is a target.'cfg(windows)' build-dep resolved against host).
- Verified: Windows (RTX 4090, all 3 sources), Linux (nix build +
  smoke test on NixOS), macOS (real hardware, real IORegistry reads).

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c0ad6cdc24

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/platform/windows/gpu_usage.rs Outdated
Comment thread src/platform/macos/gpu_usage.rs Outdated
Comment thread src/platform/linux/gpu_usage.rs
- Windows: parse PDH GPU Engine instance names (pid/luid/phys/eng), sum
  per-process utilization per physical engine (capped at 100%), max across
  engines; fixes underreporting when multiple processes share an engine
  and a latent 318-byte buffer overflow (allocated size/24 items instead
  of size bytes / count items).
- macOS: enumerate ALL IOAccelerator/AGXAccelerator services via
  IOServiceGetServices instead of only the first match; per-service
  model name for the menu.
- Linux: per-card entries from nvidia-smi (index+name) and sysfs
  (amdgpu); i915 no longer claimed (needs perf/PMU, not viable for a
  tray app).
- New tray menu group 'GPU Device' (All GPUs + per-device, shown only
  with >=2 GPUs); persisted GpuScope setting on all three platforms;
  auto-fallback to All GPUs when the selected device disappears.

Addresses the multi-GPU aggregation review comments on PR #41.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 863af91509

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/app.rs Outdated
Comment thread src/platform/macos/gpu_usage.rs Outdated
Comment thread src/platform/mod.rs
The PDH GPU Engine instance name's luid token is a per-GPU-context id — a
single physical GPU (this 4090) exposes many luids (3 observed, 727
instances). Using luid as the device id split one card into several
'GPU 0' entries. The physical GPU is the phys field, and a physical
engine is (phys, eng); per-engine utilization is the sum of all its
(process, context) instances, max across engines. Verified on a live
RTX 4090: 33 physical engines, per-engine sums stay <= 100 under load,
matching Task Manager's GPU busy. Also add a Windows GPU smoke test
(mirroring macOS/Linux).

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c7fbbeca42

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/platform/windows/gpu_usage.rs Outdated
Comment thread src/platform/windows/gpu_usage.rs
- macOS: re-enumerate accelerator services every ~30s (eGPU hot-plug) and
  never cache an empty result, so a later-attached GPU is discoverable.
- Scope selection is now session-only (device ids are only stable for the
  current boot); drop the persisted GpuScope setting on all platforms.
- app.rs: only run the stale-device recovery when a GPU sample was
  actually requested (source != CPU), so CPU mode no longer re-enumerates
  (and spawns nvidia-smi) every second.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a014cd0fbd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/platform/macos/gpu_usage.rs Outdated
Comment thread src/platform/macos/gpu_usage.rs

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6fc9717ead

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/events.rs Outdated
Windows:
- Enumerate installed adapters via DXGI (IDXGIFactory1::EnumAdapters1) so
  idle (no process-context) secondary GPUs appear in the device menu;
  software fallback adapters (e.g. Microsoft Basic Render Driver) are
  filtered out.
- Identify physical GPUs by their adapter LUID, which appears in both the
  PDH instance names and DXGI's AdapterLuid, so menu ids and sampling ids
  match exactly. The phys field is not a reliable per-adapter index (it is
  phys_0 for every adapter on some multi-GPU systems).
- Allocate the PDH array buffer with an explicit alignment (AlignedBuf) to
  avoid misaligned-access UB when PDH fills it with counter-value items.

macOS:
- Reference-count IOKit service handles (SERVICE_REFS) and release them via
  an RAII EntriesGuard, so superseded or detached device handles are
  IOObjectRelease'd exactly once when their last reference drops.

Cargo.toml: add Win32_Graphics_Dxgi to the windows feature set.
The GPU device submenu was only rebuilt on unrelated setting events, so an
eGPU attached (or detached) after startup stayed absent from the menu until
the user changed another setting. Re-enumerate and install a fresh menu in
the ShowMenu handler before show_menu(), synchronously (not via ui_update)
so the fresh menu is installed before it is displayed.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 47ca72a6de

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +219 to +222
Some(cache) => {
cache.samples_since_refresh += 1;
cache.samples_since_refresh >= GPU_REFRESH_INTERVAL
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Refresh macOS GPUs when rebuilding the menu

With the default AnimationSource::Cpu, sample_usage never calls the GPU monitor, so after the initial cache fill this counter advances only when a menu rebuild calls enumerate_gpus. Consequently, attaching an eGPU while CPU mode is selected leaves it absent until the menu has been opened roughly 30 times, rather than refreshing on open. The fresh evidence beyond the earlier cache finding is that the new ShowMenu rebuild still reaches this counter-gated cached path; explicit enumeration should force a refresh, or the refresh deadline should advance independently of GPU sampling.

Useful? React with 👍 / 👎.

The 'GPU Device' menu was shown whenever the machine has more than one
GPU, even when the animation is driven by CPU where the device selection
is irrelevant. Gate it on current_source != AnimationSource::Cpu so it only
appears for the Gpu and Both sources. The menu is already rebuilt on each
source change (SetAnimationSource) and on ShowMenu, so it appears/disappears
correctly.
@bearice
bearice merged commit 58de5d3 into master Sep 11, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant