scirs2_core/resource/
memory.rs1use crate::error::CoreResult;
7#[cfg(target_os = "linux")]
8use crate::CoreError;
9#[cfg(target_os = "linux")]
10use std::fs;
11
12#[derive(Debug, Clone)]
14pub struct MemoryInfo {
15 pub total_memory: usize,
17 pub available_memory: usize,
19 pub page_size: usize,
21 pub bandwidth_gbps: f64,
23 pub latency_ns: f64,
25 pub numa_nodes: usize,
27 pub swap_info: SwapInfo,
29 pub pressure: MemoryPressure,
31}
32
33impl Default for MemoryInfo {
34 fn default() -> Self {
35 Self {
36 total_memory: 8 * 1024 * 1024 * 1024, available_memory: 4 * 1024 * 1024 * 1024, 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 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 parts[1].parse::<u64>().ok()
72 }
73
74 #[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; let mut available_memory = 4 * 1024 * 1024 * 1024; 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; }
93 } else if line.starts_with("MemAvailable:") {
94 if let Some(value) = Self::parse_meminfo_value(line) {
95 available_memory = value * 1024; }
97 } else if line.starts_with("SwapTotal:") {
98 if let Some(value) = Self::parse_meminfo_value(line) {
99 swap_total = value * 1024; }
101 } else if line.starts_with("SwapFree:") {
102 if let Some(value) = Self::parse_meminfo_value(line) {
103 swap_free = value * 1024; }
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 #[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 #[allow(dead_code)]
145 fn get_page_size() -> usize {
146 4096 }
149
150 #[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 }
168
169 #[allow(dead_code)]
171 fn estimate_memorybandwidth() -> f64 {
172 #[cfg(target_os = "linux")]
179 {
180 if let Ok(content) = fs::read_to_string("/proc/meminfo") {
182 if content.contains("MemTotal:") {
183 return 25.0; }
186 }
187 }
188
189 20.0 }
191
192 #[allow(dead_code)]
194 fn estimate_memory_latency() -> f64 {
195 80.0 }
199
200 #[allow(dead_code)]
202 fn detect_memory_pressure() -> MemoryPressure {
203 #[cfg(target_os = "linux")]
204 {
205 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 #[cfg(target_os = "windows")]
232 fn detect_windows() -> CoreResult<Self> {
233 Ok(Self::default())
236 }
237
238 #[cfg(target_os = "macos")]
240 fn detect_macos() -> CoreResult<Self> {
241 Ok(Self::default())
243 }
244
245 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); let bandwidth_score = (self.bandwidth_gbps / 100.0).min(1.0); let latency_score = (200.0 / self.latency_ns).min(1.0); 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 pub fn optimal_chunk_size(&self) -> usize {
258 let available_mb = self.available_memory / (1024 * 1024);
260 let chunk_mb = if available_mb > 1024 {
261 64 } else if available_mb > 256 {
263 16 } else {
265 4 };
267
268 (chunk_mb * 1024 * 1024).max(self.page_size * 16) }
270
271 pub fn is_under_pressure(&self) -> bool {
273 matches!(self.pressure, MemoryPressure::High)
274 || (self.available_memory * 100 / self.total_memory) < 10 }
276
277 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#[derive(Debug, Clone, Default)]
291pub struct SwapInfo {
292 pub total: usize,
294 pub free: usize,
296 pub used: usize,
298}
299
300impl SwapInfo {
301 pub fn is_swap_active(&self) -> bool {
303 self.used > 0
304 }
305
306 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
318pub enum MemoryPressure {
319 #[default]
321 Low,
322 Medium,
324 High,
326}
327
328#[derive(Debug, Clone, Copy, PartialEq, Eq)]
330pub enum AllocationStrategy {
331 Aggressive,
333 Conservative,
335 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, free: 512 * 1024 * 1024, used: 512 * 1024 * 1024, };
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 memory.available_memory = memory.total_memory / 20; assert_eq!(
388 memory.allocation_strategy(),
389 AllocationStrategy::Conservative
390 );
391
392 memory.numa_nodes = 2;
394 memory.available_memory = memory.total_memory / 2; 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}