pub mod pdh {
use std::collections::{hash_map::Entry, HashMap};
pub const PDH_OK: u32 = 0;
pub const PDH_CSTATUS_NEW_DATA: u32 = 0x1;
pub const PDH_CSTATUS_NO_OBJECT: u32 = 0xC000_0BB8;
pub const PDH_CSTATUS_NO_COUNTER: u32 = 0xC000_0BB9;
pub const PDH_CSTATUS_INVALID_DATA: u32 = 0xC000_0BBA;
pub const PDH_CSTATUS_NO_INSTANCE: u32 = 0x8000_07D1;
pub const PDH_MORE_DATA: u32 = 0x8000_07D2;
pub const PDH_NO_DATA: u32 = 0x8000_07D5;
pub const PDH_INVALID_DATA: u32 = 0xC000_0BC6;
pub const PDH_QUERY_PERF_DATA_TIMEOUT: u32 = 0xC000_0BFE;
pub fn status_is_normal_absence(status: u32) -> bool {
matches!(
status,
PDH_CSTATUS_NO_OBJECT
| PDH_CSTATUS_NO_COUNTER
| PDH_CSTATUS_NO_INSTANCE
| PDH_NO_DATA
| PDH_CSTATUS_INVALID_DATA
| PDH_INVALID_DATA
| PDH_QUERY_PERF_DATA_TIMEOUT
)
}
pub fn item_value_is_trustworthy(cstatus: u32) -> bool {
cstatus == PDH_OK || cstatus == PDH_CSTATUS_NEW_DATA
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct InstanceLuid(pub u32, pub u32);
impl InstanceLuid {
pub fn matches(&self, high: i32, low: u32) -> bool {
let h = high as u32;
(self.0 == h && self.1 == low) || (self.1 == h && self.0 == low)
}
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct ParsedInstance {
pub pid: Option<u32>,
pub luid: Option<InstanceLuid>,
pub phys: Option<u32>,
pub part: Option<u32>,
pub eng: Option<u32>,
pub engtype: Option<String>,
}
fn parse_hex_dword(tok: &str) -> Option<u32> {
let hex = tok.strip_prefix("0x").or_else(|| tok.strip_prefix("0X"))?;
if hex.is_empty() || hex.len() > 8 {
return None;
}
u32::from_str_radix(hex, 16).ok()
}
pub fn parse_instance(name: &str) -> Option<ParsedInstance> {
let mut out = ParsedInstance::default();
let mut toks = name.split('_');
while let Some(tok) = toks.next() {
match tok {
"pid" => out.pid = Some(toks.next()?.parse().ok()?),
"luid" => {
let a = parse_hex_dword(toks.next()?)?;
let b = parse_hex_dword(toks.next()?)?;
out.luid = Some(InstanceLuid(a, b));
}
"phys" => out.phys = Some(toks.next()?.parse().ok()?),
"part" => out.part = Some(toks.next()?.parse().ok()?),
"eng" => out.eng = Some(toks.next()?.parse().ok()?),
"engtype" => {
let rest = toks.collect::<Vec<_>>().join("_");
if rest.is_empty() {
return None;
}
out.engtype = Some(rest);
break;
}
_ => return None,
}
}
if out == ParsedInstance::default() {
None
} else {
Some(out)
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct EngineHeadline {
pub engtype: String,
pub pct: f64,
}
type EngineKey = (Option<u32>, Option<u32>, Option<u32>, String);
fn engine_busy(
engine_util: &[(ParsedInstance, f64)],
high: i32,
low: u32,
) -> HashMap<EngineKey, f64> {
let mut per_engine = HashMap::new();
for (inst, v) in engine_util {
if !inst.luid.is_some_and(|l| l.matches(high, low)) {
continue;
}
let key = (
inst.phys,
inst.part,
inst.eng,
inst.engtype.clone().unwrap_or_default(),
);
*per_engine.entry(key).or_insert(0.0) += v;
}
per_engine
}
pub fn device_util(
engine_util: &[(ParsedInstance, f64)],
high: i32,
low: u32,
) -> Option<EngineHeadline> {
engine_busy(engine_util, high, low)
.into_iter()
.max_by(|a, b| a.1.total_cmp(&b.1))
.map(|((_, _, _, engtype), pct)| EngineHeadline { engtype, pct })
}
pub fn engtype_util(
engine_util: &[(ParsedInstance, f64)],
high: i32,
low: u32,
engtype: &str,
) -> Option<f64> {
engine_busy(engine_util, high, low)
.into_iter()
.filter(|((_, _, _, ty), _)| ty.eq_ignore_ascii_case(engtype))
.map(|(_, v)| v)
.max_by(f64::total_cmp)
}
#[derive(Clone, Debug, PartialEq)]
pub struct PidUtil {
pub pct: f64,
pub busiest_engtype: String,
pub compute_hint: bool,
}
pub fn per_pid_util(
engine_util: &[(ParsedInstance, f64)],
high: i32,
low: u32,
) -> HashMap<u32, PidUtil> {
let mut out: HashMap<u32, PidUtil> = HashMap::new();
for (inst, v) in engine_util {
if !inst.luid.is_some_and(|l| l.matches(high, low)) {
continue;
}
let Some(pid) = inst.pid else { continue };
let engtype = inst.engtype.clone().unwrap_or_default();
let compute =
engtype.eq_ignore_ascii_case("compute") || engtype.eq_ignore_ascii_case("cuda");
match out.entry(pid) {
Entry::Occupied(mut o) => {
let e = o.get_mut();
if *v > e.pct {
e.pct = *v;
e.busiest_engtype = engtype;
}
e.compute_hint |= compute;
}
Entry::Vacant(slot) => {
slot.insert(PidUtil {
pct: *v,
busiest_engtype: engtype,
compute_hint: compute,
});
}
}
}
out
}
pub fn per_pid_bytes(
readings: &[(ParsedInstance, f64)],
high: i32,
low: u32,
) -> HashMap<u32, u64> {
let mut out: HashMap<u32, u64> = HashMap::new();
for (inst, v) in readings {
if !inst.luid.is_some_and(|l| l.matches(high, low)) {
continue;
}
let Some(pid) = inst.pid else { continue };
*out.entry(pid).or_insert(0) += v.max(0.0) as u64;
}
out
}
pub fn adapter_bytes(readings: &[(ParsedInstance, f64)], high: i32, low: u32) -> Option<u64> {
let mut total: Option<u64> = None;
for (inst, v) in readings {
if !inst.luid.is_some_and(|l| l.matches(high, low)) {
continue;
}
*total.get_or_insert(0) += v.max(0.0) as u64;
}
total
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct PdhSnapshot {
pub at_ms: u64,
pub engine_util: Vec<(ParsedInstance, f64)>,
pub proc_dedicated: Vec<(ParsedInstance, f64)>,
pub proc_shared: Vec<(ParsedInstance, f64)>,
pub adapter_dedicated: Vec<(ParsedInstance, f64)>,
pub adapter_shared: Vec<(ParsedInstance, f64)>,
}
#[cfg(target_os = "windows")]
pub use windows_impl::{shared, SharedPdh};
#[cfg(target_os = "windows")]
mod windows_impl {
use std::sync::{Mutex, OnceLock};
use windows::core::{PCWSTR, PWSTR};
use windows::Win32::System::Performance::{
PdhAddCounterW, PdhAddEnglishCounterW, PdhCollectQueryData, PdhExpandWildCardPathW,
PdhGetFormattedCounterArrayW, PdhOpenQueryW, PdhRemoveCounter, PDH_FMT,
PDH_FMT_COUNTERVALUE_ITEM_W, PDH_FMT_DOUBLE, PDH_HCOUNTER, PDH_HQUERY,
};
const PDH_FMT_NOCAP100: PDH_FMT = PDH_FMT(0x0000_8000);
use super::{
item_value_is_trustworthy, parse_instance, status_is_normal_absence, ParsedInstance,
PdhSnapshot, PDH_CSTATUS_NO_COUNTER, PDH_CSTATUS_NO_OBJECT, PDH_MORE_DATA, PDH_OK,
PDH_QUERY_PERF_DATA_TIMEOUT,
};
const SNAPSHOT_REUSE_MS: u64 = 250;
const REEXPAND_MS: u64 = 2_000;
const STREAM_PATHS: [&str; 5] = [
r"\GPU Engine(*)\Utilization Percentage",
r"\GPU Process Memory(*)\Dedicated Usage",
r"\GPU Process Memory(*)\Shared Usage",
r"\GPU Adapter Memory(*)\Dedicated Usage",
r"\GPU Adapter Memory(*)\Shared Usage",
];
fn to_wide(s: &str) -> Vec<u16> {
s.encode_utf16().chain(std::iter::once(0)).collect()
}
enum StreamMode {
Wildcard(PDH_HCOUNTER),
Expanded {
handles: Vec<PDH_HCOUNTER>,
last_expand_ms: u64,
},
Absent,
}
struct Stream {
wildcard: &'static str,
mode: StreamMode,
}
struct QueryState {
query: PDH_HQUERY,
streams: Vec<Stream>,
cache: Option<PdhSnapshot>,
}
pub struct SharedPdh {
state: Mutex<Option<QueryState>>,
engine_object_present: bool,
}
unsafe impl Send for SharedPdh {}
unsafe impl Sync for SharedPdh {}
pub fn shared() -> &'static SharedPdh {
static SHARED: OnceLock<SharedPdh> = OnceLock::new();
SHARED.get_or_init(SharedPdh::open)
}
impl SharedPdh {
fn open() -> Self {
let mut query = PDH_HQUERY::default();
let status = unsafe { PdhOpenQueryW(PCWSTR::null(), 0, &mut query) };
if status != PDH_OK {
return SharedPdh {
state: Mutex::new(None),
engine_object_present: false,
};
}
let streams: Vec<Stream> = STREAM_PATHS
.iter()
.map(|path| Stream {
wildcard: path,
mode: add_stream(query, path),
})
.collect();
let engine_object_present = !matches!(streams[0].mode, StreamMode::Absent);
SharedPdh {
state: Mutex::new(Some(QueryState {
query,
streams,
cache: None,
})),
engine_object_present,
}
}
pub fn engine_object_present(&self) -> bool {
self.engine_object_present
}
pub fn snapshot(&self, now_ms: u64) -> Option<PdhSnapshot> {
let mut guard = self.state.lock().ok()?;
let st = guard.as_mut()?;
if let Some(cache) = &st.cache {
if now_ms.saturating_sub(cache.at_ms) < SNAPSHOT_REUSE_MS {
return Some(cache.clone());
}
}
for stream in &mut st.streams {
if let StreamMode::Expanded {
handles,
last_expand_ms,
} = &mut stream.mode
{
if now_ms.saturating_sub(*last_expand_ms) >= REEXPAND_MS {
for h in handles.drain(..) {
let _ = unsafe { PdhRemoveCounter(h) };
}
*handles = expand_and_add(st.query, stream.wildcard);
*last_expand_ms = now_ms;
}
}
}
let status = unsafe { PdhCollectQueryData(st.query) };
if status != PDH_OK {
if status == PDH_QUERY_PERF_DATA_TIMEOUT {
return Some(st.cache.clone().unwrap_or_default());
}
if status_is_normal_absence(status) {
let snap = PdhSnapshot {
at_ms: now_ms,
..Default::default()
};
st.cache = Some(snap.clone());
return Some(snap);
}
return None;
}
let mut snap = PdhSnapshot {
at_ms: now_ms,
..Default::default()
};
for (i, stream) in st.streams.iter().enumerate() {
let out = match i {
0 => &mut snap.engine_util,
1 => &mut snap.proc_dedicated,
2 => &mut snap.proc_shared,
3 => &mut snap.adapter_dedicated,
_ => &mut snap.adapter_shared,
};
let handles: &[PDH_HCOUNTER] = match &stream.mode {
StreamMode::Wildcard(h) => std::slice::from_ref(h),
StreamMode::Expanded { handles, .. } => handles,
StreamMode::Absent => continue,
};
for &h in handles {
read_formatted_array(h, out);
}
}
st.cache = Some(snap.clone());
Some(snap)
}
}
fn add_stream(query: PDH_HQUERY, path: &'static str) -> StreamMode {
let wide = to_wide(path);
let mut handle = PDH_HCOUNTER::default();
let status =
unsafe { PdhAddEnglishCounterW(query, PCWSTR(wide.as_ptr()), 0, &mut handle) };
if status == PDH_OK {
return StreamMode::Wildcard(handle);
}
if status == PDH_CSTATUS_NO_OBJECT || status == PDH_CSTATUS_NO_COUNTER {
return StreamMode::Absent;
}
let handles = expand_and_add(query, path);
if handles.is_empty() {
StreamMode::Absent
} else {
StreamMode::Expanded {
handles,
last_expand_ms: 0,
}
}
}
fn expand_and_add(query: PDH_HQUERY, path: &str) -> Vec<PDH_HCOUNTER> {
let wide = to_wide(path);
let mut len: u32 = 0;
let status = unsafe {
PdhExpandWildCardPathW(PCWSTR::null(), PCWSTR(wide.as_ptr()), None, &mut len, 0)
};
if status != PDH_MORE_DATA || len == 0 {
return Vec::new();
}
let mut buf = vec![0u16; len as usize];
let status = unsafe {
PdhExpandWildCardPathW(
PCWSTR::null(),
PCWSTR(wide.as_ptr()),
Some(PWSTR(buf.as_mut_ptr())),
&mut len,
0,
)
};
if status != PDH_OK {
return Vec::new();
}
let mut handles = Vec::new();
for entry in buf.split(|&c| c == 0) {
if entry.is_empty() {
continue;
}
let mut entry_z: Vec<u16> = entry.to_vec();
entry_z.push(0);
let mut h = PDH_HCOUNTER::default();
let status = unsafe { PdhAddCounterW(query, PCWSTR(entry_z.as_ptr()), 0, &mut h) };
if status == PDH_OK {
handles.push(h);
}
}
handles
}
fn read_formatted_array(counter: PDH_HCOUNTER, out: &mut Vec<(ParsedInstance, f64)>) {
let fmt = PDH_FMT(PDH_FMT_DOUBLE.0 | PDH_FMT_NOCAP100.0);
let mut buf_bytes: u32 = 0;
let mut count: u32 = 0;
let status = unsafe {
PdhGetFormattedCounterArrayW(counter, fmt, &mut buf_bytes, &mut count, None)
};
if status != PDH_MORE_DATA {
return;
}
let mut buf = vec![0u64; (buf_bytes as usize).div_ceil(8)];
let items_ptr = buf.as_mut_ptr() as *mut PDH_FMT_COUNTERVALUE_ITEM_W;
let status = unsafe {
PdhGetFormattedCounterArrayW(
counter,
fmt,
&mut buf_bytes,
&mut count,
Some(items_ptr),
)
};
if status != PDH_OK {
return;
}
let items = unsafe { std::slice::from_raw_parts(items_ptr, count as usize) };
for item in items {
if !item_value_is_trustworthy(item.FmtValue.CStatus) {
continue;
}
let name = match unsafe { item.szName.to_string() } {
Ok(n) => n,
Err(_) => continue,
};
if let Some(parsed) = parse_instance(&name) {
let value = unsafe { item.FmtValue.Anonymous.doubleValue };
out.push((parsed, value));
}
}
}
}
}
pub mod adapters {
use crate::model::Vendor;
#[derive(Clone, Debug, PartialEq)]
pub struct AdapterInfo {
pub ordinal: u32,
pub name: String,
pub vendor_id: u32,
pub device_id: u32,
pub luid_high: i32,
pub luid_low: u32,
pub dedicated_video_bytes: u64,
pub shared_system_bytes: u64,
pub pci_bdf: Option<String>,
}
pub fn vendor_of(vendor_id: u32) -> Vendor {
match vendor_id {
0x10DE => Vendor::Nvidia,
0x1002 => Vendor::Amd,
0x8086 => Vendor::Intel,
_ => Vendor::Unknown,
}
}
pub fn bdf_string(bus: u32, device: u32, function: u32) -> Option<String> {
if bus > 0xFF || device > 0x1F || function > 0x7 {
return None;
}
Some(format!("0000:{bus:02x}:{device:02x}.{function:x}"))
}
pub fn synthetic_device_id(vendor_id: u32, device_id: u32, ordinal: u32) -> String {
format!("wddm:{vendor_id:04x}:{device_id:04x}:{ordinal}")
}
#[cfg(target_os = "windows")]
pub use windows_impl::enumerate;
#[cfg(target_os = "windows")]
mod windows_impl {
use windows::Win32::Foundation::LUID;
use windows::Win32::Graphics::Dxgi::{
CreateDXGIFactory1, IDXGIAdapter1, IDXGIFactory1, DXGI_ADAPTER_FLAG_SOFTWARE,
};
use super::{bdf_string, AdapterInfo};
pub fn enumerate() -> Vec<AdapterInfo> {
let factory: IDXGIFactory1 = match unsafe { CreateDXGIFactory1() } {
Ok(f) => f,
Err(_) => return Vec::new(),
};
let mut out = Vec::new();
for ordinal in 0.. {
let adapter: IDXGIAdapter1 = match unsafe { factory.EnumAdapters1(ordinal) } {
Ok(a) => a,
Err(_) => break,
};
let desc = match unsafe { adapter.GetDesc1() } {
Ok(d) => d,
Err(_) => continue,
};
if desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE.0 as u32 != 0 {
continue;
}
let name = {
let len = desc
.Description
.iter()
.position(|&c| c == 0)
.unwrap_or(desc.Description.len());
String::from_utf16_lossy(&desc.Description[..len])
};
out.push(AdapterInfo {
ordinal,
name,
vendor_id: desc.VendorId,
device_id: desc.DeviceId,
luid_high: desc.AdapterLuid.HighPart,
luid_low: desc.AdapterLuid.LowPart,
dedicated_video_bytes: desc.DedicatedVideoMemory as u64,
shared_system_bytes: desc.SharedSystemMemory as u64,
pci_bdf: pci_bdf_of(desc.AdapterLuid),
});
}
out
}
fn pci_bdf_of(luid: LUID) -> Option<String> {
use windows::Wdk::Graphics::Direct3D::{
D3DKMTCloseAdapter, D3DKMTOpenAdapterFromLuid, D3DKMTQueryAdapterInfo,
D3DKMT_ADAPTERADDRESS, D3DKMT_CLOSEADAPTER, D3DKMT_OPENADAPTERFROMLUID,
D3DKMT_QUERYADAPTERINFO, KMTQAITYPE_ADAPTERADDRESS,
};
let mut open = D3DKMT_OPENADAPTERFROMLUID {
AdapterLuid: luid,
hAdapter: 0,
};
if unsafe { D3DKMTOpenAdapterFromLuid(&mut open) }.is_err() {
return None;
}
let mut addr = D3DKMT_ADAPTERADDRESS::default();
let mut query = D3DKMT_QUERYADAPTERINFO {
hAdapter: open.hAdapter,
Type: KMTQAITYPE_ADAPTERADDRESS,
pPrivateDriverData: &mut addr as *mut _ as *mut core::ffi::c_void,
PrivateDriverDataSize: std::mem::size_of::<D3DKMT_ADAPTERADDRESS>() as u32,
};
let status = unsafe { D3DKMTQueryAdapterInfo(&mut query) };
let close = D3DKMT_CLOSEADAPTER {
hAdapter: open.hAdapter,
};
let _ = unsafe { D3DKMTCloseAdapter(&close) };
if status.is_err() {
return None;
}
bdf_string(addr.BusNumber, addr.DeviceNumber, addr.FunctionNumber)
}
}
}
#[cfg(any(target_os = "windows", test))]
fn image_basename(path: &str) -> &str {
match path.rsplit(['/', '\\']).next() {
Some(base) if !base.is_empty() => base,
_ => path,
}
}
#[cfg(target_os = "windows")]
pub(crate) fn os_process_name(pid: u32) -> String {
use windows::core::PWSTR;
use windows::Win32::Foundation::CloseHandle;
use windows::Win32::System::Threading::{
OpenProcess, QueryFullProcessImageNameW, PROCESS_NAME_WIN32,
PROCESS_QUERY_LIMITED_INFORMATION,
};
let handle = match unsafe { OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, pid) } {
Ok(h) => h,
Err(_) => return format!("pid {pid}"),
};
let mut buf = [0u16; 260];
let mut len = buf.len() as u32;
let queried = unsafe {
QueryFullProcessImageNameW(
handle,
PROCESS_NAME_WIN32,
PWSTR(buf.as_mut_ptr()),
&mut len,
)
};
let _ = unsafe { CloseHandle(handle) };
if queried.is_ok() && len > 0 {
let full = String::from_utf16_lossy(&buf[..len as usize]);
let base = image_basename(&full);
if !base.is_empty() {
return base.to_string();
}
}
format!("pid {pid}")
}
#[cfg(target_os = "windows")]
pub use backend_impl::WddmBackend;
#[cfg(target_os = "windows")]
mod backend_impl {
use super::adapters::{self, AdapterInfo};
use super::{os_process_name, pdh};
use crate::backend::{BackendError, GpuBackend};
use crate::model::{now_ms, DeviceId, DynamicSample, ProcessKind, ProcessSample, StaticInfo};
pub struct WddmBackend {
devs: Vec<(AdapterInfo, DeviceId)>,
}
impl WddmBackend {
pub fn init() -> Result<Self, BackendError> {
let infos = adapters::enumerate();
if infos.is_empty() {
return Err(BackendError::Unavailable(
"no hardware DXGI adapters (software/WARP only)".into(),
));
}
let _ = pdh::shared().snapshot(now_ms());
let devs = infos
.into_iter()
.map(|a| {
let id = DeviceId(a.pci_bdf.clone().unwrap_or_else(|| {
adapters::synthetic_device_id(a.vendor_id, a.device_id, a.ordinal)
}));
(a, id)
})
.collect();
Ok(Self { devs })
}
fn adapter_of(&self, dev: &DeviceId) -> Result<&AdapterInfo, BackendError> {
self.devs
.iter()
.find(|(_, id)| id == dev)
.map(|(a, _)| a)
.ok_or_else(|| BackendError::DeviceNotFound(dev.clone()))
}
}
impl GpuBackend for WddmBackend {
fn name(&self) -> &'static str {
"wddm"
}
fn devices(&mut self) -> Vec<DeviceId> {
self.devs.iter().map(|(_, id)| id.clone()).collect()
}
fn static_info(&mut self, dev: &DeviceId) -> Result<StaticInfo, BackendError> {
let a = self.adapter_of(dev)?;
let process_hint = if pdh::shared().engine_object_present() {
None
} else {
Some(
"per-process GPU stats unavailable: no WDDM 2.0 GPU/driver \
(Windows exposes them via GPU performance counters)"
.into(),
)
};
Ok(StaticInfo {
id: dev.clone(),
vendor: adapters::vendor_of(a.vendor_id),
name: if a.name.is_empty() {
"WDDM adapter".into()
} else {
a.name.clone()
},
backend: "wddm".into(),
mem_total_bytes: (a.dedicated_video_bytes > 0).then_some(a.dedicated_video_bytes),
power_limit_mw: None,
max_sm_clock_mhz: None,
temp_slowdown_c: None,
driver_version: None,
process_hint,
source_caveat: Some(
"utilization is the busiest engine's WDDM scheduler (VidSch) \
duty-cycle, not whole-GPU capacity; Windows exposes no public \
power/temperature/clock API for this GPU"
.into(),
),
})
}
fn refresh_dynamic(&mut self, dev: &DeviceId) -> Result<DynamicSample, BackendError> {
let a = self.adapter_of(dev)?;
let snap = pdh::shared().snapshot(now_ms()).unwrap_or_default();
let (high, low) = (a.luid_high, a.luid_low);
let headline = pdh::device_util(&snap.engine_util, high, low);
Ok(DynamicSample {
ts_ms: now_ms(),
util_pct: headline.as_ref().map(|h| h.pct as f32),
util_engine: headline.map(|h| h.engtype),
mem_used_bytes: pdh::adapter_bytes(&snap.adapter_dedicated, high, low),
power_mw: None,
temp_c: None,
fan_pct: None,
sm_clock_mhz: None,
mem_clock_mhz: None,
encoder_pct: pdh::engtype_util(&snap.engine_util, high, low, "VideoEncode")
.map(|v| v as f32),
decoder_pct: pdh::engtype_util(&snap.engine_util, high, low, "VideoDecode")
.map(|v| v as f32),
throttle: None,
})
}
fn refresh_processes(
&mut self,
dev: &DeviceId,
) -> Result<Vec<ProcessSample>, BackendError> {
let a = self.adapter_of(dev)?;
let snap = pdh::shared().snapshot(now_ms()).unwrap_or_default();
let (high, low) = (a.luid_high, a.luid_low);
let util = pdh::per_pid_util(&snap.engine_util, high, low);
let mem = pdh::per_pid_bytes(&snap.proc_dedicated, high, low);
let mut pids: Vec<u32> = util.keys().chain(mem.keys()).copied().collect();
pids.sort_unstable();
pids.dedup();
Ok(pids
.into_iter()
.map(|pid| {
let u = util.get(&pid);
ProcessSample {
pid,
name: os_process_name(pid),
kind: if u.is_some_and(|u| u.compute_hint) {
ProcessKind::Compute
} else {
ProcessKind::Unknown
},
mem_bytes: mem.get(&pid).copied(),
util_pct: u.map(|u| u.pct as f32),
cpu_pct: None,
container: None,
}
})
.collect())
}
}
}
#[cfg(test)]
mod tests {
use super::adapters::{bdf_string, synthetic_device_id, vendor_of};
use super::image_basename;
use super::pdh::{
adapter_bytes, device_util, engtype_util, item_value_is_trustworthy, parse_instance,
per_pid_bytes, per_pid_util, status_is_normal_absence, InstanceLuid, ParsedInstance,
PdhSnapshot, PDH_CSTATUS_INVALID_DATA, PDH_CSTATUS_NEW_DATA, PDH_CSTATUS_NO_COUNTER,
PDH_CSTATUS_NO_INSTANCE, PDH_CSTATUS_NO_OBJECT, PDH_INVALID_DATA, PDH_MORE_DATA,
PDH_NO_DATA, PDH_OK, PDH_QUERY_PERF_DATA_TIMEOUT,
};
use crate::model::Vendor;
#[test]
fn parse_engine_instance_full_form() {
let p = parse_instance("pid_1234_luid_0x00000000_0x0000C739_phys_0_eng_3_engtype_3D")
.expect("canonical engine instance must parse");
assert_eq!(p.pid, Some(1234));
assert_eq!(p.luid, Some(InstanceLuid(0x0000_0000, 0x0000_C739)));
assert_eq!(p.phys, Some(0));
assert_eq!(p.eng, Some(3));
assert_eq!(p.engtype.as_deref(), Some("3D"));
assert_eq!(p.part, None);
}
#[test]
fn parse_process_memory_instance_without_engine_tokens() {
let p = parse_instance("pid_8232_luid_0x00000000_0x0000C32D_phys_0")
.expect("process-memory instance must parse");
assert_eq!(p.pid, Some(8232));
assert_eq!(p.luid, Some(InstanceLuid(0, 0x0000_C32D)));
assert_eq!(p.phys, Some(0));
assert_eq!(p.eng, None);
assert_eq!(p.engtype, None);
}
#[test]
fn parse_adapter_memory_instance_has_no_pid() {
let p = parse_instance("luid_0x00000000_0x0000C32D_phys_0")
.expect("adapter-memory instance must parse");
assert_eq!(p.pid, None);
assert_eq!(p.luid, Some(InstanceLuid(0, 0x0000_C32D)));
assert_eq!(p.phys, Some(0));
}
#[test]
fn parse_part_token_and_multi_word_engtype() {
let p = parse_instance(
"pid_4_luid_0x00000000_0x0000ABCD_phys_0_part_1_eng_2_engtype_Video_Decode",
)
.expect("part + multi-token engtype must parse");
assert_eq!(p.part, Some(1));
assert_eq!(p.eng, Some(2));
assert_eq!(p.engtype.as_deref(), Some("Video_Decode"));
}
#[test]
fn parse_is_keyword_driven_not_positional() {
let p = parse_instance("luid_0x00000000_0x0000C739_pid_77_phys_0")
.expect("keyword-driven grammar must accept reordered tokens");
assert_eq!(p.pid, Some(77));
assert_eq!(p.luid, Some(InstanceLuid(0, 0xC739)));
}
#[test]
fn parse_rejects_malformed_decoys() {
for (decoy, why) in [
("", "empty string"),
("_Total", "classic non-GPU PDH instance name"),
("Processor Information", "non-GPU object instance"),
("pid_12x4_luid_0x0_0x1_phys_0", "non-numeric pid"),
("pid_1234_luid_0x00000000", "luid missing its second DWORD"),
(
"pid_1234_luid_00000000_0000C739_phys_0",
"luid parts without 0x prefix",
),
("pid_luid_0x0_0x1", "keyword where a value belongs"),
(
"pid_1234_luid_0x0_0x1_phys_0_eng_0_engtype_",
"empty engtype",
),
("pid_1234_bogus_7", "unknown keyword"),
(
"pid_1234_luid_0x123456789_0x1_phys_0",
"luid DWORD wider than 32 bits",
),
("___", "nothing but separators"),
] {
assert_eq!(
parse_instance(decoy),
None,
"must reject: {why} ({decoy:?})"
);
}
}
#[test]
fn luid_matching_verifies_both_parts_in_either_order() {
let l = InstanceLuid(0x0000_0000, 0x0000_C739);
assert!(l.matches(0, 0xC739), "observed order must match");
assert!(
InstanceLuid(0x0000_C739, 0x0000_0000).matches(0, 0xC739),
"swapped printed order must also match (both parts verified)"
);
assert!(!l.matches(0, 0xC740), "one wrong part must not match");
assert!(!l.matches(1, 0xC739), "one wrong part must not match");
assert!(InstanceLuid(0xFFFF_FFFF, 0x10).matches(-1, 0x10));
}
fn eng(pid: u32, luid: (u32, u32), engn: u32, ty: &str, v: f64) -> (ParsedInstance, f64) {
(
ParsedInstance {
pid: Some(pid),
luid: Some(InstanceLuid(luid.0, luid.1)),
phys: Some(0),
part: None,
eng: Some(engn),
engtype: Some(ty.into()),
},
v,
)
}
fn memr(pid: Option<u32>, luid: (u32, u32), v: f64) -> (ParsedInstance, f64) {
(
ParsedInstance {
pid,
luid: Some(InstanceLuid(luid.0, luid.1)),
phys: Some(0),
..Default::default()
},
v,
)
}
const LUID_A: (u32, u32) = (0, 0xC739);
const LUID_B: (u32, u32) = (0, 0xBEEF);
#[test]
fn device_util_headline_is_busiest_engine_after_pid_sum() {
let readings = vec![
eng(1, LUID_A, 0, "3D", 30.0),
eng(2, LUID_A, 0, "3D", 25.0),
eng(1, LUID_A, 1, "Copy", 70.0),
];
let h = device_util(&readings, 0, 0xC739).expect("matching instances → headline");
assert_eq!(h.engtype, "Copy");
assert_eq!(h.pct, 70.0);
}
#[test]
fn device_util_preserves_nocap100_sums_over_100() {
let readings = vec![eng(1, LUID_A, 0, "3D", 60.0), eng(2, LUID_A, 0, "3D", 55.0)];
let h = device_util(&readings, 0, 0xC739).unwrap();
assert_eq!(h.pct, 115.0, "NOCAP100 sums must not be clamped to 100");
}
#[test]
fn luid_grouping_excludes_other_adapters() {
let readings = vec![eng(1, LUID_A, 0, "3D", 40.0), eng(9, LUID_B, 0, "3D", 99.0)];
let h = device_util(&readings, 0, 0xC739).unwrap();
assert_eq!(h.pct, 40.0);
let h = device_util(&readings, 0, 0xBEEF).unwrap();
assert_eq!(h.pct, 99.0);
}
#[test]
fn unmatched_luid_instances_are_unattributed() {
let readings = vec![eng(1, LUID_A, 0, "3D", 40.0)];
assert_eq!(device_util(&readings, 7, 0x1234), None);
assert!(per_pid_util(&readings, 7, 0x1234).is_empty());
let mem = vec![memr(Some(1), LUID_A, 1024.0)];
assert!(per_pid_bytes(&mem, 7, 0x1234).is_empty());
assert_eq!(adapter_bytes(&mem, 7, 0x1234), None);
}
#[test]
fn engtype_sums_for_encoder_decoder_and_absent_is_none() {
let readings = vec![
eng(1, LUID_A, 4, "VideoEncode", 30.0),
eng(2, LUID_A, 4, "VideoEncode", 20.0),
eng(3, LUID_A, 5, "VideoEncode", 60.0),
eng(1, LUID_A, 0, "3D", 90.0),
];
assert_eq!(
engtype_util(&readings, 0, 0xC739, "VideoEncode"),
Some(60.0)
);
assert_eq!(
engtype_util(&readings, 0, 0xC739, "videoencode"),
Some(60.0)
);
assert_eq!(engtype_util(&readings, 0, 0xC739, "VideoDecode"), None);
}
#[test]
fn per_pid_util_is_max_across_engines_and_names_the_busiest() {
let readings = vec![
eng(1, LUID_A, 0, "3D", 30.0),
eng(1, LUID_A, 1, "Copy", 70.0),
eng(2, LUID_A, 6, "Cuda", 15.0),
];
let m = per_pid_util(&readings, 0, 0xC739);
let p1 = &m[&1];
assert_eq!(p1.pct, 70.0);
assert_eq!(p1.busiest_engtype, "Copy");
assert!(!p1.compute_hint);
assert!(m[&2].compute_hint);
}
#[test]
fn per_pid_dedicated_bytes_join_by_pid() {
let readings = vec![
memr(Some(8232), LUID_A, 1_073_741_824.0),
memr(Some(444), LUID_A, 52_428_800.0),
memr(Some(8232), LUID_B, 999.0), ];
let m = per_pid_bytes(&readings, 0, 0xC739);
assert_eq!(m.get(&8232), Some(&1_073_741_824));
assert_eq!(m.get(&444), Some(&52_428_800));
assert_eq!(m.len(), 2);
}
#[test]
fn adapter_bytes_sums_this_adapters_instances_only() {
let a_phys1 = {
let mut r = memr(None, LUID_A, 1_000.0);
r.0.phys = Some(1);
r
};
let readings = vec![
memr(None, LUID_A, 2_147_483_648.0),
a_phys1,
memr(None, LUID_B, 7.0),
];
assert_eq!(adapter_bytes(&readings, 0, 0xC739), Some(2_147_484_648));
}
#[test]
fn absent_counters_yield_empty_aggregation() {
let snap = PdhSnapshot::default();
assert_eq!(device_util(&snap.engine_util, 0, 0xC739), None);
assert_eq!(
engtype_util(&snap.engine_util, 0, 0xC739, "VideoEncode"),
None
);
assert!(per_pid_util(&snap.engine_util, 0, 0xC739).is_empty());
assert!(per_pid_bytes(&snap.proc_dedicated, 0, 0xC739).is_empty());
assert_eq!(adapter_bytes(&snap.adapter_dedicated, 0, 0xC739), None);
}
#[test]
fn pdh_absence_status_codes_are_normal_outcomes() {
for code in [
PDH_CSTATUS_NO_OBJECT, PDH_CSTATUS_NO_COUNTER, PDH_CSTATUS_NO_INSTANCE, PDH_NO_DATA, PDH_CSTATUS_INVALID_DATA, PDH_INVALID_DATA, PDH_QUERY_PERF_DATA_TIMEOUT, ] {
assert!(
status_is_normal_absence(code),
"0x{code:08X} is a normal absence, not an error"
);
}
assert!(!status_is_normal_absence(PDH_OK));
assert!(!status_is_normal_absence(PDH_MORE_DATA));
assert!(item_value_is_trustworthy(PDH_OK));
assert!(item_value_is_trustworthy(PDH_CSTATUS_NEW_DATA));
assert!(!item_value_is_trustworthy(PDH_CSTATUS_INVALID_DATA));
}
#[test]
fn bdf_string_matches_collector_normalization() {
assert_eq!(bdf_string(1, 0, 0).as_deref(), Some("0000:01:00.0"));
assert_eq!(bdf_string(0x0A, 2, 0).as_deref(), Some("0000:0a:02.0"));
assert_eq!(bdf_string(0xFF, 0x1F, 7).as_deref(), Some("0000:ff:1f.7"));
assert_eq!(bdf_string(0x100, 0, 0), None);
assert_eq!(bdf_string(0, 0x20, 0), None);
assert_eq!(bdf_string(0, 0, 8), None);
}
#[test]
fn synthetic_id_refuses_pci_shape() {
let id = synthetic_device_id(0x10DE, 0x2684, 0);
assert_eq!(id, "wddm:10de:2684:0");
let first_segment = id.split(':').next().unwrap();
assert!(
first_segment.bytes().any(|b| !b.is_ascii_hexdigit()),
"first segment must not be pure hex, or it could dedupe as PCI: {id}"
);
}
#[test]
fn vendor_of_maps_pci_vendor_ids() {
assert_eq!(vendor_of(0x10DE), Vendor::Nvidia);
assert_eq!(vendor_of(0x1002), Vendor::Amd);
assert_eq!(vendor_of(0x8086), Vendor::Intel);
assert_eq!(vendor_of(0x1414), Vendor::Unknown);
assert_eq!(vendor_of(0xABCD), Vendor::Unknown);
}
#[test]
fn image_basename_trims_both_separator_kinds() {
assert_eq!(image_basename(r"C:\Windows\System32\dwm.exe"), "dwm.exe");
assert_eq!(image_basename("/usr/bin/python3"), "python3");
assert_eq!(image_basename("bare.exe"), "bare.exe");
assert_eq!(image_basename(r"C:\odd\"), r"C:\odd\");
}
}