Skip to main content

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