scirs2-core 0.4.3

Core utilities and common functionality for SciRS2 (scirs2-core)
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
//! # Memory Detection and Analysis
//!
//! This module provides memory system detection and analysis for
//! optimizing memory-intensive operations.

use crate::error::CoreResult;
#[cfg(target_os = "linux")]
use crate::CoreError;
#[cfg(target_os = "linux")]
use std::fs;

/// Memory system information
#[derive(Debug, Clone)]
pub struct MemoryInfo {
    /// Total physical memory in bytes
    pub total_memory: usize,
    /// Available memory in bytes
    pub available_memory: usize,
    /// Memory page size in bytes
    pub page_size: usize,
    /// Memory bandwidth estimate (GB/s)
    pub bandwidth_gbps: f64,
    /// Memory latency estimate (nanoseconds)
    pub latency_ns: f64,
    /// NUMA nodes count
    pub numa_nodes: usize,
    /// Swap space information
    pub swap_info: SwapInfo,
    /// Memory pressure indicators
    pub pressure: MemoryPressure,
}

impl Default for MemoryInfo {
    fn default() -> Self {
        Self {
            total_memory: (8u64 * 1024 * 1024 * 1024) as usize, // 8GB default
            available_memory: (4u64 * 1024 * 1024 * 1024) as usize, // 4GB default
            page_size: 4096,
            bandwidth_gbps: 20.0,
            latency_ns: 100.0,
            numa_nodes: 1,
            swap_info: SwapInfo::default(),
            pressure: MemoryPressure::default(),
        }
    }
}

impl MemoryInfo {
    /// Detect memory information
    pub fn detect() -> CoreResult<Self> {
        #[cfg(target_os = "linux")]
        return Self::detect_linux();

        #[cfg(target_os = "windows")]
        return Self::detect_windows();

        #[cfg(target_os = "macos")]
        return Self::detect_macos();

        #[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))]
        return Ok(Self::default());
    }

    fn parse_meminfo_value(line: &str) -> Option<u64> {
        let parts: Vec<&str> = line.split_whitespace().collect();
        if parts.len() < 2 {
            return None;
        }

        // The second part is typically the value in KB
        parts[1].parse::<u64>().ok()
    }

    /// Detect memory information on Linux
    #[cfg(target_os = "linux")]
    fn detect_linux() -> CoreResult<Self> {
        let meminfo = fs::read_to_string("/proc/meminfo").map_err(|e| {
            CoreError::IoError(crate::error::ErrorContext::new(format!(
                "Failed to read /proc/meminfo: {e}"
            )))
        })?;

        let mut total_memory = 8 * 1024 * 1024 * 1024; // Default 8GB
        let mut available_memory = 4 * 1024 * 1024 * 1024; // Default 4GB
        let mut swap_total = 0;
        let mut swap_free = 0;

        for line in meminfo.lines() {
            if line.starts_with("MemTotal:") {
                if let Some(value) = Self::parse_meminfo_value(line) {
                    total_memory = value * 1024; // Convert KB to bytes
                }
            } else if line.starts_with("MemAvailable:") {
                if let Some(value) = Self::parse_meminfo_value(line) {
                    available_memory = value * 1024; // Convert KB to bytes
                }
            } else if line.starts_with("SwapTotal:") {
                if let Some(value) = Self::parse_meminfo_value(line) {
                    swap_total = value * 1024; // Convert KB to bytes
                }
            } else if line.starts_with("SwapFree:") {
                if let Some(value) = Self::parse_meminfo_value(line) {
                    swap_free = value * 1024; // Convert KB to bytes
                }
            }
        }

        let page_size = Self::get_page_size();
        let numa_nodes = Self::detect_numa_nodes();
        let bandwidth_gbps = Self::estimate_memorybandwidth();
        let latency_ns = Self::estimate_memory_latency();
        let pressure = Self::detect_memory_pressure();

        let swap_info = SwapInfo {
            total: swap_total as usize,
            free: swap_free as usize,
            used: (swap_total - swap_free) as usize,
        };

        Ok(Self {
            total_memory: total_memory as usize,
            available_memory: available_memory as usize,
            page_size,
            bandwidth_gbps,
            latency_ns,
            numa_nodes,
            swap_info,
            pressure,
        })
    }

