use crate::gpu::{Gpu, GpuUse};
use nvml_wrapper::Nvml;
use nvml_wrapper::enum_wrappers::device::{
Clock, PcieUtilCounter, PerformanceState, TemperatureSensor,
};
use nvml_wrapper::enums::device::UsedGpuMemory;
use std::collections::HashMap;
pub const PER_PROCESS: bool = true;
pub struct Source {
nvml: Nvml,
last_seen: Vec<u64>,
last_sm: Vec<HashMap<u32, u32>>,
}
impl Source {
pub fn open() -> Result<(Self, String, Vec<Gpu>), String> {
let nvml = Nvml::init().map_err(|_| "no NVIDIA driver".to_string())?;
let driver = nvml.sys_driver_version().unwrap_or_else(|_| "?".into());
let cuda = nvml
.sys_cuda_driver_version()
.map(|v| format!("{}.{}", v / 1000, (v % 1000) / 10))
.unwrap_or_else(|_| "?".into());
let n = nvml.device_count().map_err(|e| e.to_string())?;
let gpus = (0..n)
.map(|i| {
let d = nvml.device_by_index(i);
let d = d.as_ref().ok();
Gpu {
index: i,
name: d
.and_then(|d| d.name().ok())
.unwrap_or_else(|| "unknown".into()),
power_limit: d
.and_then(|d| d.enforced_power_limit().ok())
.map(|mw| mw / 1000),
clock_gr_max: d.and_then(|d| d.max_clock_info(Clock::Graphics).ok()),
..Default::default()
}
})
.collect();
let source = Source {
nvml,
last_seen: vec![0; n as usize],
last_sm: vec![HashMap::new(); n as usize],
};
Ok((source, format!("Driver {driver} CUDA {cuda}"), gpus))
}
pub fn sample(&mut self, gpus: &mut [Gpu], procs: &mut HashMap<u32, GpuUse>) {
for (i, g) in gpus.iter_mut().enumerate() {
let Ok(d) = self.nvml.device_by_index(i as u32) else {
continue;
};
g.util = d.utilization_rates().ok().map(|u| u.gpu);
if let Ok(m) = d.memory_info() {
g.mem_used = m.used;
g.mem_total = m.total;
}
g.temp = d.temperature(TemperatureSensor::Gpu).ok();
g.power = d.power_usage().ok().map(|mw| mw / 1000);
g.fan = d.fan_speed(0).ok();
g.clock_gr = d.clock_info(Clock::Graphics).ok();
g.clock_mem = d.clock_info(Clock::Memory).ok();
let pcie = |c| d.pcie_throughput(c).ok().map(|kb| kb as u64 * 1024);
g.pcie_tx = pcie(PcieUtilCounter::Send);
g.pcie_rx = pcie(PcieUtilCounter::Receive);
g.enc = d.encoder_utilization().ok().map(|e| e.utilization);
g.dec = d.decoder_utilization().ok().map(|e| e.utilization);
g.pstate = d.performance_state().ok().map(pstate);
if let Ok(samples) = d.process_utilization_stats(self.last_seen[i])
&& !samples.is_empty()
{
let sm = &mut self.last_sm[i];
sm.clear();
for s in samples {
self.last_seen[i] = self.last_seen[i].max(s.timestamp);
let e = sm.entry(s.pid).or_default();
*e = (*e).max(s.sm_util);
}
}
let sm = &self.last_sm[i];
let lists = [
d.running_compute_processes(),
d.running_graphics_processes(),
];
for p in lists.into_iter().flat_map(Result::unwrap_or_default) {
let u = procs.entry(p.pid).or_default();
if u.gpus.last() == Some(&(i as u32)) {
continue;
}
u.gpus.push(i as u32);
if let UsedGpuMemory::Used(b) = p.used_gpu_memory {
u.mem = Some(u.mem.unwrap_or(0) + b);
}
if let Some(&s) = sm.get(&p.pid) {
u.sm = Some(u.sm.unwrap_or(0).max(s));
}
}
}
}
}
fn pstate(p: PerformanceState) -> String {
match p {
PerformanceState::Unknown => "P?".into(),
p => format!("P{}", p.as_c()),
}
}