scirs2_core/resource/
memory.rs

1//! # Memory Detection and Analysis
2//!
3//! This module provides memory system detection and analysis for
4//! optimizing memory-intensive operations.
5
6use crate::error::CoreResult;
7#[cfg(target_os = "linux")]
8use crate::CoreError;
9#[cfg(target_os = "linux")]
10use std::fs;
11
12/// Memory system information
13#[derive(Debug, Clone)]
14pub struct MemoryInfo {
15    /// Total physical memory in bytes
16    pub total_memory: usize,
17    /// Available memory in bytes
18    pub available_memory: usize,
19    /// Memory page size in bytes
20    pub page_size: usize,
21    /// Memory bandwidth estimate (GB/s)
22    pub bandwidth_gbps: f64,
23    /// Memory latency estimate (nanoseconds)
24    pub latency_ns: f64,
25    /// NUMA nodes count
26    pub numa_nodes: usize,
27    /// Swap space information
28    pub swap_info: SwapInfo,
29    /// Memory pressure indicators
30    pub pressure: MemoryPressure,
31}
32
33impl Default for MemoryInfo {
34    fn default() -> Self {
35        Self {
36            total_memory: 8 * 1024 * 1024 * 1024,     // 8GB default
37            available_memory: 4 * 1024 * 1024 * 1024, // 4GB default
38            page_size: 4096,
39            bandwidth_gbps: 20.0,
40            latency_ns: 100.0,
41            numa_nodes: 1,
42            swap_info: SwapInfo::default(),
43            pressure: MemoryPressure::default(),
44        }
45    }
46}
47
48impl MemoryInfo {
49    /// Detect memory information
50    pub fn detect() -> CoreResult<Self> {
51        #[cfg(target_os = "linux")]
52        return Self::detect_linux();
53
54        #[cfg(target_os = "windows")]
55        return Self::detect_windows();
56
57        #[cfg(target_os = "macos")]
58        return Self::detect_macos();
59
60        #[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))]
61        return Ok(Self::default());
62    }
63
64    fn parse_meminfo_value(line: &str) -> Option<u64> {
65        let parts: Vec<&str> = line.split_whitespace().collect();
66        if parts.len() < 2 {
67            return None;
68        }
69
70        // The second part is typically the value in KB
71        parts[1].parse::<u64>().ok()
72    }
73
74    /// Detect memory information on Linux
75    #[cfg(target_os = "linux")]
76    fn detect_linux() -> CoreResult<Self> {
77        let meminfo = fs::read_to_string("/proc/meminfo").map_err(|e| {
78            CoreError::IoError(crate::error::ErrorContext::new(format!(
79                "Failed to read /proc/meminfo: {e}"
80            )))
81        })?;
82
83        let mut total_memory = 8 * 1024 * 1024 * 1024; // Default 8GB
84        let mut available_memory = 4 * 1024 * 1024 * 1024; // Default 4GB
85        let mut swap_total = 0;
86        let mut swap_free = 0;
87
88        for line in meminfo.lines() {
89            if line.starts_with("MemTotal:") {
90                if let Some(value) = Self::parse_meminfo_value(line) {
91                    total_memory = value * 1024; // Convert KB to bytes
92                }
93            } else if line.starts_with("MemAvailable:") {
94                if let Some(value) = Self::parse_meminfo_value(line) {
95                    available_memory = value * 1024; // Convert KB to bytes
96                }
97            } else if line.starts_with("SwapTotal:") {
98                if let Some(value) = Self::parse_meminfo_value(line) {
99                    swap_total = value * 1024; // Convert KB to bytes
100                }
101            } else if line.starts_with("SwapFree:") {
102                if let Some(value) = Self::parse_meminfo_value(line) {
103                    swap_free = value * 1024; // Convert KB to bytes
104                }
105            }
106        }
107
108        let page_size = Self::get_page_size();
109        let numa_nodes = Self::detect_numa_nodes();
110        let bandwidth_gbps = Self::estimate_memorybandwidth();
111        let latency_ns = Self::estimate_memory_latency();
112        let pressure = Self::detect_memory_pressure();
113
114        let swap_info = SwapInfo {
115            total: swap_total as usize,
116            free: swap_free as usize,
117            used: (swap_total - swap_free) as usize,
118        };
119
120        Ok(Self {
121            total_memory: total_memory as usize,
122            available_memory: available_memory as usize,
123            page_size,
124            bandwidth_gbps,
125            latency_ns,
126            numa_nodes,
127            swap_info,
128            pressure,
129        })
130    }
131
132    /// Parse value from /proc/meminfo line
133    #[allow(dead_code)]
134    fn parse_meminfo_line(line: &str) -> Option<usize> {
135        let parts: Vec<&str> = line.split_whitespace().collect();
136        if parts.len() >= 2 {
137            parts[1].parse().ok()
138        } else {
139            None
140        }
141    }
142
143    /// Get system page size
144    #[allow(dead_code)]
145    fn get_page_size() -> usize {
146        // Use a simplified implementation without libc dependency
147        4096 // Most common page size on modern systems
148    }
149
150    /// Detect NUMA nodes
151    #[allow(dead_code)]
152    fn detect_numa_nodes() -> usize {
153        #[cfg(target_os = "linux")]
154        {
155            if let Ok(entries) = fs::read_dir("/sys/devices/system/node") {
156                let node_count = entries
157                    .filter_map(|entry| entry.ok())
158                    .filter(|entry| entry.file_name().to_string_lossy().starts_with("node"))
159                    .count();
160                if node_count > 0 {
161                    return node_count;
162                }
163            }
164        }
165
166        1 // Default to single NUMA node
167    }
168
169    /// Estimate memory bandwidth
170    #[allow(dead_code)]
171    fn estimate_memorybandwidth() -> f64 {
172        // This is a simplified estimation
173        // In a real implementation, we might:
174        // 1. Read from hardware databases
175        // 2. Run memory benchmarks
176        // 3. Query system information APIs
177
178        #[cfg(target_os = "linux")]
179        {
180            // Try to estimate based on memory type
181            if let Ok(content) = fs::read_to_string("/proc/meminfo") {
182                if content.contains("MemTotal:") {
183                    // Rough estimation based on typical memory configurations
184                    return 25.0; // GB/s for typical DDR4
185                }
186            }
187        }
188
189        20.0 // Default estimation
190    }
191
192    /// Estimate memory latency
193    #[allow(dead_code)]
194    fn estimate_memory_latency() -> f64 {
195        // Simplified estimation
196        // Real implementation might benchmark memory access patterns
197        80.0 // nanoseconds - typical DDR4 latency
198    }
199
200    /// Detect memory pressure
201    #[allow(dead_code)]
202    fn detect_memory_pressure() -> MemoryPressure {
203        #[cfg(target_os = "linux")]
204        {
205            // Check memory pressure via PSI (Pressure Stall Information)
206            if let Ok(content) = fs::read_to_string("/proc/pressure/memory") {
207                for line in content.lines() {
208                    if line.starts_with("some avg10=") {
209                        if let Some(pressure_str) = line.split('=').nth(1) {
210                            if let Some(pressure_val) = pressure_str.split_whitespace().next() {
211                                if let Ok(pressure) = pressure_val.parse::<f64>() {
212                                    return if pressure > 50.0 {
213                                        MemoryPressure::High
214                                    } else if pressure > 20.0 {
215                                        MemoryPressure::Medium
216                                    } else {
217                                        MemoryPressure::Low
218                                    };
219                                }
220                            }
221                        }
222                    }
223                }
224            }
225        }
226
227        MemoryPressure::Low
228    }
229
230    /// Detect memory information on Windows
231    #[cfg(target_os = "windows")]
232    fn detect_windows() -> CoreResult<Self> {
233        // Windows implementation would use GetPhysicallyInstalledSystemMemory
234        // and GlobalMemoryStatusEx APIs
235        Ok(Self::default())
236    }
237
238    /// Detect memory information on macOS
239    #[cfg(target_os = "macos")]
240    fn detect_macos() -> CoreResult<Self> {
241        // macOS implementation would use sysctl
242        Ok(Self::default())
243    }
244
245    /// Calculate performance score (0.0 to 1.0)
246    pub fn performance_score(&self) -> f64 {
247        let capacity_score =
248            (self.total_memory as f64 / (64.0 * 1024.0 * 1024.0 * 1024.0)).min(1.0); // Normalize to 64GB
249        let bandwidth_score = (self.bandwidth_gbps / 100.0).min(1.0); // Normalize to 100 GB/s
250        let latency_score = (200.0 / self.latency_ns).min(1.0); // Lower latency is better
251        let availability_score = self.available_memory as f64 / self.total_memory as f64;
252
253        (capacity_score + bandwidth_score + latency_score + availability_score) / 4.0
254    }
255
256    /// Get optimal chunk size for memory operations
257    pub fn optimal_chunk_size(&self) -> usize {
258        // Base chunk size on available memory and page size
259        let available_mb = self.available_memory / (1024 * 1024);
260        let chunk_mb = if available_mb > 1024 {
261            64 // 64MB for systems with >1GB available
262        } else if available_mb > 256 {
263            16 // 16MB for systems with >256MB available
264        } else {
265            4 // 4MB for smaller systems
266        };
267
268        (chunk_mb * 1024 * 1024).max(self.page_size * 16) // At least 16 pages
269    }
270
271    /// Check if memory pressure is high
272    pub fn is_under_pressure(&self) -> bool {
273        matches!(self.pressure, MemoryPressure::High)
274            || (self.available_memory * 100 / self.total_memory) < 10 // Less than 10% available
275    }
276
277    /// Get recommended memory allocation strategy
278    pub fn allocation_strategy(&self) -> AllocationStrategy {
279        if self.numa_nodes > 1 {
280            AllocationStrategy::NumaAware
281        } else if self.is_under_pressure() {
282            AllocationStrategy::Conservative
283        } else {
284            AllocationStrategy::Aggressive
285        }
286    }
287}
288
289/// Swap space information
290#[derive(Debug, Clone, Default)]
291pub struct SwapInfo {
292    /// Total swap space in bytes
293    pub total: usize,
294    /// Free swap space in bytes
295    pub free: usize,
296    /// Used swap space in bytes
297    pub used: usize,
298}
299
300impl SwapInfo {
301    /// Check if swap is being used
302    pub fn is_swap_active(&self) -> bool {
303        self.used > 0
304    }
305
306    /// Get swap usage percentage
307    pub fn usage_percentage(&self) -> f64 {
308        if self.total == 0 {
309            0.0
310        } else {
311            (self.used as f64 / self.total as f64) * 100.0
312        }
313    }
314}
315
316/// Memory pressure levels
317#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
318pub enum MemoryPressure {
319    /// Low memory pressure
320    #[default]
321    Low,
322    /// Medium memory pressure
323    Medium,
324    /// High memory pressure
325    High,
326}
327
328/// Memory allocation strategies
329#[derive(Debug, Clone, Copy, PartialEq, Eq)]
330pub enum AllocationStrategy {
331    /// Aggressive allocation for performance
332    Aggressive,
333    /// Conservative allocation to avoid pressure
334    Conservative,
335    /// NUMA-aware allocation for multi-node systems
336    NumaAware,
337}
338
339#[cfg(test)]
340mod tests {
341    use super::*;
342
343    #[test]
344    fn test_memory_detection() {
345        let memoryinfo = MemoryInfo::detect();
346        assert!(memoryinfo.is_ok());
347
348        let memory = memoryinfo.unwrap();
349        assert!(memory.total_memory > 0);
350        assert!(memory.available_memory > 0);
351        assert!(memory.available_memory <= memory.total_memory);
352        assert!(memory.page_size > 0);
353    }
354
355    #[test]
356    fn test_performance_score() {
357        let memory = MemoryInfo::default();
358        let score = memory.performance_score();
359        assert!((0.0..=1.0).contains(&score));
360    }
361
362    #[test]
363    fn test_optimal_chunk_size() {
364        let memory = MemoryInfo::default();
365        let chunk_size = memory.optimal_chunk_size();
366        assert!(chunk_size >= memory.page_size * 16);
367    }
368
369    #[test]
370    fn test_swap_info() {
371        let swap = SwapInfo {
372            total: 1024 * 1024 * 1024, // 1GB
373            free: 512 * 1024 * 1024,   // 512MB
374            used: 512 * 1024 * 1024,   // 512MB
375        };
376
377        assert!(swap.is_swap_active());
378        assert_eq!(swap.usage_percentage(), 50.0);
379    }
380
381    #[test]
382    fn test_allocation_strategy() {
383        let mut memory = MemoryInfo::default();
384
385        // Test conservative strategy under pressure
386        memory.available_memory = memory.total_memory / 20; // 5% available
387        assert_eq!(
388            memory.allocation_strategy(),
389            AllocationStrategy::Conservative
390        );
391
392        // Test NUMA-aware strategy
393        memory.numa_nodes = 2;
394        memory.available_memory = memory.total_memory / 2; // 50% available
395        assert_eq!(memory.allocation_strategy(), AllocationStrategy::NumaAware);
396    }
397
398    #[test]
399    fn test_memory_pressure() {
400        assert_eq!(MemoryPressure::Low, MemoryPressure::Low);
401        assert_ne!(MemoryPressure::Low, MemoryPressure::High);
402    }
403}