    /// Parse value from /proc/meminfo line
    #[allow(dead_code)]
    fn parse_meminfo_line(line: &str) -> Option<usize> {
        let parts: Vec<&str> = line.split_whitespace().collect();
        if parts.len() >= 2 {
            parts[1].parse().ok()
        } else {
            None
        }
    }

    /// Get system page size
    #[allow(dead_code)]
    fn get_page_size() -> usize {
        // Use a simplified implementation without libc dependency
        4096 // Most common page size on modern systems
    }

    /// Detect NUMA nodes
    #[allow(dead_code)]
    fn detect_numa_nodes() -> usize {
        #[cfg(target_os = "linux")]
        {
            if let Ok(entries) = fs::read_dir("/sys/devices/system/node") {
                let node_count = entries
                    .filter_map(|entry| entry.ok())
                    .filter(|entry| entry.file_name().to_string_lossy().starts_with("node"))
                    .count();
                if node_count > 0 {
                    return node_count;
                }
            }
        }

        1 // Default to single NUMA node
    }

    /// Estimate memory bandwidth
    #[allow(dead_code)]
    fn estimate_memorybandwidth() -> f64 {
        // This is a simplified estimation
        // In a real implementation, we might:
        // 1. Read from hardware databases
        // 2. Run memory benchmarks
        // 3. Query system information APIs

        #[cfg(target_os = "linux")]
        {
            // Try to estimate based on memory type
            if let Ok(content) = fs::read_to_string("/proc/meminfo") {
                if content.contains("MemTotal:") {
                    // Rough estimation based on typical memory configurations
                    return 25.0; // GB/s for typical DDR4
                }
            }
        }

        20.0 // Default estimation
    }

    /// Estimate memory latency
    #[allow(dead_code)]
    fn estimate_memory_latency() -> f64 {
        // Simplified estimation
        // Real implementation might benchmark memory access patterns
        80.0 // nanoseconds - typical DDR4 latency
    }

    /// Detect memory pressure
    #[allow(dead_code)]
    fn detect_memory_pressure() -> MemoryPressure {
        #[cfg(target_os = "linux")]
        {
            // Check memory pressure via PSI (Pressure Stall Information)
            if let Ok(content) = fs::read_to_string("/proc/pressure/memory") {
                for line in content.lines() {
                    if line.starts_with("some avg10=") {
                        if let Some(pressure_str) = line.split('=').nth(1) {
                            if let Some(pressure_val) = pressure_str.split_whitespace().next() {
                                if let Ok(pressure) = pressure_val.parse::<f64>() {
                                    return if pressure > 50.0 {
                                        MemoryPressure::High
                                    } else if pressure > 20.0 {
                                        MemoryPressure::Medium
                                    } else {
                                        MemoryPressure::Low
                                    };
                                }
                            }
                        }
                    }
                }
            }
        }

        MemoryPressure::Low
    }

    /// Detect memory information on Windows
    #[cfg(target_os = "windows")]
    fn detect_windows() -> CoreResult<Self> {
        // Windows implementation would use GetPhysicallyInstalledSystemMemory
        // and GlobalMemoryStatusEx APIs
        Ok(Self::default())
    }

    /// Detect memory information on macOS
    #[cfg(target_os = "macos")]
    fn detect_macos() -> CoreResult<Self> {
        // macOS implementation would use sysctl
        Ok(Self::default())
    }

    /// Calculate performance score (0.0 to 1.0)
    pub fn performance_score(&self) -> f64 {
        let capacity_score =
            (self.total_memory as f64 / (64.0 * 1024.0 * 1024.0 * 1024.0)).min(1.0); // Normalize to 64GB
        let bandwidth_score = (self.bandwidth_gbps / 100.0).min(1.0); // Normalize to 100 GB/s
        let latency_score = (200.0 / self.latency_ns).min(1.0); // Lower latency is better
        let availability_score = self.available_memory as f64 / self.total_memory as f64;

        (capacity_score + bandwidth_score + latency_score + availability_score) / 4.0
    }

