use std::collections::HashMap;
use std::ffi::CStr;
use std::os::raw::c_uint;
use nvml_wrapper::Nvml;
use nvml_wrapper::enum_wrappers::device::HostVgpuMode;
use nvml_wrapper::error::{nvml_try, nvml_try_count};
use crate::device::types::{VgpuHostInfo, VgpuInfo};
use crate::utils::get_hostname;
const VGPU_STRING_BUFFER: usize = 96;
const MAX_VGPU_INSTANCES_PER_DEVICE: usize = 256;
fn host_mode_label(mode: HostVgpuMode) -> &'static str {
match mode {
HostVgpuMode::NonSriov => "NonSriov",
HostVgpuMode::Sriov => "Sriov",
}
}
#[allow(dead_code)] fn host_mode_code(mode: HostVgpuMode) -> u32 {
match mode {
HostVgpuMode::NonSriov => 0,
HostVgpuMode::Sriov => 1,
}
}
pub fn collect_vgpu_info(nvml: &Nvml) -> Vec<VgpuHostInfo> {
let mut out = Vec::new();
let device_count = match nvml.device_count() {
Ok(n) => n,
Err(_) => return out,
};
let hostname = get_hostname();
for index in 0..device_count {
let device = match nvml.device_by_index(index) {
Ok(d) => d,
Err(_) => continue,
};
let host_mode = match device.vgpu_host_mode() {
Ok(m) => m,
Err(_) => continue,
};
let gpu_uuid = device.uuid().unwrap_or_else(|_| format!("GPU-{index}"));
let gpu_name = device.name().unwrap_or_else(|_| "Unknown GPU".to_string());
let (scheduler_policy, scheduler_arr_mode) = match device.vgpu_scheduler_state() {
Ok(state) => (state.scheduler_policy, state.arr_mode),
Err(_) => (0, 0),
};
let is_arr_supported = device
.vgpu_scheduler_capabilities()
.map(|caps| caps.is_arr_mode_supported)
.unwrap_or(false);
let vgpus = active_vgpus_ffi(nvml, &device)
.into_iter()
.map(|id| collect_single_vgpu(nvml, &device, id))
.collect();
let mut detail = HashMap::new();
detail.insert("vgpu_capable".to_string(), "true".to_string());
if is_arr_supported {
detail.insert("arr_supported".to_string(), "true".to_string());
}
out.push(VgpuHostInfo {
host_id: hostname.clone(),
hostname: hostname.clone(),
instance: hostname.clone(),
gpu_index: index,
gpu_uuid,
gpu_name,
host_mode: host_mode_label(host_mode).to_string(),
scheduler_policy,
scheduler_arr_mode,
is_arr_supported,
vgpus,
detail,
});
}
out
}
fn collect_single_vgpu(nvml: &Nvml, device: &nvml_wrapper::Device, instance_id: u32) -> VgpuInfo {
let uuid = vgpu_uuid(nvml, instance_id).unwrap_or_default();
let vm_id = vgpu_vm_id(nvml, instance_id).unwrap_or_default();
let fb_used_bytes = vgpu_fb_usage(nvml, instance_id).unwrap_or(0);
let (vgpu_type_name, fb_total_bytes) = vgpu_type_info(nvml, instance_id).unwrap_or_default();
let (gpu_utilization, memory_utilization, is_active) =
match device.vgpu_accounting_pids(instance_id) {
Ok(pids) if !pids.is_empty() => {
match device.vgpu_accounting_instance(instance_id, pids[0]) {
Ok(stats) => (
stats.gpu_utilization,
stats.memory_utilization,
stats.is_running,
),
Err(_) => (None, None, true),
}
}
_ => (None, None, false),
};
VgpuInfo {
instance_id,
uuid,
vm_id,
vgpu_type_name,
fb_used_bytes,
fb_total_bytes,
gpu_utilization,
memory_utilization,
is_active,
}
}
fn active_vgpus_ffi(nvml: &Nvml, device: &nvml_wrapper::Device) -> Vec<u32> {
let Ok(sym) = nvml.lib().nvmlDeviceGetActiveVgpus.as_ref() else {
return Vec::new();
};
unsafe {
let handle = device.handle();
let mut count: c_uint = 0;
if nvml_try_count(sym(handle, &mut count, core::ptr::null_mut())).is_err() {
return Vec::new();
}
if count == 0 {
return Vec::new();
}
if count as usize > MAX_VGPU_INSTANCES_PER_DEVICE {
return Vec::new();
}
let mut count_in: c_uint = count;
let mut buf: Vec<u32> = vec![0; count as usize];
if nvml_try(sym(handle, &mut count_in, buf.as_mut_ptr())).is_err() {
return Vec::new();
}
let written = (count_in as usize).min(buf.len());
buf.truncate(written);
buf
}
}
fn vgpu_uuid(nvml: &Nvml, instance_id: u32) -> Option<String> {
let sym = nvml.lib().nvmlVgpuInstanceGetUUID.as_ref().ok()?;
let mut buf = vec![0_i8; VGPU_STRING_BUFFER];
let result = unsafe {
sym(
instance_id,
buf.as_mut_ptr() as *mut _,
VGPU_STRING_BUFFER as c_uint,
)
};
nvml_try(result).ok()?;
let cstr = unsafe { CStr::from_ptr(buf.as_ptr() as *const _) };
cstr.to_str().ok().map(|s| s.to_string())
}
fn vgpu_vm_id(nvml: &Nvml, instance_id: u32) -> Option<String> {
let sym = nvml.lib().nvmlVgpuInstanceGetVmID.as_ref().ok()?;
let mut buf = vec![0_i8; VGPU_STRING_BUFFER];
let mut vm_id_type: c_uint = 0;
let result = unsafe {
sym(
instance_id,
buf.as_mut_ptr() as *mut _,
VGPU_STRING_BUFFER as c_uint,
&mut vm_id_type,
)
};
nvml_try(result).ok()?;
let cstr = unsafe { CStr::from_ptr(buf.as_ptr() as *const _) };
cstr.to_str().ok().map(|s| s.to_string())
}
fn vgpu_fb_usage(nvml: &Nvml, instance_id: u32) -> Option<u64> {
let sym = nvml.lib().nvmlVgpuInstanceGetFbUsage.as_ref().ok()?;
let mut usage: u64 = 0;
let result = unsafe { sym(instance_id, &mut usage) };
nvml_try(result).ok()?;
Some(usage)
}
fn vgpu_type_info(nvml: &Nvml, instance_id: u32) -> Option<(String, u64)> {
let type_sym = nvml.lib().nvmlVgpuInstanceGetType.as_ref().ok()?;
let mut type_id: c_uint = 0;
let result = unsafe { type_sym(instance_id, &mut type_id) };
nvml_try(result).ok()?;
let name_sym = nvml.lib().nvmlVgpuTypeGetName.as_ref().ok()?;
let mut buf = vec![0_i8; VGPU_STRING_BUFFER];
let mut size: c_uint = VGPU_STRING_BUFFER as c_uint;
let result = unsafe { name_sym(type_id, buf.as_mut_ptr() as *mut _, &mut size) };
let name = if nvml_try(result).is_ok() {
unsafe { CStr::from_ptr(buf.as_ptr() as *const _) }
.to_str()
.unwrap_or("")
.to_string()
} else {
String::new()
};
let fb_sym = nvml.lib().nvmlVgpuTypeGetFramebufferSize.as_ref().ok()?;
let mut fb_total: u64 = 0;
let result = unsafe { fb_sym(type_id, &mut fb_total) };
nvml_try(result).ok()?;
Some((name, fb_total))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn host_mode_label_maps_known_variants() {
assert_eq!(host_mode_label(HostVgpuMode::NonSriov), "NonSriov");
assert_eq!(host_mode_label(HostVgpuMode::Sriov), "Sriov");
}
#[test]
fn host_mode_code_is_stable() {
assert_eq!(host_mode_code(HostVgpuMode::NonSriov), 0);
assert_eq!(host_mode_code(HostVgpuMode::Sriov), 1);
}
}