feat: add vendor-neutral Windows GPU metrics via DXGI and PDH (#348)
## Summary Closes #346. The AMD and Intel Windows readers were WMI-only baselines: `utilization`, `used_memory`, `frequency`, and `power_consumption` were hardcoded to `0`, `get_process_info()` returned an empty `Vec`, and `total_memory` was wrong for any card above 4 GB because `Win32_VideoController.AdapterRAM` is a `uint32` in the WMI schema. This adds `src/device/readers/windows_gpu_perf.rs`, a shared layer built on two OS facilities that need no vendor SDK, consumed by both vendor readers rather than duplicated. | Source | Supplies | |---|---| | DXGI `DXGI_ADAPTER_DESC1` | true 64-bit `DedicatedVideoMemory`, adapter LUID, PCI vendor/device ids | | PDH `\GPU Engine(*)` | device utilization | | PDH `\GPU Adapter Memory(*)` | system-wide used VRAM | | PDH `\GPU Process Memory(*)` | per-process VRAM, which populates `get_process_info()` | Level Zero keeps precedence over this layer for Intel. Per-field provenance goes in the existing `Source: *` detail keys, and `Metrics Source` composes as backends contribute (`WMI + DXGI + PDH`), following the pattern #248 established. ## Design decisions worth reviewing **The PDH query is persistent.** `Utilization Percentage` is a rate counter: a single `PdhCollectQueryData` establishes a baseline and yields nothing usable. Rather than sleeping inside the reader to manufacture a second sample, the query is opened once and each poll contributes one collection. The first poll after start-up reports no utilization; every poll after that reports the rate over the real interval. `get_process_info()` reuses that sample via `latest()` instead of collecting again, which would halve the interval the rate is computed over. **Utilization sums within an engine but maxes across engines.** Each sample is one process's share of one engine, so summing across processes gives the engine's busy fraction. Summing across a card's several 3D and Compute engines yields figures well above 100% and would peg every gauge. The maximum is what Task Manager's headline GPU percentage reports. This is a deliberate deviation from the issue text, which said "summing the 3D and Compute engine types per LUID"; flagging it explicitly. **DXGI `QueryVideoMemoryInfo` is process-scoped.** MSDN defines `CurrentUsage` and `Budget` as this process's view, not the system's. Reading either as the device's used memory would understate a busy GPU by whatever other processes hold, so both are exposed as clearly labelled diagnostic detail fields (`VRAM Usage (this process)`) and `used_memory` comes from the PDH adapter counter instead. **Video engines are excluded from utilization.** A compositor decoding video keeps `VideoDecode` busy while the shader cores idle; folding it in would make an idle desktop report a large non-zero load. **Counters are added with `PdhAddEnglishCounterW`.** Counter path components are localized on non-English Windows, so the literal English path only resolves through the English-specific entry point. ## Verification **No CI job in this repository compiles all-smi for Windows.** The only Windows job (`windows-service` in `ci.yml`) is gated behind `vars.ENABLE_WINDOWS_SERVICE_SMOKE`, which is unset, and its own comment states it has never executed. Windows-only code therefore ships with zero automated coverage by default. Two things were done about that: 1. The module is gated `cfg(any(target_os = "windows", test))`, matching the existing `intel_gpu_sysfs` pattern, so the counter-instance parsing, the utilization aggregation, the PNPDeviceID matching, and the field application are all exercised by the Linux test runner. 28 new tests. 2. The FFI itself was verified by cross-compiling to the real release target from macOS. | Gate | Result | |---|---| | `cargo fmt --check` | pass | | `cargo clippy --all-targets -- -D warnings` | pass | | `cargo test` | 3118 pass, 0 fail | | `cargo xwin check --target x86_64-pc-windows-msvc` | pass | | `cargo xwin clippy --target x86_64-pc-windows-msvc -- -D warnings` | pass for this change | The Windows cross-check needed `cargo-xwin` plus `llvm-lib` (rustup's `llvm-tools` provides `llvm-ar`, which acts as `llvm-lib` under that name); `zstd-sys` otherwise blocks any Windows cross-compile from macOS. ## Pre-existing findings, not fixed here Windows clippy reports three lints in files this PR does not touch, surfaced only because nothing had ever linted that target: - `src/device/cpu_windows.rs:49` and `:63`, collapsible `if` - `src/device/windows_temp/amd_ryzen.rs:228`, collapsible `if` Left alone to keep this diff scoped. Worth a follow-up together with a CI job that actually compiles the Windows target, which `windows-latest` runners would do for free on this public repository. ## Not verified Acceptance criteria that need real hardware: non-zero utilization on a busy AMD and Intel Windows machine, correct VRAM on a card above 4 GB, and populated per-process rows. The `windows.gpu.perf_counters` doctor check exists to produce exactly that evidence from an operator's machine; it distinguishes "no DXGI adapters", "DXGI works but PDH publishes no instances" (normal on VMs and RDP), and the full path.
J
Jeongkyu Shin committed
55c6a1a5375e24de056190fdcf70305eec4e3ab0
Parent: dd17ebd
Committed by GitHub <noreply@github.com>
on 8/7/2026, 2:36:05 AM