use crate::device::GpuReader;
use crate::device::readers::windows_gpu_perf::{self, ids::AdapterLuid};
use crate::device::types::{GPU_METRIC_UNAVAILABLE, GpuInfo, ProcessInfo};
use crate::utils::get_hostname;
use chrono::Local;
use serde::Deserialize;
use std::collections::HashMap;
use std::sync::Mutex;
use wmi::WMIConnection;
thread_local! {
static WMI_CONNECTION: std::cell::RefCell<Option<WMIConnection>> = const { std::cell::RefCell::new(None) };
}
fn with_wmi_connection<T, F: FnOnce(&WMIConnection) -> T>(f: F) -> Option<T> {
WMI_CONNECTION.with(|cell| {
let mut conn_ref = cell.borrow_mut();
if conn_ref.is_none() {
match WMIConnection::new() {
Ok(wmi_con) => {
*conn_ref = Some(wmi_con);
}
Err(e) => {
eprintln!("AMD GPU: Failed to create WMI connection: {e}");
}
}
}
conn_ref.as_ref().map(f)
})
}
#[derive(Deserialize, Debug, Clone)]
#[serde(rename_all = "PascalCase")]
struct Win32VideoController {
name: Option<String>,
adapter_r_a_m: Option<u64>, driver_version: Option<String>,
video_processor: Option<String>,
pnp_device_i_d: Option<String>, status: Option<String>,
adapter_d_a_c_type: Option<String>,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
struct VideoControllerName {
name: Option<String>,
}
pub struct AmdWindowsGpuReader {
adapter_index: Mutex<HashMap<AdapterLuid, (usize, String)>>,
}
impl Default for AmdWindowsGpuReader {
fn default() -> Self {
Self::new()
}
}
impl AmdWindowsGpuReader {
pub fn new() -> Self {
Self {
adapter_index: Mutex::new(HashMap::new()),
}
}
fn query_amd_gpus(&self) -> Vec<GpuInfo> {
with_wmi_connection(|wmi_con| {
let mut gpu_list = Vec::new();
let result: Result<Vec<Win32VideoController>, _> = wmi_con
.raw_query("SELECT Name, AdapterRAM, DriverVersion, VideoProcessor, PNPDeviceID, Status, AdapterDACType FROM Win32_VideoController");
if let Ok(controllers) = result {
let hostname = get_hostname();
let time = Local::now().format("%Y-%m-%d %H:%M:%S").to_string();
for (idx, controller) in controllers.iter().enumerate() {
let name = controller.name.clone().unwrap_or_default();
let name_lower = name.to_lowercase();
if !name_lower.contains("amd")
&& !name_lower.contains("radeon")
&& !name_lower.contains("ati")
{
continue;
}
let uuid = controller
.pnp_device_i_d
.clone()
.unwrap_or_else(|| format!("AMD-GPU-{idx}"));
let total_memory = controller.adapter_r_a_m.unwrap_or(0);
let mut detail = HashMap::new();
if let Some(ref driver) = controller.driver_version {
detail.insert("Driver Version".to_string(), driver.clone());
}
if let Some(ref processor) = controller.video_processor {
detail.insert("Video Processor".to_string(), processor.clone());
}
if let Some(ref status) = controller.status {
detail.insert("Status".to_string(), status.clone());
}
if let Some(ref dac_type) = controller.adapter_d_a_c_type {
detail.insert("DAC Type".to_string(), dac_type.clone());
}
detail.insert("Metrics Source".to_string(), "WMI".to_string());
detail.insert(
"Note".to_string(),
"Temperature, power, and fan need the AMD ADL library".to_string(),
);
detail.insert("Source: Utilization".to_string(), "unavailable".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(
"Source: Memory".to_string(),
if total_memory > 0 { "WMI" } else { "unavailable" }.to_string(),
);
gpu_list.push(GpuInfo {
uuid,
time: time.clone(),
name,
device_type: "GPU".to_string(),
host_id: hostname.clone(),
hostname: hostname.clone(),
instance: hostname.clone(),
utilization: GPU_METRIC_UNAVAILABLE,
ane_utilization: 0.0,
dla_utilization: None,
tensorcore_utilization: None,
temperature: 0, used_memory: 0, total_memory,
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,
});
}
}
gpu_list
})
.unwrap_or_default()
}
fn augment_with_windows_perf(&self, gpus: &mut [GpuInfo]) {
let adapter_index = windows_gpu_perf::augment_gpus(gpus);
if let Ok(mut guard) = self.adapter_index.lock() {
*guard = adapter_index;
}
}
}
impl GpuReader for AmdWindowsGpuReader {
fn get_gpu_info(&self) -> Vec<GpuInfo> {
let mut gpus = self.query_amd_gpus();
self.augment_with_windows_perf(&mut gpus);
crate::device::readers::amd_adl::augment(&mut gpus);
gpus
}
fn get_process_info(&self) -> Vec<ProcessInfo> {
let Ok(adapter_index) = self.adapter_index.lock() else {
return Vec::new();
};
windows_gpu_perf::process_rows_with(&adapter_index, || {
let mut gpus = self.query_amd_gpus();
windows_gpu_perf::augment_gpus(&mut gpus)
})
}
}
pub fn has_amd_gpu_windows() -> bool {
let wmi_con = match WMIConnection::new() {
Ok(w) => w,
Err(e) => {
eprintln!("AMD GPU detection: Failed to create WMI connection: {e}");
return false;
}
};
let query_result: Result<Vec<VideoControllerName>, _> =
wmi_con.raw_query("SELECT Name FROM Win32_VideoController");
match query_result {
Ok(controllers) => {
for controller in controllers {
if let Some(name) = &controller.name {
let name_lower = name.to_lowercase();
if name_lower.contains("amd")
|| name_lower.contains("radeon")
|| name_lower.contains("ati")
{
return true;
}
}
}
}
Err(e) => {
eprintln!("AMD GPU detection: WMI query failed: {e}");
return false;
}
}
false
}