// GPU-hog forensics (M4 step 3 triage): when gpu=/vsw= balloons at full // clock, the question is WHICH process is loading the GPU. Samples the // per-process "GPU Engine" performance counters (two PDH collections a few // hundred ms apart), sums utilization per pid across engines, and returns // the top consumers as one printable line. Companion to clk= — clk= says // contention-vs-P-state, this names the contender. #pragma once #include namespace sauna { // Blocking (~sampleMs). Returns e.g. // "chrome.exe(1234) 41% dwm.exe(888) 12% spatial_light.exe(77) 9%" // or "" when the counters are unavailable. Skips entries below 5%. std::string TopGpuConsumers(int sampleMs = 300, int maxEntries = 4); // CPU-side stall forensics (M4 step 3 follow-up: episodes with [gpu hogs] // at 0% and 2 s tick stretches = kernel-level stall, not GPU work). // One PDH pass sampling kernel pressure (_Total % DPC / % interrupt / // % privileged time) plus the top per-process CPU consumers (per-core // scale: 100 = one core). High DPC/interrupt = a driver is storming // (LatencyMon territory); everything low while the machine still stalls // points at SMI/firmware, which Windows cannot see. Blocking ~sampleMs. // Returns e.g. "dpc=4.1% int=0.8% priv=22% top: MsMpEng 180 dwm 95". std::string CpuStallForensics(int sampleMs = 300, int maxEntries = 4); } // namespace sauna