use std::path::Path;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
pub enum MemoryVariant {
Discrete,
Integrated,
}
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
pub fn read_memory_bytes(device_dir: &Path, variant: MemoryVariant) -> (u64, u64) {
if variant == MemoryVariant::Integrated {
return (0, 0);
}
let mut total = read_u64(&device_dir.join("mem_info_vram_total")).unwrap_or(0);
let mut used = read_u64(&device_dir.join("mem_info_vram_used")).unwrap_or(0);
if total == 0 {
total = read_u64(&device_dir.join("tile0").join("vram0").join("total_bytes")).unwrap_or(0);
}
if used == 0 {
used = read_u64(&device_dir.join("tile0").join("vram0").join("used_bytes")).unwrap_or(0);
}
(used, total)
}
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
pub fn read_frequency_mhz(device_dir: &Path) -> u32 {
if let Some(mhz) = read_u32(&device_dir.join("gt_cur_freq_mhz")) {
return mhz;
}
if let Some(raw) = read_u64(
&device_dir
.join("tile0")
.join("gt0")
.join("freq0")
.join("cur_freq"),
) {
let mhz = if raw > 100_000 { raw / 1_000_000 } else { raw };
if mhz <= u64::from(u32::MAX) {
return mhz as u32;
}
}
0
}
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
pub fn read_temperature_celsius(device_dir: &Path) -> u32 {
let hwmon_root = device_dir.join("hwmon");
let iter = match std::fs::read_dir(&hwmon_root) {
Ok(i) => i,
Err(_) => return 0,
};
for entry in iter.flatten() {
if let Some(milli) = read_u64(&entry.path().join("temp1_input")) {
return (milli / 1000) as u32;
}
}
0
}
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
pub fn read_power_watts(device_dir: &Path) -> f64 {
let hwmon_root = device_dir.join("hwmon");
let iter = match std::fs::read_dir(&hwmon_root) {
Ok(i) => i,
Err(_) => return 0.0,
};
for entry in iter.flatten() {
if let Some(uw) = read_u64(&entry.path().join("power1_average")) {
return uw as f64 / 1_000_000.0;
}
}
0.0
}
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
pub fn read_fan_rpm(device_dir: &Path) -> Option<u32> {
let hwmon_root = device_dir.join("hwmon");
let iter = std::fs::read_dir(&hwmon_root).ok()?;
for entry in iter.flatten() {
if let Some(rpm) = read_u32(&entry.path().join("fan1_input"))
&& rpm > 0
{
return Some(rpm);
}
}
None
}
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
pub fn read_u64(path: &Path) -> Option<u64> {
std::fs::read_to_string(path)
.ok()?
.trim()
.parse::<u64>()
.ok()
}
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
pub fn read_u32(path: &Path) -> Option<u32> {
std::fs::read_to_string(path)
.ok()?
.trim()
.parse::<u32>()
.ok()
}
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
pub fn has_nonzero_u64(path: &Path) -> bool {
read_u64(path).map(|v| v > 0).unwrap_or(false)
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::tempdir;
#[test]
fn read_memory_integrated_returns_zero() {
let dir = tempdir().unwrap();
let (used, total) = read_memory_bytes(dir.path(), MemoryVariant::Integrated);
assert_eq!(used, 0);
assert_eq!(total, 0);
}
#[test]
fn read_memory_discrete_via_i915() {
let dir = tempdir().unwrap();
fs::write(dir.path().join("mem_info_vram_total"), "17179869184\n").unwrap();
fs::write(dir.path().join("mem_info_vram_used"), "1073741824\n").unwrap();
let (used, total) = read_memory_bytes(dir.path(), MemoryVariant::Discrete);
assert_eq!(total, 17_179_869_184);
assert_eq!(used, 1_073_741_824);
}
#[test]
fn read_memory_discrete_via_xe() {
let dir = tempdir().unwrap();
let xe = dir.path().join("tile0").join("vram0");
fs::create_dir_all(&xe).unwrap();
fs::write(xe.join("total_bytes"), "12884901888\n").unwrap();
fs::write(xe.join("used_bytes"), "536870912\n").unwrap();
let (used, total) = read_memory_bytes(dir.path(), MemoryVariant::Discrete);
assert_eq!(total, 12_884_901_888);
assert_eq!(used, 536_870_912);
}
#[test]
fn frequency_prefers_i915_path() {
let dir = tempdir().unwrap();
fs::write(dir.path().join("gt_cur_freq_mhz"), "2100\n").unwrap();
assert_eq!(read_frequency_mhz(dir.path()), 2100);
}
#[test]
fn frequency_xe_hz_path() {
let dir = tempdir().unwrap();
let freq = dir.path().join("tile0").join("gt0").join("freq0");
fs::create_dir_all(&freq).unwrap();
fs::write(freq.join("cur_freq"), "2500000000\n").unwrap();
assert_eq!(read_frequency_mhz(dir.path()), 2500);
}
#[test]
fn frequency_xe_mhz_path() {
let dir = tempdir().unwrap();
let freq = dir.path().join("tile0").join("gt0").join("freq0");
fs::create_dir_all(&freq).unwrap();
fs::write(freq.join("cur_freq"), "2300\n").unwrap();
assert_eq!(read_frequency_mhz(dir.path()), 2300);
}
#[test]
fn temperature_handles_milli_celsius() {
let dir = tempdir().unwrap();
let hwmon = dir.path().join("hwmon").join("hwmon3");
fs::create_dir_all(&hwmon).unwrap();
fs::write(hwmon.join("temp1_input"), "72500\n").unwrap();
assert_eq!(read_temperature_celsius(dir.path()), 72);
}
#[test]
fn temperature_missing_returns_zero() {
let dir = tempdir().unwrap();
assert_eq!(read_temperature_celsius(dir.path()), 0);
}
#[test]
fn power_handles_microwatts() {
let dir = tempdir().unwrap();
let hwmon = dir.path().join("hwmon").join("hwmon2");
fs::create_dir_all(&hwmon).unwrap();
fs::write(hwmon.join("power1_average"), "185500000\n").unwrap();
let w = read_power_watts(dir.path());
assert!((w - 185.5).abs() < 0.01, "got {w}");
}
#[test]
fn fan_rpm_handles_hwmon() {
let dir = tempdir().unwrap();
let hwmon = dir.path().join("hwmon").join("hwmon2");
fs::create_dir_all(&hwmon).unwrap();
fs::write(hwmon.join("fan1_input"), "1730\n").unwrap();
assert_eq!(read_fan_rpm(dir.path()), Some(1730));
}
#[test]
fn has_nonzero_u64_works() {
let dir = tempdir().unwrap();
let f = dir.path().join("x");
fs::write(&f, "0\n").unwrap();
assert!(!has_nonzero_u64(&f));
fs::write(&f, "42\n").unwrap();
assert!(has_nonzero_u64(&f));
assert!(!has_nonzero_u64(&dir.path().join("missing")));
}
}