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/config.rs
Line
Count
Source
1
//! Configuration structures for denet monitoring
2
//!
3
//! This module provides builder patterns and configuration structs to replace
4
//! scattered parameters throughout the codebase.
5
6
use crate::core::constants::defaults;
7
use crate::error::{DenetError, Result};
8
use std::path::PathBuf;
9
use std::time::Duration;
10
11
/// Output format options for monitoring data
12
#[derive(Clone, Debug, PartialEq, Default)]
13
pub enum OutputFormat {
14
    #[default]
15
    JsonLines,
16
    Json,
17
    Csv,
18
}
19
20
impl std::str::FromStr for OutputFormat {
21
    type Err = DenetError;
22
23
10
    fn from_str(s: &str) -> Result<Self> {
24
10
        match s.to_lowercase().as_str() {
25
10
            "json" => 
Ok(OutputFormat::Json)2
,
26
8
            "jsonl" | 
"jsonlines"6
=>
Ok(OutputFormat::JsonLines)3
,
27
5
            "csv" => 
Ok(OutputFormat::Csv)3
,
28
2
            _ => Err(DenetError::InvalidConfiguration(format!(
29
2
                "Unknown output format: {s}"
30
2
            ))),
31
        }
32
10
    }
33
}
34
35
impl std::fmt::Display for OutputFormat {
36
3
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37
3
        match self {
38
1
            OutputFormat::Json => write!(f, "json"),
39
1
            OutputFormat::JsonLines => write!(f, "jsonl"),
40
1
            OutputFormat::Csv => write!(f, "csv"),
41
        }
42
3
    }
