use crate::device::GpuReader;
use crate::device::readers::common_cache::{DeviceStaticInfo, MAX_DEVICES};
use crate::device::readers::intel_gpu_engine::{
EngineState, apply_engine_readout, refresh_with_lock,
};
use crate::device::readers::intel_gpu_fdinfo::{
build_intel_drm_basenames, build_intel_process_infos,
};
use crate::device::readers::intel_gpu_names::{
classify_intel_architecture, resolve_intel_gpu_name,
};
use crate::device::readers::intel_gpu_sysfs::{
MemoryVariant, has_nonzero_u64, read_fan_rpm, read_frequency_mhz, read_memory_bytes,
read_power_watts, read_temperature_celsius,
};
use crate::device::types::{GpuInfo, ProcessInfo};
use crate::utils::get_hostname;
use chrono::Local;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::{Mutex, OnceLock};
const MAX_GPU_POWER_WATTS: f64 = 750.0; const MAX_GPU_TEMP_CELSIUS: u32 = 125; const MAX_GPU_FREQ_MHZ: u32 = 5000;
const MAX_GPU_MEMORY_BYTES: u64 = 96 * 1024 * 1024 * 1024; const MAX_GPU_UTILIZATION: f64 = 100.0;
struct IntelGpuCard {
index: u32,
card_path: PathBuf,
driver: String,
device_id: u32,
variant: MemoryVariant,
static_info: OnceLock<DeviceStaticInfo>,
engine_state: Mutex<EngineState>,
#[cfg(feature = "level_zero")]
level_zero_state: Mutex<crate::device::readers::intel_gpu_level_zero::LevelZeroState>,
}
fn variant_label(variant: MemoryVariant) -> &'static str {
match variant {
MemoryVariant::Discrete => "Discrete",
MemoryVariant::Integrated => "Integrated",
}
}
pub struct IntelGpuReader {
cards: Vec<IntelGpuCard>,
intel_drm_basenames: HashMap<String, usize>,
proc_root: PathBuf,
}
impl Default for IntelGpuReader {
fn default() -> Self {
Self::new()
}
}
impl IntelGpuReader {
pub fn new() -> Self {
Self::new_with_roots(Path::new("/sys/class/drm"), Path::new("/proc"))
}
#[cfg(test)]
fn new_from_root(drm_root: &Path) -> Self {
Self::new_with_roots(drm_root, Path::new("/proc"))
}
fn new_with_roots(drm_root: &Path, proc_root: &Path) -> Self {
let cards = discover_cards(drm_root);
let card_refs: Vec<(PathBuf, usize)> = cards
.iter()
.enumerate()
.map(|(i, c)| (c.card_path.clone(), i))
.collect();
let intel_drm_basenames = build_intel_drm_basenames(&card_refs, drm_root);
Self {
cards,
intel_drm_basenames,
proc_root: proc_root.to_path_buf(),
}
}
fn ensure_static_info<'a>(&self, card: &'a IntelGpuCard) -> &'a DeviceStaticInfo {
card.static_info.get_or_init(|| {
let device_dir = card.card_path.join("device");
let name = resolve_device_name(&device_dir, card.device_id);
let mut detail = HashMap::new();
detail.insert("Device ID".to_string(), format!("{:#06x}", card.device_id));
detail.insert(
"Variant".to_string(),
variant_label(card.variant).to_string(),
);
if !card.driver.is_empty() {
detail.insert("Driver".to_string(), card.driver.clone());
}
if let Some(bus) = read_pci_bus_id(&device_dir) {
detail.insert("PCI Bus".to_string(), bus);
}
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(),
);
if card.variant == MemoryVariant::Integrated {
detail.insert(
"Memory".to_string(),
"Shared system memory (no dedicated VRAM)".to_string(),
);
}
detail.insert(
"Metrics Source".to_string(),
"sysfs (engine counters)".to_string(),
);
DeviceStaticInfo::with_details(name, None, detail)
})
}
}
impl GpuReader for IntelGpuReader {
fn get_gpu_info(&self) -> Vec<GpuInfo> {
let hostname = get_hostname();
let time = Local::now().format("%Y-%m-%d %H:%M:%S").to_string();
let mut out = Vec::with_capacity(self.cards.len());
for card in &self.cards {
let static_info = self.ensure_static_info(card);
let mut detail = static_info.detail.clone();
let device_dir = card.card_path.join("device");
let (used_memory, total_memory) = read_memory_bytes(&device_dir, card.variant);
let frequency = read_frequency_mhz(&device_dir);
let temperature = read_temperature_celsius(&device_dir);
let power_consumption = read_power_watts(&device_dir);
let fan_rpm = read_fan_rpm(&device_dir);
let temperature = temperature.min(MAX_GPU_TEMP_CELSIUS);
let frequency = frequency.min(MAX_GPU_FREQ_MHZ);
let power_consumption = power_consumption.clamp(0.0, MAX_GPU_POWER_WATTS);
let total_memory = total_memory.min(MAX_GPU_MEMORY_BYTES);
let used_memory = used_memory.min(total_memory);
sources::decorate_static_sources(
&mut detail,
total_memory,
temperature,
power_consumption,
frequency,
fan_rpm,
);
let readout = refresh_with_lock(&card.engine_state, &device_dir);
let utilization = readout.primary_utilization.clamp(0.0, MAX_GPU_UTILIZATION);
apply_engine_readout(&mut detail, &readout);
sources::decorate_utilization_source(&mut detail, &readout);
let uuid = build_uuid(card, &device_dir);
out.push(GpuInfo {
uuid,
time: time.clone(),
name: static_info.name.clone(),
device_type: "GPU".to_string(),
host_id: hostname.clone(),
hostname: hostname.clone(),
instance: hostname.clone(),
utilization,
ane_utilization: 0.0,
dla_utilization: None,
tensorcore_utilization: None,
temperature,
used_memory,
total_memory,
frequency,
power_consumption,
gpu_core_count: None,
temperature_threshold_slowdown: None,
temperature_threshold_shutdown: None,
temperature_threshold_max_operating: None,
temperature_threshold_acoustic: None,
performance_state: None,
numa_node_id: None,
gsp_firmware_mode: None,
gsp_firmware_version: None,
nvlink_remote_devices: Vec::new(),
gpm_metrics: None,
detail,
});
#[cfg(feature = "level_zero")]
level_zero_glue::augment(card, &mut out, &device_dir);
}
out
}
fn get_process_info(&self) -> Vec<ProcessInfo> {
let mut card_uuids = HashMap::with_capacity(self.cards.len());
for (idx, card) in self.cards.iter().enumerate() {
card_uuids.insert(idx, build_uuid(card, &card.card_path.join("device")));
}
build_intel_process_infos(&self.intel_drm_basenames, &card_uuids, &self.proc_root)
}
}
fn discover_cards(drm_root: &Path) -> Vec<IntelGpuCard> {
let entries = match std::fs::read_dir(drm_root) {
Ok(e) => e,
Err(_) => return Vec::new(),
};
let mut cards = Vec::new();
for entry in entries.flatten() {
if cards.len() >= MAX_DEVICES {
break;
}
let path = entry.path();
let name = match path.file_name().and_then(|n| n.to_str()) {
Some(n) => n.to_string(),
None => continue,
};
if !is_card_node(&name) {
continue;
}
let device_dir = path.join("device");
if !is_intel_vendor(&device_dir) {
continue;
}
let driver = resolve_driver(&device_dir);
if driver != "i915" && driver != "xe" {
continue;
}
let device_id = read_device_id(&device_dir).unwrap_or(0);
let variant = classify_variant(&device_dir);
let index = parse_card_index(&name);
cards.push(IntelGpuCard {
index,
card_path: path,
driver,
device_id,
variant,
static_info: OnceLock::new(),
engine_state: Mutex::new(EngineState::empty()),
#[cfg(feature = "level_zero")]
level_zero_state: Mutex::new(
crate::device::readers::intel_gpu_level_zero::LevelZeroState::empty(),
),
});
}
cards.sort_by_key(|c| c.index);
cards
}
fn is_card_node(name: &str) -> bool {
if let Some(rest) = name.strip_prefix("card") {
!rest.is_empty() && rest.chars().all(|c| c.is_ascii_digit())
} else {
false
}
}
fn parse_card_index(name: &str) -> u32 {
name.strip_prefix("card")
.and_then(|s| s.parse::<u32>().ok())
.unwrap_or(0)
}
fn is_intel_vendor(device_dir: &Path) -> bool {
match std::fs::read_to_string(device_dir.join("vendor")) {
Ok(s) => s.trim().eq_ignore_ascii_case("0x8086"),
Err(_) => false,
}
}
fn resolve_driver(device_dir: &Path) -> String {
match std::fs::read_link(device_dir.join("driver")) {
Ok(target) => target
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("")
.to_string(),
Err(_) => String::new(),
}
}
fn read_device_id(device_dir: &Path) -> Option<u32> {
let s = std::fs::read_to_string(device_dir.join("device")).ok()?;
parse_hex_u32(s.trim())
}
fn parse_hex_u32(s: &str) -> Option<u32> {
let stripped = s
.strip_prefix("0x")
.or_else(|| s.strip_prefix("0X"))
.unwrap_or(s);
u32::from_str_radix(stripped, 16).ok()
}
fn read_pci_bus_id(device_dir: &Path) -> Option<String> {
let link = std::fs::read_link(device_dir).ok()?;
link.file_name()
.and_then(|n| n.to_str())
.map(|s| s.to_string())
}
fn build_uuid(card: &IntelGpuCard, device_dir: &Path) -> String {
if let Some(bus) = read_pci_bus_id(device_dir) {
format!("Intel-GPU-{bus}")
} else {
format!("Intel-GPU-card{}", card.index)
}
}
fn classify_variant(device_dir: &Path) -> MemoryVariant {
if has_nonzero_u64(&device_dir.join("mem_info_vram_total"))
|| has_nonzero_u64(&device_dir.join("tile0").join("vram0").join("total_bytes"))
{
MemoryVariant::Discrete
} else {
MemoryVariant::Integrated
}
}
fn resolve_device_name(device_dir: &Path, device_id: u32) -> String {
if let Ok(label) = std::fs::read_to_string(device_dir.join("label")) {
let trimmed = label.trim();
if !trimmed.is_empty() {
return trimmed.to_string();
}
}
resolve_intel_gpu_name(device_id)
}
#[path = "intel_gpu_linux/detection.rs"]
mod detection;
pub fn has_intel_client_gpu() -> bool {
detection::has_intel_client_gpu_from_root(Path::new("/sys/class/drm"))
}
#[cfg(feature = "level_zero")]
#[path = "intel_gpu_linux/level_zero_glue.rs"]
mod level_zero_glue;
#[path = "intel_gpu_linux/sources.rs"]
mod sources;
#[cfg(test)]
#[path = "intel_gpu_linux/tests.rs"]
mod tests;