add intel gpu monitoring via i915/xe sysfs
This commit is contained in:
parent
df2bc487fd
commit
50f04372b1
2 changed files with 126 additions and 29 deletions
|
|
@ -1,5 +1,6 @@
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
pub struct GpuInfo {
|
pub struct GpuInfo {
|
||||||
pub usage: u32,
|
pub usage: u32,
|
||||||
|
|
@ -15,16 +16,41 @@ pub enum GpuBackend {
|
||||||
hwmon_path: Option<String>,
|
hwmon_path: Option<String>,
|
||||||
},
|
},
|
||||||
Nvidia,
|
Nvidia,
|
||||||
|
Intel {
|
||||||
|
card_path: String,
|
||||||
|
device_path: String,
|
||||||
|
hwmon_path: Option<String>,
|
||||||
|
prev_rc6: Option<(u64, Instant)>,
|
||||||
|
},
|
||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn read_sysfs(path: &str) -> Option<String> {
|
||||||
|
fs::read_to_string(path).ok().map(|s| s.trim().to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_sysfs_u64(path: &str) -> Option<u64> {
|
||||||
|
read_sysfs(path)?.parse().ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_hwmon(driver_name: &str) -> Option<String> {
|
||||||
|
for i in 0..32 {
|
||||||
|
if let Some(name) = read_sysfs(&format!("/sys/class/hwmon/hwmon{i}/name")) {
|
||||||
|
if name == driver_name {
|
||||||
|
return Some(format!("/sys/class/hwmon/hwmon{i}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
pub fn detect_gpu() -> GpuBackend {
|
pub fn detect_gpu() -> GpuBackend {
|
||||||
// AMD: look for gpu_busy_percent exposed by the amdgpu driver
|
// AMD: look for gpu_busy_percent exposed by the amdgpu driver
|
||||||
for i in 0..8 {
|
for i in 0..8 {
|
||||||
let p = format!("/sys/class/drm/card{i}/device/gpu_busy_percent");
|
let p = format!("/sys/class/drm/card{i}/device/gpu_busy_percent");
|
||||||
if fs::read_to_string(&p).is_ok() {
|
if fs::read_to_string(&p).is_ok() {
|
||||||
let card = format!("/sys/class/drm/card{i}/device");
|
let card = format!("/sys/class/drm/card{i}/device");
|
||||||
let hwmon = find_amd_hwmon();
|
let hwmon = find_hwmon("amdgpu");
|
||||||
return GpuBackend::Amd {
|
return GpuBackend::Amd {
|
||||||
card_path: card,
|
card_path: card,
|
||||||
hwmon_path: hwmon,
|
hwmon_path: hwmon,
|
||||||
|
|
@ -40,38 +66,42 @@ pub fn detect_gpu() -> GpuBackend {
|
||||||
if nvidia_ok {
|
if nvidia_ok {
|
||||||
return GpuBackend::Nvidia;
|
return GpuBackend::Nvidia;
|
||||||
}
|
}
|
||||||
|
// Intel: look for i915 or xe driver
|
||||||
|
for i in 0..8 {
|
||||||
|
let driver_link = format!("/sys/class/drm/card{i}/device/driver");
|
||||||
|
if let Some(drv) = fs::read_link(&driver_link)
|
||||||
|
.ok()
|
||||||
|
.and_then(|p| p.file_name().map(|n| n.to_string_lossy().into_owned()))
|
||||||
|
{
|
||||||
|
if drv == "i915" || drv == "xe" {
|
||||||
|
let card_path = format!("/sys/class/drm/card{i}");
|
||||||
|
let device_path = format!("/sys/class/drm/card{i}/device");
|
||||||
|
let hwmon = find_hwmon(&drv);
|
||||||
|
return GpuBackend::Intel {
|
||||||
|
card_path,
|
||||||
|
device_path,
|
||||||
|
hwmon_path: hwmon,
|
||||||
|
prev_rc6: None,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
GpuBackend::None
|
GpuBackend::None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_amd_hwmon() -> Option<String> {
|
|
||||||
for i in 0..32 {
|
|
||||||
let name = format!("/sys/class/hwmon/hwmon{i}/name");
|
|
||||||
if fs::read_to_string(&name).ok()?.trim() == "amdgpu" {
|
|
||||||
return Some(format!("/sys/class/hwmon/hwmon{i}"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_amd(card: &str, hwmon: Option<&String>) -> Option<GpuInfo> {
|
fn read_amd(card: &str, hwmon: Option<&String>) -> Option<GpuInfo> {
|
||||||
let usage: u32 = fs::read_to_string(format!("{card}/gpu_busy_percent"))
|
let usage: u32 = read_sysfs(&format!("{card}/gpu_busy_percent"))?
|
||||||
.ok()?
|
|
||||||
.trim()
|
|
||||||
.parse()
|
.parse()
|
||||||
.ok()?;
|
.ok()?;
|
||||||
let vram_used: u64 = fs::read_to_string(format!("{card}/mem_info_vram_used"))
|
let vram_used: u64 = read_sysfs(&format!("{card}/mem_info_vram_used"))?
|
||||||
.ok()?
|
|
||||||
.trim()
|
|
||||||
.parse()
|
.parse()
|
||||||
.ok()?;
|
.ok()?;
|
||||||
let vram_total: u64 = fs::read_to_string(format!("{card}/mem_info_vram_total"))
|
let vram_total: u64 = read_sysfs(&format!("{card}/mem_info_vram_total"))?
|
||||||
.ok()?
|
|
||||||
.trim()
|
|
||||||
.parse()
|
.parse()
|
||||||
.ok()?;
|
.ok()?;
|
||||||
let temp_c = hwmon
|
let temp_c = hwmon
|
||||||
.and_then(|h| fs::read_to_string(format!("{h}/temp1_input")).ok())
|
.and_then(|h| read_sysfs(&format!("{h}/temp1_input")))
|
||||||
.and_then(|s| s.trim().parse::<i32>().ok())
|
.and_then(|s| s.parse::<i32>().ok())
|
||||||
.map_or(0, |mc| mc / 1000);
|
.map_or(0, |mc| mc / 1000);
|
||||||
Some(GpuInfo {
|
Some(GpuInfo {
|
||||||
usage,
|
usage,
|
||||||
|
|
@ -107,13 +137,77 @@ fn read_nvidia() -> Option<GpuInfo> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn emit_gpu(out: &mut impl Write, backend: &GpuBackend) {
|
fn read_intel(
|
||||||
|
card: &str,
|
||||||
|
device: &str,
|
||||||
|
hwmon: Option<&String>,
|
||||||
|
prev_rc6: &mut Option<(u64, Instant)>,
|
||||||
|
) -> GpuInfo {
|
||||||
|
let usage = read_intel_usage(card, prev_rc6).unwrap_or(0);
|
||||||
|
|
||||||
|
// VRAM - only present on discrete GPUs (Arc)
|
||||||
|
let vram_total = read_sysfs_u64(&format!("{device}/mem_info_vram_total")).unwrap_or(0);
|
||||||
|
let vram_used = if vram_total > 0 {
|
||||||
|
read_sysfs_u64(&format!("{device}/mem_info_vram_used")).unwrap_or(0)
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
let temp_c = hwmon
|
||||||
|
.and_then(|h| read_sysfs(&format!("{h}/temp1_input")))
|
||||||
|
.and_then(|s| s.parse::<i32>().ok())
|
||||||
|
.map_or(0, |mc| mc / 1000);
|
||||||
|
|
||||||
|
GpuInfo {
|
||||||
|
usage,
|
||||||
|
vram_used_gb: vram_used as f64 / 1_073_741_824.0,
|
||||||
|
vram_total_gb: vram_total as f64 / 1_073_741_824.0,
|
||||||
|
temp_c,
|
||||||
|
vendor: "intel",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_intel_usage(card: &str, prev_rc6: &mut Option<(u64, Instant)>) -> Option<u32> {
|
||||||
|
// RC6 is the GPU idle state - compute usage from residency delta
|
||||||
|
let rc6_ms = read_sysfs_u64(&format!("{card}/gt/gt0/rc6_residency_ms"))
|
||||||
|
.or_else(|| read_sysfs_u64(&format!("{card}/power/rc6_residency_ms")))?;
|
||||||
|
|
||||||
|
let now = Instant::now();
|
||||||
|
let usage = if let Some((prev_ms, prev_time)) = prev_rc6.take() {
|
||||||
|
let dt_ms = now.duration_since(prev_time).as_millis() as u64;
|
||||||
|
if dt_ms > 0 {
|
||||||
|
let idle_ms = rc6_ms.saturating_sub(prev_ms);
|
||||||
|
let idle_pct = (idle_ms * 100 / dt_ms).min(100);
|
||||||
|
(100 - idle_pct) as u32
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
0 // first reading, no delta yet
|
||||||
|
};
|
||||||
|
|
||||||
|
*prev_rc6 = Some((rc6_ms, now));
|
||||||
|
Some(usage)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn emit_gpu(out: &mut impl Write, backend: &mut GpuBackend) {
|
||||||
let info = match backend {
|
let info = match backend {
|
||||||
GpuBackend::Amd {
|
GpuBackend::Amd {
|
||||||
card_path,
|
card_path,
|
||||||
hwmon_path,
|
hwmon_path,
|
||||||
} => read_amd(card_path, hwmon_path.as_ref()),
|
} => read_amd(card_path, hwmon_path.as_ref()),
|
||||||
GpuBackend::Nvidia => read_nvidia(),
|
GpuBackend::Nvidia => read_nvidia(),
|
||||||
|
GpuBackend::Intel {
|
||||||
|
card_path,
|
||||||
|
device_path,
|
||||||
|
hwmon_path,
|
||||||
|
prev_rc6,
|
||||||
|
} => Some(read_intel(
|
||||||
|
card_path,
|
||||||
|
device_path,
|
||||||
|
hwmon_path.as_ref(),
|
||||||
|
prev_rc6,
|
||||||
|
)),
|
||||||
GpuBackend::None => return,
|
GpuBackend::None => return,
|
||||||
};
|
};
|
||||||
if let Some(g) = info {
|
if let Some(g) = info {
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ fn main() {
|
||||||
let mut out = io::BufWriter::new(stdout.lock());
|
let mut out = io::BufWriter::new(stdout.lock());
|
||||||
let mut prev: Vec<cpu::Sample> = vec![];
|
let mut prev: Vec<cpu::Sample> = vec![];
|
||||||
let mut freqs: Vec<f64> = vec![];
|
let mut freqs: Vec<f64> = vec![];
|
||||||
let gpu = gpu::detect_gpu();
|
let mut gpu = gpu::detect_gpu();
|
||||||
let mut tick = 0u64;
|
let mut tick = 0u64;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
|
@ -43,11 +43,14 @@ fn main() {
|
||||||
|
|
||||||
if tick.is_multiple_of(4) {
|
if tick.is_multiple_of(4) {
|
||||||
temp::emit_temp(&mut out);
|
temp::emit_temp(&mut out);
|
||||||
// AMD sysfs is instant; NVIDIA calls nvidia-smi so runs less often
|
// AMD/Intel read sysfs (instant); NVIDIA shells out so runs less often
|
||||||
match &gpu {
|
let emit = match &gpu {
|
||||||
gpu::GpuBackend::Amd { .. } => gpu::emit_gpu(&mut out, &gpu),
|
gpu::GpuBackend::Amd { .. } | gpu::GpuBackend::Intel { .. } => true,
|
||||||
gpu::GpuBackend::Nvidia if tick.is_multiple_of(8) => gpu::emit_gpu(&mut out, &gpu),
|
gpu::GpuBackend::Nvidia => tick.is_multiple_of(8),
|
||||||
_ => {}
|
gpu::GpuBackend::None => false,
|
||||||
|
};
|
||||||
|
if emit {
|
||||||
|
gpu::emit_gpu(&mut out, &mut gpu);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue