use std::{fs, path::Path};
use super::GpuEntry;
use crate::metrics::push_history;
const INTEL_VENDOR_ID: u32 = 0x8086;
pub fn refresh(gpus: &mut Vec<GpuEntry>) {
let drm = Path::new("/sys/class/drm");
if !drm.exists() {
return;
}
let mut card_paths: Vec<_> = match fs::read_dir(drm) {
Ok(rd) => rd
.filter_map(|e| e.ok())
.filter(|e| {
let n = e.file_name();
let n = n.to_string_lossy();
n.starts_with("card") && !n.contains('-')
})
.map(|e| e.path())
.filter(|p| is_intel_gpu(p))
.collect(),
Err(_) => return,
};
card_paths.sort();
if card_paths.is_empty() {
return;
}
if gpus.len() != card_paths.len() {
*gpus = card_paths
.iter()
.map(|p| GpuEntry::new(gpu_name(p)))
.collect();
}
for (i, path) in card_paths.iter().enumerate() {
let dev = path.join("device");
let utilization = read_utilization(&dev);
let (mem_used, mem_total, mem_is_gtt) = read_memory(&dev);
let temperature = hwmon_temp(&dev);
let power_watts = hwmon_power(&dev);
let gpu = &mut gpus[i];
gpu.utilization = utilization;
gpu.mem_used = mem_used;
gpu.mem_total = mem_total;
gpu.mem_is_gtt = mem_is_gtt;
gpu.temperature = temperature;
gpu.power_watts = power_watts;
push_history(&mut gpu.util_history, utilization);
let mem_pct = if mem_total > 0 {
mem_used as f32 / mem_total as f32 * 100.0
} else {
0.0
};
push_history(&mut gpu.mem_history, mem_pct);
}
}
fn is_intel_gpu(card_path: &Path) -> bool {
let dev_path = card_path.join("device");
if let Ok(uevent) = fs::read_to_string(dev_path.join("uevent")) {
for line in uevent.lines() {
if let Some(id) = line.strip_prefix("PCI_ID=") {
if let Ok(vendor_id) = u32::from_str_radix(id, 16) {
if vendor_id != INTEL_VENDOR_ID {
return false;
}
break;
}
}
}
} else {
return false;
}
let has_i915 = dev_path.join("gt_act_freq_mhz").exists()
|| dev_path.join("gt_boost_freq_mhz").exists()
|| dev_path.join("gt_max_freq_mhz").exists()
|| dev_path.join("gt_min_freq_mhz").exists()
|| dev_path.join("gt_rpufreq_mhz").exists();
let has_xe = dev_path.join("gt_act_freq_mhz").exists()
|| dev_path.join("gt_boost_freq_mhz").exists()
|| dev_path.join("gt_max_freq_mhz").exists()
|| dev_path.join("gt_min_freq_mhz").exists()
|| dev_path.join("gt_rpufreq_mhz").exists()
|| dev_path.join("perfmon_gtact").exists();
has_i915 || has_xe
}
fn read_utilization(dev_path: &Path) -> f32 {
let act_freq = fs::read_to_string(dev_path.join("gt_act_freq_mhz"))
.ok()
.and_then(|s| s.trim().parse::<u32>().ok())
.unwrap_or(0);
let max_freq = fs::read_to_string(dev_path.join("gt_max_freq_mhz"))
.ok()
.and_then(|s| s.trim().parse::<u32>().ok())
.unwrap_or(0);
if max_freq > 0 {
(act_freq as f32 / max_freq as f32 * 100.0).min(100.0)
} else {
0.0
}
}
fn read_memory(dev_path: &Path) -> (u64, u64, bool) {
let lmem_total = fs::read_to_string(dev_path.join("mem_info_lmem_total"))
.ok()
.and_then(|s| s.trim().parse::<u64>().ok())
.unwrap_or(0);
if lmem_total > 0 {
let lmem_used = fs::read_to_string(dev_path.join("mem_info_lmem_used"))
.ok()
.and_then(|s| s.trim().parse::<u64>().ok())
.unwrap_or(0);
(lmem_used, lmem_total, false)
} else {
(0, 0, false)
}
}
fn gpu_name(card_path: &Path) -> String {
if let Ok(n) = fs::read_to_string(card_path.join("device/uevent")) {
for line in n.lines() {
if let Some(id) = line.strip_prefix("PCI_ID=") {
if let Ok(model) = fs::read_to_string(card_path.join("device/model")) {
let model = model.trim();
if !model.is_empty() {
return format!("Intel {} [{id}]", model.replace('\n', " "));
}
}
return format!("Intel GPU [{id}]");
}
}
}
card_path
.file_name()
.map(|n| n.to_string_lossy().into_owned())
.unwrap_or_else(|| "Intel GPU".to_string())
}
fn hwmon_temp(dev_path: &Path) -> Option<u32> {
let hwmon = fs::read_dir(dev_path.join("hwmon")).ok()?;
for entry in hwmon.filter_map(|e| e.ok()) {
if let Ok(val) = fs::read_to_string(entry.path().join("temp1_input")) {
if let Ok(millideg) = val.trim().parse::<u32>() {
return Some(millideg / 1000);
}
}
}
None
}
fn hwmon_power(dev_path: &Path) -> Option<f32> {
let hwmon = fs::read_dir(dev_path.join("hwmon")).ok()?;
for entry in hwmon.filter_map(|e| e.ok()) {
if let Ok(val) = fs::read_to_string(entry.path().join("power1_average")) {
if let Ok(uw) = val.trim().parse::<u64>() {
return Some(uw as f32 / 1_000_000.0);
}
}
}
None
}