1use crate::{HardwareQueryError, Result};
3use std::collections::HashMap;
4use std::process::Command;
5
6#[derive(Debug, Clone)]
8pub struct MacOSCPUInfo {
9 pub brand_string: String,
10 pub vendor: String,
11 pub cpu_type: String,
12 pub cpu_subtype: String,
13 pub physical_cpu: u32,
14 pub logical_cpu: u32,
15 pub cpu_freq: u64,
16 pub cpu_freq_max: u64,
17 pub cpu_freq_min: u64,
18 pub l1_icache_size: u64,
19 pub l1_dcache_size: u64,
20 pub l2_cache_size: u64,
21 pub l3_cache_size: u64,
22 pub cache_line_size: u64,
23 pub features: Vec<String>,
24}
25
26impl MacOSCPUInfo {
27 pub fn query() -> Result<Self> {
29 let mut cpu_info = MacOSCPUInfo {
30 brand_string: String::new(),
31 vendor: String::new(),
32 cpu_type: String::new(),
33 cpu_subtype: String::new(),
34 physical_cpu: 0,
35 logical_cpu: 0,
36 cpu_freq: 0,
37 cpu_freq_max: 0,
38 cpu_freq_min: 0,
39 l1_icache_size: 0,
40 l1_dcache_size: 0,
41 l2_cache_size: 0,
42 l3_cache_size: 0,
43 cache_line_size: 0,
44 features: Vec::new(),
45 };
46
47 let sysctl_queries = [
49 ("machdep.cpu.brand_string", "brand_string"),
50 ("machdep.cpu.vendor", "vendor"),
51 ("hw.cputype", "cpu_type"),
52 ("hw.cpusubtype", "cpu_subtype"),
53 ("hw.physicalcpu", "physical_cpu"),
54 ("hw.logicalcpu", "logical_cpu"),
55 ("hw.cpufrequency", "cpu_freq"),
56 ("hw.cpufrequency_max", "cpu_freq_max"),
57 ("hw.cpufrequency_min", "cpu_freq_min"),
58 ("hw.l1icachesize", "l1_icache_size"),
59 ("hw.l1dcachesize", "l1_dcache_size"),
60 ("hw.l2cachesize", "l2_cache_size"),
61 ("hw.l3cachesize", "l3_cache_size"),
62 ("hw.cachelinesize", "cache_line_size"),
63 ];
64
65 for (sysctl_key, field_name) in &sysctl_queries {
66 if let Ok(output) = Command::new("sysctl").args(["-n", sysctl_key]).output() {
67 let value = String::from_utf8_lossy(&output.stdout).trim().to_string();
68
69 match *field_name {
70 "brand_string" => cpu_info.brand_string = value,
71 "vendor" => cpu_info.vendor = value,
72 "cpu_type" => cpu_info.cpu_type = value,
73 "cpu_subtype" => cpu_info.cpu_subtype = value,
74 "physical_cpu" => cpu_info.physical_cpu = value.parse().unwrap_or(0),
75 "logical_cpu" => cpu_info.logical_cpu = value.parse().unwrap_or(0),
76 "cpu_freq" => cpu_info.cpu_freq = value.parse().unwrap_or(0),
77 "cpu_freq_max" => cpu_info.cpu_freq_max = value.parse().unwrap_or(0),
78 "cpu_freq_min" => cpu_info.cpu_freq_min = value.parse().unwrap_or(0),
79 "l1_icache_size" => cpu_info.l1_icache_size = value.parse().unwrap_or(0),
80 "l1_dcache_size" => cpu_info.l1_dcache_size = value.parse().unwrap_or(0),
81 "l2_cache_size" => cpu_info.l2_cache_size = value.parse().unwrap_or(0),
82 "l3_cache_size" => cpu_info.l3_cache_size = value.parse().unwrap_or(0),
83 "cache_line_size" => cpu_info.cache_line_size = value.parse().unwrap_or(0),
84 _ => {}
85 }
86 }
87 }
88
89 cpu_info.features = Self::get_cpu_features()?;
91
92 Ok(cpu_info)
93 }
94
95 fn get_cpu_features() -> Result<Vec<String>> {
97 let mut features = Vec::new();
98
99 let feature_queries = [
100 "machdep.cpu.features",
101 "machdep.cpu.leaf7_features",
102 "machdep.cpu.extfeatures",
103 ];
104
105 for query in &feature_queries {
106 if let Ok(output) = Command::new("sysctl").args(["-n", query]).output() {
107 let feature_str = String::from_utf8_lossy(&output.stdout);
108 let query_features: Vec<String> = feature_str
109 .split_whitespace()
110 .map(|s| s.to_string())
111 .collect();
112 features.extend(query_features);
113 }
114 }
115
116 Ok(features)
117 }
118
119 pub fn get_temperature() -> Result<Option<f32>> {
121 if let Ok(output) = Command::new("powermetrics")
123 .args([
124 "--samplers",
125 "smc",
126 "-n",
127 "1",
128 "--hide-cpu-duty-cycle",
129 "--show-process-coalition",
130 ])
131 .output()
132 {
133 let output_str = String::from_utf8_lossy(&output.stdout);
134 for line in output_str.lines() {
135 if line.contains("CPU die temperature") {
136 if let Some(temp_str) = line.split(':').nth(1) {
137 let temp_str = temp_str.trim().replace("C", "");
138 if let Ok(temp) = temp_str.parse::<f32>() {
139 return Ok(Some(temp));
140 }
141 }
142 }
143 }
144 }
145
146 Ok(None)
148 }
149
150 pub fn get_architecture_info() -> Result<HashMap<String, String>> {
152 let mut arch_info = HashMap::new();
153
154 let arch_queries = [
155 ("hw.targettype", "target_type"),
156 ("hw.machine", "machine"),
157 ("hw.model", "model"),
158 ("machdep.cpu.family", "cpu_family"),
159 ("machdep.cpu.model", "cpu_model"),
160 ("machdep.cpu.stepping", "stepping"),
161 ("machdep.cpu.microcode_version", "microcode_version"),
162 ];
163
164 for (sysctl_key, info_key) in &arch_queries {
165 if let Ok(output) = Command::new("sysctl").args(["-n", sysctl_key]).output() {
166 let value = String::from_utf8_lossy(&output.stdout).trim().to_string();
167 arch_info.insert(info_key.to_string(), value);
168 }
169 }
170
171 Ok(arch_info)
172 }
173}
174
175#[derive(Debug, Clone)]
177pub struct MacOSGPUInfo {
178 pub device_name: String,
179 pub vendor_name: String,
180 pub device_id: String,
181 pub vendor_id: String,
182 pub pci_class: String,
183 pub metal_support: bool,
184 pub memory_mb: u64,
185}
186
187impl MacOSGPUInfo {
188 pub fn query_all() -> Result<Vec<Self>> {
190 let mut gpus = Vec::new();
191
192 if let Ok(output) = Command::new("system_profiler")
193 .args(["SPDisplaysDataType", "-xml"])
194 .output()
195 {
196 let output_str = String::from_utf8_lossy(&output.stdout);
197 if output_str.contains("Graphics/Displays:") {
201 }
204 }
205
206 if let Ok(output) = Command::new("ioreg")
208 .args(["-r", "-d", "1", "-w", "0", "-c", "IOPCIDevice"])
209 .output()
210 {
211 let output_str = String::from_utf8_lossy(&output.stdout);
212 }
215
216 Ok(gpus)
217 }
218
219 pub fn get_metal_info() -> Result<HashMap<String, String>> {
221 let mut metal_info = HashMap::new();
222
223 if let Ok(output) = Command::new("xcrun").args(["metal", "-version"]).output() {
225 let version_str = String::from_utf8_lossy(&output.stdout);
226 metal_info.insert("metal_version".to_string(), version_str.trim().to_string());
227 }
228
229 Ok(metal_info)
230 }
231}
232
233#[derive(Debug, Clone)]
235pub struct MacOSMemoryInfo {
236 pub physical_memory: u64,
237 pub user_memory: u64,
238 pub wired_memory: u64,
239 pub compressed_memory: u64,
240 pub memory_pressure: String,
241 pub swap_usage: u64,
242}
243
244impl MacOSMemoryInfo {
245 pub fn query() -> Result<Self> {
247 let mut mem_info = MacOSMemoryInfo {
248 physical_memory: 0,
249 user_memory: 0,
250 wired_memory: 0,
251 compressed_memory: 0,
252 memory_pressure: String::new(),
253 swap_usage: 0,
254 };
255
256 if let Ok(output) = Command::new("sysctl").args(["-n", "hw.memsize"]).output() {
258 let mem_str = String::from_utf8_lossy(&output.stdout);
259 mem_info.physical_memory = mem_str.trim().parse().unwrap_or(0);
260 }
261
262 if let Ok(output) = Command::new("vm_stat").output() {
264 let vm_stat_str = String::from_utf8_lossy(&output.stdout);
265
266 for line in vm_stat_str.lines() {
267 if let Some((key, value)) = line.split_once(':') {
268 let key = key.trim();
269 let value = value
270 .trim()
271 .replace(".", "")
272 .replace("pages", "")
273 .trim()
274 .to_string();
275
276 if let Ok(pages) = value.parse::<u64>() {
277 let bytes = pages * 4096;
279
280 match key {
281 "Pages free" => {
282 }
284 "Pages wired down" => mem_info.wired_memory = bytes,
285 "Pages active" => mem_info.user_memory += bytes,
286 "Pages inactive" => mem_info.user_memory += bytes,
287 "Pages occupied by compressor" => mem_info.compressed_memory = bytes,
288 _ => {}
289 }
290 }
291 }
292 }
293 }
294
295 if let Ok(output) = Command::new("memory_pressure").output() {
297 let pressure_str = String::from_utf8_lossy(&output.stdout);
298 mem_info.memory_pressure = pressure_str.trim().to_string();
299 }
300
301 if let Ok(output) = Command::new("sysctl").args(["-n", "vm.swapusage"]).output() {
303 let swap_str = String::from_utf8_lossy(&output.stdout);
304 if let Some(used_start) = swap_str.find("used = ") {
306 if let Some(used_end) = swap_str[used_start + 7..].find("M") {
307 let used_str = &swap_str[used_start + 7..used_start + 7 + used_end];
308 if let Ok(used_mb) = used_str.parse::<u64>() {
309 mem_info.swap_usage = used_mb * 1024 * 1024; }
311 }
312 }
313 }
314
315 Ok(mem_info)
316 }
317
318 pub fn get_memory_modules() -> Result<Vec<MacOSMemoryModule>> {
320 let mut modules = Vec::new();
321
322 if let Ok(output) = Command::new("system_profiler")
323 .args(["SPMemoryDataType", "-xml"])
324 .output()
325 {
326 let output_str = String::from_utf8_lossy(&output.stdout);
327 }
330
331 Ok(modules)
332 }
333}
334
335#[derive(Debug, Clone)]
336pub struct MacOSMemoryModule {
337 pub size_gb: u64,
338 pub speed_mhz: u32,
339 pub memory_type: String,
340 pub manufacturer: String,
341 pub part_number: String,
342 pub serial_number: String,
343 pub slot: String,
344}
345
346pub struct MacOSSystemInfo;
348
349impl MacOSSystemInfo {
350 pub fn get_system_version() -> Result<HashMap<String, String>> {
352 let mut version_info = HashMap::new();
353
354 if let Ok(output) = Command::new("sw_vers").output() {
355 let version_str = String::from_utf8_lossy(&output.stdout);
356
357 for line in version_str.lines() {
358 if let Some((key, value)) = line.split_once(':') {
359 let key = key.trim();
360 let value = value.trim();
361
362 match key {
363 "ProductName" => {
364 version_info.insert("product_name".to_string(), value.to_string())
365 }
366 "ProductVersion" => {
367 version_info.insert("product_version".to_string(), value.to_string())
368 }
369 "BuildVersion" => {
370 version_info.insert("build_version".to_string(), value.to_string())
371 }
372 _ => None,
373 };
374 }
375 }
376 }
377
378 Ok(version_info)
379 }
380
381 pub fn get_hardware_model() -> Result<String> {
383 if let Ok(output) = Command::new("sysctl").args(["-n", "hw.model"]).output() {
384 Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
385 } else {
386 Err(HardwareQueryError::system_info_unavailable(
387 "Cannot get hardware model",
388 ))
389 }
390 }
391
392 pub fn get_uptime() -> Result<u64> {
394 if let Ok(output) = Command::new("sysctl")
395 .args(["-n", "kern.boottime"])
396 .output()
397 {
398 let boottime_str = String::from_utf8_lossy(&output.stdout);
399 Ok(0) } else {
403 Err(HardwareQueryError::system_info_unavailable(
404 "Cannot get uptime",
405 ))
406 }
407 }
408}