43
}
44
45
/// Configuration for process monitoring behavior
46
#[derive(Clone, Debug)]
47
pub struct MonitorConfig {
48
    /// Base sampling interval
49
    pub base_interval: Duration,
50
    /// Maximum sampling interval for adaptive sampling
51
    pub max_interval: Duration,
52
    /// Whether to show I/O since process start instead of since monitoring start
53
    pub since_process_start: bool,
54
    /// Whether to include child processes in monitoring
55
    pub include_children: bool,
56
    /// Maximum monitoring duration (None for unlimited)
57
    pub max_duration: Option<Duration>,
58
    /// Enable eBPF profiling
59
    pub enable_ebpf: bool,
60
}
61
62
impl Default for MonitorConfig {
63
10
    fn default() -> Self {
64
10
        Self {
65
10
            base_interval: defaults::BASE_INTERVAL,
66
10
            max_interval: defaults::MAX_INTERVAL,
67
10
            since_process_start: false,
68
10
            include_children: true,
69
10
            max_duration: None,
70
10
            enable_ebpf: false,
71
10
        }
72
10
    }
73
}
74
75
impl MonitorConfig {
76
    /// Create a new monitor configuration builder
77
1
    pub fn builder() -> MonitorConfigBuilder {
78
1
        MonitorConfigBuilder::default()
79
1
    }
80
81
    /// Validate the configuration
82
9
    pub fn validate(&self) -> Result<()> {
83
9
        if self.base_interval > self.max_interval {
84
2
            return Err(DenetError::InvalidConfiguration(
85
2
                "Base interval cannot be greater than max interval".to_string(),
86
2
            ));
87
7
        }
88
7
        if self.base_interval.is_zero() {
89
1
            return Err(DenetError::InvalidConfiguration(
90
1
                "Base interval cannot be zero".to_string(),
91
1
            ));
92
6
        }
93
6
        Ok(())
94
9
    }
95
}
96
97
/// Configuration for output behavior
98
#[derive(Clone, Debug)]
99
pub struct OutputConfig {
100
    /// File to write output to (None for no file output)
101
    pub output_file: Option<PathBuf>,
102
    /// Output format
103
    pub format: OutputFormat,
104
    /// Whether to store samples in memory
105
    pub store_in_memory: bool,
106
    /// Whether to suppress stdout output
107
    pub quiet: bool,
108
    /// Whether to update output in place (vs new lines)
109
    pub update_in_place: bool,
110
}
111
112
impl Default for OutputConfig {
113
7
    fn default() -> Self {
114
7
        Self {
115
7
            output_file: None,
116
7
            format: OutputFormat::default(),
117
7
            store_in_memory: true,
118
7
            quiet: false,
119
7
            update_in_place: true,
120
7
        }
121
7
    }
122
}
123
124
impl OutputConfig {
125
    /// Create a new output configuration builder
126
1
    pub fn builder() -> OutputConfigBuilder {
127
1
        OutputConfigBuilder::default()
128
1
    }
129
}
130
131
/// Builder for MonitorConfig
132
#[derive(Default)]
133
pub struct MonitorConfigBuilder {
134
    base_interval: Option<Duration>,
135
    max_interval: Option<Duration>,
136
    since_process_start: Option<bool>,
137
    include_children: Option<bool>,
138
    max_duration: Option<Duration>,
139
    enable_ebpf: Option<bool>,
140
}
141
142
impl MonitorConfigBuilder {
143
1
    pub fn base_interval(mut self, interval: Duration) -> Self {
144
1
        self.base_interval = Some(interval);
145
1
        self
146
1
    }
147
148
2
    pub fn base_interval_ms(mut self, ms: u64) -> Self {
149
2
        self.base_interval = Some(Duration::from_millis(ms));
150
2
        self
151
2
    }
152
153
1
    pub fn max_interval(mut self, interval: Duration) -> Self {
154
1
        self.max_interval = Some(interval);
155
1
        self
156
1
    }
157
158
2
    pub fn max_interval_ms(mut self, ms: u64) -> Self {
159
2
        self.max_interval = Some(Duration::from_millis(ms));
160
2
        self
161
2
    }
162
163
1
    pub fn since_process_start(mut self, since_start: bool) -> Self {
164
1
        self.since_process_start = Some(since_start);
165
1
        self
166
1
    }
167
168
1
    pub fn include_children(mut self, include: bool) -> Self {
169
1
        self.include_children = Some(include);
170
1
        self
171
1
    }
172
173
1
    pub fn max_duration(mut self, duration: Duration) -> Self {
174
1
        self.max_duration = Some(duration);
175
1
        self
176
1
    }
177
178
2
    pub fn max_duration_secs(mut self, secs: u64) -> Self {
179
2
        if secs > 0 {
180
1
            self.max_duration = Some(Duration::from_secs(secs));
181
1
        }
182
2
        self
183
2
    }
184
185
1
    pub fn enable_ebpf(mut self, enable: bool) -> Self {
186
1
        self.enable_ebpf = Some(enable);
187
1
        self
188
1
    }
189
190
6
    pub fn build(self) -> Result<MonitorConfig> {
191
6
        let config = MonitorConfig {
192
6
            base_interval: self.base_interval.unwrap_or(defaults::BASE_INTERVAL),
193
6
            max_interval: self.max_interval.unwrap_or(defaults::MAX_INTERVAL),
194
6
            since_process_start: self.since_process_start.unwrap_or(false),
195
6
            include_children: self.include_children.unwrap_or(true),
196
6
            max_duration: self.max_duration,
197
6
            enable_ebpf: self.enable_ebpf.unwrap_or(false),
198
6
        };
199
6
        config.validate()
?1
;
200
5
        Ok(config)
201
6
    }
202
}
203
204
/// Builder for OutputConfig
205
#[derive(Default)]
206
pub struct OutputConfigBuilder {
207
    output_file: Option<PathBuf>,
208
    format: Option<OutputFormat>,
209
    store_in_memory: Option<bool>,
210
    quiet: Option<bool>,
211
    update_in_place: Option<bool>,
212
}
213
214
impl OutputConfigBuilder {
215
1
    pub fn output_file<P: Into<PathBuf>>(mut self, path: P) -> Self {
216
1
        self.output_file = Some(path.into());
217
1
        self
218
1
    }
219
220
1
    pub fn format(mut self, format: OutputFormat) -> Self {
221
1
        self.format = Some(format);
222
1
        self
223
1
    }
224
225
2
    pub fn format_str(mut self, format: &str) -> Result<Self> {
226
2
        self.format = Some(format.parse()
?1
);
227
1
        Ok(self)
228
2
    }
229
230
1
    pub fn store_in_memory(mut self, store: bool) -> Self {
231
1
        self.store_in_memory = Some(store);
232
1
        self
233
1
    }
234
235
1
    pub fn quiet(mut self, quiet: bool) -> Self {
236
1
        self.quiet = Some(quiet);
237
1
        self
238
1
    }
239
240
1
    pub fn update_in_place(mut self, update: bool) -> Self {
241
1
        self.update_in_place = Some(update);
242
1
        self
243
1
    }
244
245
3
    pub fn build(self) -> OutputConfig {
246
3
        OutputConfig {
247
3
            output_file: self.output_file,
248
3
            format: self.format.unwrap_or_default(),
249
3
            store_in_memory: self.store_in_memory.unwrap_or(true),
250
3
            quiet: self.quiet.unwrap_or(false),
251
3
            update_in_place: self.update_in_place.unwrap_or(true),
252
3
        }
253
3
    }
254
}
255
256
/// Combined configuration for monitoring operations
257
#[derive(Clone, Debug, Default)]
258
pub struct DenetConfig {
259
    pub monitor: MonitorConfig,
260
    pub output: OutputConfig,
261
}
262
263
impl DenetConfig {
264
1
    pub fn builder() -> DenetConfigBuilder {
265
1
        DenetConfigBuilder::default()
266
1
    }
267
}
268
269
/// Builder for DenetConfig
270
#[derive(Default)]
271
pub struct DenetConfigBuilder {
272
    monitor: Option<MonitorConfig>,
273
    output: Option<OutputConfig>,
274
}
275
276
impl DenetConfigBuilder {
277
2
    pub fn monitor(mut self, config: MonitorConfig) -> Self {
278
2
        self.monitor = Some(config);
279
2
        self
280
2
    }
281
282
1
    pub fn output(mut self, config: OutputConfig) -> Self {
283
1
        self.output = Some(config);
284
1
        self
285
1
    }
286
287
3
    pub fn build(self) -> DenetConfig {
288
3
        DenetConfig {
289
3
            monitor: self.monitor.unwrap_or_default(),
290
3
            output: self.output.unwrap_or_default(),
291
3
        }
292
3
    }
293
}
294
295
#[cfg(test)]
296
mod tests {
297
    use super::*;
298
    use std::str::FromStr;
299
    use std::time::Duration;
300
301
    #[test]
302
1
    fn test_output_format_from_str() {
303
1
        // Test valid formats
304
1
        assert_eq!(OutputFormat::from_str("json").unwrap(), OutputFormat::Json);
305
1
        assert_eq!(
306
1
            OutputFormat::from_str("jsonl").unwrap(),
307
1
            OutputFormat::JsonLines
308
1
        );
309
1
        assert_eq!(
310
1
            OutputFormat::from_str("jsonlines").unwrap(),
311
1
            OutputFormat::JsonLines
312
1
        );
313
1
        assert_eq!(OutputFormat::from_str("csv").unwrap(), OutputFormat::Csv);
314
315
        // Test case insensitivity
316
1
        assert_eq!(OutputFormat::from_str("JSON").unwrap(), OutputFormat::Json);
317
1
        assert_eq!(
318
1
            OutputFormat::from_str("JSONL").unwrap(),
319
1
            OutputFormat::JsonLines
320
1
        );
321
1
        assert_eq!(OutputFormat::from_str("CSV").unwrap(), OutputFormat::Csv);
322
323
        // Test invalid format
324
1
        let result = OutputFormat::from_str("invalid");
325
1
        assert!(
matches!0
(result, Err(DenetError::InvalidConfiguration(_))));
326
1
    }
327
328
    #[test]
329
1
    fn test_output_format_display() {
330
1
        assert_eq!(OutputFormat::Json.to_string(), "json");
331
1
        assert_eq!(OutputFormat::JsonLines.to_string(), "jsonl");
332
1
        assert_eq!(OutputFormat::Csv.to_string(), "csv");
333
1
    }
334
335
    #[test]
336
1
    fn test_output_format_default() {
337
1
        assert_eq!(OutputFormat::default(), OutputFormat::JsonLines);
338
1
    }
339
340
    #[test]
341
1
    fn test_monitor_config_default() {
342
1
        let config = MonitorConfig::default();
343
1
        assert_eq!(config.base_interval, defaults::BASE_INTERVAL);
344
1
        assert_eq!(config.max_interval, defaults::MAX_INTERVAL);
345
1
        assert!(!config.since_process_start);
346
1
        assert!(config.include_children);
347
1
        assert!(config.max_duration.is_none());
348
1
        assert!(!config.enable_ebpf);
349
1
    }
350
351
    #[test]
352
1
    fn test_monitor_config_builder() {
353
1
        let config = MonitorConfig::builder().build().unwrap();
354
1
        assert_eq!(config.base_interval, defaults::BASE_INTERVAL);
355
1
        assert_eq!(config.max_interval, defaults::MAX_INTERVAL);
356
1
    }
357
358
    #[test]
359
1
    fn test_monitor_config_validate_success() {
360
1
        let config = MonitorConfig::default();
361
1
        assert!(config.validate().is_ok());
362
1
    }
363
364
    #[test]
365
1
    fn test_monitor_config_validate_base_greater_than_max() {
366
1
        let config = MonitorConfig {
367
1
            base_interval: Duration::from_millis(2000),
368
1
            max_interval: Duration::from_millis(1000),
369
1
            ..Default::default()
370
1
        };
371
1
        let result = config.validate();
372
1
        assert!(
matches!0
(result, Err(DenetError::InvalidConfiguration(_))));
373
1
        if let Err(DenetError::InvalidConfiguration(msg)) = result {
374
1
            assert!(msg.contains("Base interval cannot be greater than max interval"));
375
0
        }
376
1
    }
377
378
    #[test]
379
1
    fn test_monitor_config_validate_zero_base_interval() {
380
1
        let config = MonitorConfig {
381
1
            base_interval: Duration::from_millis(0),
382
1
            ..Default::default()
383
1
        };
384
1
        let result = config.validate();
385
1
        assert!(
matches!0
(result, Err(DenetError::InvalidConfiguration(_))));
386
1
        if let Err(DenetError::InvalidConfiguration(msg)) = result {
387
1
            assert!(msg.contains("Base interval cannot be zero"));
388
0
        }
389
1
    }
390
391
    #[test]
392
1
    fn test_output_config_default() {
393
1
        let config = OutputConfig::default();
394
1
        assert!(config.output_file.is_none());
395
1
        assert_eq!(config.format, OutputFormat::JsonLines);
396
1
        assert!(config.store_in_memory);
397
1
        assert!(!config.quiet);
398
1
        assert!(config.update_in_place);
399
1
    }
400
401
    #[test]
402
1
    fn test_output_config_builder() {
403
1
        let config = OutputConfig::builder().build();
404
1
        assert!(config.output_file.is_none());
405
1
        assert_eq!(config.format, OutputFormat::JsonLines);
406
1
    }
407
408
    #[test]
409
1
    fn test_monitor_config_builder_all_options() {
410
1
        let config = MonitorConfigBuilder::default()
411
1
            .base_interval(Duration::from_millis(200))
412
1
            .max_interval(Duration::from_millis(2000))
413
1
            .since_process_start(true)
414
1
            .include_children(false)
415
1
            .max_duration(Duration::from_secs(60))
416
1
            .enable_ebpf(true)
417
1
            .build()
418
1
            .unwrap();
419
1
420
1
        assert_eq!(config.base_interval, Duration::from_millis(200));
421
1
        assert_eq!(config.max_interval, Duration::from_millis(2000));
422
1
        assert!(config.since_process_start);
423
1
        assert!(!config.include_children);
424
1
        assert_eq!(config.max_duration, Some(Duration::from_secs(60)));
425
1
        assert!(config.enable_ebpf);
426
1
    }
427
428
    #[test]
429
1
    fn test_monitor_config_builder_ms_methods() {
430
1
        let config = MonitorConfigBuilder::default()
431
1
            .base_interval_ms(300)
432
1
            .max_interval_ms(3000)
433
1
            .build()
434
1
            .unwrap();
435
1
436
1
        assert_eq!(config.base_interval, Duration::from_millis(300));
437
1
        assert_eq!(config.max_interval, Duration::from_millis(3000));
438
1
    }
439
440
    #[test]
441
1
    fn test_monitor_config_builder_max_duration_secs() {
442
1
        let config = MonitorConfigBuilder::default()
443
1
            .max_duration_secs(120)
444
1
            .build()
445
1
            .unwrap();
446
1
447
1
        assert_eq!(config.max_duration, Some(Duration::from_secs(120)));
448
1
    }
449
450
    #[test]
451
1
    fn test_monitor_config_builder_max_duration_secs_zero() {
452
1
        let config = MonitorConfigBuilder::default()
453
1
            .max_duration_secs(0)
454
1
            .build()
455
1
            .unwrap();
456
1
457
1
        assert!(config.max_duration.is_none());
458
1
    }
459
460
    #[test]
461
1
    fn test_monitor_config_builder_validation_fails() {
462
1
        let result = MonitorConfigBuilder::default()
463
1
            .base_interval_ms(2000)
464
1
            .max_interval_ms(1000)
465
1
            .build();
466
1
467
1
        assert!(result.is_err());
468
1
    }
469
470
    #[test]
471
1
    fn test_output_config_builder_all_options() {
472
1
        let config = OutputConfigBuilder::default()
473
1
            .output_file("output.json")
474
1
            .format(OutputFormat::Json)
475
1
            .store_in_memory(false)
476
1
            .quiet(true)
477
1
            .update_in_place(false)
478
1
            .build();
479
1
480
1
        assert_eq!(config.output_file, Some(PathBuf::from("output.json")));
481
1
        assert_eq!(config.format, OutputFormat::Json);
482
1
        assert!(!config.store_in_memory);
483
1
        assert!(config.quiet);
484
1
        assert!(!config.update_in_place);
485
1
    }
486
487
    #[test]
488
1
    fn test_output_config_builder_format_str() {
489
1
        let result = OutputConfigBuilder::default().format_str("csv");
490
1
        assert!(result.is_ok());
491
1
        let config = result.unwrap().build();
492
1
        assert_eq!(config.format, OutputFormat::Csv);
493
1
    }
494
495
    #[test]
496
1
    fn test_output_config_builder_format_str_invalid() {
497
1
        let result = OutputConfigBuilder::default().format_str("invalid");
498
1
        assert!(result.is_err());
499
1
    }
500
501
    #[test]
502
1
    fn test_denet_config_default() {
503
1
        let config = DenetConfig::default();
504
1
        assert_eq!(config.monitor.base_interval, defaults::BASE_INTERVAL);
505
1
        assert_eq!(config.output.format, OutputFormat::JsonLines);
506
1
    }
507
508
    #[test]
509
1
    fn test_denet_config_builder() {
510
1
        let config = DenetConfig::builder().build();
511
1
        assert_eq!(config.monitor.base_interval, defaults::BASE_INTERVAL);
512
1
        assert_eq!(config.output.format, OutputFormat::JsonLines);
513
1
    }
514
515
    #[test]
516
1
    fn test_denet_config_builder_with_configs() {
517
1
        let monitor_config = MonitorConfig {
518
1
            base_interval: Duration::from_millis(250),
519
1
            ..Default::default()
520
1
        };
521
1
        let output_config = OutputConfig {
522
1
            format: OutputFormat::Csv,
523
1
            ..Default::default()
524
1
        };
525
1
526
1
        let config = DenetConfigBuilder::default()
527
1
            .monitor(monitor_config.clone())
528
1
            .output(output_config.clone())
529
1
            .build();
530
1
531
1
        assert_eq!(config.monitor.base_interval, monitor_config.base_interval);
532
1
        assert_eq!(config.output.format, output_config.format);
533
1
    }
534
535
    #[test]
536
1
    fn test_denet_config_builder_partial() {
537
1
        let monitor_config = MonitorConfig {
538
1
            base_interval: Duration::from_millis(250),
539
1
            ..Default::default()
540
1
        };
541
1
542
1
        let config = DenetConfigBuilder::default()
543
1
            .monitor(monitor_config)
544
1
            .build();
545
1
546
1
        assert_eq!(config.monitor.base_interval, Duration::from_millis(250));
547
1
        assert_eq!(config.output.format, OutputFormat::JsonLines); // Default
548
1
    }
549
}