fix: unify GPU detail keys with the shared reader convention (#351)
## Summary
Follow-up to #348 and #349. Every reader that publishes fan speed, clocks, or temperatures through the `detail` map uses the same key with the unit carried in the **value**, not the key:
| Reader | Key | Value |
|---|---|---|
| `amd.rs` (Linux) | `Fan Speed` | `1450 RPM` |
| `intel_gpu_linux/sources.rs` | `Fan Speed` | `1450 RPM` |
| `intel_gpu_level_zero/apply.rs` | `Fan Speed` | `1450 RPM` |
| `amd_adl.rs` (#349) | `Fan Speed (RPM)` | `1450` |
The ADL reader was the odd one out, and #348 did the same thing with its two VRAM diagnostics. This aligns them.
## Why fan speed specifically had a cost
The other renames are consistency. Fan speed was a real defect:
`intel_gpu_level_zero::apply_fan` guards an overwrite with `detail.contains_key("Fan Speed")` (`apply.rs:118`). A reader spelling the key differently silently opts out of that coordination. And anything that later promotes fan speed to a proper `GpuInfo` field, which is now tracked as its own issue, would have to special-case every spelling instead of reading one key.
No live bug today, since the ADL reader is AMD-only and the guard is in the Intel path, but the two would collide the moment either moves.
## Changes
| Before | After | Value |
|---|---|---|
| `Fan Speed (RPM)` | `Fan Speed` | `1450 RPM` |
| `Memory Clock (MHz)` | `Memory Clock` | `1250 MHz` |
| `Hotspot Temperature (C)` | `Hotspot Temperature` | `81 C` |
| `Memory Temperature (C)` | `Memory Temperature` | `70 C` |
| `Memory Controller Activity (%)` | `Memory Controller Activity` | `44%` |
| `VRAM Budget (this process)` | unchanged | `7000000000 bytes` |
| `VRAM Usage (this process)` | unchanged | `123456 bytes` |
The two VRAM keys keep their qualifier: `(this process)` is a **scope**, not a unit. Those DXGI figures are process-scoped rather than system-wide, and dropping the qualifier would invite exactly the misreading it exists to prevent. Only their values gained the `bytes` unit, matching `VRAM Total` in the existing readers.
`Temperature` is now emitted **only when the reading is below zero**. It exists to preserve a sub-zero die that the unsigned `GpuInfo.temperature` floors at 0, and on every normal poll it merely duplicated that field.
## Guard against recurrence
`detail_keys_follow_the_shared_reader_convention` asserts both the new spellings and the *absence* of the old ones, and its comment records the two concrete costs of diverging. The next reader to publish these quantities fails a test rather than drifting quietly.
## Compatibility
None of these keys have appeared in a tagged release, so no consumer depends on the old spellings.
## Verification
| Gate | Result |
|---|---|
| `cargo fmt --check` | pass |
| `cargo clippy --all-targets -- -D warnings` | exit 0 |
| `cargo test` | exit 0, 3176 passed, 0 failed across 23 binaries |
| `cargo xwin check --target x86_64-pc-windows-msvc` | exit 0, 0 warnings |
Note on the test count: figures quoted in #348 and #349 were read through a shell pipe that intermittently truncated cargo's output, so they undercounted slightly. Counting from a captured file gives 3176 here. The authoritative signal in all three PRs was the exit code, which was 0 throughout.
## Follow-ups filed separately
- Promoting fan speed to a real `GpuInfo` field so it reaches the TUI and Prometheus, not just `snapshot` JSON. Four readers would benefit; `Source: Fan` already exports as `source__fan` while the value does not.
- ADL `AdapterInfo` for multi-AMD-GPU attribution. J
Jeongkyu Shin committed
cafb05425ed1c0722efbfc553c861cc2e83e50aa
Parent: e659a6f
Committed by GitHub <noreply@github.com>
on 8/7/2026, 4:27:17 PM