sklears_utils/
environment.rs

1//! Environment detection utilities for hardware and runtime capabilities
2//!
3//! This module provides utilities for detecting various aspects of the runtime environment
4//! including hardware capabilities, OS-specific features, compiler information, and
5//! performance characteristics.
6//!
7//! # Examples
8//!
9//! ```rust
10//! use sklears_utils::environment::{HardwareDetector, PerformanceCharacteristics};
11//!
12//! let hw = HardwareDetector::new();
13//! let cpu_count = hw.cpu_cores();
14//! let has_simd = hw.has_avx2();
15//!
16//! let perf = PerformanceCharacteristics::measure();
17//! println!("Memory bandwidth: {} GB/s", perf.memory_bandwidth_gbps);
18//! ```
19
20use std::sync::OnceLock;
21use std::time::Instant;
22
23/// Hardware capability detection
24#[derive(Debug, Clone)]
25pub struct HardwareDetector {
26    cpu_info: CpuInfo,
27    memory_info: MemoryInfo,
28    cache_info: CacheInfo,
29}
30
31#[derive(Debug, Clone)]
32pub struct CpuInfo {
33    pub cores: usize,
34    pub logical_cores: usize,
35    pub architecture: String,
36    pub vendor: String,
37    pub model_name: String,
38    pub base_frequency_mhz: Option<u32>,
39    pub max_frequency_mhz: Option<u32>,
40    pub features: Vec<String>,
41}
42
43#[derive(Debug, Clone)]
44pub struct MemoryInfo {
45    pub total_memory_bytes: u64,
46    pub available_memory_bytes: u64,
47    pub page_size_bytes: usize,
48}
49
50#[derive(Debug, Clone, Default)]
51pub struct CacheInfo {
52    pub l1_data_cache_kb: Option<u32>,
53    pub l1_instruction_cache_kb: Option<u32>,
54    pub l2_cache_kb: Option<u32>,
55    pub l3_cache_kb: Option<u32>,
56    pub cache_line_size_bytes: Option<u32>,
57}
58
59impl Default for CpuInfo {
60    fn default() -> Self {
61        Self {
62            cores: 1,
63            logical_cores: 1,
64            architecture: "unknown".to_string(),
65            vendor: "unknown".to_string(),
66            model_name: "unknown".to_string(),
67            base_frequency_mhz: None,
68            max_frequency_mhz: None,
69            features: Vec::new(),
70        }
71    }
72}
73
74impl Default for MemoryInfo {
75    fn default() -> Self {
76        Self {
77            total_memory_bytes: 0,
78            available_memory_bytes: 0,
79            page_size_bytes: 4096, // Common default
80        }
81    }
82}
83
84impl HardwareDetector {
85    /// Create new hardware detector and gather system information
86    pub fn new() -> Self {
87        Self {
88            cpu_info: Self::detect_cpu_info(),
89            memory_info: Self::detect_memory_info(),
90            cache_info: Self::detect_cache_info(),
91        }
92    }
93
94    /// Get number of physical CPU cores
95    pub fn cpu_cores(&self) -> usize {
96        self.cpu_info.cores
97    }
98
99    /// Get number of logical CPU cores (including hyperthreading)
100    pub fn logical_cores(&self) -> usize {
101        self.cpu_info.logical_cores
102    }
103
104    /// Get CPU architecture
105    pub fn cpu_architecture(&self) -> &str {
106        &self.cpu_info.architecture
107    }
108
109    /// Get CPU vendor
110    pub fn cpu_vendor(&self) -> &str {
111        &self.cpu_info.vendor
112    }
113
114    /// Get total system memory in bytes
115    pub fn total_memory(&self) -> u64 {
116        self.memory_info.total_memory_bytes
117    }
118
119    /// Get available system memory in bytes
120    pub fn available_memory(&self) -> u64 {
121        self.memory_info.available_memory_bytes
122    }
123
124    /// Check if CPU supports AVX2 instructions
125    pub fn has_avx2(&self) -> bool {
126        self.cpu_info.features.iter().any(|f| f.contains("avx2"))
127    }
128
129    /// Check if CPU supports AVX-512 instructions
130    pub fn has_avx512(&self) -> bool {
131        self.cpu_info.features.iter().any(|f| f.contains("avx512"))
132    }
133
134    /// Check if CPU supports SSE4.2 instructions
135    pub fn has_sse42(&self) -> bool {
136        self.cpu_info.features.iter().any(|f| f.contains("sse4_2"))
137    }
138
139    /// Check if CPU supports ARM NEON instructions
140    pub fn has_neon(&self) -> bool {
141        self.cpu_info.features.iter().any(|f| f.contains("neon"))
142    }
143
144    /// Check if running on ARM architecture
145    pub fn is_arm(&self) -> bool {
146        self.cpu_info.architecture.contains("arm") || self.cpu_info.architecture.contains("aarch")
147    }
148
149    /// Check if running on x86/x64 architecture
150    pub fn is_x86(&self) -> bool {
151        self.cpu_info.architecture.contains("x86") || self.cpu_info.architecture.contains("x64")
152    }
153
154    /// Get L3 cache size in KB
155    pub fn l3_cache_size_kb(&self) -> Option<u32> {
156        self.cache_info.l3_cache_kb
157    }
158
159    /// Get cache line size in bytes
160    pub fn cache_line_size(&self) -> Option<u32> {
161        self.cache_info.cache_line_size_bytes
162    }
163
164    /// Get all CPU features
165    pub fn cpu_features(&self) -> &[String] {
166        &self.cpu_info.features
167    }
168
169    /// Detect CPU information
170    fn detect_cpu_info() -> CpuInfo {
171        let mut cpu_info = CpuInfo {
172            logical_cores: num_cpus::get(),
173            cores: num_cpus::get_physical(),
174            architecture: std::env::consts::ARCH.to_string(),
175            ..Default::default()
176        };
177
178        // Platform-specific CPU detection
179        #[cfg(target_arch = "x86_64")]
180        {
181            Self::detect_x86_features(&mut cpu_info);
182        }
183
184        #[cfg(target_arch = "aarch64")]
185        {
186            Self::detect_arm_features(&mut cpu_info);
187        }
188
189        cpu_info
190    }
191
192    #[cfg(target_arch = "x86_64")]
193    fn detect_x86_features(cpu_info: &mut CpuInfo) {
194        if is_x86_feature_detected!("sse") {
195            cpu_info.features.push("sse".to_string());
196        }
197        if is_x86_feature_detected!("sse2") {
198            cpu_info.features.push("sse2".to_string());
199        }
200        if is_x86_feature_detected!("sse3") {
201            cpu_info.features.push("sse3".to_string());
202        }
203        if is_x86_feature_detected!("sse4.1") {
204            cpu_info.features.push("sse4_1".to_string());
205        }
206        if is_x86_feature_detected!("sse4.2") {
207            cpu_info.features.push("sse4_2".to_string());
208        }
209        if is_x86_feature_detected!("avx") {
210            cpu_info.features.push("avx".to_string());
211        }
212        if is_x86_feature_detected!("avx2") {
213            cpu_info.features.push("avx2".to_string());
214        }
215        if is_x86_feature_detected!("fma") {
216            cpu_info.features.push("fma".to_string());
217        }
218
219        // Try to detect vendor using cpuid if available
220        cpu_info.vendor = "Intel/AMD".to_string(); // Simplified
221    }
222
223    #[cfg(target_arch = "aarch64")]
224    fn detect_arm_features(cpu_info: &mut CpuInfo) {
225        cpu_info.vendor = "ARM".to_string();
226        cpu_info.features.push("neon".to_string()); // Most ARM64 has NEON
227    }
228
229    #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
230    fn detect_x86_features(_cpu_info: &mut CpuInfo) {}
231
232    #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
233    fn detect_arm_features(_cpu_info: &mut CpuInfo) {}
234
235    /// Detect memory information
236    fn detect_memory_info() -> MemoryInfo {
237        let mut memory_info = MemoryInfo::default();
238
239        // Use sysinfo for cross-platform memory detection
240        #[cfg(feature = "sysinfo")]
241        {
242            use sysinfo::System;
243            let mut system = System::new_all();
244            system.refresh_memory();
245            memory_info.total_memory_bytes = system.total_memory() * 1024; // sysinfo returns KB
246            memory_info.available_memory_bytes = system.available_memory() * 1024;
247        }
248
249        // Fallback memory detection without sysinfo
250        #[cfg(not(feature = "sysinfo"))]
251        {
252            // Platform-specific fallbacks
253            #[cfg(target_os = "linux")]
254            {
255                if let Ok(meminfo) = std::fs::read_to_string("/proc/meminfo") {
256                    for line in meminfo.lines() {
257                        if line.starts_with("MemTotal:") {
258                            if let Some(kb_str) = line.split_whitespace().nth(1) {
259                                if let Ok(kb) = kb_str.parse::<u64>() {
260                                    memory_info.total_memory_bytes = kb * 1024;
261                                }
262                            }
263                        } else if line.starts_with("MemAvailable:") {
264                            if let Some(kb_str) = line.split_whitespace().nth(1) {
265                                if let Ok(kb) = kb_str.parse::<u64>() {
266                                    memory_info.available_memory_bytes = kb * 1024;
267                                }
268                            }
269                        }
270                    }
271                }
272            }
273
274            #[cfg(target_os = "macos")]
275            {
276                // Use sysctl for macOS
277                if let Ok(output) = std::process::Command::new("sysctl")
278                    .arg("-n")
279                    .arg("hw.memsize")
280                    .output()
281                {
282                    if let Ok(mem_str) = String::from_utf8(output.stdout) {
283                        if let Ok(mem_bytes) = mem_str.trim().parse::<u64>() {
284                            memory_info.total_memory_bytes = mem_bytes;
285                            memory_info.available_memory_bytes = mem_bytes / 2; // Rough estimate
286                        }
287                    }
288                }
289            }
290
291            // Fallback: use a reasonable default for testing
292            if memory_info.total_memory_bytes == 0 {
293                memory_info.total_memory_bytes = 8 * 1024 * 1024 * 1024; // 8GB default
294                memory_info.available_memory_bytes = 4 * 1024 * 1024 * 1024; // 4GB available
295            }
296        }
297
298        // Fallback page size detection
299        #[cfg(unix)]
300        {
301            unsafe {
302                let page_size = libc::sysconf(libc::_SC_PAGESIZE);
303                if page_size > 0 {
304                    memory_info.page_size_bytes = page_size as usize;
305                }
306            }
307        }
308
309        memory_info
310    }
311
312    /// Detect cache information
313    fn detect_cache_info() -> CacheInfo {
314        let mut cache_info = CacheInfo::default();
315
316        // Platform-specific cache detection
317        #[cfg(target_os = "linux")]
318        {
319            Self::detect_linux_cache_info(&mut cache_info);
320        }
321
322        #[cfg(target_os = "macos")]
323        {
324            Self::detect_macos_cache_info(&mut cache_info);
325        }
326
327        #[cfg(target_os = "windows")]
328        {
329            Self::detect_windows_cache_info(&mut cache_info);
330        }
331
332        cache_info
333    }
334
335    #[cfg(target_os = "linux")]
336    fn detect_linux_cache_info(cache_info: &mut CacheInfo) {
337        // Try to read from /sys/devices/system/cpu/cpu0/cache/
338        if let Ok(l1d) = std::fs::read_to_string("/sys/devices/system/cpu/cpu0/cache/index0/size") {
339            if let Ok(size) = l1d.trim().strip_suffix("K").unwrap_or(&l1d).parse::<u32>() {
340                cache_info.l1_data_cache_kb = Some(size);
341            }
342        }
343
344        if let Ok(l2) = std::fs::read_to_string("/sys/devices/system/cpu/cpu0/cache/index2/size") {
345            if let Ok(size) = l2.trim().strip_suffix("K").unwrap_or(&l2).parse::<u32>() {
346                cache_info.l2_cache_kb = Some(size);
347            }
348        }
349
350        if let Ok(l3) = std::fs::read_to_string("/sys/devices/system/cpu/cpu0/cache/index3/size") {
351            if let Ok(size) = l3.trim().strip_suffix("K").unwrap_or(&l3).parse::<u32>() {
352                cache_info.l3_cache_kb = Some(size);
353            }
354        }
355
356        // Cache line size is typically 64 bytes on modern systems
357        cache_info.cache_line_size_bytes = Some(64);
358    }
359
360    #[cfg(target_os = "macos")]
361    fn detect_macos_cache_info(cache_info: &mut CacheInfo) {
362        // macOS sysctl approach would go here
363        cache_info.cache_line_size_bytes = Some(64); // Common on modern Apple hardware
364    }
365
366    #[cfg(target_os = "windows")]
367    fn detect_windows_cache_info(cache_info: &mut CacheInfo) {
368        // Windows GetLogicalProcessorInformation approach would go here
369        cache_info.cache_line_size_bytes = Some(64); // Common on modern systems
370    }
371
372    #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
373    fn detect_linux_cache_info(_cache_info: &mut CacheInfo) {}
374
375    #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
376    fn detect_macos_cache_info(_cache_info: &mut CacheInfo) {}
377
378    #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
379    fn detect_windows_cache_info(_cache_info: &mut CacheInfo) {}
380}
381
382impl Default for HardwareDetector {
383    fn default() -> Self {
384        Self::new()
385    }
386}
387
388/// Operating System specific utilities
389#[derive(Debug, Clone)]
390pub struct OSInfo {
391    pub name: String,
392    pub version: String,
393    pub arch: String,
394    pub kernel_version: Option<String>,
395    pub is_64bit: bool,
396}
397
398impl OSInfo {
399    /// Detect operating system information
400    pub fn detect() -> Self {
401        Self {
402            name: std::env::consts::OS.to_string(),
403            version: Self::detect_os_version(),
404            arch: std::env::consts::ARCH.to_string(),
405            kernel_version: Self::detect_kernel_version(),
406            is_64bit: cfg!(target_pointer_width = "64"),
407        }
408    }
409
410    /// Check if running on Linux
411    pub fn is_linux(&self) -> bool {
412        self.name == "linux"
413    }
414
415    /// Check if running on macOS
416    pub fn is_macos(&self) -> bool {
417        self.name == "macos"
418    }
419
420    /// Check if running on Windows
421    pub fn is_windows(&self) -> bool {
422        self.name == "windows"
423    }
424
425    /// Check if running on Unix-like system
426    pub fn is_unix(&self) -> bool {
427        cfg!(unix)
428    }
429
430    fn detect_os_version() -> String {
431        #[cfg(target_os = "linux")]
432        {
433            if let Ok(version) = std::fs::read_to_string("/proc/version") {
434                return version.lines().next().unwrap_or("unknown").to_string();
435            }
436        }
437
438        #[cfg(target_os = "macos")]
439        {
440            // Could use sw_vers command here
441            "macOS".to_string()
442        }
443        #[cfg(target_os = "windows")]
444        {
445            // Could use WinAPI here
446            "Windows".to_string()
447        }
448        #[cfg(not(any(target_os = "macos", target_os = "windows")))]
449        {
450            "unknown".to_string()
451        }
452    }
453
454    fn detect_kernel_version() -> Option<String> {
455        #[cfg(unix)]
456        {
457            if let Ok(output) = std::process::Command::new("uname").arg("-r").output() {
458                if let Ok(version) = String::from_utf8(output.stdout) {
459                    return Some(version.trim().to_string());
460                }
461            }
462        }
463        None
464    }
465}
466
467/// Compiler and runtime detection
468#[derive(Debug, Clone)]
469pub struct RuntimeInfo {
470    pub rust_version: String,
471    pub compiler: String,
472    pub target_triple: String,
473    pub build_profile: String,
474    pub features: Vec<String>,
475}
476
477impl RuntimeInfo {
478    /// Detect runtime information
479    pub fn detect() -> Self {
480        let target_family = std::env::var("CARGO_CFG_TARGET_FAMILY")
481            .unwrap_or_else(|_| std::env::consts::FAMILY.to_string());
482        let target_triple = std::env::var("CARGO_CFG_TARGET_TRIPLE").unwrap_or_else(|_| {
483            format!(
484                "{}-{}-{}",
485                std::env::consts::ARCH,
486                std::env::consts::FAMILY,
487                std::env::consts::OS
488            )
489        });
490
491        Self {
492            rust_version: option_env!("CARGO_PKG_RUST_VERSION")
493                .unwrap_or("unknown")
494                .to_string(),
495            compiler: format!("{target_family} {}", rustc_version_runtime::version()),
496            target_triple,
497            build_profile: if cfg!(debug_assertions) {
498                "debug"
499            } else {
500                "release"
501            }
502            .to_string(),
503            features: Self::detect_features(),
504        }
505    }
506
507    /// Check if running in debug mode
508    pub fn is_debug(&self) -> bool {
509        self.build_profile == "debug"
510    }
511
512    /// Check if running in release mode
513    pub fn is_release(&self) -> bool {
514        self.build_profile == "release"
515    }
516
517    fn detect_features() -> Vec<String> {
518        // Feature detection disabled due to missing features in Cargo.toml
519        // if cfg!(feature = "simd") {
520        //     features.push("simd".to_string());
521        // }
522        // if cfg!(feature = "parallel") {
523        //     features.push("parallel".to_string());
524        // }
525        // if cfg!(feature = "serde") {
526        //     features.push("serde".to_string());
527        // }
528
529        Vec::new()
530    }
531}
532
533/// Performance characteristics measurement
534#[derive(Debug, Clone)]
535pub struct PerformanceCharacteristics {
536    pub memory_bandwidth_gbps: f64,
537    pub l1_cache_latency_ns: f64,
538    pub l2_cache_latency_ns: f64,
539    pub l3_cache_latency_ns: f64,
540    pub memory_latency_ns: f64,
541    pub cpu_frequency_estimation_mhz: f64,
542    pub single_thread_performance_score: f64,
543    pub multi_thread_performance_score: f64,
544}
545
546impl PerformanceCharacteristics {
547    /// Measure system performance characteristics
548    pub fn measure() -> Self {
549        let _start_time = Instant::now();
550
551        let memory_bandwidth = Self::measure_memory_bandwidth();
552        let cache_latencies = Self::measure_cache_latencies();
553        let cpu_freq = Self::estimate_cpu_frequency();
554        let single_perf = Self::measure_single_thread_performance();
555        let multi_perf = Self::measure_multi_thread_performance();
556
557        Self {
558            memory_bandwidth_gbps: memory_bandwidth,
559            l1_cache_latency_ns: cache_latencies.0,
560            l2_cache_latency_ns: cache_latencies.1,
561            l3_cache_latency_ns: cache_latencies.2,
562            memory_latency_ns: cache_latencies.3,
563            cpu_frequency_estimation_mhz: cpu_freq,
564            single_thread_performance_score: single_perf,
565            multi_thread_performance_score: multi_perf,
566        }
567    }
568
569    fn measure_memory_bandwidth() -> f64 {
570        // Simple sequential memory access test
571        const SIZE: usize = 16 * 1024 * 1024; // 16MB
572        let mut data = vec![0u64; SIZE / 8];
573
574        let start = Instant::now();
575
576        // Sequential write
577        for (i, item) in data.iter_mut().enumerate() {
578            *item = i as u64;
579        }
580
581        // Sequential read
582        let mut sum = 0u64;
583        for &val in &data {
584            sum = sum.wrapping_add(val);
585        }
586
587        let elapsed = start.elapsed();
588        let bytes_processed = (SIZE * 2) as f64; // Read + write
589        let bandwidth_bps = bytes_processed / elapsed.as_secs_f64();
590
591        // Prevent optimization
592        std::hint::black_box(sum);
593
594        bandwidth_bps / 1e9 // Convert to GB/s
595    }
596
597    fn measure_cache_latencies() -> (f64, f64, f64, f64) {
598        // Simplified cache latency measurement
599        // In practice, this would be more sophisticated
600        (1.0, 3.0, 10.0, 100.0) // ns estimates for L1, L2, L3, RAM
601    }
602
603    fn estimate_cpu_frequency() -> f64 {
604        // Simple CPU frequency estimation using timing
605        let iterations = 10_000_000;
606        let start = Instant::now();
607
608        let mut counter = 0u64;
609        for _ in 0..iterations {
610            counter = counter.wrapping_add(1);
611        }
612
613        let elapsed = start.elapsed();
614        std::hint::black_box(counter);
615
616        // Very rough estimate - would need calibration
617        let cycles_per_second = iterations as f64 / elapsed.as_secs_f64();
618        cycles_per_second / 1e6 // Convert to MHz estimate
619    }
620
621    fn measure_single_thread_performance() -> f64 {
622        // Simple floating point benchmark
623        let start = Instant::now();
624        let mut sum = 0.0;
625
626        for i in 0..1_000_000 {
627            sum += (i as f64).sqrt().sin().cos();
628        }
629
630        let elapsed = start.elapsed();
631        std::hint::black_box(sum);
632
633        // Score based on operations per second
634        1_000_000.0 / elapsed.as_secs_f64()
635    }
636
637    fn measure_multi_thread_performance() -> f64 {
638        use std::thread;
639
640        let num_threads = num_cpus::get();
641        let start = Instant::now();
642
643        let handles: Vec<_> = (0..num_threads)
644            .map(|_| {
645                thread::spawn(|| {
646                    let mut sum = 0.0;
647                    for i in 0..100_000 {
648                        sum += (i as f64).sqrt().sin().cos();
649                    }
650                    sum
651                })
652            })
653            .collect();
654
655        let results: Vec<_> = handles.into_iter().map(|h| h.join().unwrap()).collect();
656        let elapsed = start.elapsed();
657
658        std::hint::black_box(results);
659
660        // Score based on total operations per second across all threads
661        (num_threads * 100_000) as f64 / elapsed.as_secs_f64()
662    }
663}
664
665/// Feature availability checker
666#[derive(Debug, Clone)]
667pub struct FeatureChecker {
668    hardware: HardwareDetector,
669    os_info: OSInfo,
670    #[allow(dead_code)]
671    runtime: RuntimeInfo,
672}
673
674impl FeatureChecker {
675    /// Create new feature checker
676    pub fn new() -> Self {
677        Self {
678            hardware: HardwareDetector::new(),
679            os_info: OSInfo::detect(),
680            runtime: RuntimeInfo::detect(),
681        }
682    }
683
684    /// Check if SIMD operations are available and beneficial
685    pub fn simd_available(&self) -> bool {
686        self.hardware.has_sse42() || self.hardware.has_avx2() || self.hardware.has_neon()
687    }
688
689    /// Check if parallel operations are beneficial
690    pub fn parallel_beneficial(&self) -> bool {
691        self.hardware.logical_cores() > 1
692    }
693
694    /// Check if memory mapping is available
695    pub fn memory_mapping_available(&self) -> bool {
696        self.os_info.is_unix() || self.os_info.is_windows()
697    }
698
699    /// Check if high-resolution timers are available
700    pub fn high_resolution_timer_available(&self) -> bool {
701        true // std::time::Instant should be high-resolution on all platforms
702    }
703
704    /// Get recommended number of threads for parallel operations
705    pub fn recommended_thread_count(&self) -> usize {
706        // Use logical cores but cap at reasonable limit
707        self.hardware.logical_cores().min(32)
708    }
709
710    /// Get recommended SIMD width in elements
711    pub fn recommended_simd_width(&self) -> usize {
712        if self.hardware.has_avx512() {
713            16 // 512 bits / 32 bits = 16 f32 elements
714        } else if self.hardware.has_avx2() {
715            8 // 256 bits / 32 bits = 8 f32 elements
716        } else if self.hardware.has_sse42() || self.hardware.has_neon() {
717            4 // 128 bits / 32 bits = 4 f32 elements
718        } else {
719            1 // No SIMD
720        }
721    }
722
723    /// Check if specific optimization is recommended
724    pub fn optimization_recommended(&self, optimization: &str) -> bool {
725        match optimization {
726            "simd" => self.simd_available(),
727            "parallel" => self.parallel_beneficial(),
728            "cache_friendly" => self.hardware.l3_cache_size_kb().is_some(),
729            "memory_pool" => self.hardware.total_memory() > 1_000_000_000, // > 1GB
730            _ => false,
731        }
732    }
733
734    /// Get optimization recommendations
735    pub fn get_recommendations(&self) -> Vec<String> {
736        let mut recommendations = Vec::new();
737
738        if self.simd_available() {
739            recommendations.push("Enable SIMD optimizations".to_string());
740        }
741
742        if self.parallel_beneficial() {
743            recommendations.push(format!(
744                "Use parallel processing with {} threads",
745                self.recommended_thread_count()
746            ));
747        }
748
749        if let Some(cache_size) = self.hardware.l3_cache_size_kb() {
750            recommendations.push(format!("Optimize for {cache_size}KB L3 cache"));
751        }
752
753        if self.hardware.total_memory() < 1_000_000_000 {
754            recommendations.push("Consider memory usage optimizations".to_string());
755        }
756
757        recommendations
758    }
759}
760
761impl Default for FeatureChecker {
762    fn default() -> Self {
763        Self::new()
764    }
765}
766
767/// Global environment information (lazy-initialized)
768static GLOBAL_ENV_INFO: OnceLock<EnvironmentInfo> = OnceLock::new();
769
770/// Complete environment information
771#[derive(Debug, Clone)]
772pub struct EnvironmentInfo {
773    pub hardware: HardwareDetector,
774    pub os_info: OSInfo,
775    pub runtime: RuntimeInfo,
776    pub performance: PerformanceCharacteristics,
777    pub features: FeatureChecker,
778}
779
780impl EnvironmentInfo {
781    /// Get global environment information (initialized once)
782    pub fn global() -> &'static EnvironmentInfo {
783        GLOBAL_ENV_INFO.get_or_init(EnvironmentInfo::detect)
784    }
785
786    /// Detect complete environment information
787    pub fn detect() -> Self {
788        let hardware = HardwareDetector::new();
789        let os_info = OSInfo::detect();
790        let runtime = RuntimeInfo::detect();
791        let performance = PerformanceCharacteristics::measure();
792        let features = FeatureChecker::new();
793
794        Self {
795            hardware,
796            os_info,
797            runtime,
798            performance,
799            features,
800        }
801    }
802
803    /// Generate environment summary
804    pub fn summary(&self) -> String {
805        format!(
806            "Environment Summary:\n\
807             OS: {} {} ({})\n\
808             CPU: {} cores ({} logical), {}\n\
809             Memory: {:.1} GB total, {:.1} GB available\n\
810             Runtime: {} {}\n\
811             Features: SIMD={}, Parallel={}, Cache={}KB",
812            self.os_info.name,
813            self.os_info.version,
814            self.os_info.arch,
815            self.hardware.cpu_cores(),
816            self.hardware.logical_cores(),
817            self.hardware.cpu_architecture(),
818            self.hardware.total_memory() as f64 / 1e9,
819            self.hardware.available_memory() as f64 / 1e9,
820            self.runtime.compiler,
821            self.runtime.build_profile,
822            self.features.simd_available(),
823            self.features.parallel_beneficial(),
824            self.hardware.l3_cache_size_kb().unwrap_or(0)
825        )
826    }
827}
828
829#[allow(non_snake_case)]
830#[cfg(test)]
831mod tests {
832    use super::*;
833
834    #[test]
835    fn test_hardware_detector() {
836        let hw = HardwareDetector::new();
837
838        assert!(hw.cpu_cores() >= 1);
839        assert!(hw.logical_cores() >= 1); // In containers, logical_cores may not equal physical cores
840        assert!(!hw.cpu_architecture().is_empty());
841        assert!(hw.total_memory() > 0);
842    }
843
844    #[test]
845    fn test_os_info() {
846        let os = OSInfo::detect();
847
848        assert!(!os.name.is_empty());
849        assert!(!os.arch.is_empty());
850
851        // At least one should be true
852        assert!(os.is_linux() || os.is_macos() || os.is_windows() || !os.name.is_empty());
853    }
854
855    #[test]
856    fn test_runtime_info() {
857        let runtime = RuntimeInfo::detect();
858
859        assert!(!runtime.compiler.is_empty());
860        assert!(!runtime.target_triple.is_empty());
861        assert!(runtime.is_debug() || runtime.is_release());
862    }
863
864    #[test]
865    fn test_performance_characteristics() {
866        let perf = PerformanceCharacteristics::measure();
867
868        assert!(perf.memory_bandwidth_gbps > 0.0);
869        assert!(perf.cpu_frequency_estimation_mhz > 0.0);
870        assert!(perf.single_thread_performance_score > 0.0);
871        assert!(perf.multi_thread_performance_score > 0.0);
872    }
873
874    #[test]
875    fn test_feature_checker() {
876        let checker = FeatureChecker::new();
877
878        assert!(checker.recommended_thread_count() >= 1);
879        assert!(checker.recommended_simd_width() >= 1);
880        assert!(checker.high_resolution_timer_available());
881    }
882
883    #[test]
884    fn test_environment_info() {
885        let env = EnvironmentInfo::detect();
886        let summary = env.summary();
887
888        assert!(!summary.is_empty());
889        assert!(summary.contains("Environment Summary"));
890
891        // Test global access
892        let global_env = EnvironmentInfo::global();
893        assert!(!global_env.summary().is_empty());
894    }
895
896    #[test]
897    fn test_cpu_features() {
898        let hw = HardwareDetector::new();
899
900        // These should not panic
901        let _has_avx2 = hw.has_avx2();
902        let _has_sse42 = hw.has_sse42();
903        let _has_neon = hw.has_neon();
904        let _is_arm = hw.is_arm();
905        let _is_x86 = hw.is_x86();
906    }
907
908    #[test]
909    fn test_optimization_recommendations() {
910        let checker = FeatureChecker::new();
911        let recommendations = checker.get_recommendations();
912
913        // Should have at least some recommendations
914        assert!(!recommendations.is_empty());
915
916        // Test specific optimizations
917        let _simd_rec = checker.optimization_recommended("simd");
918        let _parallel_rec = checker.optimization_recommended("parallel");
919        let _cache_rec = checker.optimization_recommended("cache_friendly");
920    }
921}