use std::collections::HashSet;
use joule_profiler_core::time::get_timestamp_micros;
use log::{debug, trace};
use crate::{Device, DeviceSupport, Result, counters::PowerMeasurement, error::NvmlError};
#[cfg_attr(test, mockall::automock)]
#[allow(clippy::ref_option_ref)]
pub trait NvmlHardware: Send + Sync + 'static {
fn new() -> Result<Self>
where
Self: Sized;
#[allow(clippy::needless_lifetimes)]
fn init_devices<'a>(&mut self, spec: Option<&'a HashSet<u32>>) -> Result<Vec<Device>>;
fn get_energy(&self, device: Device) -> Result<u64>;
fn get_power(&self, device: Device) -> Result<PowerMeasurement>;
fn get_vram_usage(&self, device: Device) -> Result<u64>;
fn get_utilization(&self, device: Device) -> Result<u32>;
}
pub struct NvmlWrapperHardware {
pub nvml: nvml_wrapper::Nvml,
}
impl NvmlHardware for NvmlWrapperHardware {
fn new() -> Result<Self> {
debug!("Attempting to initialize NVML reader");
let nvml = nvml_wrapper::Nvml::init().map_err(|err| match err {
nvml_wrapper::error::NvmlError::DriverNotLoaded => NvmlError::NoDriverLoaded,
nvml_wrapper::error::NvmlError::NoPermission => NvmlError::NoPermission,
_ => err.into(),
})?;
Ok(Self { nvml })
}
fn init_devices(&mut self, spec: Option<&HashSet<u32>>) -> Result<Vec<Device>> {
trace!("Discovering GPU devices.");
let device_count = self.nvml.device_count()?;
let devices: Vec<_> = (0..device_count)
.flat_map(|i| {
let device = self.nvml.device_by_index(i)?;
let uuid = device.uuid()?;
trace!("Discovered GPU device, UUID: {uuid}, index: {i}.");
if let Some(spec) = &spec
&& !spec.contains(&i)
{
trace!("Ignoring device {uuid}.");
return Ok::<Option<Device>, NvmlError>(None);
}
let mut support = DeviceSupport::empty();
if device.total_energy_consumption().is_ok() {
support |= DeviceSupport::Energy;
} else if device.power_usage().is_ok() {
support |= DeviceSupport::Power;
}
if device.memory_info().is_ok() {
support |= DeviceSupport::Vram;
}
if device.utilization_rates().is_ok() {
support |= DeviceSupport::Utilization;
}
debug!("Device {uuid}, compatibility: {support:?}");
if support.is_empty() {
trace!("No support detected for device {uuid}, ignored.");
Ok::<Option<Device>, NvmlError>(None)
} else {
Ok(Some(Device { index: i, support }))
}
})
.flatten()
.collect();
Ok(devices)
}
fn get_energy(&self, device: Device) -> Result<u64> {
trace!("Retrieving energy for GPU device {}.", device.index);
Ok(self
.nvml
.device_by_index(device.index)?
.total_energy_consumption()?)
}
fn get_power(&self, device: Device) -> Result<PowerMeasurement> {
trace!("Retrieving power for GPU device {}.", device.index);
Ok(self
.nvml
.device_by_index(device.index)?
.power_usage()
.map(|power| PowerMeasurement {
timestamp: get_timestamp_micros(),
power,
})?)
}
fn get_vram_usage(&self, device: Device) -> Result<u64> {
trace!("Retrieving VRAM usage for GPU device {}.", device.index);
Ok(self.nvml.device_by_index(device.index)?.memory_info()?.used)
}
fn get_utilization(&self, device: Device) -> Result<u32> {
trace!(
"Retrieving GPU utilization for GPU device {}.",
device.index
);
Ok(self
.nvml
.device_by_index(device.index)?
.utilization_rates()?
.gpu)
}
}