use std::fmt; use std::str::FromStr; use sysinfo::{CpuRefreshKind, RefreshKind, System}; use raw_cpuid::CpuId; use wgpu::DeviceType; use std::collections::HashSet; mod build_info { include!(concat!(env!("OUT_DIR"), "/built.rs")); } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Memory { bytes: u64, } impl Memory { pub const fn from_bytes(bytes: u64) -> Self { Self { bytes } } pub const fn from_kb(kb: u64) -> Self { Self { bytes: kb * 1024 } } pub const fn from_mb(mb: u64) -> Self { Self { bytes: mb * 1024 * 1024 } } pub const fn from_gb(gb: u64) -> Self { Self { bytes: gb * 1024 * 1024 * 1024 } } pub const fn as_bytes(&self) -> u64 { self.bytes } pub const fn as_kb(&self) -> u64 { self.bytes / 1024 } pub const fn as_mb(&self) -> u64 { self.bytes / (1024 * 1024) } pub const fn as_gb(&self) -> u64 { self.bytes / (1024 * 1024 * 1024) } pub fn format_human(&self) -> String { const UNITS: [&str; 4] = ["B", "KB", "MB", "GB"]; let mut size = self.bytes as f64; let mut unit_index = 0; while size >= 1024.0 && unit_index < UNITS.len() - 1 { size /= 1024.0; unit_index += 1; } format!("{:.2} {}", size, UNITS[unit_index]) } pub fn verbose_info(&self) -> String { format!( "{} ({} bytes, {} KB, {} MB, {} GB)", self.format_human(), self.as_bytes(), self.as_kb(), self.as_mb(), self.as_gb() ) } } impl fmt::Display for Memory { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.format_human()) } } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum CPUBrand { Intel, AMD, SnapDragon, Apple, Other(String), } impl From<&str> for CPUBrand { fn from(s: &str) -> Self { let sl = s.to_lowercase(); if sl.contains("intel") { Self::Intel } else if sl.contains("amd") { Self::AMD } else if sl.contains("snapdragon") { Self::SnapDragon } else if sl.contains("apple") { Self::Apple } else { Self::Other(s.to_string()) } } } impl fmt::Display for CPUBrand { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Intel => write!(f, "Intel"), Self::AMD => write!(f, "AMD"), Self::SnapDragon => write!(f, "SnapDragon"), Self::Apple => write!(f, "Apple"), Self::Other(s) => write!(f, "{}", s), } } } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum CPUArch { X86, X86_64, AArch64, Other(String), } impl FromStr for CPUArch { type Err = (); fn from_str(s: &str) -> Result { Ok(match s { "x86" => Self::X86, "x86_64" => Self::X86_64, "arm" => Self::AArch64, "aarch64" => Self::AArch64, _ => Self::Other(s.to_string()), }) } } impl fmt::Display for CPUArch { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::X86 => write!(f, "x86"), Self::X86_64 => write!(f, "x86_64"), Self::AArch64 => write!(f, "AArch64"), Self::Other(s) => write!(f, "{}", s), } } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct ClockSpeed(pub u32); impl ClockSpeed { pub fn as_mhz(&self) -> u32 { self.0 } pub fn as_ghz(&self) -> f32 { self.0 as f32 / 1000.0 } } impl fmt::Display for ClockSpeed { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:.2} GHz ({} MHz)", self.as_ghz(), self.as_mhz()) } } #[derive(Debug, Clone, PartialEq, Eq)] pub struct CPU { pub brand: CPUBrand, pub arch: CPUArch, pub name: String, pub vendor_id: String, pub physical_cores: Option, pub logical_cores: Option, pub max_clock_speed: ClockSpeed, pub current_clock_speed: ClockSpeed, pub l1_cache: Option, pub l2_cache: Option, pub l3_cache: Option, } impl CPU { pub fn current() -> Self { let mut sys = System::new_with_specifics(RefreshKind::default().with_cpu(CpuRefreshKind::everything())); sys.refresh_cpu_all(); let cpu_opt = sys.cpus().first(); let brand = cpu_opt.map(|cpu| cpu.brand().into()) .unwrap_or(CPUBrand::Other("unknown".into())); let name = cpu_opt.map(|cpu| cpu.name().to_string()) .unwrap_or_else(|| "unknown".into()); let vendor_id = cpu_opt.map(|cpu| cpu.vendor_id().to_string()) .unwrap_or_else(|| "unknown".into()); let max_clock_speed = cpu_opt.map(|cpu| ClockSpeed(cpu.frequency() as u32)) .unwrap_or(ClockSpeed(0)); let current_clock_speed = max_clock_speed; let logical_cores = cpu_opt.map(|_| sys.cpus().len() as u8); let physical_cores = sysinfo::System::physical_core_count().map(|pc| pc as u8); let cpuid = CpuId::new(); let mut l1_cache = None; let mut l2_cache = None; let mut l3_cache = None; if let Some(iter) = cpuid.get_cache_parameters() { for cache in iter { match cache.level() { 1 => { let size = cache.physical_line_partitions() * cache.coherency_line_size() * cache.associativity(); if size > 0 { l1_cache = Some(Memory::from_bytes(size.try_into().unwrap())); } }, 2 => { let size = cache.physical_line_partitions() * cache.coherency_line_size() * cache.associativity(); if size > 0 { l2_cache = Some(Memory::from_bytes(size.try_into().unwrap())); } }, 3 => { let size = (cache.physical_line_partitions() as u64) * (cache.coherency_line_size() as u64) * (cache.associativity() as u64); if size > 0 { l3_cache = Some(Memory::from_bytes(size)); } }, _ => {} } } } Self { brand, arch: std::env::consts::ARCH.parse().unwrap_or(CPUArch::Other("unknown".into())), name, vendor_id, physical_cores, logical_cores, max_clock_speed, current_clock_speed, l1_cache, l2_cache, l3_cache, } } pub fn is_intel(&self) -> bool { matches!(self.brand, CPUBrand::Intel) } pub fn is_amd(&self) -> bool { matches!(self.brand, CPUBrand::AMD) } pub fn is_arm(&self) -> bool { matches!(self.brand,CPUBrand::Intel) } pub fn is_high_clock(&self) -> bool { self.max_clock_speed.0 > 3000 } pub fn verbose_info(&self) -> String { format!( "CPU Information:\n\ - Brand: {}\n\ - Architecture: {}\n\ - Name: {}\n\ - Vendor ID: {}\n\ - Cores: {} physical, {} logical\n\ - Clock Speed: {} (max), {} (current)\n\ - Cache: L1 {}, L2 {}, L3 {}", self.brand, self.arch, self.name, self.vendor_id, self.physical_cores.map(|c| c.to_string()).unwrap_or_else(|| "unknown".into()), self.logical_cores.map(|c| c.to_string()).unwrap_or_else(|| "unknown".into()), self.max_clock_speed, self.current_clock_speed, self.l1_cache.map(|c| c.format_human()).unwrap_or_else(|| "unknown".into()), self.l2_cache.map(|c| c.format_human()).unwrap_or_else(|| "unknown".into()), self.l3_cache.map(|c| c.format_human()).unwrap_or_else(|| "unknown".into()), ) } } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum GPUBrand { Nvidia, AMD, Intel, Other(String), } impl From<&str> for GPUBrand { fn from(s: &str) -> Self { let sl = s.to_lowercase(); if sl.contains("nvidia") || sl.contains("geforce") { Self::Nvidia } else if sl.contains("amd") || sl.contains("radeon") { Self::AMD } else if sl.contains("intel") { Self::Intel } else { Self::Other(s.to_string()) } } } impl fmt::Display for GPUBrand { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Nvidia => write!(f, "NVIDIA"), Self::AMD => write!(f, "AMD"), Self::Intel => write!(f, "Intel"), Self::Other(s) => write!(f, "{}", s), } } } #[derive(Debug, Clone, PartialEq, Eq)] pub struct GPU { pub brand: GPUBrand, pub name: String, pub device_type: DeviceType, pub vram: Memory, pub driver_version: Option, } impl GPU { pub fn current() -> Vec { let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { backends: wgpu::Backends::all(), ..Default::default() }); instance .enumerate_adapters(wgpu::Backends::all()) .iter() .map(|adapter| { let info = adapter.get_info(); GPU { brand: info.name.as_str().into(), name: info.name.to_string(), device_type: info.device_type, vram: Memory::from_bytes(0), driver_version: Some(info.driver.to_string()), } }) .collect() } pub fn is_integrated(&self) -> bool { matches!(self.brand, GPUBrand::Intel) } pub fn is_dedicated(&self) -> bool { !self.is_integrated() } pub fn is_mobile(&self) -> bool { let lower_name = self.name.to_lowercase(); lower_name.contains("adreno") || lower_name.contains("mali") || lower_name.contains("apple") || lower_name.contains("mobile") || lower_name.contains("snapdragon") } pub fn verbose_info(&self) -> String { format!( "GPU Information:\n\ - Brand: {}\n\ - Name: {}\n\ - VRAM: {}\n\ - Driver: {}", self.brand, self.name, self.vram.format_human(), self.driver_version.as_deref().unwrap_or("Unknown") ) } fn unique_id(&self) -> String { format!("{}-{:?}", self.name, self.device_type) } } #[derive(Debug, Clone, PartialEq, Eq)] pub struct SystemMemory { pub total: Memory, pub used: Memory, pub free: Memory, pub available: Memory, pub swap_total: Memory, pub swap_used: Memory, pub swap_free: Memory, } impl SystemMemory { pub fn current() -> Self { let mut system = System::new(); system.refresh_memory(); Self { total: Memory::from_bytes(system.total_memory()), used: Memory::from_bytes(system.used_memory()), free: Memory::from_bytes(system.free_memory()), available: Memory::from_bytes(system.available_memory()), swap_total: Memory::from_bytes(system.total_swap()), swap_used: Memory::from_bytes(system.used_swap()), swap_free: Memory::from_bytes(system.free_swap()), } } pub fn verbose_info(&self) -> String { format!( "Memory Information:\n\ - RAM: {} total, {} used, {} free, {} available\n\ - Swap: {} total, {} used, {} free", self.total.format_human(), self.used.format_human(), self.free.format_human(), self.available.format_human(), self.swap_total.format_human(), self.swap_used.format_human(), self.swap_free.format_human() ) } } #[derive(Debug, Clone, PartialEq, Eq)] pub struct CompileInfo { pub timestamp: String, pub pkg_version: String, pub pkg_name: String, pub target_arch: String, pub target_os: String, pub target_env: String, } impl CompileInfo { pub fn current() -> Self { Self { timestamp: build_info::BUILT_TIME_UTC.to_string(), pkg_version: build_info::PKG_VERSION.to_string(), pkg_name: build_info::PKG_NAME.to_string(), target_arch: build_info::CFG_TARGET_ARCH.to_string(), target_os: build_info::CFG_OS.to_string(), target_env: build_info::CFG_ENV.to_string(), } } pub fn verbose_info(&self) -> String { format!( "Build Information:\n\ - Timestamp: {}\n\ - Package: {} v{}\n\ - Target: {} {} {}\n\ ", self.timestamp, self.pkg_name, self.pkg_version, self.target_arch, self.target_os, self.target_env ) } } #[derive(Debug, Clone, PartialEq, Eq)] pub struct SystemMetadata { pub cpu: CPU, pub memory: SystemMemory, pub gpus: Vec, pub compile_info: CompileInfo, } impl SystemMetadata { pub fn current() -> Self { Self { cpu: CPU::current(), memory: SystemMemory::current(), gpus: GPU::current(), compile_info: CompileInfo::current(), } } pub fn main_gpu(&self) -> Option<&GPU> { self.gpus .iter() .find(|g| g.is_dedicated()) .or_else(|| self.gpus.iter().find(|g| g.is_integrated())) .or_else(|| self.gpus.first()) } pub fn verbose_summary(&self) -> String { let main_gpu = self.main_gpu(); let main_gpu_info = main_gpu .map(|gpu| format!("Main GPU:\n{}", gpu.verbose_info())) .unwrap_or_else(|| "No main GPU detected".to_string()); let mut seen_gpu_ids = HashSet::new(); let mut other_gpus_info = Vec::new(); if let Some(gpu) = main_gpu { seen_gpu_ids.insert(gpu.unique_id()); } for gpu in &self.gpus { let gpu_id = gpu.unique_id(); if !seen_gpu_ids.contains(&gpu_id) { other_gpus_info.push(format!("[{:?}] {}", gpu.device_type, gpu.verbose_info())); seen_gpu_ids.insert(gpu_id); } } let other_gpu_list = if other_gpus_info.is_empty() { String::new() } else { format!("Other GPUs:\n{}", other_gpus_info.join("\n\n")) }; format!( "System Metadata:\n\n{}\n\n{}\n\n{}\n\n{}\n\n{}", self.cpu.verbose_info(), main_gpu_info, other_gpu_list, self.memory.verbose_info(), self.compile_info.verbose_info() ) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_memory_conversions() { let mem = Memory::from_gb(8); assert_eq!(mem.as_bytes(), 8 * 1024 * 1024 * 1024); assert_eq!(mem.as_mb(), 8 * 1024); assert_eq!(mem.as_kb(), 8 * 1024 * 1024); } #[test] fn test_system_metadata() { let metadata = SystemMetadata::current(); assert!(!metadata.cpu.name.is_empty()); assert!(metadata.memory.total.as_bytes() > 0); assert!(!metadata.compile_info.pkg_version.is_empty()); } }