Coverage Report

Created: 2025-06-23 14:17

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}