/home/b/lab/denet/src/core/monitoring_utils.rs
Line | Count | Source |
1 | | //! Monitoring utilities for common monitoring loop patterns |
2 | | //! |
3 | | //! This module provides reusable monitoring functionality to eliminate |
4 | | //! code duplication across the codebase. |
5 | | |
6 | | use crate::core::constants::{sampling, timeouts}; |
7 | | use crate::core::process_monitor::ProcessMonitor; |
8 | | use crate::monitor::Metrics; |
9 | | use std::sync::atomic::{AtomicBool, Ordering}; |
10 | | use std::sync::Arc; |
11 | | use std::time::{Duration, Instant}; |
12 | | |
13 | | /// Configuration for monitoring loops |
14 | | #[derive(Debug, Clone)] |
15 | | pub struct MonitoringConfig { |
16 | | /// Interval between samples |
17 | | pub sample_interval: Duration, |
18 | | /// Optional timeout for the monitoring loop |
19 | | pub timeout: Option<Duration>, |
20 | | /// Whether to continue monitoring after process exits |
21 | | pub monitor_after_exit: bool, |
22 | | /// Additional samples to collect after process exits |
23 | | pub final_sample_count: u32, |
24 | | /// Delay between final samples |
25 | | pub final_sample_delay: Duration, |
26 | | } |
27 | | |
28 | | impl Default for MonitoringConfig { |
29 | 17 | fn default() -> Self { |
30 | 17 | Self { |
31 | 17 | sample_interval: sampling::STANDARD, |
32 | 17 | timeout: None, |
33 | 17 | monitor_after_exit: false, |
34 | 17 | final_sample_count: 0, |
35 | 17 | final_sample_delay: crate::core::constants::delays::STANDARD, |
36 | 17 | } |
37 | 17 | } |
38 | | } |
39 | | |
40 | | impl MonitoringConfig { |
41 | | /// Create a new monitoring configuration |
42 | 12 | pub fn new() -> Self { |
43 | 12 | Self::default() |
44 | 12 | } |
45 | | |
46 | | /// Set the sample interval |
47 | 9 | pub fn with_sample_interval(mut self, interval: Duration) -> Self { |
48 | 9 | self.sample_interval = interval; |
49 | 9 | self |
50 | 9 | } |
51 | | |
52 | | /// Set the timeout |
53 | 7 | pub fn with_timeout(mut self, timeout: Duration) -> Self { |
54 | 7 | self.timeout = Some(timeout); |
55 | 7 | self |
56 | 7 | } |
57 | | |
58 | | /// Enable monitoring after process exit with specified sample count |
59 | 4 | pub fn with_final_samples(mut self, count: u32, delay: Duration) -> Self { |
60 | 4 | self.monitor_after_exit = true; |
61 | 4 | self.final_sample_count = count; |
62 | 4 | self.final_sample_delay = delay; |
63 | 4 | self |
64 | 4 | } |
65 | | |
66 | | /// Quick configuration for fast sampling |
67 | 2 | pub fn fast_sampling() -> Self { |
68 | 2 | Self::new().with_sample_interval(sampling::FAST) |
69 | 2 | } |
70 | | |
71 | | /// Quick configuration for test scenarios |
72 | 1 | pub fn for_tests() -> Self { |
73 | 1 | Self::new() |
74 | 1 | .with_sample_interval(sampling::FAST) |
75 | 1 | .with_timeout(timeouts::TEST) |
76 | 1 | .with_final_samples(5, crate::core::constants::delays::STANDARD) |
77 | 1 | } |
78 | | } |
79 | | |
80 | | /// Result of a monitoring session |
81 | | #[derive(Debug)] |
82 | | pub struct MonitoringResult { |
83 | | /// All collected metrics samples |
84 | | pub samples: Vec<Metrics>, |
85 | | /// Total monitoring duration |
86 | | pub duration: Duration, |
87 | | /// Whether monitoring was stopped due to timeout |
88 | | pub timed_out: bool, |
89 | | /// Whether monitoring was interrupted by signal |
90 | | pub interrupted: bool, |
91 | | } |
92 | | |
93 | | impl MonitoringResult { |
94 | | /// Get the last sample if available |
95 | 2 | pub fn last_sample(&self) -> Option<&Metrics> { |
96 | 2 | self.samples.last() |
97 | 2 | } |
98 | | |
99 | | /// Get the first sample if available |
100 | 2 | pub fn first_sample(&self) -> Option<&Metrics> { |
101 | 2 | self.samples.first() |
102 | 2 | } |
103 | | |
104 | | /// Check if any samples were collected |
105 | 2 | pub fn has_samples(&self) -> bool { |
106 | 2 | !self.samples.is_empty() |
107 | 2 | } |
108 | | |
109 | | /// Get sample count |
110 | 2 | pub fn sample_count(&self) -> usize { |
111 | 2 | self.samples.len() |
112 | 2 | } |
113 | | } |
114 | | |
115 | | /// A reusable monitoring loop that eliminates common duplication |
116 | | pub struct MonitoringLoop { |
117 | | config: MonitoringConfig, |
118 | | interrupt_signal: Option<Arc<AtomicBool>>, |
119 | | } |
120 | | |
121 | | impl MonitoringLoop { |
122 | | /// Create a new monitoring loop with default configuration |
123 | 4 | pub fn new() -> Self { |
124 | 4 | Self { |
125 | 4 | config: MonitoringConfig::default(), |
126 | 4 | interrupt_signal: None, |
127 | 4 | } |
128 | 4 | } |
129 | | |
130 | | /// Create a monitoring loop with specific configuration |
131 | 4 | pub fn with_config(config: MonitoringConfig) -> Self { |
132 | 4 | Self { |
133 | 4 | config, |
134 | 4 | interrupt_signal: None, |
135 | 4 | } |
136 | 4 | } |
137 | | |
138 | | /// Set an interrupt signal (e.g., for Ctrl+C handling) |
139 | 1 | pub fn with_interrupt_signal(mut self, signal: Arc<AtomicBool>) -> Self { |
140 | 1 | self.interrupt_signal = Some(signal); |
141 | 1 | self |
142 | 1 | } |
143 | | |
144 | | /// Run the monitoring loop with a custom processor function |
145 | 3 | pub fn run_with_processor<F>( |
146 | 3 | &self, |
147 | 3 | mut monitor: ProcessMonitor, |
148 | 3 | mut processor: F, |
149 | 3 | ) -> MonitoringResult |
150 | 3 | where |
151 | 3 | F: FnMut(&Metrics), |
152 | 3 | { |
153 | 3 | let mut samples = Vec::new(); |
154 | 3 | let start_time = Instant::now(); |
155 | 3 | let mut timed_out = false; |
156 | 3 | let mut interrupted = false; |
157 | | |
158 | | // Main monitoring loop |
159 | 5 | while monitor.is_running() { |
160 | | // Check for timeout |
161 | 2 | if let Some(timeout) = self.config.timeout { |
162 | 2 | if start_time.elapsed() >= timeout { |
163 | 0 | timed_out = true; |
164 | 0 | break; |
165 | 2 | } |
166 | 0 | } |
167 | | |
168 | | // Check for interrupt signal |
169 | 2 | if let Some(ref signal0 ) = self.interrupt_signal { |
170 | 0 | if !signal.load(Ordering::SeqCst) { |
171 | 0 | interrupted = true; |
172 | 0 | break; |
173 | 0 | } |
174 | 2 | } |
175 | | |
176 | | // Sample metrics |
177 | 2 | if let Some(metrics) = monitor.sample_metrics() { |
178 | 2 | processor(&metrics); |
179 | 2 | samples.push(metrics); |
180 | 2 | }0 |
181 | | |
182 | | // Sleep between samples |
183 | 2 | std::thread::sleep(self.config.sample_interval); |
184 | | } |
185 | | |
186 | | // Collect final samples if configured |
187 | 3 | if self.config.monitor_after_exit && self.config.final_sample_count > 00 { |
188 | 0 | for _ in 0..self.config.final_sample_count { |
189 | 0 | std::thread::sleep(self.config.final_sample_delay); |
190 | 0 | if let Some(metrics) = monitor.sample_metrics() { |
191 | 0 | processor(&metrics); |
192 | 0 | samples.push(metrics); |
193 | 0 | } |
194 | | } |
195 | 3 | } |
196 | | |
197 | 3 | MonitoringResult { |
198 | 3 | samples, |
199 | 3 | duration: start_time.elapsed(), |
200 | 3 | timed_out, |
201 | 3 | interrupted, |
202 | 3 | } |
203 | 3 | } |
204 | | |
205 | | /// Run the monitoring loop and collect all samples |
206 | 3 | pub fn run(&self, monitor: ProcessMonitor) -> MonitoringResult { |
207 | 3 | self.run_with_processor(monitor, |_| {}2 ) |
208 | 3 | } |
209 | | |
210 | | /// Run the monitoring loop with progress callback |
211 | 0 | pub fn run_with_progress<F>( |
212 | 0 | &self, |
213 | 0 | monitor: ProcessMonitor, |
214 | 0 | progress_callback: F, |
215 | 0 | ) -> MonitoringResult |
216 | 0 | where |
217 | 0 | F: Fn(usize, &Metrics), |
218 | 0 | { |
219 | 0 | let mut sample_count = 0; |
220 | 0 | self.run_with_processor(monitor, |metrics| { |
221 | 0 | sample_count += 1; |
222 | 0 | progress_callback(sample_count, metrics); |
223 | 0 | }) |
224 | 0 | } |
225 | | } |
226 | | |
227 | | impl Default for MonitoringLoop { |
228 | 1 | fn default() -> Self { |
229 | 1 | Self::new() |
230 | 1 | } |
231 | | } |
232 | | |
233 | | /// Quick function for simple monitoring scenarios |
234 | 3 | pub fn monitor_until_completion( |
235 | 3 | monitor: ProcessMonitor, |
236 | 3 | sample_interval: Duration, |
237 | 3 | timeout: Option<Duration>, |
238 | 3 | ) -> MonitoringResult { |
239 | 3 | let config = MonitoringConfig::new().with_sample_interval(sample_interval); |
240 | | |
241 | 3 | let config = if let Some(timeout) = timeout { |
242 | 3 | config.with_timeout(timeout) |
243 | | } else { |
244 | 0 | config |
245 | | }; |
246 | | |
247 | 3 | MonitoringLoop::with_config(config).run(monitor) |
248 | 3 | } |
249 | | |
250 | | /// Quick function for test monitoring scenarios |
251 | 0 | pub fn monitor_for_test(monitor: ProcessMonitor) -> MonitoringResult { |
252 | 0 | MonitoringLoop::with_config(MonitoringConfig::for_tests()).run(monitor) |
253 | 0 | } |
254 | | |
255 | | /// Quick function for monitoring with progress output |
256 | 0 | pub fn monitor_with_progress<F>( |
257 | 0 | monitor: ProcessMonitor, |
258 | 0 | sample_interval: Duration, |
259 | 0 | progress_callback: F, |
260 | 0 | ) -> MonitoringResult |
261 | 0 | where |
262 | 0 | F: Fn(usize, &Metrics), |
263 | 0 | { |
264 | 0 | let config = MonitoringConfig::new().with_sample_interval(sample_interval); |
265 | 0 | MonitoringLoop::with_config(config).run_with_progress(monitor, progress_callback) |
266 | 0 | } |
267 | | |
268 | | #[cfg(test)] |
269 | | mod tests { |
270 | | use super::*; |
271 | | use crate::core::constants::delays; |
272 | | |
273 | | #[test] |
274 | 1 | fn test_monitoring_config_builder() { |
275 | 1 | let config = MonitoringConfig::new() |
276 | 1 | .with_sample_interval(sampling::FAST) |
277 | 1 | .with_timeout(timeouts::SHORT) |
278 | 1 | .with_final_samples(3, delays::STANDARD); |
279 | 1 | |
280 | 1 | assert_eq!(config.sample_interval, sampling::FAST); |
281 | 1 | assert_eq!(config.timeout, Some(timeouts::SHORT)); |
282 | 1 | assert_eq!(config.final_sample_count, 3); |
283 | 1 | assert!(config.monitor_after_exit); |
284 | 1 | } |
285 | | |
286 | | #[test] |
287 | 1 | fn test_monitoring_config_presets() { |
288 | 1 | let fast_config = MonitoringConfig::fast_sampling(); |
289 | 1 | assert_eq!(fast_config.sample_interval, sampling::FAST); |
290 | | |
291 | 1 | let test_config = MonitoringConfig::for_tests(); |
292 | 1 | assert_eq!(test_config.sample_interval, sampling::FAST); |
293 | 1 | assert_eq!(test_config.timeout, Some(timeouts::TEST)); |
294 | 1 | assert_eq!(test_config.final_sample_count, 5); |
295 | 1 | } |
296 | | |
297 | | #[test] |
298 | 1 | fn test_monitoring_result_methods() { |
299 | 1 | let samples = vec![Metrics::default(), Metrics::default()]; |
300 | 1 | |
301 | 1 | let result = MonitoringResult { |
302 | 1 | samples, |
303 | 1 | duration: Duration::from_secs(1), |
304 | 1 | timed_out: false, |
305 | 1 | interrupted: false, |
306 | 1 | }; |
307 | 1 | |
308 | 1 | assert!(result.has_samples()); |
309 | 1 | assert_eq!(result.sample_count(), 2); |
310 | 1 | assert!(result.first_sample().is_some()); |
311 | 1 | assert!(result.last_sample().is_some()); |
312 | | |
313 | | // Test empty result |
314 | 1 | let empty_result = MonitoringResult { |
315 | 1 | samples: vec![], |
316 | 1 | duration: Duration::from_secs(0), |
317 | 1 | timed_out: false, |
318 | 1 | interrupted: false, |
319 | 1 | }; |
320 | 1 | |
321 | 1 | assert!(!empty_result.has_samples()); |
322 | 1 | assert_eq!(empty_result.sample_count(), 0); |
323 | 1 | assert!(empty_result.first_sample().is_none()); |
324 | 1 | assert!(empty_result.last_sample().is_none()); |
325 | 1 | } |
326 | | |
327 | | #[test] |
328 | 1 | fn test_monitoring_config_defaults() { |
329 | 1 | let config = MonitoringConfig::default(); |
330 | 1 | assert_eq!(config.sample_interval, sampling::STANDARD); |
331 | 1 | assert_eq!(config.timeout, None); |
332 | 1 | assert!(!config.monitor_after_exit); |
333 | 1 | assert_eq!(config.final_sample_count, 0); |
334 | 1 | assert_eq!(config.final_sample_delay, delays::STANDARD); |
335 | 1 | } |
336 | | |
337 | | #[test] |
338 | 1 | fn test_monitoring_config_new() { |
339 | 1 | let config = MonitoringConfig::new(); |
340 | 1 | assert_eq!(config.sample_interval, sampling::STANDARD); |
341 | 1 | assert_eq!(config.timeout, None); |
342 | 1 | assert!(!config.monitor_after_exit); |
343 | 1 | assert_eq!(config.final_sample_count, 0); |
344 | 1 | assert_eq!(config.final_sample_delay, delays::STANDARD); |
345 | 1 | } |
346 | | |
347 | | #[test] |
348 | 1 | fn test_monitoring_config_chaining() { |
349 | 1 | let config = MonitoringConfig::new() |
350 | 1 | .with_sample_interval(sampling::SLOW) |
351 | 1 | .with_timeout(timeouts::MEDIUM) |
352 | 1 | .with_final_samples(10, delays::SHORT); |
353 | 1 | |
354 | 1 | assert_eq!(config.sample_interval, sampling::SLOW); |
355 | 1 | assert_eq!(config.timeout, Some(timeouts::MEDIUM)); |
356 | 1 | assert!(config.monitor_after_exit); |
357 | 1 | assert_eq!(config.final_sample_count, 10); |
358 | 1 | assert_eq!(config.final_sample_delay, delays::SHORT); |
359 | 1 | } |
360 | | |
361 | | #[test] |
362 | 1 | fn test_monitoring_loop_creation() { |
363 | 1 | let loop1 = MonitoringLoop::new(); |
364 | 1 | assert_eq!(loop1.config.sample_interval, sampling::STANDARD); |
365 | 1 | assert!(loop1.interrupt_signal.is_none()); |
366 | | |
367 | 1 | let config = MonitoringConfig::fast_sampling(); |
368 | 1 | let loop2 = MonitoringLoop::with_config(config.clone()); |
369 | 1 | assert_eq!(loop2.config.sample_interval, config.sample_interval); |
370 | | |
371 | 1 | let interrupt = Arc::new(AtomicBool::new(true)); |
372 | 1 | let loop3 = MonitoringLoop::new().with_interrupt_signal(interrupt.clone()); |
373 | 1 | assert!(loop3.interrupt_signal.is_some()); |
374 | 1 | } |
375 | | |
376 | | #[test] |
377 | 1 | fn test_monitoring_loop_default() { |
378 | 1 | let loop1 = MonitoringLoop::default(); |
379 | 1 | let loop2 = MonitoringLoop::new(); |
380 | 1 | |
381 | 1 | assert_eq!(loop1.config.sample_interval, loop2.config.sample_interval); |
382 | 1 | assert_eq!(loop1.config.timeout, loop2.config.timeout); |
383 | 1 | } |
384 | | |
385 | | #[test] |
386 | 1 | fn test_monitoring_result_flags() { |
387 | 1 | let result = MonitoringResult { |
388 | 1 | samples: vec![], |
389 | 1 | duration: Duration::from_secs(5), |
390 | 1 | timed_out: true, |
391 | 1 | interrupted: false, |
392 | 1 | }; |
393 | 1 | |
394 | 1 | assert!(result.timed_out); |
395 | 1 | assert!(!result.interrupted); |
396 | | |
397 | 1 | let result = MonitoringResult { |
398 | 1 | samples: vec![], |
399 | 1 | duration: Duration::from_secs(3), |
400 | 1 | timed_out: false, |
401 | 1 | interrupted: true, |
402 | 1 | }; |
403 | 1 | |
404 | 1 | assert!(!result.timed_out); |
405 | 1 | assert!(result.interrupted); |
406 | 1 | } |
407 | | |
408 | | #[test] |
409 | 1 | fn test_convenience_functions_exist() { |
410 | | // These functions should exist and compile, but we can't easily test them |
411 | | // without a real ProcessMonitor instance. We test their signatures here. |
412 | | use std::time::Duration; |
413 | | |
414 | | // Test that the functions can be called (they'll fail due to no process, but that's OK) |
415 | 1 | let dummy_monitor = match ProcessMonitor::new( |
416 | 1 | vec!["true".to_string()], |
417 | 1 | Duration::from_millis(100), |
418 | 1 | Duration::from_millis(1000), |
419 | 1 | ) { |
420 | 1 | Ok(m) => m, |
421 | 0 | Err(_) => return, // Skip test if we can't create a monitor |
422 | | }; |
423 | | |
424 | 1 | let _result = monitor_until_completion( |
425 | 1 | dummy_monitor, |
426 | 1 | Duration::from_millis(10), |
427 | 1 | Some(Duration::from_millis(100)), |
428 | 1 | ); |
429 | 1 | } |
430 | | |
431 | | #[test] |
432 | 1 | fn test_configuration_edge_cases() { |
433 | 1 | // Test with zero final samples (should not enable monitor_after_exit) |
434 | 1 | let config = MonitoringConfig::new().with_final_samples(0, delays::STANDARD); |
435 | 1 | |
436 | 1 | assert!(config.monitor_after_exit); // It's still set to true by the method |
437 | 1 | assert_eq!(config.final_sample_count, 0); |
438 | | |
439 | | // Test with very small intervals |
440 | 1 | let config = MonitoringConfig::new().with_sample_interval(Duration::from_millis(1)); |
441 | 1 | |
442 | 1 | assert_eq!(config.sample_interval, Duration::from_millis(1)); |
443 | | |
444 | | // Test with very large timeout |
445 | 1 | let config = MonitoringConfig::new().with_timeout(Duration::from_secs(3600)); |
446 | 1 | |
447 | 1 | assert_eq!(config.timeout, Some(Duration::from_secs(3600))); |
448 | 1 | } |
449 | | } |