oxirs_arq/
system_load_monitor.rs1use anyhow::Result;
7use std::sync::atomic::{AtomicU64, Ordering};
8use std::sync::Arc;
9use std::time::{Duration, Instant};
10
11#[derive(Debug, Clone)]
13pub struct SystemLoadMonitor {
14 cpu_usage: Arc<AtomicU64>,
16 memory_usage: Arc<AtomicU64>,
18 last_update: Arc<std::sync::Mutex<Instant>>,
20 update_interval: Duration,
22}
23
24impl SystemLoadMonitor {
25 pub fn new() -> Self {
27 Self::with_update_interval(Duration::from_secs(1))
28 }
29
30 pub fn with_update_interval(interval: Duration) -> Self {
32 Self {
33 cpu_usage: Arc::new(AtomicU64::new(0)),
34 memory_usage: Arc::new(AtomicU64::new(0)),
35 last_update: Arc::new(std::sync::Mutex::new(Instant::now())),
36 update_interval: interval,
37 }
38 }
39
40 pub fn cpu_usage(&self) -> f64 {
42 self.maybe_update();
43 f64::from_bits(self.cpu_usage.load(Ordering::Relaxed)) / 100.0
44 }
45
46 pub fn memory_usage(&self) -> f64 {
48 self.maybe_update();
49 f64::from_bits(self.memory_usage.load(Ordering::Relaxed)) / 100.0
50 }
51
52 pub fn overall_load(&self) -> f64 {
54 let cpu = self.cpu_usage();
55 let mem = self.memory_usage();
56
57 (cpu * 0.6 + mem * 0.4).min(1.0)
59 }
60
61 pub fn is_high_load(&self, threshold: f64) -> bool {
63 self.overall_load() > threshold
64 }
65
66 pub fn is_low_load(&self, threshold: f64) -> bool {
68 self.overall_load() < threshold
69 }
70
71 pub fn recommended_concurrency(&self, max_concurrency: usize) -> usize {
73 let load = self.overall_load();
74
75 let scale_factor = if load < 0.5 {
82 1.0
83 } else if load < 0.7 {
84 0.75
85 } else if load < 0.8 {
86 0.5
87 } else if load < 0.9 {
88 0.4
89 } else {
90 0.25
91 };
92
93 ((max_concurrency as f64 * scale_factor).max(1.0) as usize).min(max_concurrency)
94 }
95
96 fn maybe_update(&self) {
98 let mut last_update = self.last_update.lock().expect("Lock poisoned");
99
100 if last_update.elapsed() < self.update_interval {
101 return; }
103
104 *last_update = Instant::now();
106 drop(last_update); if let Ok((cpu, memory)) = self.read_system_metrics() {
110 self.cpu_usage.store(cpu.to_bits(), Ordering::Relaxed);
111 self.memory_usage.store(memory.to_bits(), Ordering::Relaxed);
112 }
113 }
114
115 fn read_system_metrics(&self) -> Result<(f64, f64)> {
117 #[cfg(target_os = "linux")]
121 {
122 self.read_linux_metrics()
123 }
124
125 #[cfg(target_os = "macos")]
126 {
127 self.read_macos_metrics()
128 }
129
130 #[cfg(target_os = "windows")]
131 {
132 self.read_windows_metrics()
133 }
134
135 #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
136 {
137 Ok((50.0, 50.0)) }
140 }
141
142 #[cfg(target_os = "linux")]
143 fn read_linux_metrics(&self) -> Result<(f64, f64)> {
144 let cpu = self.estimate_cpu_from_loadavg()?;
149 let memory = self.estimate_memory_from_available()?;
150
151 Ok((cpu, memory))
152 }
153
154 #[cfg(target_os = "macos")]
155 fn read_macos_metrics(&self) -> Result<(f64, f64)> {
156 let cpu = 30.0; let memory = 40.0;
162
163 Ok((cpu, memory))
164 }
165
166 #[cfg(target_os = "windows")]
167 fn read_windows_metrics(&self) -> Result<(f64, f64)> {
168 let cpu = 40.0;
172 let memory = 45.0;
173
174 Ok((cpu, memory))
175 }
176
177 #[cfg(target_os = "linux")]
178 fn estimate_cpu_from_loadavg(&self) -> Result<f64> {
179 use std::fs;
181
182 if let Ok(loadavg) = fs::read_to_string("/proc/loadavg") {
183 if let Some(load_str) = loadavg.split_whitespace().next() {
184 if let Ok(load) = load_str.parse::<f64>() {
185 let num_cpus = std::thread::available_parallelism()
186 .map(|n| n.get())
187 .unwrap_or(1) as f64;
188 return Ok((load / num_cpus * 100.0).min(100.0));
190 }
191 }
192 }
193
194 Ok(30.0) }
196
197 #[cfg(target_os = "linux")]
198 fn estimate_memory_from_available(&self) -> Result<f64> {
199 use std::fs;
201
202 if let Ok(meminfo) = fs::read_to_string("/proc/meminfo") {
203 let mut total = 0u64;
204 let mut available = 0u64;
205
206 for line in meminfo.lines() {
207 if line.starts_with("MemTotal:") {
208 if let Some(val) = line.split_whitespace().nth(1) {
209 total = val.parse().unwrap_or(0);
210 }
211 } else if line.starts_with("MemAvailable:") {
212 if let Some(val) = line.split_whitespace().nth(1) {
213 available = val.parse().unwrap_or(0);
214 }
215 }
216 }
217
218 if total > 0 {
219 let used = total.saturating_sub(available);
220 return Ok((used as f64 / total as f64 * 100.0).min(100.0));
221 }
222 }
223
224 Ok(50.0) }
226}
227
228impl Default for SystemLoadMonitor {
229 fn default() -> Self {
230 Self::new()
231 }
232}
233
234#[derive(Debug)]
236pub struct AdaptiveConcurrencyController {
237 monitor: SystemLoadMonitor,
239 max_concurrency: usize,
241 current_concurrency: Arc<AtomicU64>,
243 high_load_threshold: f64,
245 low_load_threshold: f64,
247 adjustment_interval: Duration,
249 last_adjustment: Arc<std::sync::Mutex<Instant>>,
251}
252
253impl AdaptiveConcurrencyController {
254 pub fn new(max_concurrency: usize) -> Self {
256 Self {
257 monitor: SystemLoadMonitor::new(),
258 max_concurrency,
259 current_concurrency: Arc::new(AtomicU64::new(max_concurrency as u64)),
260 high_load_threshold: 0.75, low_load_threshold: 0.40, adjustment_interval: Duration::from_secs(5),
263 last_adjustment: Arc::new(std::sync::Mutex::new(Instant::now())),
264 }
265 }
266
267 pub fn current_concurrency(&self) -> usize {
269 self.current_concurrency.load(Ordering::Relaxed) as usize
270 }
271
272 pub fn update_concurrency(&self) {
274 let mut last_adj = self.last_adjustment.lock().expect("Lock poisoned");
275
276 if last_adj.elapsed() < self.adjustment_interval {
277 return; }
279
280 *last_adj = Instant::now();
281 drop(last_adj);
282
283 let load = self.monitor.overall_load();
284 let current = self.current_concurrency.load(Ordering::Relaxed) as usize;
285
286 let new_concurrency = if load > self.high_load_threshold {
287 ((current as f64 * 0.75).max(1.0) as usize).min(self.max_concurrency)
289 } else if load < self.low_load_threshold {
290 ((current as f64 * 1.25) as usize).min(self.max_concurrency)
292 } else {
293 self.monitor.recommended_concurrency(self.max_concurrency)
295 };
296
297 self.current_concurrency
298 .store(new_concurrency as u64, Ordering::Relaxed);
299 }
300
301 pub fn monitor(&self) -> &SystemLoadMonitor {
303 &self.monitor
304 }
305
306 pub fn with_thresholds(mut self, high: f64, low: f64) -> Self {
308 self.high_load_threshold = high.clamp(0.0, 1.0);
309 self.low_load_threshold = low.clamp(0.0, 1.0);
310 self
311 }
312
313 pub fn with_adjustment_interval(mut self, interval: Duration) -> Self {
315 self.adjustment_interval = interval;
316 self
317 }
318}
319
320#[cfg(test)]
321mod tests {
322 use super::*;
323
324 #[test]
325 fn test_system_load_monitor_creation() {
326 let monitor = SystemLoadMonitor::new();
327
328 let cpu = monitor.cpu_usage();
330 let mem = monitor.memory_usage();
331
332 assert!((0.0..=100.0).contains(&cpu));
333 assert!((0.0..=100.0).contains(&mem));
334 }
335
336 #[test]
337 fn test_overall_load_calculation() {
338 let monitor = SystemLoadMonitor::new();
339 let load = monitor.overall_load();
340
341 assert!((0.0..=1.0).contains(&load));
343 }
344
345 #[test]
346 fn test_recommended_concurrency() {
347 let monitor = SystemLoadMonitor::new();
348 let rec = monitor.recommended_concurrency(16);
349
350 assert!(rec >= 1);
352 assert!(rec <= 16);
353 }
354
355 #[test]
356 fn test_high_low_load_detection() {
357 let monitor = SystemLoadMonitor::new();
358
359 let is_high = monitor.is_high_load(0.75);
361 let is_low = monitor.is_low_load(0.40);
362
363 if is_high {
365 assert!(!is_low);
366 }
367 }
368
369 #[test]
370 fn test_adaptive_concurrency_controller() {
371 let controller = AdaptiveConcurrencyController::new(16);
372
373 let initial = controller.current_concurrency();
374 assert_eq!(initial, 16);
375
376 controller.update_concurrency();
378
379 let after_update = controller.current_concurrency();
380 assert!(after_update >= 1);
381 assert!(after_update <= 16);
382 }
383
384 #[test]
385 fn test_concurrency_adjustment_interval() {
386 let controller = AdaptiveConcurrencyController::new(16);
387
388 controller.update_concurrency();
390 let first = controller.current_concurrency();
391
392 controller.update_concurrency();
394 let second = controller.current_concurrency();
395
396 assert_eq!(first, second, "Concurrency should not change immediately");
397 }
398
399 #[test]
400 fn test_threshold_configuration() {
401 let controller = AdaptiveConcurrencyController::new(16).with_thresholds(0.80, 0.30);
402
403 assert_eq!(controller.high_load_threshold, 0.80);
404 assert_eq!(controller.low_load_threshold, 0.30);
405 }
406
407 #[test]
408 fn test_adjustment_interval_configuration() {
409 let interval = Duration::from_secs(10);
410 let controller = AdaptiveConcurrencyController::new(16).with_adjustment_interval(interval);
411
412 assert_eq!(controller.adjustment_interval, interval);
413 }
414
415 #[test]
416 fn test_monitor_access() {
417 let controller = AdaptiveConcurrencyController::new(16);
418 let monitor = controller.monitor();
419
420 let cpu = monitor.cpu_usage();
422 let mem = monitor.memory_usage();
423
424 assert!((0.0..=100.0).contains(&cpu));
425 assert!((0.0..=100.0).contains(&mem));
426 }
427
428 #[test]
429 fn test_concurrency_bounds() {
430 let controller = AdaptiveConcurrencyController::new(8);
431
432 for _ in 0..10 {
434 controller.update_concurrency();
435 let current = controller.current_concurrency();
436 assert!(current >= 1, "Concurrency should never be zero");
437 assert!(current <= 8, "Concurrency should not exceed max");
438 }
439 }
440}