use crate::device::GpuReader;
use crate::device::readers::intel_gpu_names::{
classify_intel_architecture, classify_intel_variant,
};
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!("Intel 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 IntelWindowsGpuReader {
#[cfg(all_smi_level_zero)]
level_zero_state:
Mutex<HashMap<String, crate::device::readers::intel_gpu_level_zero::LevelZeroState>>,
adapter_index: Mutex<crate::device::readers::windows_gpu_perf::AdapterIndex>,
}
impl Default for IntelWindowsGpuReader {
fn default() -> Self {
Self::new()
}
}
impl IntelWindowsGpuReader {
pub fn new() -> Self {
Self {
#[cfg(all_smi_level_zero)]
level_zero_state: Mutex::new(HashMap::new()),
adapter_index: Mutex::new(Default::default()),
}
}
fn query_intel_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();
if !is_intel_gpu_name(&name) {
continue;
}
let uuid = controller
.pnp_device_i_d
.clone()
.unwrap_or_else(|| format!("Intel-GPU-{idx}"));
let total_memory = controller.adapter_r_a_m.unwrap_or(0);
const FOUR_GB: u64 = 4 * 1024 * 1024 * 1024;
if total_memory == 0 {
eprintln!("Intel GPU '{name}': VRAM size unavailable (reported as 0)");
} else if total_memory >= FOUR_GB - (512 * 1024 * 1024) {
eprintln!(
"Intel GPU '{name}': VRAM reported as {total_memory} bytes, may be inaccurate for >4GB GPUs due to WMI 32-bit limitation"
);
}
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());
}
if let Some(variant) = classify_intel_variant(&name) {
detail.insert("Variant".to_string(), variant.to_string());
}
let arch = classify_intel_architecture(&name);
detail.insert("Architecture".to_string(), arch.label().to_string());
detail.insert(
"SYCL Capable".to_string(),
arch.sycl_capable_label().to_string(),
);
detail.insert("Metrics Source".to_string(), "WMI".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: Memory".to_string(),
if total_memory > 0 { "WMI" } else { "unavailable" }.to_string(),
);
detail.insert("Source: Fan".to_string(), "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()
}
}
impl GpuReader for IntelWindowsGpuReader {
fn get_gpu_info(&self) -> Vec<GpuInfo> {
let mut gpus = self.query_intel_gpus();
let adapter_index = crate::device::readers::windows_gpu_perf::augment_gpus(&mut gpus);
if let Ok(mut guard) = self.adapter_index.lock() {
*guard = adapter_index;
}
#[cfg(all_smi_level_zero)]
self.augment_with_level_zero(&mut gpus);
for gpu in &mut gpus {
annotate_missing_metrics(gpu);
}
gpus
}
fn get_process_info(&self) -> Vec<ProcessInfo> {
use crate::device::readers::windows_gpu_perf;
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_intel_gpus();
windows_gpu_perf::augment_gpus(&mut gpus)
})
}
}
#[cfg(all_smi_level_zero)]
impl IntelWindowsGpuReader {
fn augment_with_level_zero(&self, gpus: &mut [GpuInfo]) {
use crate::device::readers::intel_gpu_level_zero as l0;
let bdfs = l0::enumerated_pci_bdfs();
if bdfs.is_empty() {
return;
}
let mut states = match self.level_zero_state.lock() {
Ok(s) => s,
Err(_) => return,
};
for (gpu, bdf) in gpus.iter_mut().zip(bdfs.iter()) {
let state = states
.entry(gpu.uuid.clone())
.or_insert_with(l0::LevelZeroState::empty);
if let Some(readout) = l0::refresh(state, bdf) {
l0::apply_to_gpu_info(gpu, &readout, l0::ApplyPlatform::Windows);
}
}
}
}
fn annotate_missing_metrics(gpu: &mut GpuInfo) {
use crate::device::readers::detail_keys::missing_metric_sources;
const REPORTED: &[&str] = &["Temperature", "Power", "Frequency", "Utilization"];
let missing = missing_metric_sources(&gpu.detail, REPORTED);
if missing.is_empty() {
gpu.detail.remove("Note");
return;
}
gpu.detail.insert(
"Note".to_string(),
format!(
"{} unavailable: install the Intel graphics driver (ze_loader.dll), \
or this GPU exposes no such sensor",
missing.join(", ")
),
);
}
pub fn has_intel_gpu_windows() -> bool {
let wmi_con = match WMIConnection::new() {
Ok(w) => w,
Err(e) => {
eprintln!("Intel 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
&& is_intel_gpu_name(name)
{
return true;
}
}
}
Err(e) => {
eprintln!("Intel GPU detection: WMI query failed: {e}");
return false;
}
}
false
}
pub fn is_intel_gpu_name(name: &str) -> bool {
let lower = name.to_lowercase();
if !lower.contains("intel") {
return false;
}
const FAMILY_TOKENS: &[&str] = &[
"arc",
"iris",
"uhd graphics",
"hd graphics",
"xe graphics",
"intel graphics",
"xe-lpg",
"battlemage",
"lunarlake",
"lunar lake",
"xe2",
"xe3",
"panther lake",
"pantherlake",
];
FAMILY_TOKENS.iter().any(|t| lower.contains(t))
}
#[cfg(test)]
#[path = "intel_gpu_windows/tests.rs"]
mod tests;