use crate::domain::{GpuDevice, GpuVendor};
pub fn parse_nvidia_smi_output(output: &str) -> Result<Vec<GpuDevice>, String> {
let mut devices = Vec::new();
for line in output.lines() {
let line = line.trim();
if line.is_empty() {
continue;
}
let parts: Vec<&str> = line.split(',').map(|s| s.trim()).collect();
if parts.len() < 4 {
continue;
}
let index: u32 = parts[0].parse().unwrap_or(devices.len() as u32);
let name = parts[1].to_string();
let uuid = parts[2].to_string();
let memory_total_mb: u64 = parts[3].parse().unwrap_or(0);
let memory_free_mb = if parts.len() > 4 {
parts[4].parse().ok()
} else {
None
};
let pci_bus_id = if parts.len() > 5 {
Some(parts[5].to_string())
} else {
None
};
let driver_version = if parts.len() > 6 && !parts[6].is_empty() {
Some(parts[6].to_string())
} else {
None
};
let compute_capability = if parts.len() > 7 && !parts[7].is_empty() {
Some(parts[7].to_string())
} else {
None
};
let mut device = GpuDevice {
index,
name,
uuid,
memory_total_mb,
memory_free_mb,
pci_bus_id,
vendor: "NVIDIA".to_string(),
vendor_enum: GpuVendor::Nvidia,
driver_version,
compute_capability,
detection_method: "nvidia-smi".to_string(),
..Default::default()
};
device.set_memory_string();
devices.push(device);
}
Ok(devices)
}
pub fn parse_lspci_gpu_output(output: &str) -> Result<Vec<GpuDevice>, String> {
let mut devices = Vec::new();
let mut gpu_index = 0;
for line in output.lines() {
let line_lower = line.to_lowercase();
if !line_lower.contains("vga") && !line_lower.contains("3d") {
continue;
}
let pci_id = extract_pci_id(line);
let (vendor_enum, vendor_name) = if let Some(ref pci) = pci_id {
let vendor_id = pci.split(':').next().unwrap_or("");
let vendor = GpuVendor::from_pci_vendor(vendor_id);
(vendor.clone(), vendor.name().to_string())
} else {
(GpuVendor::Unknown, "Unknown".to_string())
};
let name = line
.split_once(':')
.map(|(_, rest)| rest.trim())
.unwrap_or(line)
.to_string();
let device = GpuDevice {
index: gpu_index,
name,
uuid: format!("lspci-gpu-{}", gpu_index),
pci_id: pci_id.clone().unwrap_or_default(),
vendor: vendor_name,
vendor_enum,
detection_method: "lspci".to_string(),
..Default::default()
};
devices.push(device);
gpu_index += 1;
}
Ok(devices)
}
fn extract_pci_id(line: &str) -> Option<String> {
let mut search_start = 0;
while let Some(start) = line[search_start..].find('[') {
let abs_start = search_start + start;
if let Some(end) = line[abs_start..].find(']') {
let bracket_content = &line[abs_start + 1..abs_start + end];
if bracket_content.len() == 9 && bracket_content.chars().nth(4) == Some(':') {
let parts: Vec<&str> = bracket_content.split(':').collect();
if parts.len() == 2
&& parts[0].len() == 4
&& parts[1].len() == 4
&& parts[0].chars().all(|c| c.is_ascii_hexdigit())
&& parts[1].chars().all(|c| c.is_ascii_hexdigit())
{
return Some(bracket_content.to_string());
}
}
search_start = abs_start + end + 1;
} else {
break;
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_nvidia_smi_output() {
let output = "0, NVIDIA GeForce RTX 3090, GPU-12345678-1234-1234-1234-123456789012, 24576, 24000, 00000000:01:00.0, 535.129.03, 8.6";
let devices = parse_nvidia_smi_output(output).unwrap();
assert_eq!(devices.len(), 1);
assert_eq!(devices[0].name, "NVIDIA GeForce RTX 3090");
assert_eq!(devices[0].memory_total_mb, 24576);
assert_eq!(devices[0].vendor, "NVIDIA");
}
#[test]
fn test_parse_lspci_gpu_output() {
let output = r#"01:00.0 VGA compatible controller [0300]: NVIDIA Corporation GA102 [GeForce RTX 3090] [10de:2204] (rev a1)
00:02.0 VGA compatible controller [0300]: Intel Corporation Device [8086:9a49] (rev 01)"#;
let devices = parse_lspci_gpu_output(output).unwrap();
assert_eq!(devices.len(), 2);
assert_eq!(devices[0].vendor, "NVIDIA");
assert_eq!(devices[1].vendor, "Intel");
}
#[test]
fn test_extract_pci_id() {
assert_eq!(extract_pci_id("[10de:2204]"), Some("10de:2204".to_string()));
assert_eq!(
extract_pci_id("NVIDIA [10de:2204] (rev a1)"),
Some("10de:2204".to_string())
);
assert_eq!(extract_pci_id("No PCI ID here"), None);
}
}