#[derive(Debug, Clone)]
pub struct HardwareInfo {
pub cpu_model: String,
pub cpu_cores: usize,
pub simd_type: &'static str,
pub gpu_name: Option<String>,
pub memory_gb: f64,
}
impl HardwareInfo {
pub fn detect() -> Self {
let cpu_cores = std::thread::available_parallelism()
.map(|p| p.get())
.unwrap_or(1);
let simd_type = Self::detect_simd();
let cpu_model = batuta_common::sys::get_cpu_info();
let gpu_name = Self::detect_gpu();
let memory_gb = Self::read_memory_gb();
Self {
cpu_model,
cpu_cores,
simd_type,
gpu_name,
memory_gb,
}
}
fn detect_simd() -> &'static str {
#[cfg(target_arch = "x86_64")]
{
if std::arch::is_x86_feature_detected!("avx512f") {
return "AVX-512";
}
if std::arch::is_x86_feature_detected!("avx2") {
return "AVX2";
}
if std::arch::is_x86_feature_detected!("avx") {
return "AVX";
}
if std::arch::is_x86_feature_detected!("sse4.2") {
return "SSE4.2";
}
"SSE2"
}
#[cfg(target_arch = "aarch64")]
{
"NEON"
}
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
{
"Scalar"
}
}
fn detect_gpu() -> Option<String> {
#[cfg(target_os = "linux")]
{
Self::detect_gpu_linux()
}
#[cfg(target_os = "macos")]
{
return Self::detect_gpu_macos();
}
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
{
None
}
}
#[cfg(target_os = "linux")]
fn detect_gpu_linux() -> Option<String> {
let output = std::process::Command::new("nvidia-smi")
.args(["--query-gpu=name", "--format=csv,noheader"])
.output()
.ok()?;
if !output.status.success() {
return None;
}
String::from_utf8(output.stdout)
.ok()
.map(|s| s.lines().next().unwrap_or("").trim().to_string())
.filter(|s| !s.is_empty())
}
#[cfg(target_os = "macos")]
fn detect_gpu_macos() -> Option<String> {
let output = std::process::Command::new("system_profiler")
.args(["SPDisplaysDataType"])
.output()
.ok()?;
if !output.status.success() {
return None;
}
let text = String::from_utf8_lossy(&output.stdout);
text.lines()
.find(|line| line.contains("Chipset Model:"))
.and_then(|line| line.split(':').nth(1))
.map(|s| s.trim().to_string())
}
fn read_memory_gb() -> f64 {
#[cfg(target_os = "linux")]
{
Self::read_memory_gb_linux()
}
#[cfg(target_os = "macos")]
{
return Self::read_memory_gb_macos();
}
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
{
0.0
}
}
#[cfg(target_os = "linux")]
fn read_memory_gb_linux() -> f64 {
let contents = match std::fs::read_to_string("/proc/meminfo") {
Ok(c) => c,
Err(_) => return 0.0,
};
contents
.lines()
.find(|line| line.starts_with("MemTotal:"))
.and_then(|line| line.split_whitespace().nth(1))
.and_then(|kb_str| kb_str.parse::<u64>().ok())
.map_or(0.0, |kb| kb as f64 / 1_048_576.0)
}
#[cfg(target_os = "macos")]
fn read_memory_gb_macos() -> f64 {
let output = match std::process::Command::new("sysctl")
.args(["-n", "hw.memsize"])
.output()
{
Ok(o) => o,
Err(_) => return 0.0,
};
String::from_utf8(output.stdout)
.ok()
.and_then(|s| s.trim().parse::<u64>().ok())
.map_or(0.0, |bytes| bytes as f64 / 1_073_741_824.0)
}
}
#[derive(Debug, Clone, Default)]
pub struct MemoryBreakdown {
pub total_kb: u64,
pub used_kb: u64,
pub cached_kb: u64,
pub buffers_kb: u64,
pub available_kb: u64,
}
impl MemoryBreakdown {
pub fn usage_percent(&self) -> f64 {
if self.total_kb > 0 {
((self.total_kb - self.available_kb) as f64 / self.total_kb as f64) * 100.0
} else {
0.0
}
}
pub fn format_kb(kb: u64) -> String {
if kb >= 1_048_576 {
format!("{:.1}G", kb as f64 / 1_048_576.0)
} else if kb >= 1024 {
format!("{:.1}M", kb as f64 / 1024.0)
} else {
format!("{}K", kb)
}
}
}
#[derive(Debug, Clone, Default)]
pub struct NetworkMetrics {
pub rx_bytes: u64,
pub tx_bytes: u64,
pub rx_rate: f64,
pub tx_rate: f64,
}
impl NetworkMetrics {
pub fn format_rate(bytes_per_sec: f64) -> String {
if bytes_per_sec >= 1_073_741_824.0 {
format!("{:.1} GB/s", bytes_per_sec / 1_073_741_824.0)
} else if bytes_per_sec >= 1_048_576.0 {
format!("{:.1} MB/s", bytes_per_sec / 1_048_576.0)
} else if bytes_per_sec >= 1024.0 {
format!("{:.1} KB/s", bytes_per_sec / 1024.0)
} else {
format!("{:.0} B/s", bytes_per_sec)
}
}
}
#[derive(Debug, Clone, Default)]
pub struct DiskMetrics {
pub mount: String,
pub total_bytes: u64,
pub used_bytes: u64,
pub usage_percent: f64,
}
impl DiskMetrics {
pub fn format_bytes(bytes: u64) -> String {
batuta_common::fmt::format_bytes_compact(bytes)
}
}
#[derive(Debug, Clone, Default)]
pub struct LoadMetrics {
pub bricks_per_second: f64,
pub total_bricks: u64,
pub avg_latency_us: f64,
pub cpu_usage: f64,
pub per_core_usage: Vec<f64>,
pub ops_per_second: f64,
pub bytes_per_second: f64,
pub memory: MemoryBreakdown,
pub network: NetworkMetrics,
pub disks: Vec<DiskMetrics>,
}