lean_ctx/core/
system_info.rs1use std::fmt;
5
6#[derive(Debug, Clone)]
7pub struct SystemInfo {
8 pub os: String,
9 pub arch: String,
10 pub cpu_brand: String,
11 pub cpu_cores: usize,
12 pub memory_gb: f64,
13}
14
15impl fmt::Display for SystemInfo {
16 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
17 write!(
18 f,
19 "{} {} | {} ({} cores) | {:.1} GB RAM",
20 self.os, self.arch, self.cpu_brand, self.cpu_cores, self.memory_gb
21 )
22 }
23}
24
25pub fn collect() -> SystemInfo {
26 SystemInfo {
27 os: std::env::consts::OS.to_string(),
28 arch: std::env::consts::ARCH.to_string(),
29 cpu_brand: read_cpu_brand(),
30 cpu_cores: std::thread::available_parallelism().map_or(1, std::num::NonZero::get),
31 memory_gb: read_memory_gb(),
32 }
33}
34
35fn read_cpu_brand() -> String {
36 #[cfg(target_os = "macos")]
37 {
38 std::process::Command::new("sysctl")
39 .args(["-n", "machdep.cpu.brand_string"])
40 .output()
41 .ok()
42 .and_then(|o| String::from_utf8(o.stdout).ok())
43 .map_or_else(|| "Unknown CPU".to_string(), |s| s.trim().to_string())
44 }
45 #[cfg(target_os = "linux")]
46 {
47 std::fs::read_to_string("/proc/cpuinfo")
48 .ok()
49 .and_then(|s| {
50 s.lines()
51 .find(|l| l.starts_with("model name"))
52 .and_then(|l| l.split(':').nth(1))
53 .map(|v| v.trim().to_string())
54 })
55 .unwrap_or_else(|| "Unknown CPU".to_string())
56 }
57 #[cfg(target_os = "windows")]
58 {
59 std::process::Command::new("powershell")
60 .args([
61 "-NoProfile",
62 "-Command",
63 "(Get-CimInstance Win32_Processor).Name",
64 ])
65 .output()
66 .ok()
67 .and_then(|o| String::from_utf8(o.stdout).ok())
68 .map(|s| s.trim().to_string())
69 .filter(|s| !s.is_empty())
70 .unwrap_or_else(|| "Unknown CPU".to_string())
71 }
72 #[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
73 {
74 "Unknown CPU".to_string()
75 }
76}
77
78fn read_memory_gb() -> f64 {
79 #[cfg(target_os = "macos")]
80 {
81 std::process::Command::new("sysctl")
82 .args(["-n", "hw.memsize"])
83 .output()
84 .ok()
85 .and_then(|o| String::from_utf8(o.stdout).ok())
86 .and_then(|s| s.trim().parse::<u64>().ok())
87 .map_or(0.0, |bytes| bytes as f64 / (1024.0 * 1024.0 * 1024.0))
88 }
89 #[cfg(target_os = "linux")]
90 {
91 std::fs::read_to_string("/proc/meminfo")
92 .ok()
93 .and_then(|s| {
94 s.lines().find(|l| l.starts_with("MemTotal")).and_then(|l| {
95 l.split_whitespace()
96 .nth(1)
97 .and_then(|v| v.parse::<u64>().ok())
98 })
99 })
100 .map_or(0.0, |kb| kb as f64 / (1024.0 * 1024.0))
101 }
102 #[cfg(target_os = "windows")]
103 {
104 std::process::Command::new("powershell")
107 .args([
108 "-NoProfile",
109 "-Command",
110 "(Get-CimInstance Win32_ComputerSystem).TotalPhysicalMemory",
111 ])
112 .output()
113 .ok()
114 .and_then(|o| String::from_utf8(o.stdout).ok())
115 .and_then(|s| s.trim().parse::<u64>().ok())
116 .map_or(0.0, |bytes| bytes as f64 / (1024.0 * 1024.0 * 1024.0))
117 }
118 #[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
119 {
120 0.0
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127
128 #[test]
129 fn collect_returns_valid_info() {
130 let info = collect();
131 assert!(!info.os.is_empty(), "`info.os` is empty");
132 assert!(!info.arch.is_empty(), "`info.arch` is empty");
133 assert!(info.cpu_cores >= 1);
134 }
135
136 #[test]
137 fn display_is_readable() {
138 let info = collect();
139 let s = format!("{info}");
140 assert!(s.contains(&info.os));
141 assert!(s.contains("cores"));
142 }
143}