SIGN IN SIGN UP

feat(intel-gpu): per-process GPU memory accounting via fdinfo (#250)

* feat(intel-gpu): add fdinfo parser and DRM-fd-to-card mapper

Introduces a new stateless module `intel_gpu_fdinfo` that parses Intel DRM client `/proc/<pid>/fdinfo/<fd>` blocks and correlates fds back to a reader-known card index. Provides:

- `parse_fdinfo` — pure-string parser for the i915 and xe schemas. Handles truncated / malformed input without panicking, rejects foreign drivers (amdgpu / nvidia), and normalises memory values from kB to bytes.
- `build_intel_drm_basenames` — walks `/sys/class/drm` once to find every `cardN` and `renderD<M>` minor that maps to one of the reader-enumerated Intel PCI devices. Both nodes share a card index so modern Vulkan / oneAPI / ffmpeg workloads opening the render node are captured.
- `intel_drm_fds_for_pid` — reads `/proc/<pid>/fd/` and returns the fds pointing at known Intel DRM nodes. Permission errors degrade silently.
- `collect_intel_gpu_processes` — top-level aggregator. Walks `/proc`, dedupes fds by `drm-client-id` per process+card (avoiding N× over-counting from `dup(2)`d fds) but sums across distinct clients (multi-context workloads). Returns deterministic, PID-sorted output.

The module is path-injection friendly: every public helper accepts an explicit `proc_root` / `drm_root` so the entire walker is testable under `tempfile::tempdir` fixtures without touching the real `/proc` or `/sys`.

23 unit tests cover: i915 + xe parsing, integrated vs discrete schemas, foreign-driver rejection, truncated input, kB-to-bytes conversion, the card/renderD mapping for one and two cards, AMD render-node rejection, connector-child filtering, client-id dedup, distinct-client summing, multi-card grouping, and graceful no-Intel-card short-circuit.

Refs #247

* feat(intel-gpu): wire fdinfo per-process accounting into IntelGpuReader

Replaces the `Vec::new()` stub in `IntelGpuReader::get_process_info()` with a full implementation built on top of the new `intel_gpu_fdinfo` module. The reader now:

- Caches an `intel_drm_basenames` map at construction (one entry per `cardN` and `renderD<M>` known to belong to an Intel PCI device), so the per-process refresh is a flat `/proc` walk with no extra sysfs probing.
- Threads a `proc_root` field through the constructor for test injection — production stays `/proc`.
- Builds a `card_index -> uuid` table on each call and delegates to `build_intel_process_infos`, which collects `(pid, card_index, used_memory_bytes)` aggregates, performs one minimal sysinfo refresh, and merges sysinfo metadata (cpu_percent, user, state, rss, vms, etc.) into the final `ProcessInfo` rows. Pattern matches AMD's reader exactly so cross-vendor consumers see a consistent shape.

To stay under the 500-line per-file budget after the integration, two cohesive subsections moved into siblings:

- `intel_gpu_linux/detection.rs` — `has_intel_client_gpu_from_root` and `line_matches_intel_gpu`. The public `has_intel_client_gpu` is now a 4-line wrapper.
- `intel_gpu_fdinfo/enrichment.rs` — the sysinfo merge helper. The parent module re-exports `build_intel_process_infos`, so the public API is unchanged.

File sizes after refactor (all <500):
- `intel_gpu_linux.rs`        474
- `intel_gpu_fdinfo.rs`       483
- `intel_gpu_fdinfo/enrichment.rs` 107
- `intel_gpu_fdinfo/tests.rs`     494
- `intel_gpu_linux/detection.rs`  86

Behaviour on hosts without Intel-GPU-using processes is unchanged: an empty basename map (no Intel GPUs detected) short-circuits to `Vec::new()`, and the fdinfo walker returns empty when no process holds an Intel DRM fd. The stretch goal (per-process engine-time deltas) is intentionally deferred to a follow-up; v1 reports `gpu_utilization = 0.0` per process.

Refs #247

* test(intel-gpu): add fdinfo integration tests and update ARCHITECTURE

Adds three end-to-end tests against `IntelGpuReader::get_process_info()` driven by a synthetic procfs and DRM sysfs tree:

- `get_process_info_returns_empty_when_no_intel_cards` — guarantees no regression on AMD-only or NVIDIA-only hosts: the empty basename map short-circuits to `Vec::new()` without touching `/proc`.
- `get_process_info_collects_fdinfo_from_render_node` — full pipeline: a synthetic Intel card with a matching `renderD<M>` render node, plus a synthetic `/proc/<pid>/fdinfo/<fd>` containing the i915 schema, must yield exactly one populated `ProcessInfo` with the correct PID, `device_id`, `device_uuid` (`Intel-GPU-<bus>` format matching `get_gpu_info`), and `used_memory` (16384 kB -> 16777216 bytes).
- `get_process_info_default_filter_keeps_uses_gpu_processes` — verifies the Intel reader is compatible with the trait's default `get_gpu_processes` filter (every emitted row has `uses_gpu = true`).

Also updates `docs/ARCHITECTURE.md`: the Intel client GPU section now lists per-process GPU memory accounting alongside engine-busy utilization, including the i915 / xe key sets parsed and the `drm-client-id` dedup behaviour.

Refs #247

* style(intel-gpu): apply rustfmt to fdinfo module

`cargo fmt --check` (which CI enforces) flagged three formatting nits in the new module: a path-join chain that fits on one line, a `let` binding with an unnecessary multi-line break, and a function signature that fits on one line. Run rustfmt to bring the new code in line with the rest of the workspace.

No behaviour change. All 42 fdinfo / intel_gpu_linux tests still pass; clippy clean on both `--lib --tests` and the default bin target.

* docs(intel-gpu): clarify new_with_roots doc and surface per-process fdinfo in README and manpage

new_with_roots is called by production IntelGpuReader::new(), not test-only. Update the doc comment to reflect that it is an internal constructor accepting arbitrary roots, with production code routing through IntelGpuReader::new.

README and manpage were updated in the engine-utilization PR (#249) but neither mentioned the per-process GPU memory tracking added by this PR. Add a bullet under the Intel Arc section in README and extend the manpage Intel entry to include the fdinfo-based process accounting, mirroring the detail already present in ARCHITECTURE.md line 217.
J
Jeongkyu Shin committed
c95c55916ba34a980f3e79eb3334b7d2eca530be
Parent: 42a8dd9
Committed by GitHub <noreply@github.com> on 5/27/2026, 3:41:47 AM