Skip to main content

laminar_core/detect/
config.rs

1//! # Auto-Configuration Builder
2//!
3//! Generates optimal configuration based on detected system capabilities.
4//!
5//! ## Usage
6//!
7//! ```rust,ignore
8//! use laminar_core::detect::{SystemCapabilities, RecommendedConfig};
9//!
10//! let caps = SystemCapabilities::detect();
11//! let config = caps.recommended_config();
12//!
13//! println!("Recommended cores: {}", config.num_cores);
14//! println!("Use io_uring: {}", config.use_io_uring);
15//! ```
16
17use super::{logical_cpu_count, physical_cpu_count};
18
19/// Recommended configuration based on detected capabilities.
20#[derive(Debug, Clone)]
21#[allow(clippy::struct_excessive_bools)]
22pub struct RecommendedConfig {
23    // ===== Thread-Per-Core =====
24    /// Recommended number of cores to use.
25    pub num_cores: usize,
26    /// Whether to enable CPU pinning.
27    pub cpu_pinning: bool,
28    /// Whether NUMA-aware allocation should be enabled.
29    pub numa_aware: bool,
30
31    // ===== I/O =====
32    /// Whether to use `io_uring` for I/O.
33    pub use_io_uring: bool,
34    /// Whether to use SQPOLL mode.
35    pub io_uring_sqpoll: bool,
36    /// Whether to use IOPOLL mode (for `NVMe`).
37    pub io_uring_iopoll: bool,
38
39    // ===== Network =====
40    /// Whether to use XDP for network processing.
41    pub use_xdp: bool,
42
43    // ===== Memory =====
44    /// Whether to use huge pages.
45    pub use_huge_pages: bool,
46    /// Recommended arena size per core.
47    pub arena_size: usize,
48    /// Recommended state store size per core.
49    pub state_store_size: usize,
50    /// Recommended queue capacity.
51    pub queue_capacity: usize,
52
53    // ===== Performance =====
54    /// Detected cache line size.
55    pub cache_line_size: usize,
56}
57
58impl RecommendedConfig {
59    /// Generate recommended configuration from system capabilities.
60    #[must_use]
61    pub fn from_capabilities(caps: &super::SystemCapabilities) -> Self {
62        Self {
63            // Thread-per-core
64            num_cores: Self::calculate_num_cores(caps),
65            cpu_pinning: caps.cpu_count > 1,
66            numa_aware: caps.numa_nodes > 1,
67
68            // I/O
69            use_io_uring: caps.io_uring.is_usable(),
70            io_uring_sqpoll: caps.io_uring.sqpoll_supported,
71            io_uring_iopoll: caps.io_uring.iopoll_supported
72                && caps.storage.device_type.supports_iopoll(),
73
74            // Network
75            use_xdp: caps.xdp.is_usable(),
76
77            // Memory
78            use_huge_pages: caps.memory.huge_pages_available && caps.memory.huge_pages_free > 0,
79            arena_size: Self::calculate_arena_size(caps),
80            state_store_size: Self::calculate_state_store_size(caps),
81            queue_capacity: Self::calculate_queue_capacity(caps),
82
83            // Performance
84            cache_line_size: caps.cache_line_size,
85        }
86    }
87
88    /// Calculate the recommended number of cores.
89    fn calculate_num_cores(_caps: &super::SystemCapabilities) -> usize {
90        let physical = physical_cpu_count();
91        let logical = logical_cpu_count();
92
93        // For thread-per-core, prefer physical cores to avoid SMT contention
94        // But if only 1-2 physical cores, use logical to get some parallelism
95        if physical <= 2 {
96            logical.min(4)
97        } else {
98            // Leave 1 core for OS/background if we have many cores
99            if physical > 8 {
100                physical - 1
101            } else {
102                physical
103            }
104        }
105    }
106
107    /// Calculate the recommended arena size per core.
108    fn calculate_arena_size(caps: &super::SystemCapabilities) -> usize {
109        let memory_per_core = caps.memory.available_memory / caps.cpu_count as u64;
110
111        // Use 1/8 of per-core memory for arena, capped at 64MB
112        let arena = (memory_per_core / 8) as usize;
113        arena.clamp(1024 * 1024, 64 * 1024 * 1024) // 1MB - 64MB
114    }
115
116    /// Calculate the recommended state store size per core.
117    fn calculate_state_store_size(caps: &super::SystemCapabilities) -> usize {
118        let memory_per_core = caps.memory.available_memory / caps.cpu_count as u64;
119
120        // Use 1/4 of per-core memory for state, capped at 1GB
121        let state_size = (memory_per_core / 4) as usize;
122        state_size.clamp(16 * 1024 * 1024, 1024 * 1024 * 1024) // 16MB - 1GB
123    }
124
125    /// Calculate the recommended queue capacity.
126    fn calculate_queue_capacity(caps: &super::SystemCapabilities) -> usize {
127        // Higher capacity for more memory
128        if caps.memory.total_memory > 32 * 1024 * 1024 * 1024 {
129            131_072 // 128K entries
130        } else if caps.memory.total_memory > 8 * 1024 * 1024 * 1024 {
131            65_536 // 64K entries
132        } else {
133            16_384 // 16K entries
134        }
135    }
136
137    /// Generate a human-readable summary.
138    #[must_use]
139    pub fn summary(&self) -> String {
140        use std::fmt::Write;
141
142        let mut s = String::new();
143
144        let _ = writeln!(s, "Recommended Configuration:");
145        let _ = writeln!(
146            s,
147            "  Cores: {} (pinned: {})",
148            self.num_cores, self.cpu_pinning
149        );
150        let _ = writeln!(s, "  NUMA-aware: {}", self.numa_aware);
151
152        let _ = writeln!(
153            s,
154            "  io_uring: {} (SQPOLL: {}, IOPOLL: {})",
155            self.use_io_uring, self.io_uring_sqpoll, self.io_uring_iopoll
156        );
157        let _ = writeln!(s, "  XDP: {}", self.use_xdp);
158
159        let _ = writeln!(s, "  Huge pages: {}", self.use_huge_pages);
160        let _ = writeln!(s, "  Arena size: {} MB", self.arena_size / (1024 * 1024));
161        let _ = writeln!(
162            s,
163            "  State store: {} MB",
164            self.state_store_size / (1024 * 1024)
165        );
166        let _ = writeln!(s, "  Queue capacity: {}", self.queue_capacity);
167        let _ = writeln!(s, "  Cache line: {} bytes", self.cache_line_size);
168
169        s
170    }
171
172    /// Check if this configuration uses all advanced features.
173    #[must_use]
174    pub fn is_optimal(&self) -> bool {
175        self.use_io_uring && self.io_uring_sqpoll && self.cpu_pinning
176    }
177
178    /// Get a performance tier based on capabilities.
179    #[must_use]
180    pub fn performance_tier(&self) -> PerformanceTier {
181        if self.use_io_uring && self.io_uring_sqpoll && self.io_uring_iopoll && self.numa_aware {
182            PerformanceTier::Maximum
183        } else if self.use_io_uring && self.io_uring_sqpoll {
184            PerformanceTier::High
185        } else if self.use_io_uring {
186            PerformanceTier::Good
187        } else {
188            PerformanceTier::Basic
189        }
190    }
191}
192
193impl Default for RecommendedConfig {
194    fn default() -> Self {
195        Self {
196            num_cores: logical_cpu_count(),
197            cpu_pinning: false,
198            numa_aware: false,
199            use_io_uring: false,
200            io_uring_sqpoll: false,
201            io_uring_iopoll: false,
202            use_xdp: false,
203            use_huge_pages: false,
204            arena_size: 16 * 1024 * 1024,
205            state_store_size: 256 * 1024 * 1024,
206            queue_capacity: 65_536,
207            cache_line_size: 64,
208        }
209    }
210}
211
212/// Performance tier based on available features.
213#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
214pub enum PerformanceTier {
215    /// Standard syscall-based I/O
216    Basic,
217    /// `io_uring` basic mode
218    Good,
219    /// `io_uring` with SQPOLL
220    High,
221    /// Full optimization (`io_uring` SQPOLL+IOPOLL, NUMA, XDP)
222    Maximum,
223}
224
225impl std::fmt::Display for PerformanceTier {
226    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
227        match self {
228            PerformanceTier::Basic => write!(f, "Basic"),
229            PerformanceTier::Good => write!(f, "Good"),
230            PerformanceTier::High => write!(f, "High"),
231            PerformanceTier::Maximum => write!(f, "Maximum"),
232        }
233    }
234}
235
236#[cfg(test)]
237#[allow(clippy::field_reassign_with_default)]
238mod tests {
239    use super::super::SystemCapabilities;
240    use super::*;
241
242    #[test]
243    fn test_recommended_config_default() {
244        let config = RecommendedConfig::default();
245        assert!(config.num_cores >= 1);
246        assert!(!config.cpu_pinning);
247        assert!(!config.use_io_uring);
248    }
249
250    #[test]
251    fn test_recommended_config_from_capabilities() {
252        let caps = SystemCapabilities::detect();
253        let config = RecommendedConfig::from_capabilities(caps);
254
255        assert!(config.num_cores >= 1);
256        assert!(config.arena_size >= 1024 * 1024);
257        assert!(config.state_store_size >= 16 * 1024 * 1024);
258        assert!(config.cache_line_size >= 32);
259    }
260
261    #[test]
262    fn test_recommended_config_summary() {
263        let config = RecommendedConfig::default();
264        let summary = config.summary();
265
266        assert!(summary.contains("Cores:"));
267        assert!(summary.contains("io_uring:"));
268        assert!(summary.contains("NUMA-aware:"));
269    }
270
271    #[test]
272    fn test_performance_tier_ordering() {
273        assert!(PerformanceTier::Basic < PerformanceTier::Good);
274        assert!(PerformanceTier::Good < PerformanceTier::High);
275        assert!(PerformanceTier::High < PerformanceTier::Maximum);
276    }
277
278    #[test]
279    fn test_performance_tier_display() {
280        assert_eq!(format!("{}", PerformanceTier::Basic), "Basic");
281        assert_eq!(format!("{}", PerformanceTier::Maximum), "Maximum");
282    }
283
284    #[test]
285    fn test_is_optimal() {
286        let mut config = RecommendedConfig::default();
287        assert!(!config.is_optimal());
288
289        config.use_io_uring = true;
290        config.io_uring_sqpoll = true;
291        config.cpu_pinning = true;
292        assert!(config.is_optimal());
293    }
294
295    #[test]
296    fn test_performance_tier_basic() {
297        let config = RecommendedConfig::default();
298        assert_eq!(config.performance_tier(), PerformanceTier::Basic);
299    }
300
301    #[test]
302    fn test_performance_tier_good() {
303        let mut config = RecommendedConfig::default();
304        config.use_io_uring = true;
305        assert_eq!(config.performance_tier(), PerformanceTier::Good);
306    }
307
308    #[test]
309    fn test_performance_tier_high() {
310        let mut config = RecommendedConfig::default();
311        config.use_io_uring = true;
312        config.io_uring_sqpoll = true;
313        assert_eq!(config.performance_tier(), PerformanceTier::High);
314    }
315
316    #[test]
317    fn test_performance_tier_maximum() {
318        let mut config = RecommendedConfig::default();
319        config.use_io_uring = true;
320        config.io_uring_sqpoll = true;
321        config.io_uring_iopoll = true;
322        config.numa_aware = true;
323        assert_eq!(config.performance_tier(), PerformanceTier::Maximum);
324    }
325}