oxios_kernel/
resource_monitor.rs1use chrono::{DateTime, Utc};
8use parking_lot::RwLock;
9use serde::{Deserialize, Serialize};
10use std::collections::VecDeque;
11use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
12use std::sync::Arc;
13use sysinfo::System;
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct ResourceSnapshot {
18 pub timestamp: DateTime<Utc>,
20 pub cpu_percent: f32,
22 pub memory_used_mb: u64,
24 pub memory_total_mb: u64,
26 pub active_agents: usize,
28 pub pending_tasks: usize,
30 pub total_token_usage: u64,
32 pub disk_used_gb: f64,
34 pub load_avg_1m: f32,
36}
37
38#[derive(Debug, Clone, Copy)]
40pub struct OverloadThreshold {
41 pub cpu_percent: f32,
43 pub memory_percent: f32,
45 pub load_avg: f32,
47}
48
49impl Default for OverloadThreshold {
50 fn default() -> Self {
51 Self {
52 cpu_percent: 90.0,
53 memory_percent: 90.0,
54 load_avg: 8.0,
55 }
56 }
57}
58
59pub struct ResourceMonitor {
64 interval_secs: u64,
66 history_max: usize,
68 history: RwLock<VecDeque<ResourceSnapshot>>,
69 total_token_usage: AtomicU64,
70 active_agents: AtomicUsize,
71 pending_tasks: AtomicUsize,
72 overload_threshold: OverloadThreshold,
73 sys: parking_lot::Mutex<System>,
75}
76
77impl Default for ResourceMonitor {
78 fn default() -> Self {
79 Self::new(60, 60)
80 }
81}
82
83impl ResourceMonitor {
84 pub fn new(interval_secs: u64, history_max: usize) -> Self {
86 Self {
87 interval_secs,
88 history_max,
89 history: RwLock::new(VecDeque::with_capacity(history_max)),
90 total_token_usage: AtomicU64::new(0),
91 active_agents: AtomicUsize::new(0),
92 pending_tasks: AtomicUsize::new(0),
93 overload_threshold: OverloadThreshold::default(),
94 sys: parking_lot::Mutex::new(System::new_all()),
95 }
96 }
97
98 pub fn snapshot(&self) -> ResourceSnapshot {
103 let mut sys = self.sys.lock();
104 sys.refresh_all();
105
106 let cpu_percent =
108 sys.cpus().iter().map(|c| c.cpu_usage()).sum::<f32>() / sys.cpus().len().max(1) as f32;
109
110 let total_memory = sys.total_memory();
111 let used_memory = sys.used_memory();
112 let memory_total_mb = total_memory / (1024 * 1024);
113 let memory_used_mb = used_memory / (1024 * 1024);
114
115 let load_avg_1m = System::load_average().one as f32;
116
117 let disk_used_gb = estimate_disk_usage();
118
119 ResourceSnapshot {
120 timestamp: Utc::now(),
121 cpu_percent,
122 memory_used_mb,
123 memory_total_mb,
124 active_agents: self.active_agents.load(Ordering::Relaxed),
125 pending_tasks: self.pending_tasks.load(Ordering::Relaxed),
126 total_token_usage: self.total_token_usage.load(Ordering::Relaxed),
127 disk_used_gb,
128 load_avg_1m,
129 }
130 }
131
132 pub fn record_snapshot(&self) {
137 let snap = self.snapshot();
138 let mut history = self.history.write();
139 if history.len() >= self.history_max {
140 history.pop_front();
141 }
142 history.push_back(snap);
143 }
144
145 pub fn start_sampling(self: &Arc<Self>) -> tokio::task::JoinHandle<()> {
150 let monitor = Arc::clone(self);
151 let interval = self.interval_secs;
152 tokio::spawn(async move {
153 let mut ticker = tokio::time::interval(std::time::Duration::from_secs(interval));
154 loop {
155 ticker.tick().await;
156 monitor.record_snapshot();
157 }
158 })
159 }
160
161 pub fn history(&self, last_n: usize) -> Vec<ResourceSnapshot> {
163 let guard = self.history.read();
164 let n = last_n.min(guard.len());
165 guard.iter().rev().take(n).cloned().collect()
166 }
167
168 pub fn is_overloaded(&self) -> bool {
170 let snap = self.snapshot();
171 let memory_percent = if snap.memory_total_mb > 0 {
172 (snap.memory_used_mb as f32 / snap.memory_total_mb as f32) * 100.0
173 } else {
174 0.0
175 };
176
177 snap.cpu_percent >= self.overload_threshold.cpu_percent
178 || memory_percent >= self.overload_threshold.memory_percent
179 || snap.load_avg_1m >= self.overload_threshold.load_avg
180 }
181
182 pub fn set_active_agents(&self, count: usize) {
184 self.active_agents.store(count, Ordering::Relaxed);
185 }
186
187 pub fn set_pending_tasks(&self, count: usize) {
189 self.pending_tasks.store(count, Ordering::Relaxed);
190 }
191
192 pub fn add_token_usage(&self, tokens: u64) {
194 self.total_token_usage.fetch_add(tokens, Ordering::Relaxed);
195 }
196
197 pub fn overload_threshold(&self) -> OverloadThreshold {
199 self.overload_threshold
200 }
201}
202
203fn estimate_disk_usage() -> f64 {
206 let cwd = std::env::current_dir().unwrap_or_default();
207 walk_dir_size(&cwd) as f64 / (1024.0 * 1024.0 * 1024.0)
208}
209
210fn walk_dir_size(path: &std::path::Path) -> u64 {
212 let mut total = 0u64;
213 if let Ok(entries) = std::fs::read_dir(path) {
214 for entry in entries.flatten() {
215 let meta = entry.metadata();
216 if let Ok(m) = meta {
217 if m.is_file() {
218 total += m.len();
219 } else if m.is_dir() {
220 total += walk_dir_size(&entry.path());
221 }
222 }
223 }
224 }
225 total
226}
227
228#[cfg(test)]
229mod tests {
230 use super::*;
231
232 #[test]
233 fn test_snapshot_structure() {
234 let monitor = ResourceMonitor::default();
235 let snap = monitor.snapshot();
236
237 assert!(snap.timestamp <= Utc::now());
238 assert!(snap.cpu_percent >= 0.0);
240 assert!(snap.memory_used_mb >= 0);
241 assert!(snap.memory_total_mb >= 0);
242 assert!(snap.active_agents >= 0);
243 assert!(snap.pending_tasks >= 0);
244 assert!(snap.total_token_usage >= 0);
245 assert!(snap.disk_used_gb >= 0.0);
246 assert!(snap.load_avg_1m >= 0.0);
247 }
248
249 #[test]
250 fn test_is_overloaded_default_threshold() {
251 let monitor = ResourceMonitor::default();
252 let _ = monitor.is_overloaded();
256 }
257
258 #[test]
259 fn test_is_overloaded_high_thresholds_not_overloaded() {
260 let monitor = ResourceMonitor::default();
263 let result = monitor.is_overloaded();
266 let _ = result;
269 }
270
271 #[test]
272 fn test_history_management() {
273 let monitor = ResourceMonitor::new(1, 5);
274
275 assert!(monitor.history(10).is_empty());
277
278 for _ in 0..3 {
280 monitor.record_snapshot();
281 }
282
283 let history = monitor.history(10);
285 assert_eq!(history.len(), 3);
286 }
287
288 #[test]
289 fn test_history_eviction() {
290 let monitor = ResourceMonitor::new(1, 3);
291
292 for _ in 0..5 {
294 monitor.record_snapshot();
295 }
296
297 let history = monitor.history(10);
299 assert_eq!(history.len(), 3);
300 }
301
302 #[test]
303 fn test_set_active_agents() {
304 let monitor = ResourceMonitor::default();
305 monitor.set_active_agents(5);
306 let snap = monitor.snapshot();
307 assert_eq!(snap.active_agents, 5);
308 }
309
310 #[test]
311 fn test_set_pending_tasks() {
312 let monitor = ResourceMonitor::default();
313 monitor.set_pending_tasks(3);
314 let snap = monitor.snapshot();
315 assert_eq!(snap.pending_tasks, 3);
316 }
317
318 #[test]
319 fn test_add_token_usage() {
320 let monitor = ResourceMonitor::default();
321 monitor.add_token_usage(100);
322 monitor.add_token_usage(200);
323 let snap = monitor.snapshot();
324 assert_eq!(snap.total_token_usage, 300);
325 }
326
327 #[test]
328 fn test_overload_threshold_default() {
329 let threshold = OverloadThreshold::default();
330 assert_eq!(threshold.cpu_percent, 90.0);
331 assert_eq!(threshold.memory_percent, 90.0);
332 assert_eq!(threshold.load_avg, 8.0);
333 }
334
335 #[test]
336 fn test_overload_threshold_custom() {
337 let threshold = OverloadThreshold {
338 cpu_percent: 75.0,
339 memory_percent: 80.0,
340 load_avg: 4.0,
341 };
342 assert_eq!(threshold.cpu_percent, 75.0);
343 assert_eq!(threshold.memory_percent, 80.0);
344 assert_eq!(threshold.load_avg, 4.0);
345 }
346
347 #[test]
348 fn test_history_last_n() {
349 let monitor = ResourceMonitor::new(1, 10);
350 let empty = monitor.history(5);
351 assert!(empty.is_empty());
352
353 let many = monitor.history(100);
354 assert!(many.is_empty());
355 }
356
357 #[test]
358 fn test_load_average_struct() {
359 let la = System::load_average();
360 assert!(la.one >= 0.0);
361 assert!(la.five >= 0.0);
362 assert!(la.fifteen >= 0.0);
363 }
364}