    /// Get optimal chunk size for memory operations
    pub fn optimal_chunk_size(&self) -> usize {
        // Base chunk size on available memory and page size
        let available_mb = self.available_memory / (1024 * 1024);
        let chunk_mb = if available_mb > 1024 {
            64 // 64MB for systems with >1GB available
        } else if available_mb > 256 {
            16 // 16MB for systems with >256MB available
        } else {
            4 // 4MB for smaller systems
        };

        (chunk_mb * 1024 * 1024).max(self.page_size * 16) // At least 16 pages
    }

    /// Check if memory pressure is high
    pub fn is_under_pressure(&self) -> bool {
        matches!(self.pressure, MemoryPressure::High)
            || (self.available_memory * 100 / self.total_memory) < 10 // Less than 10% available
    }

    /// Get recommended memory allocation strategy
    pub fn allocation_strategy(&self) -> AllocationStrategy {
        if self.numa_nodes > 1 {
            AllocationStrategy::NumaAware
        } else if self.is_under_pressure() {
            AllocationStrategy::Conservative
        } else {
            AllocationStrategy::Aggressive
        }
    }
}

/// Swap space information
#[derive(Debug, Clone, Default)]
pub struct SwapInfo {
    /// Total swap space in bytes
    pub total: usize,
    /// Free swap space in bytes
    pub free: usize,
    /// Used swap space in bytes
    pub used: usize,
}

impl SwapInfo {
    /// Check if swap is being used
    pub fn is_swap_active(&self) -> bool {
        self.used > 0
    }

    /// Get swap usage percentage
    pub fn usage_percentage(&self) -> f64 {
        if self.total == 0 {
            0.0
        } else {
            (self.used as f64 / self.total as f64) * 100.0
        }
    }
}

/// Memory pressure levels
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum MemoryPressure {
    /// Low memory pressure
    #[default]
    Low,
    /// Medium memory pressure
    Medium,
    /// High memory pressure
    High,
}

/// Memory allocation strategies
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AllocationStrategy {
    /// Aggressive allocation for performance
    Aggressive,
    /// Conservative allocation to avoid pressure
    Conservative,
    /// NUMA-aware allocation for multi-node systems
    NumaAware,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_memory_detection() {
        let memoryinfo = MemoryInfo::detect();
        assert!(memoryinfo.is_ok());

        let memory = memoryinfo.expect("Operation failed");
        assert!(memory.total_memory > 0);
        assert!(memory.available_memory > 0);
        assert!(memory.available_memory <= memory.total_memory);
        assert!(memory.page_size > 0);
    }

    #[test]
    fn test_performance_score() {
        let memory = MemoryInfo::default();
        let score = memory.performance_score();
        assert!((0.0..=1.0).contains(&score));
    }

    #[test]
    fn test_optimal_chunk_size() {
        let memory = MemoryInfo::default();
        let chunk_size = memory.optimal_chunk_size();
        assert!(chunk_size >= memory.page_size * 16);
    }

    #[test]
    fn test_swap_info() {
        let swap = SwapInfo {
            total: 1024 * 1024 * 1024, // 1GB
            free: 512 * 1024 * 1024,   // 512MB
            used: 512 * 1024 * 1024,   // 512MB
        };

        assert!(swap.is_swap_active());
        assert_eq!(swap.usage_percentage(), 50.0);
    }

    #[test]
    fn test_allocation_strategy() {
        let mut memory = MemoryInfo::default();

        // Test conservative strategy under pressure
        memory.available_memory = memory.total_memory / 20; // 5% available
        assert_eq!(
            memory.allocation_strategy(),
            AllocationStrategy::Conservative
        );

        // Test NUMA-aware strategy
        memory.numa_nodes = 2;
        memory.available_memory = memory.total_memory / 2; // 50% available
        assert_eq!(memory.allocation_strategy(), AllocationStrategy::NumaAware);
    }

    #[test]
    fn test_memory_pressure() {
        assert_eq!(MemoryPressure::Low, MemoryPressure::Low);
        assert_ne!(MemoryPressure::Low, MemoryPressure::High);
    }
}