quantrs2_core/platform/
detector.rs

1//! Platform detection implementation
2
3use super::capabilities::*;
4use std::env;
5
6/// Detect comprehensive platform capabilities
7pub fn detect_platform_capabilities() -> PlatformCapabilities {
8    // Try to use SciRS2's platform detection if available
9    // TODO: Use SciRS2's platform detection when available
10
11    // Fallback to our own detection
12    PlatformCapabilities {
13        cpu: detect_cpu_capabilities(),
14        gpu: detect_gpu_capabilities(),
15        memory: detect_memory_capabilities(),
16        platform_type: detect_platform_type(),
17        os: detect_operating_system(),
18        architecture: detect_architecture(),
19    }
20}
21
22/// Detect CPU capabilities
23fn detect_cpu_capabilities() -> CpuCapabilities {
24    let logical_cores = num_cpus::get();
25    let physical_cores = num_cpus::get_physical();
26
27    CpuCapabilities {
28        physical_cores,
29        logical_cores,
30        simd: detect_simd_capabilities(),
31        cache: detect_cache_info(),
32        base_clock_mhz: detect_cpu_frequency(),
33        vendor: detect_cpu_vendor(),
34        model_name: detect_cpu_model(),
35    }
36}
37
38/// Detect CPU frequency in MHz
39fn detect_cpu_frequency() -> Option<f32> {
40    use sysinfo::System;
41
42    let mut sys = System::new();
43    sys.refresh_cpu_all();
44
45    // Get frequency from first CPU (all cores typically have same base frequency)
46    sys.cpus().first().map(|cpu| cpu.frequency() as f32)
47}
48
49/// Detect SIMD capabilities
50fn detect_simd_capabilities() -> SimdCapabilities {
51    // Try to use SciRS2's SIMD detection if available
52    // TODO: Use SciRS2's SIMD capability detection when available
53
54    #[cfg(target_arch = "x86_64")]
55    {
56        SimdCapabilities {
57            sse: is_x86_feature_detected!("sse"),
58            sse2: is_x86_feature_detected!("sse2"),
59            sse3: is_x86_feature_detected!("sse3"),
60            ssse3: is_x86_feature_detected!("ssse3"),
61            sse4_1: is_x86_feature_detected!("sse4.1"),
62            sse4_2: is_x86_feature_detected!("sse4.2"),
63            avx: is_x86_feature_detected!("avx"),
64            avx2: is_x86_feature_detected!("avx2"),
65            avx512: cfg!(target_feature = "avx512f"),
66            fma: is_x86_feature_detected!("fma"),
67            neon: false,
68            sve: false,
69        }
70    }
71
72    #[cfg(target_arch = "aarch64")]
73    {
74        SimdCapabilities {
75            sse: false,
76            sse2: false,
77            sse3: false,
78            ssse3: false,
79            sse4_1: false,
80            sse4_2: false,
81            avx: false,
82            avx2: false,
83            avx512: false,
84            fma: false,
85            neon: cfg!(target_feature = "neon"),
86            sve: cfg!(target_feature = "sve"),
87        }
88    }
89
90    #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
91    {
92        SimdCapabilities {
93            sse: false,
94            sse2: false,
95            sse3: false,
96            ssse3: false,
97            sse4_1: false,
98            sse4_2: false,
99            avx: false,
100            avx2: false,
101            avx512: false,
102            fma: false,
103            neon: false,
104            sve: false,
105        }
106    }
107}
108
109/// Detect cache information
110const fn detect_cache_info() -> CacheInfo {
111    // Basic implementation - can be enhanced with platform-specific detection
112    CacheInfo {
113        l1_data: Some(32 * 1024),        // 32KB default
114        l1_instruction: Some(32 * 1024), // 32KB default
115        l2: Some(256 * 1024),            // 256KB default
116        l3: Some(8 * 1024 * 1024),       // 8MB default
117        line_size: Some(64),             // 64 byte cache line default
118    }
119}
120
121/// Detect CPU vendor
122fn detect_cpu_vendor() -> String {
123    use sysinfo::System;
124
125    let mut sys = System::new();
126    sys.refresh_cpu_all();
127
128    // Extract vendor from CPU brand string
129    if let Some(cpu) = sys.cpus().first() {
130        let brand = cpu.brand();
131        if brand.contains("Intel") {
132            return "Intel".to_string();
133        } else if brand.contains("AMD") {
134            return "AMD".to_string();
135        } else if brand.contains("Apple") {
136            return "Apple".to_string();
137        } else if brand.contains("ARM") {
138            return "ARM".to_string();
139        } else if brand.contains("Qualcomm") {
140            return "Qualcomm".to_string();
141        }
142        // Return brand if no known vendor found
143        brand.to_string()
144    } else {
145        "Unknown".to_string()
146    }
147}
148
149/// Detect CPU model
150fn detect_cpu_model() -> String {
151    use sysinfo::System;
152
153    let mut sys = System::new();
154    sys.refresh_cpu_all();
155
156    // Get CPU brand/model name
157    sys.cpus()
158        .first()
159        .map(|cpu| cpu.brand().to_string())
160        .unwrap_or_else(|| "Unknown".to_string())
161}
162
163/// Detect GPU capabilities
164const fn detect_gpu_capabilities() -> GpuCapabilities {
165    // Check for GPU availability
166    let devices = Vec::new();
167
168    // Try to detect WebGPU devices (cross-platform)
169    // Note: This is a placeholder - actual implementation would use wgpu
170
171    GpuCapabilities {
172        available: false,
173        devices,
174        primary_device: None,
175    }
176}
177
178/// Detect memory capabilities
179fn detect_memory_capabilities() -> MemoryCapabilities {
180    use sysinfo::System;
181
182    let mut sys = System::new_all();
183    sys.refresh_memory();
184
185    MemoryCapabilities {
186        total_memory: sys.total_memory() as usize,
187        available_memory: sys.available_memory() as usize,
188        bandwidth_gbps: detect_memory_bandwidth(),
189        numa_nodes: detect_numa_nodes(),
190        hugepage_support: detect_hugepage_support(),
191    }
192}
193
194/// Detect memory bandwidth in GB/s
195fn detect_memory_bandwidth() -> Option<f32> {
196    #[cfg(target_os = "linux")]
197    {
198        // Try to read DMI information
199        if let Ok(output) = std::process::Command::new("dmidecode")
200            .args(["-t", "memory"])
201            .output()
202        {
203            if output.status.success() {
204                if let Ok(text) = String::from_utf8(output.stdout) {
205                    // Look for "Speed:" lines in DMI output
206                    for line in text.lines() {
207                        if line.contains("Speed:") && line.contains("MT/s") {
208                            // Extract speed value
209                            if let Some(speed_str) = line.split_whitespace().nth(1) {
210                                if let Ok(speed_mts) = speed_str.parse::<f32>() {
211                                    // Estimate bandwidth: speed (MT/s) * bus width (8 bytes) / 1000
212                                    // This is a rough estimate assuming DDR with 64-bit bus
213                                    let bandwidth_gbps = (speed_mts * 8.0) / 1000.0;
214                                    return Some(bandwidth_gbps);
215                                }
216                            }
217                        }
218                    }
219                }
220            }
221        }
222
223        // Fallback: estimate based on total memory
224        // Modern DDR4: ~20-40 GB/s, DDR5: ~40-80 GB/s
225        Some(25.0) // Conservative estimate
226    }
227
228    #[cfg(target_os = "macos")]
229    {
230        // macOS: Use sysctl to get memory info
231        if let Ok(output) = std::process::Command::new("sysctl")
232            .arg("hw.memsize")
233            .output()
234        {
235            if output.status.success() {
236                // Estimate based on Apple Silicon vs Intel
237                // M1/M2/M3: ~100-400 GB/s unified memory
238                // Intel: ~20-40 GB/s
239                if std::process::Command::new("sysctl")
240                    .arg("machdep.cpu.brand_string")
241                    .output()
242                    .ok()
243                    .and_then(|o| String::from_utf8(o.stdout).ok())
244                    .map(|s| s.contains("Apple"))
245                    .unwrap_or(false)
246                {
247                    return Some(200.0); // Apple Silicon estimate
248                }
249                return Some(30.0); // Intel Mac estimate
250            }
251        }
252        Some(30.0)
253    }
254
255    #[cfg(target_os = "windows")]
256    {
257        // Windows: Rough estimate based on typical RAM speeds
258        // DDR4-3200: ~25 GB/s, DDR4-2666: ~21 GB/s
259        Some(25.0)
260    }
261
262    #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
263    {
264        None
265    }
266}
267
268/// Detect number of NUMA nodes
269fn detect_numa_nodes() -> usize {
270    #[cfg(target_os = "linux")]
271    {
272        // Check /sys/devices/system/node/ for node directories
273        if let Ok(entries) = std::fs::read_dir("/sys/devices/system/node") {
274            let node_count = entries
275                .filter_map(|e| e.ok())
276                .filter(|e| {
277                    e.file_name().to_string_lossy().starts_with("node") && e.file_name() != "node"
278                })
279                .count();
280
281            if node_count > 0 {
282                return node_count;
283            }
284        }
285
286        // Fallback: try numactl
287        if let Ok(output) = std::process::Command::new("numactl")
288            .arg("--hardware")
289            .output()
290        {
291            if output.status.success() {
292                if let Ok(text) = String::from_utf8(output.stdout) {
293                    // Look for "available: N nodes"
294                    for line in text.lines() {
295                        if line.contains("available:") && line.contains("nodes") {
296                            if let Some(word) = line.split_whitespace().nth(1) {
297                                if let Ok(n) = word.parse::<usize>() {
298                                    return n;
299                                }
300                            }
301                        }
302                    }
303                }
304            }
305        }
306
307        1 // Default to 1 NUMA node
308    }
309
310    #[cfg(target_os = "macos")]
311    {
312        // macOS typically doesn't expose NUMA topology on consumer hardware
313        // Server-grade Mac Pros might have NUMA, but it's not common
314        1
315    }
316
317    #[cfg(target_os = "windows")]
318    {
319        // Windows: Could use GetNumaHighestNodeNumber, but requires unsafe FFI
320        // For now, assume single NUMA node unless on server hardware
321        // Most desktop/laptop systems have 1 NUMA node
322        1
323    }
324
325    #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
326    {
327        1
328    }
329}
330
331/// Detect hugepage support
332fn detect_hugepage_support() -> bool {
333    #[cfg(target_os = "linux")]
334    {
335        std::path::Path::new("/sys/kernel/mm/hugepages").exists()
336    }
337    #[cfg(not(target_os = "linux"))]
338    {
339        false
340    }
341}
342
343/// Detect platform type
344fn detect_platform_type() -> PlatformType {
345    // Check for cloud/container environments
346    if env::var("KUBERNETES_SERVICE_HOST").is_ok()
347        || env::var("ECS_CONTAINER_METADATA_URI").is_ok()
348        || env::var("AWS_EXECUTION_ENV").is_ok()
349        || env::var("GOOGLE_CLOUD_PROJECT").is_ok()
350        || env::var("AZURE_FUNCTIONS_ENVIRONMENT").is_ok()
351    {
352        return PlatformType::Cloud;
353    }
354
355    // Check for mobile platforms
356    if cfg!(target_os = "android") || cfg!(target_os = "ios") {
357        return PlatformType::Mobile;
358    }
359
360    // Detect server vs desktop based on hardware characteristics
361    let logical_cores = num_cpus::get();
362    let physical_cores = num_cpus::get_physical();
363
364    use sysinfo::System;
365    let mut sys = System::new_all();
366    sys.refresh_memory();
367    let total_memory_gb = sys.total_memory() / (1024 * 1024 * 1024);
368
369    // Server heuristics:
370    // - High core count (>16 logical cores)
371    // - Large memory (>64 GB)
372    // - NUMA nodes > 1
373    // - Specific CPU model indicators
374    let is_server = logical_cores > 16
375        || total_memory_gb > 64
376        || detect_numa_nodes() > 1
377        || detect_cpu_model().contains("Xeon")
378        || detect_cpu_model().contains("EPYC")
379        || detect_cpu_model().contains("Threadripper");
380
381    if is_server {
382        PlatformType::Server
383    } else if cfg!(any(target_arch = "arm", target_arch = "aarch64")) && !cfg!(target_os = "macos")
384    {
385        // ARM but not macOS might be embedded
386        PlatformType::Embedded
387    } else {
388        PlatformType::Desktop
389    }
390}
391
392/// Detect operating system
393const fn detect_operating_system() -> OperatingSystem {
394    #[cfg(target_os = "linux")]
395    {
396        OperatingSystem::Linux
397    }
398    #[cfg(target_os = "windows")]
399    {
400        OperatingSystem::Windows
401    }
402    #[cfg(target_os = "macos")]
403    {
404        OperatingSystem::MacOS
405    }
406    #[cfg(target_os = "freebsd")]
407    {
408        OperatingSystem::FreeBSD
409    }
410    #[cfg(target_os = "android")]
411    {
412        OperatingSystem::Android
413    }
414    #[cfg(not(any(
415        target_os = "linux",
416        target_os = "windows",
417        target_os = "macos",
418        target_os = "freebsd",
419        target_os = "android"
420    )))]
421    {
422        OperatingSystem::Unknown
423    }
424}
425
426/// Detect architecture
427const fn detect_architecture() -> Architecture {
428    #[cfg(target_arch = "x86_64")]
429    {
430        Architecture::X86_64
431    }
432    #[cfg(target_arch = "aarch64")]
433    {
434        Architecture::Aarch64
435    }
436    #[cfg(target_arch = "riscv64")]
437    {
438        Architecture::Riscv64
439    }
440    #[cfg(target_arch = "wasm32")]
441    {
442        Architecture::Wasm32
443    }
444    #[cfg(not(any(
445        target_arch = "x86_64",
446        target_arch = "aarch64",
447        target_arch = "riscv64",
448        target_arch = "wasm32"
449    )))]
450    {
451        Architecture::Unknown
452    }
453}