use crate::{GpuDeviceInfo, GpuProcessEntry, HypomnesisError, ProcessGpuInfo, Result};
#[cfg(any(
feature = "nvml",
all(windows, feature = "dxgi"),
feature = "nvidia-smi-fallback"
))]
use crate::GpuQuerySource;
#[cfg(feature = "nvml")]
mod nvml;
#[cfg(all(windows, feature = "dxgi"))]
mod dxgi;
#[cfg(feature = "nvidia-smi-fallback")]
mod nvidia_smi;
#[allow(clippy::missing_const_for_fn)] pub fn device_count() -> Result<u32> {
#[cfg(feature = "nvml")]
if let Some(count) = nvml::device_count() {
return Ok(count);
}
#[cfg(all(windows, feature = "dxgi"))]
if let Some(count) = dxgi::device_count() {
return Ok(count);
}
Err(HypomnesisError::NoGpuSource)
}
#[allow(unused_variables)] #[allow(clippy::missing_const_for_fn)] pub fn device_info(index: u32) -> Result<GpuDeviceInfo> {
#[cfg(feature = "nvml")]
if let Some(snap) = nvml::query(index) {
#[cfg(all(windows, feature = "dxgi"))]
let name = dxgi::adapter_name(index).or(snap.device_name);
#[cfg(not(all(windows, feature = "dxgi")))]
let name = snap.device_name;
return Ok(GpuDeviceInfo {
index,
name,
total_bytes: snap.device_total,
free_bytes: snap.device_free,
used_bytes: snap.device_used,
});
}
#[cfg(all(windows, feature = "dxgi"))]
if let Some(d) = dxgi::query(index) {
return Ok(GpuDeviceInfo {
index,
name: d.adapter_name,
total_bytes: d.dedicated_video_memory,
free_bytes: d.dedicated_video_memory.saturating_sub(d.current_usage),
used_bytes: d.current_usage,
});
}
#[cfg(feature = "nvidia-smi-fallback")]
if let Some(result) = nvidia_smi::query(index) {
return Ok(GpuDeviceInfo {
index,
name: None,
total_bytes: result.total_bytes,
free_bytes: result.total_bytes.saturating_sub(result.used_bytes),
used_bytes: result.used_bytes,
});
}
bounds_check(index)?;
Err(HypomnesisError::NoGpuSource)
}
#[allow(unused_variables)] #[allow(clippy::missing_const_for_fn)] pub fn process_gpu_info(device_index: u32) -> Result<ProcessGpuInfo> {
#[cfg(all(windows, feature = "dxgi"))]
if let Some(d) = dxgi::query(device_index) {
return Ok(ProcessGpuInfo {
used_bytes: d.current_usage,
is_per_process: true,
source: GpuQuerySource::Dxgi,
});
}
#[cfg(feature = "nvml")]
if let Some(snap) = nvml::query(device_index)
&& let Some(used) = snap.process_used_bytes
{
return Ok(ProcessGpuInfo {
used_bytes: used,
is_per_process: true,
source: GpuQuerySource::Nvml,
});
}
#[cfg(feature = "nvidia-smi-fallback")]
if let Some(result) = nvidia_smi::query(device_index) {
return Ok(ProcessGpuInfo {
used_bytes: result.used_bytes,
is_per_process: false,
source: GpuQuerySource::NvidiaSmi,
});
}
bounds_check(device_index)?;
Err(HypomnesisError::NoGpuSource)
}
#[cfg(all(windows, feature = "dxgi"))]
#[must_use]
pub(crate) fn dxgi_non_nvidia_devices(starting_index: u32) -> Vec<(GpuDeviceInfo, ProcessGpuInfo)> {
dxgi::enumerate_non_nvidia()
.into_iter()
.enumerate()
.map(|(offset, entry)| {
#[allow(clippy::as_conversions, clippy::cast_possible_truncation)]
let index = starting_index.saturating_add(offset as u32);
let total_bytes = if entry.dedicated_video_memory > 0 {
entry.dedicated_video_memory
} else {
entry.shared_system_memory
};
let used_bytes = entry.current_usage;
let free_bytes = total_bytes.saturating_sub(used_bytes);
(
GpuDeviceInfo {
index,
name: entry.adapter_name,
total_bytes,
free_bytes,
used_bytes,
},
ProcessGpuInfo {
used_bytes,
is_per_process: true,
source: GpuQuerySource::Dxgi,
},
)
})
.collect()
}
#[allow(unused_variables)] #[allow(clippy::missing_const_for_fn)] pub fn gpu_processes(device_index: u32) -> Result<Vec<GpuProcessEntry>> {
#[cfg(feature = "nvml")]
if let Some(rows) = nvml::list_compute_processes(device_index) {
let entries: Vec<GpuProcessEntry> = rows
.into_iter()
.map(|(pid, used_bytes)| {
#[cfg(target_os = "linux")]
let name = read_proc_comm(pid);
#[cfg(not(target_os = "linux"))]
let name = None;
GpuProcessEntry {
pid,
name,
used_bytes,
source: GpuQuerySource::Nvml,
}
})
.collect();
return Ok(entries);
}
#[cfg(feature = "nvidia-smi-fallback")]
if let Some(rows) = nvidia_smi::query_compute_apps(device_index) {
let entries: Vec<GpuProcessEntry> = rows
.into_iter()
.map(|app| GpuProcessEntry {
pid: app.pid,
name: app.name,
used_bytes: app.used_bytes,
source: GpuQuerySource::NvidiaSmi,
})
.collect();
return Ok(entries);
}
bounds_check(device_index)?;
Err(HypomnesisError::NoGpuSource)
}
#[cfg(all(target_os = "linux", feature = "nvml"))]
fn read_proc_comm(pid: u32) -> Option<String> {
let path = format!("/proc/{pid}/comm");
let content = std::fs::read_to_string(&path).ok()?;
let trimmed = content.trim_end_matches('\n').trim();
if trimmed.is_empty() {
None
} else {
Some(trimmed.to_owned())
}
}
#[allow(unused_variables)] #[allow(clippy::missing_const_for_fn)] #[allow(clippy::unnecessary_wraps)] fn bounds_check(index: u32) -> Result<()> {
#[cfg(feature = "nvml")]
if let Some(count) = nvml::device_count() {
return if index >= count {
Err(HypomnesisError::DeviceIndexOutOfRange { index, count })
} else {
Ok(())
};
}
#[cfg(all(windows, feature = "dxgi"))]
if let Some(count) = dxgi::device_count() {
return if index >= count {
Err(HypomnesisError::DeviceIndexOutOfRange { index, count })
} else {
Ok(())
};
}
Ok(())
}