Skip to main content

cbtop/app/
hardware.rs

1//! Hardware detection and system metrics types.
2
3/// Hardware information detected at startup
4#[derive(Debug, Clone)]
5pub struct HardwareInfo {
6    /// CPU model name
7    pub cpu_model: String,
8    /// Number of CPU cores
9    pub cpu_cores: usize,
10    /// SIMD capability
11    pub simd_type: &'static str,
12    /// GPU name (if available)
13    pub gpu_name: Option<String>,
14    /// Total system memory in GB
15    pub memory_gb: f64,
16}
17
18impl HardwareInfo {
19    /// Detect hardware at startup
20    pub fn detect() -> Self {
21        let cpu_cores = std::thread::available_parallelism()
22            .map(|p| p.get())
23            .unwrap_or(1);
24
25        // Detect SIMD capability
26        let simd_type = Self::detect_simd();
27
28        // Get CPU model via batuta-common
29        let cpu_model = batuta_common::sys::get_cpu_info();
30
31        // Try to get GPU name
32        let gpu_name = Self::detect_gpu();
33
34        // Get total memory
35        let memory_gb = Self::read_memory_gb();
36
37        Self {
38            cpu_model,
39            cpu_cores,
40            simd_type,
41            gpu_name,
42            memory_gb,
43        }
44    }
45
46    fn detect_simd() -> &'static str {
47        #[cfg(target_arch = "x86_64")]
48        {
49            if std::arch::is_x86_feature_detected!("avx512f") {
50                return "AVX-512";
51            }
52            if std::arch::is_x86_feature_detected!("avx2") {
53                return "AVX2";
54            }
55            if std::arch::is_x86_feature_detected!("avx") {
56                return "AVX";
57            }
58            if std::arch::is_x86_feature_detected!("sse4.2") {
59                return "SSE4.2";
60            }
61            "SSE2"
62        }
63        #[cfg(target_arch = "aarch64")]
64        {
65            "NEON"
66        }
67        #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
68        {
69            "Scalar"
70        }
71    }
72
73    fn detect_gpu() -> Option<String> {
74        #[cfg(target_os = "linux")]
75        {
76            Self::detect_gpu_linux()
77        }
78        #[cfg(target_os = "macos")]
79        {
80            return Self::detect_gpu_macos();
81        }
82        #[cfg(not(any(target_os = "linux", target_os = "macos")))]
83        {
84            None
85        }
86    }
87
88    #[cfg(target_os = "linux")]
89    fn detect_gpu_linux() -> Option<String> {
90        let output = std::process::Command::new("nvidia-smi")
91            .args(["--query-gpu=name", "--format=csv,noheader"])
92            .output()
93            .ok()?;
94        if !output.status.success() {
95            return None;
96        }
97        String::from_utf8(output.stdout)
98            .ok()
99            .map(|s| s.lines().next().unwrap_or("").trim().to_string())
100            .filter(|s| !s.is_empty())
101    }
102
103    #[cfg(target_os = "macos")]
104    fn detect_gpu_macos() -> Option<String> {
105        let output = std::process::Command::new("system_profiler")
106            .args(["SPDisplaysDataType"])
107            .output()
108            .ok()?;
109        if !output.status.success() {
110            return None;
111        }
112        let text = String::from_utf8_lossy(&output.stdout);
113        text.lines()
114            .find(|line| line.contains("Chipset Model:"))
115            .and_then(|line| line.split(':').nth(1))
116            .map(|s| s.trim().to_string())
117    }
118
119    fn read_memory_gb() -> f64 {
120        #[cfg(target_os = "linux")]
121        {
122            Self::read_memory_gb_linux()
123        }
124        #[cfg(target_os = "macos")]
125        {
126            return Self::read_memory_gb_macos();
127        }
128        #[cfg(not(any(target_os = "linux", target_os = "macos")))]
129        {
130            0.0
131        }
132    }
133
134    #[cfg(target_os = "linux")]
135    fn read_memory_gb_linux() -> f64 {
136        let contents = match std::fs::read_to_string("/proc/meminfo") {
137            Ok(c) => c,
138            Err(_) => return 0.0,
139        };
140        contents
141            .lines()
142            .find(|line| line.starts_with("MemTotal:"))
143            .and_then(|line| line.split_whitespace().nth(1))
144            .and_then(|kb_str| kb_str.parse::<u64>().ok())
145            .map_or(0.0, |kb| kb as f64 / 1_048_576.0)
146    }
147
148    #[cfg(target_os = "macos")]
149    fn read_memory_gb_macos() -> f64 {
150        let output = match std::process::Command::new("sysctl")
151            .args(["-n", "hw.memsize"])
152            .output()
153        {
154            Ok(o) => o,
155            Err(_) => return 0.0,
156        };
157        String::from_utf8(output.stdout)
158            .ok()
159            .and_then(|s| s.trim().parse::<u64>().ok())
160            .map_or(0.0, |bytes| bytes as f64 / 1_073_741_824.0)
161    }
162}
163
164/// Memory breakdown metrics (PMAT-012 UI-04)
165#[derive(Debug, Clone, Default)]
166pub struct MemoryBreakdown {
167    /// Total RAM in KB
168    pub total_kb: u64,
169    /// Used RAM in KB
170    pub used_kb: u64,
171    /// Cached RAM in KB
172    pub cached_kb: u64,
173    /// Buffers in KB
174    pub buffers_kb: u64,
175    /// Available RAM in KB
176    pub available_kb: u64,
177}
178
179impl MemoryBreakdown {
180    /// Usage percentage
181    pub fn usage_percent(&self) -> f64 {
182        if self.total_kb > 0 {
183            ((self.total_kb - self.available_kb) as f64 / self.total_kb as f64) * 100.0
184        } else {
185            0.0
186        }
187    }
188
189    /// Format KB as human-readable
190    pub fn format_kb(kb: u64) -> String {
191        if kb >= 1_048_576 {
192            format!("{:.1}G", kb as f64 / 1_048_576.0)
193        } else if kb >= 1024 {
194            format!("{:.1}M", kb as f64 / 1024.0)
195        } else {
196            format!("{}K", kb)
197        }
198    }
199}
200
201/// Network metrics (PMAT-012 UI-07 P2)
202#[derive(Debug, Clone, Default)]
203pub struct NetworkMetrics {
204    /// Total bytes received
205    pub rx_bytes: u64,
206    /// Total bytes transmitted
207    pub tx_bytes: u64,
208    /// Receive rate in bytes/sec
209    pub rx_rate: f64,
210    /// Transmit rate in bytes/sec
211    pub tx_rate: f64,
212}
213
214impl NetworkMetrics {
215    /// Format bytes as human-readable rate
216    pub fn format_rate(bytes_per_sec: f64) -> String {
217        if bytes_per_sec >= 1_073_741_824.0 {
218            format!("{:.1} GB/s", bytes_per_sec / 1_073_741_824.0)
219        } else if bytes_per_sec >= 1_048_576.0 {
220            format!("{:.1} MB/s", bytes_per_sec / 1_048_576.0)
221        } else if bytes_per_sec >= 1024.0 {
222            format!("{:.1} KB/s", bytes_per_sec / 1024.0)
223        } else {
224            format!("{:.0} B/s", bytes_per_sec)
225        }
226    }
227}
228
229/// Disk metrics (PMAT-012 UI-08 P2)
230#[derive(Debug, Clone, Default)]
231pub struct DiskMetrics {
232    /// Mount point
233    pub mount: String,
234    /// Total space in bytes
235    pub total_bytes: u64,
236    /// Used space in bytes
237    pub used_bytes: u64,
238    /// Usage percentage
239    pub usage_percent: f64,
240}
241
242impl DiskMetrics {
243    /// Format bytes as human-readable
244    pub fn format_bytes(bytes: u64) -> String {
245        batuta_common::fmt::format_bytes_compact(bytes)
246    }
247}
248
249/// Real-time load metrics
250#[derive(Debug, Clone, Default)]
251pub struct LoadMetrics {
252    /// Bricks executed per second
253    pub bricks_per_second: f64,
254    /// Total bricks executed
255    pub total_bricks: u64,
256    /// Average latency per brick in microseconds
257    pub avg_latency_us: f64,
258    /// Measured CPU usage from /proc/stat
259    pub cpu_usage: f64,
260    /// Per-core CPU usage (PMAT-012 UI-02)
261    pub per_core_usage: Vec<f64>,
262    /// Operations per second (FLOPS for GEMM)
263    pub ops_per_second: f64,
264    /// Bytes processed per second
265    pub bytes_per_second: f64,
266    /// Memory breakdown (PMAT-012 UI-04)
267    pub memory: MemoryBreakdown,
268    /// Network metrics (PMAT-012 UI-07 P2)
269    pub network: NetworkMetrics,
270    /// Disk metrics (PMAT-012 UI-08 P2)
271    pub disks: Vec<DiskMetrics>,
272}