Skip to main content

laminar_core/detect/
mod.rs

1//! # Automatic Hardware/Software Feature Detection
2//!
3//! Runtime detection and automatic configuration of platform-specific features.
4//!
5//! ## Overview
6//!
7//! This module provides unified detection of system capabilities including:
8//!
9//! - **Kernel Version**: Linux kernel features (`io_uring`, XDP requirements)
10//! - **CPU Features**: SIMD capabilities (AVX2, AVX-512, NEON), cache configuration
11//! - **I/O Capabilities**: `io_uring` support levels, storage type detection
12//! - **Network**: XDP/eBPF availability
13//! - **Memory**: NUMA topology, huge pages, total memory
14//!
15//! ## Usage
16//!
17//! ```rust
18//! use laminar_core::detect::SystemCapabilities;
19//!
20//! // Detect all system capabilities (cached after first call)
21//! let caps = SystemCapabilities::detect();
22//!
23//! // Print system summary
24//! println!("{}", caps.summary());
25//!
26//! // Get recommended configuration
27//! let config = caps.recommended_config();
28//! println!("Recommended cores: {}", config.num_cores);
29//! println!("Use io_uring: {}", config.use_io_uring);
30//! ```
31//!
32//! ## Auto-Configuration
33//!
34//! Use [`SystemCapabilities::recommended_config()`] to get an optimized configuration
35//! based on detected hardware:
36//!
37//! ```rust,ignore
38//! use laminar_core::detect::SystemCapabilities;
39//! use laminar_core::tpc::TpcConfig;
40//! use laminar_core::io_uring::IoUringConfig;
41//!
42//! let caps = SystemCapabilities::detect();
43//! let recommended = caps.recommended_config();
44//!
45//! // Apply to TPC runtime
46//! let tpc_config = TpcConfig::builder()
47//!     .num_cores(recommended.num_cores)
48//!     .cpu_pinning(recommended.cpu_pinning)
49//!     .numa_aware(recommended.numa_aware)
50//!     .build()?;
51//! ```
52//!
53//! ## Platform Support
54//!
55//! | Feature | Linux | Windows | macOS |
56//! |---------|-------|---------|-------|
57//! | Kernel version | Full | N/A | Darwin |
58//! | CPU features | Full | Full | Full |
59//! | NUMA nodes | Full | Single | Single |
60//! | io_uring | Full | N/A | N/A |
61//! | XDP | Full | N/A | N/A |
62//! | Storage detection | Full | Limited | Limited |
63//! | Huge pages | Full | Limited | Limited |
64
65mod config;
66mod cpu;
67mod io;
68mod kernel;
69
70pub use config::{PerformanceTier, RecommendedConfig};
71pub use cpu::{
72    cache_line_size, is_smt_enabled, logical_cpu_count, physical_cpu_count, CpuFeatures, SimdLevel,
73};
74pub use io::{IoUringCapabilities, MemoryInfo, StorageInfo, StorageType, XdpCapabilities};
75pub use kernel::KernelVersion;
76
77use crate::numa::NumaTopology;
78use std::path::Path;
79use std::sync::OnceLock;
80
81/// Detected system capabilities.
82///
83/// This struct aggregates all detected hardware and software capabilities
84/// into a single view, making it easy to configure `LaminarDB` optimally.
85///
86/// Detection is performed lazily and cached for efficiency.
87#[derive(Debug, Clone)]
88pub struct SystemCapabilities {
89    // ===== Platform =====
90    /// Detected platform.
91    pub platform: Platform,
92    /// Detected kernel version (Linux/macOS only).
93    pub kernel_version: Option<KernelVersion>,
94
95    // ===== CPU =====
96    /// Number of logical CPUs.
97    pub cpu_count: usize,
98    /// Number of physical CPU cores.
99    pub physical_cores: usize,
100    /// Number of NUMA nodes.
101    pub numa_nodes: usize,
102    /// Cache line size in bytes.
103    pub cache_line_size: usize,
104    /// CPU feature flags.
105    pub cpu_features: CpuFeatures,
106
107    // ===== I/O =====
108    /// `io_uring` capabilities.
109    pub io_uring: IoUringCapabilities,
110    /// XDP capabilities.
111    pub xdp: XdpCapabilities,
112    /// Storage information (for data directory).
113    pub storage: StorageInfo,
114
115    // ===== Memory =====
116    /// Memory information.
117    pub memory: MemoryInfo,
118}
119
120/// Global cached capabilities.
121static CACHED_CAPABILITIES: OnceLock<SystemCapabilities> = OnceLock::new();
122
123impl SystemCapabilities {
124    /// Detect all system capabilities.
125    ///
126    /// This method is safe to call multiple times - results are cached
127    /// after the first detection.
128    #[must_use]
129    pub fn detect() -> &'static Self {
130        CACHED_CAPABILITIES.get_or_init(Self::detect_uncached)
131    }
132
133    /// Detect capabilities without caching.
134    ///
135    /// Use [`detect()`](Self::detect) for normal usage.
136    #[must_use]
137    pub fn detect_uncached() -> Self {
138        let kernel_version = KernelVersion::detect();
139        let cpu_features = CpuFeatures::detect();
140        let numa_topology = NumaTopology::detect();
141
142        Self {
143            platform: Platform::detect(),
144            kernel_version,
145
146            cpu_count: logical_cpu_count(),
147            physical_cores: physical_cpu_count(),
148            numa_nodes: numa_topology.num_nodes(),
149            cache_line_size: cache_line_size(),
150            cpu_features,
151
152            io_uring: IoUringCapabilities::from_kernel_version(kernel_version.as_ref()),
153            xdp: XdpCapabilities::from_kernel_version(kernel_version.as_ref()),
154            storage: StorageInfo::default(), // Detect lazily with detect_storage()
155
156            memory: MemoryInfo::detect(),
157        }
158    }
159
160    /// Detect capabilities with storage detection for a specific path.
161    #[must_use]
162    pub fn detect_with_storage<P: AsRef<Path>>(data_path: P) -> Self {
163        let mut caps = Self::detect_uncached();
164        caps.storage = StorageInfo::detect(data_path);
165        caps
166    }
167
168    /// Update storage detection for a new path.
169    pub fn update_storage<P: AsRef<Path>>(&mut self, data_path: P) {
170        self.storage = StorageInfo::detect(data_path);
171    }
172
173    /// Get recommended configuration based on detected capabilities.
174    #[must_use]
175    pub fn recommended_config(&self) -> RecommendedConfig {
176        RecommendedConfig::from_capabilities(self)
177    }
178
179    /// Generate a human-readable summary of all capabilities.
180    #[must_use]
181    pub fn summary(&self) -> String {
182        use std::fmt::Write;
183
184        let mut s = String::new();
185
186        let _ = writeln!(s, "System Capabilities:");
187        let _ = writeln!(s, "  Platform: {}", self.platform);
188        if let Some(ref kv) = self.kernel_version {
189            let _ = writeln!(s, "  Kernel: {kv}");
190        }
191
192        let _ = writeln!(s);
193        let _ = writeln!(s, "CPU:");
194        let _ = writeln!(s, "  Logical CPUs: {}", self.cpu_count);
195        let _ = writeln!(s, "  Physical cores: {}", self.physical_cores);
196        let _ = writeln!(s, "  NUMA nodes: {}", self.numa_nodes);
197        let _ = writeln!(s, "  Cache line: {} bytes", self.cache_line_size);
198        let _ = writeln!(s, "  SIMD: {}", self.cpu_features.simd_level());
199        let _ = writeln!(s, "  Features: {}", self.cpu_features.summary());
200
201        let _ = writeln!(s);
202        let _ = writeln!(s, "I/O:");
203        let _ = writeln!(s, "  {}", self.io_uring.summary());
204        let _ = writeln!(s, "  {}", self.xdp.summary());
205        let _ = writeln!(s, "  Storage: {}", self.storage.summary());
206
207        let _ = writeln!(s);
208        let _ = writeln!(s, "Memory:");
209        let _ = writeln!(s, "  {}", self.memory.summary());
210
211        let recommended = self.recommended_config();
212        let _ = writeln!(s);
213        let _ = writeln!(s, "Performance Tier: {}", recommended.performance_tier());
214
215        s
216    }
217
218    /// Check if all advanced features are available.
219    #[must_use]
220    pub fn is_fully_optimized(&self) -> bool {
221        self.io_uring.is_usable() && self.io_uring.sqpoll_supported && self.cpu_count > 1
222    }
223
224    /// Check if the system meets minimum requirements for `LaminarDB`.
225    #[must_use]
226    pub fn meets_minimum_requirements(&self) -> bool {
227        // Minimum: 1 CPU, 512MB RAM
228        self.cpu_count >= 1 && self.memory.total_memory >= 512 * 1024 * 1024
229    }
230
231    /// Get a list of missing features that would improve performance.
232    #[must_use]
233    pub fn missing_features(&self) -> Vec<&'static str> {
234        let mut missing = Vec::new();
235
236        if !self.io_uring.feature_enabled {
237            missing.push("io_uring feature not enabled (compile with --features io-uring)");
238        } else if !self.io_uring.available {
239            missing.push("io_uring not available (requires Linux 5.1+)");
240        }
241
242        if !self.io_uring.sqpoll_supported && self.io_uring.available {
243            missing.push("io_uring SQPOLL not supported (requires Linux 5.11+)");
244        }
245
246        if !self.io_uring.iopoll_supported && self.io_uring.available {
247            missing.push("io_uring IOPOLL not supported (requires Linux 5.19+ and NVMe)");
248        }
249
250        if !self.xdp.feature_enabled {
251            missing.push("XDP feature not enabled (compile with --features xdp)");
252        }
253
254        if self.numa_nodes <= 1 && self.cpu_count > 8 {
255            missing.push("NUMA topology not detected (may need hwloc feature)");
256        }
257
258        if !self.memory.huge_pages_available {
259            missing.push("Huge pages not available");
260        }
261
262        missing
263    }
264
265    /// Log the detected capabilities.
266    pub fn log_summary(&self) {
267        tracing::info!("Platform: {}", self.platform);
268        if let Some(ref kv) = self.kernel_version {
269            tracing::info!("Kernel: {kv}");
270        }
271        tracing::info!(
272            "CPU: {} logical, {} physical, {} NUMA nodes",
273            self.cpu_count,
274            self.physical_cores,
275            self.numa_nodes
276        );
277        tracing::info!("SIMD: {}", self.cpu_features.simd_level());
278        tracing::info!("io_uring: {}", self.io_uring.summary());
279        tracing::info!("XDP: {}", self.xdp.summary());
280        tracing::info!("Memory: {:.1} GB", self.memory.total_memory_gb());
281        tracing::info!(
282            "Performance tier: {}",
283            self.recommended_config().performance_tier()
284        );
285    }
286}
287
288/// Detected platform.
289#[derive(Debug, Clone, Copy, PartialEq, Eq)]
290pub enum Platform {
291    /// Linux
292    Linux,
293    /// macOS
294    MacOS,
295    /// Windows
296    Windows,
297    /// FreeBSD
298    FreeBSD,
299    /// Other/unknown
300    Other,
301}
302
303impl Platform {
304    /// Detect the current platform.
305    #[must_use]
306    pub const fn detect() -> Self {
307        #[cfg(target_os = "linux")]
308        {
309            Self::Linux
310        }
311        #[cfg(target_os = "macos")]
312        {
313            Self::MacOS
314        }
315        #[cfg(target_os = "windows")]
316        {
317            Self::Windows
318        }
319        #[cfg(target_os = "freebsd")]
320        {
321            Self::FreeBSD
322        }
323        #[cfg(not(any(
324            target_os = "linux",
325            target_os = "macos",
326            target_os = "windows",
327            target_os = "freebsd"
328        )))]
329        {
330            Self::Other
331        }
332    }
333
334    /// Check if this is a Unix-like platform.
335    #[must_use]
336    pub const fn is_unix(&self) -> bool {
337        matches!(self, Self::Linux | Self::MacOS | Self::FreeBSD)
338    }
339
340    /// Check if `io_uring` is potentially available.
341    #[must_use]
342    pub const fn supports_io_uring(&self) -> bool {
343        matches!(self, Self::Linux)
344    }
345
346    /// Check if XDP is potentially available.
347    #[must_use]
348    pub const fn supports_xdp(&self) -> bool {
349        matches!(self, Self::Linux)
350    }
351}
352
353impl std::fmt::Display for Platform {
354    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
355        match self {
356            Self::Linux => write!(f, "Linux"),
357            Self::MacOS => write!(f, "macOS"),
358            Self::Windows => write!(f, "Windows"),
359            Self::FreeBSD => write!(f, "FreeBSD"),
360            Self::Other => write!(f, "Other"),
361        }
362    }
363}
364
365#[cfg(test)]
366mod tests {
367    use super::*;
368
369    #[test]
370    fn test_system_capabilities_detect() {
371        let caps = SystemCapabilities::detect();
372
373        assert!(caps.cpu_count >= 1);
374        assert!(caps.physical_cores >= 1);
375        assert!(caps.numa_nodes >= 1);
376        assert!(caps.cache_line_size >= 32);
377        assert!(caps.memory.total_memory > 0);
378    }
379
380    #[test]
381    fn test_system_capabilities_cached() {
382        // Multiple calls should return the same instance
383        let caps1 = SystemCapabilities::detect();
384        let caps2 = SystemCapabilities::detect();
385
386        assert_eq!(caps1.cpu_count, caps2.cpu_count);
387        assert_eq!(caps1.numa_nodes, caps2.numa_nodes);
388    }
389
390    #[test]
391    fn test_system_capabilities_summary() {
392        let caps = SystemCapabilities::detect();
393        let summary = caps.summary();
394
395        assert!(summary.contains("System Capabilities"));
396        assert!(summary.contains("CPU:"));
397        assert!(summary.contains("I/O:"));
398        assert!(summary.contains("Memory:"));
399    }
400
401    #[test]
402    fn test_system_capabilities_recommended_config() {
403        let caps = SystemCapabilities::detect();
404        let config = caps.recommended_config();
405
406        assert!(config.num_cores >= 1);
407        assert!(config.arena_size > 0);
408        assert!(config.state_store_size > 0);
409    }
410
411    #[test]
412    fn test_system_capabilities_meets_minimum() {
413        let caps = SystemCapabilities::detect();
414        assert!(caps.meets_minimum_requirements());
415    }
416
417    #[test]
418    fn test_system_capabilities_missing_features() {
419        let caps = SystemCapabilities::detect();
420        let missing = caps.missing_features();
421
422        // Just verify it returns something (may or may not be empty)
423        // The test won't fail either way
424        let _ = missing;
425    }
426
427    #[test]
428    fn test_platform_detect() {
429        let platform = Platform::detect();
430
431        #[cfg(target_os = "linux")]
432        assert_eq!(platform, Platform::Linux);
433
434        #[cfg(target_os = "macos")]
435        assert_eq!(platform, Platform::MacOS);
436
437        #[cfg(target_os = "windows")]
438        assert_eq!(platform, Platform::Windows);
439    }
440
441    #[test]
442    fn test_platform_is_unix() {
443        assert!(Platform::Linux.is_unix());
444        assert!(Platform::MacOS.is_unix());
445        assert!(!Platform::Windows.is_unix());
446    }
447
448    #[test]
449    fn test_platform_supports_io_uring() {
450        assert!(Platform::Linux.supports_io_uring());
451        assert!(!Platform::MacOS.supports_io_uring());
452        assert!(!Platform::Windows.supports_io_uring());
453    }
454
455    #[test]
456    fn test_platform_display() {
457        assert_eq!(format!("{}", Platform::Linux), "Linux");
458        assert_eq!(format!("{}", Platform::MacOS), "macOS");
459        assert_eq!(format!("{}", Platform::Windows), "Windows");
460    }
461
462    #[test]
463    fn test_detect_with_storage() {
464        // This test just verifies the method works
465        let caps = SystemCapabilities::detect_with_storage("/");
466
467        assert!(caps.cpu_count >= 1);
468        // Storage type should be detected (or Unknown)
469    }
470
471    #[test]
472    fn test_update_storage() {
473        let mut caps = SystemCapabilities::detect_uncached();
474        caps.update_storage("/");
475
476        // Verify update completed without panic
477    }
478}