hardware_query/platform/
macos.rs

1/// Enhanced platform-specific hardware detection for macOS
2use crate::{HardwareQueryError, Result};
3use std::collections::HashMap;
4use std::process::Command;
5
6/// macOS-specific CPU information
7#[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    /// Query detailed CPU information from macOS sysctl
28    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        // Get CPU information using sysctl
48        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        // Get CPU features
90        cpu_info.features = Self::get_cpu_features()?;
91
92        Ok(cpu_info)
93    }
94
95    /// Get CPU features from macOS sysctl
96    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    /// Get CPU temperature using powermetrics (requires sudo)
120    pub fn get_temperature() -> Result<Option<f32>> {
121        // Try to get temperature using powermetrics
122        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        // Alternative: try using iStat or other tools
147        Ok(None)
148    }
149
150    /// Get detailed CPU architecture information
151    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/// macOS-specific GPU information
176#[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    /// Query GPU information from macOS system_profiler
189    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            // Parse system_profiler XML output
198            // This is a simplified implementation - a full implementation would parse XML
199
200            if output_str.contains("Graphics/Displays:") {
201                // Extract GPU information from system_profiler output
202                // This would require proper XML parsing
203            }
204        }
205
206        // Alternative: use ioreg command
207        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            // Parse ioreg output for GPU devices
213            // This is a simplified implementation
214        }
215
216        Ok(gpus)
217    }
218
219    /// Check Metal support and capabilities
220    pub fn get_metal_info() -> Result<HashMap<String, String>> {
221        let mut metal_info = HashMap::new();
222
223        // Use Metal command-line tools if available
224        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/// macOS-specific memory information
234#[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    /// Query memory information from macOS system tools
246    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        // Get physical memory
257        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        // Get memory usage details
263        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                        // Assuming 4KB page size
278                        let bytes = pages * 4096;
279
280                        match key {
281                            "Pages free" => {
282                                // Calculate available memory
283                            }
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        // Get memory pressure
296        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        // Get swap usage
302        if let Ok(output) = Command::new("sysctl").args(["-n", "vm.swapusage"]).output() {
303            let swap_str = String::from_utf8_lossy(&output.stdout);
304            // Parse swap usage string
305            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; // Convert to bytes
310                    }
311                }
312            }
313        }
314
315        Ok(mem_info)
316    }
317
318    /// Get detailed memory module information using system_profiler
319    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            // Parse system_profiler XML output for memory modules
328            // This would require proper XML parsing
329        }
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
346/// macOS-specific system information
347pub struct MacOSSystemInfo;
348
349impl MacOSSystemInfo {
350    /// Get system version and build information
351    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    /// Get hardware model information
382    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    /// Get system uptime
393    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            // Parse boottime and calculate uptime
400            // This would require parsing the boottime format
401            Ok(0) // Placeholder
402        } else {
403            Err(HardwareQueryError::system_info_unavailable(
404                "Cannot get uptime",
405            ))
406        }
407    }
408}