pub mod adapters;
pub mod ffi;
pub mod sensors;
#[cfg(target_os = "windows")]
pub mod loader;
use crate::device::readers::windows_gpu_perf::note_metrics_source;
use crate::device::types::{GpuInfo, MAX_GPU_FAN_RPM};
use sensors::AdlReadout;
pub fn apply_to_gpu_info(gpu: &mut GpuInfo, readout: &AdlReadout) {
if readout.is_empty() {
return;
}
let mut applied: Vec<&str> = Vec::new();
if let Some((temperature, source)) = readout.primary_temperature_c() {
gpu.temperature = temperature.max(0) as u32;
if temperature < 0 {
gpu.detail
.insert("Temperature".to_string(), format!("{temperature} C"));
}
gpu.detail
.insert("Source: Temperature".to_string(), source.to_string());
applied.push("temperature");
}
if let Some(hotspot) = readout.temperature_hotspot_c {
gpu.detail
.insert("Hotspot Temperature".to_string(), format!("{hotspot} C"));
}
if let Some(memory) = readout.temperature_mem_c {
gpu.detail
.insert("Memory Temperature".to_string(), format!("{memory} C"));
}
if let Some(power) = readout.power_w {
gpu.power_consumption = power;
gpu.detail
.insert("Source: Power".to_string(), "ADL".to_string());
applied.push("power");
}
if let Some(clock) = readout.clock_gfx_mhz {
gpu.frequency = clock;
gpu.detail
.insert("Source: Frequency".to_string(), "ADL".to_string());
applied.push("clocks");
}
if let Some(clock) = readout.clock_mem_mhz {
gpu.detail
.insert("Memory Clock".to_string(), format!("{clock} MHz"));
}
if let Some(rpm) = readout.fan_rpm {
let rpm = rpm.min(MAX_GPU_FAN_RPM);
gpu.fan_speed_rpm = Some(rpm);
gpu.detail
.insert("Fan Speed".to_string(), format!("{rpm} RPM"));
gpu.detail
.insert("Source: Fan".to_string(), "ADL".to_string());
applied.push("fan");
}
if let Some(activity) = readout.activity_gfx_pct {
gpu.utilization = activity;
gpu.detail
.insert("Source: Utilization".to_string(), "ADL".to_string());
applied.push("utilization");
}
if let Some(activity) = readout.activity_mem_pct {
gpu.detail.insert(
"Memory Controller Activity".to_string(),
format!("{activity:.0}%"),
);
}
note_metrics_source(&mut gpu.detail, "ADL");
gpu.detail.insert(
"Note".to_string(),
format!("via AMD ADL (PMLog): {}", applied.join(", ")),
);
}
#[cfg(target_os = "windows")]
pub fn augment(gpus: &mut [GpuInfo]) {
use adapters::AttributionPlan;
let inventory = if gpus.len() > 1 {
loader::adapter_inventory()
} else {
None
};
let uuids: Vec<&str> = gpus.iter().map(|gpu| gpu.uuid.as_str()).collect();
match adapters::plan_attribution(&uuids, inventory.as_deref()) {
AttributionPlan::SoleGpu => {
let Some(output) = loader::sample() else {
return;
};
let readout = sensors::extract(&output);
apply_to_gpu_info(&mut gpus[0], &readout);
}
AttributionPlan::PerCard(matches) => {
for card in matches {
let Some(output) = card
.adl_indices
.iter()
.find_map(|&index| loader::sample_adapter(index))
else {
continue;
};
let readout = sensors::extract(&output);
apply_to_gpu_info(&mut gpus[card.gpu_index], &readout);
}
}
AttributionPlan::Decline => {}
}
}
#[cfg(not(target_os = "windows"))]
pub fn augment(_gpus: &mut [GpuInfo]) {}
#[cfg(test)]
mod tests {
use super::*;
use crate::device::types::GPU_METRIC_UNAVAILABLE;
use std::collections::HashMap;
fn baseline_gpu() -> GpuInfo {
let mut detail = HashMap::new();
detail.insert("Metrics Source".to_string(), "WMI + DXGI + PDH".to_string());
detail.insert("Source: Utilization".to_string(), "PDH".to_string());
detail.insert("Source: Temperature".to_string(), "unavailable".to_string());
detail.insert("Source: Power".to_string(), "unavailable".to_string());
detail.insert("Source: Frequency".to_string(), "unavailable".to_string());
detail.insert("Source: Fan".to_string(), "unavailable".to_string());
detail.insert(
"Note".to_string(),
"Temperature, power, and fan need the AMD ADL library".to_string(),
);
GpuInfo {
uuid: "PCI\\VEN_1002&DEV_744C".to_string(),
time: String::new(),
name: "AMD Radeon RX 7900 XTX".to_string(),
device_type: "GPU".to_string(),
host_id: String::new(),
hostname: String::new(),
instance: String::new(),
utilization: 12.0,
ane_utilization: 0.0,
dla_utilization: None,
tensorcore_utilization: None,
temperature: 0,
used_memory: 1_073_741_824,
total_memory: 25_769_803_776,
frequency: 0,
power_consumption: GPU_METRIC_UNAVAILABLE,
gpu_core_count: None,
temperature_threshold_slowdown: None,
temperature_threshold_shutdown: None,
temperature_threshold_max_operating: None,
temperature_threshold_acoustic: None,
performance_state: None,
fan_speed_rpm: None,
numa_node_id: None,
gsp_firmware_mode: None,
gsp_firmware_version: None,
nvlink_remote_devices: Vec::new(),
gpm_metrics: None,
detail,
}
}
fn full_readout() -> AdlReadout {
AdlReadout {
temperature_edge_c: Some(62),
temperature_gfx_c: None,
temperature_hotspot_c: Some(81),
temperature_mem_c: Some(70),
power_w: Some(310.0),
fan_rpm: Some(1450),
clock_gfx_mhz: Some(2400),
clock_mem_mhz: Some(1250),
activity_gfx_pct: Some(97.0),
activity_mem_pct: Some(44.0),
}
}
#[test]
fn fills_in_everything_wddm_cannot_provide() {
let mut gpu = baseline_gpu();
apply_to_gpu_info(&mut gpu, &full_readout());
assert_eq!(gpu.temperature, 62);
assert_eq!(gpu.power_consumption, 310.0);
assert_eq!(gpu.power_consumption_reading(), Some(310.0));
assert_eq!(gpu.frequency, 2400);
assert_eq!(gpu.detail["Hotspot Temperature"], "81 C");
assert_eq!(gpu.detail["Memory Temperature"], "70 C");
assert_eq!(gpu.fan_speed_rpm, Some(1450));
assert_eq!(gpu.detail["Fan Speed"], "1450 RPM");
assert_eq!(gpu.detail["Memory Clock"], "1250 MHz");
assert_eq!(gpu.detail["Memory Controller Activity"], "44%");
assert_eq!(gpu.detail["Source: Temperature"], "ADL (edge)");
assert_eq!(gpu.detail["Source: Power"], "ADL");
assert_eq!(gpu.detail["Source: Frequency"], "ADL");
assert_eq!(gpu.detail["Source: Fan"], "ADL");
assert_eq!(gpu.detail["Metrics Source"], "WMI + DXGI + PDH + ADL");
}
#[test]
fn detail_keys_follow_the_shared_reader_convention() {
let mut gpu = baseline_gpu();
apply_to_gpu_info(&mut gpu, &full_readout());
for (key, expected) in [
("Fan Speed", "1450 RPM"),
("Memory Clock", "1250 MHz"),
("Hotspot Temperature", "81 C"),
("Memory Temperature", "70 C"),
("Memory Controller Activity", "44%"),
] {
assert_eq!(gpu.detail.get(key).map(String::as_str), Some(expected));
}
for stale in [
"Fan Speed (RPM)",
"Memory Clock (MHz)",
"Hotspot Temperature (C)",
"Memory Temperature (C)",
"Memory Controller Activity (%)",
] {
assert!(!gpu.detail.contains_key(stale), "{stale} should not exist");
}
}
#[test]
fn a_normal_temperature_adds_no_redundant_detail_key() {
let mut gpu = baseline_gpu();
apply_to_gpu_info(&mut gpu, &full_readout());
assert_eq!(gpu.temperature, 62);
assert!(!gpu.detail.contains_key("Temperature"));
}
#[test]
fn a_garbled_fan_reading_is_clamped_before_either_write() {
let mut gpu = baseline_gpu();
apply_to_gpu_info(
&mut gpu,
&AdlReadout {
fan_rpm: Some(u32::MAX),
..Default::default()
},
);
assert_eq!(gpu.fan_speed_rpm, Some(MAX_GPU_FAN_RPM));
assert_eq!(gpu.detail["Fan Speed"], format!("{MAX_GPU_FAN_RPM} RPM"));
}
#[test]
fn adl_utilization_outranks_the_pdh_figure() {
let mut gpu = baseline_gpu();
assert_eq!(gpu.utilization, 12.0);
apply_to_gpu_info(&mut gpu, &full_readout());
assert_eq!(gpu.utilization, 97.0);
assert_eq!(gpu.detail["Source: Utilization"], "ADL");
}
#[test]
fn a_partial_readout_leaves_the_rest_of_the_baseline_alone() {
let mut gpu = baseline_gpu();
let readout = AdlReadout {
temperature_edge_c: Some(55),
..Default::default()
};
apply_to_gpu_info(&mut gpu, &readout);
assert_eq!(gpu.temperature, 55);
assert_eq!(gpu.utilization, 12.0);
assert_eq!(gpu.detail["Source: Utilization"], "PDH");
assert_eq!(gpu.detail["Source: Power"], "unavailable");
assert_eq!(gpu.power_consumption_reading(), None);
assert_eq!(gpu.detail["Metrics Source"], "WMI + DXGI + PDH + ADL");
}
#[test]
fn the_note_names_only_the_fields_adl_actually_produced() {
let mut gpu = baseline_gpu();
apply_to_gpu_info(
&mut gpu,
&AdlReadout {
temperature_edge_c: Some(55),
..Default::default()
},
);
assert_eq!(gpu.detail["Note"], "via AMD ADL (PMLog): temperature");
assert_eq!(gpu.detail["Source: Power"], "unavailable");
let mut full = baseline_gpu();
apply_to_gpu_info(&mut full, &full_readout());
assert_eq!(
full.detail["Note"],
"via AMD ADL (PMLog): temperature, power, clocks, fan, utilization"
);
}
#[test]
fn a_sub_zero_die_floors_the_unsigned_field_but_keeps_the_real_value() {
let mut gpu = baseline_gpu();
apply_to_gpu_info(
&mut gpu,
&AdlReadout {
temperature_edge_c: Some(-8),
..Default::default()
},
);
assert_eq!(gpu.temperature, 0);
assert_eq!(gpu.detail["Temperature"], "-8 C");
}
#[test]
fn an_empty_readout_changes_nothing_at_all() {
let mut gpu = baseline_gpu();
let before = gpu.clone();
apply_to_gpu_info(&mut gpu, &AdlReadout::default());
assert_eq!(gpu.temperature, before.temperature);
assert_eq!(gpu.utilization, before.utilization);
assert_eq!(gpu.detail["Metrics Source"], "WMI + DXGI + PDH");
assert_eq!(gpu.detail["Source: Temperature"], "unavailable");
assert!(!gpu.detail.contains_key("Hotspot Temperature"));
assert!(gpu.fan_speed_rpm.is_none());
assert!(!gpu.detail.contains_key("Fan Speed"));
}
#[test]
fn applying_twice_does_not_grow_the_source_string() {
let mut gpu = baseline_gpu();
let readout = full_readout();
apply_to_gpu_info(&mut gpu, &readout);
apply_to_gpu_info(&mut gpu, &readout);
assert_eq!(gpu.detail["Metrics Source"], "WMI + DXGI + PDH + ADL");
}
#[test]
fn a_single_gpu_never_depends_on_adapterinfo() {
use adapters::{AttributionPlan, plan_attribution};
let gpu = baseline_gpu();
let uuids = [gpu.uuid.as_str()];
assert_eq!(plan_attribution(&uuids, None), AttributionPlan::SoleGpu);
assert_eq!(
plan_attribution(&uuids, Some(&[])),
AttributionPlan::SoleGpu
);
}
#[test]
fn multi_gpu_attribution_declines_without_a_validated_inventory() {
use adapters::{AttributionPlan, plan_attribution};
let uuids = ["PCI\\VEN_1002&DEV_744C", "PCI\\VEN_1002&DEV_164E"];
assert_eq!(plan_attribution(&uuids, None), AttributionPlan::Decline);
}
#[test]
fn augment_is_inert_when_attribution_is_refused() {
let mut gpus = vec![baseline_gpu(), baseline_gpu()];
augment(&mut gpus);
for gpu in &gpus {
assert_eq!(gpu.detail["Metrics Source"], "WMI + DXGI + PDH");
assert_eq!(gpu.temperature, 0);
}
#[cfg(not(target_os = "windows"))]
{
let mut single = vec![baseline_gpu()];
augment(&mut single);
assert_eq!(single[0].detail["Metrics Source"], "WMI + DXGI + PDH");
assert_eq!(single[0].temperature, 0);
}
}
}