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/monitor/metrics.rs
Line
Count
Source
1
//! Metrics data structures and utilities
2
//!
3
//! This module contains all the data structures used to represent
4
//! process monitoring metrics and summaries.
5
6
use serde::{Deserialize, Serialize};
7
use std::time::{SystemTime, UNIX_EPOCH};
8
9
/// Metadata about a monitored process
10
#[derive(Serialize, Deserialize, Debug, Clone)]
11
pub struct ProcessMetadata {
12
    pub pid: usize,
13
    pub cmd: Vec<String>,
14
    pub executable: String,
15
    pub t0_ms: u64,
16
}
17
18
impl ProcessMetadata {
19
1
    pub fn new(pid: usize, cmd: Vec<String>, executable: String) -> Self {
20
1
        let t0_ms = SystemTime::now()
21
1
            .duration_since(UNIX_EPOCH)
22
1
            .map(|d| d.as_millis() as u64)
23
1
            .unwrap_or(0);
24
1
25
1
        Self {
26
1
            pid,
27
1
            cmd,
28
1
            executable,
29
1
            t0_ms,
30
1
        }
31
1
    }
32
}
33
34
/// Single point-in-time metrics for a process
35
#[derive(Serialize, Deserialize, Debug, Clone)]
36
pub struct Metrics {
37
    pub ts_ms: u64,
38
    pub cpu_usage: f32,
39
    pub mem_rss_kb: u64,
40
    pub mem_vms_kb: u64,
41
    pub disk_read_bytes: u64,
42
    pub disk_write_bytes: u64,
43
    pub net_rx_bytes: u64,
44
    pub net_tx_bytes: u64,
45
    pub thread_count: usize,
46
    pub uptime_secs: u64,
47
    pub cpu_core: Option<u32>,
48
}
49
50
impl Metrics {
51
    /// Create a new metrics instance with current timestamp
52
13
    pub fn new() -> Self {
53
13
        let ts_ms = SystemTime::now()
54
13
            .duration_since(UNIX_EPOCH)
55
13
            .map(|d| d.as_millis() as u64)
56
13
            .unwrap_or(0);
57
13
58
13
        Self {
59
13
            ts_ms,
60
13
            cpu_usage: 0.0,
61
13
            mem_rss_kb: 0,
62
13
            mem_vms_kb: 0,
63
13
            disk_read_bytes: 0,
64
13
            disk_write_bytes: 0,
65
13
            net_rx_bytes: 0,
66
13
            net_tx_bytes: 0,
67
13
            thread_count: 0,
68
13
            uptime_secs: 0,
69
13
            cpu_core: None,
70
13
        }
71
13
    }
72
}
73
74
impl Default for Metrics {
75
3
    fn default() -> Self {
76
3
        Self::new()
77
3
    }
78
}
79
80
/// Metrics for a process tree (parent + children)
81
#[derive(Serialize, Deserialize, Debug, Clone)]
82
pub struct ProcessTreeMetrics {
83
    pub ts_ms: u64,
84
    pub parent: Option<Metrics>,
85
    pub children: Vec<ChildProcessMetrics>,
86
    pub aggregated: Option<AggregatedMetrics>,
87
}
88
89
/// Metrics for a child process
90
#[derive(Serialize, Deserialize, Debug, Clone)]
91
pub struct ChildProcessMetrics {
92
    pub pid: usize,
93
    pub command: String,
94
    pub metrics: Metrics,
95
}
96
97
/// Aggregated metrics across multiple processes
98
#[derive(Serialize, Deserialize, Debug, Clone)]
99
pub struct AggregatedMetrics {
100
    pub ts_ms: u64,
101
    pub cpu_usage: f32,
102
    pub mem_rss_kb: u64,
103
    pub mem_vms_kb: u64,
104
    pub disk_read_bytes: u64,
105
    pub disk_write_bytes: u64,
106
    pub net_rx_bytes: u64,
107
    pub net_tx_bytes: u64,
108
    pub thread_count: usize,
109
    pub process_count: usize,
110
    pub uptime_secs: u64,
111
112
    /// eBPF profiling data (optional)
113
    #[cfg(feature = "ebpf")]
114
    #[serde(skip_serializing_if = "Option::is_none")]
115
    pub ebpf: Option<crate::ebpf::EbpfMetrics>,
116
117
    #[cfg(not(feature = "ebpf"))]
118
    #[serde(skip_serializing_if = "Option::is_none")]
119
    pub ebpf: Option<serde_json::Value>,
120
}
121
122
impl AggregatedMetrics {
123
    /// Create aggregated metrics from a collection of individual metrics
124
3
    pub fn from_metrics(metrics: &[Metrics]) -> Self {
125
3
        if metrics.is_empty() {
126
1
            return Self::default();
127
2
        }
128
2
129
2
        let ts_ms = metrics[0].ts_ms;
130
2
        let mut cpu_usage = 0.0;
131
2
        let mut mem_rss_kb = 0;
132
2
        let mut mem_vms_kb = 0;
133
2
        let mut disk_read_bytes = 0;
134
2
        let mut disk_write_bytes = 0;
135
2
        let mut net_rx_bytes = 0;
136
2
        let mut net_tx_bytes = 0;
137
2
        let mut thread_count = 0;
138
2
        let mut max_uptime = 0;
139
140
5
        for 
metric3
in metrics {
141
3
            cpu_usage += metric.cpu_usage;
142
3
            mem_rss_kb += metric.mem_rss_kb;
143
3
            mem_vms_kb += metric.mem_vms_kb;
144
3
            disk_read_bytes += metric.disk_read_bytes;
145
3
            disk_write_bytes += metric.disk_write_bytes;
146
3
            net_rx_bytes += metric.net_rx_bytes;
147
3
            net_tx_bytes += metric.net_tx_bytes;
148
3
            thread_count += metric.thread_count;
149
3
            max_uptime = max_uptime.max(metric.uptime_secs);
150
3
        }
151
152
2
        Self {
153
2
            ts_ms,
154
2
            cpu_usage,
155
2
            mem_rss_kb,
156
2
            mem_vms_kb,
157
2
            disk_read_bytes,
158
2
            disk_write_bytes,
159
2
            net_rx_bytes,
160
2
            net_tx_bytes,
161
2
            thread_count,
162
2
            process_count: metrics.len(),
163
2
            uptime_secs: max_uptime,
164
2
            ebpf: None, // eBPF metrics are added separately
165
2
        }
166
3
    }
167
}
168
169
impl Default for AggregatedMetrics {
170
6
    fn default() -> Self {
171
6
        let ts_ms = SystemTime::now()
172
6
            .duration_since(UNIX_EPOCH)
173
6
            .map(|d| d.as_millis() as u64)
174
6
            .unwrap_or(0);
175
6
176
6
        Self {
177
6
            ts_ms,
178
6
            cpu_usage: 0.0,
179
6
            mem_rss_kb: 0,
180
6
            mem_vms_kb: 0,
181
6
            disk_read_bytes: 0,
182
6
            disk_write_bytes: 0,
183
6
            net_rx_bytes: 0,
184
6
            net_tx_bytes: 0,
185
6
            thread_count: 0,
186
6
            process_count: 0,
187
6
            uptime_secs: 0,
188
6
            ebpf: None,
189
6
        }
190
6
    }
191
}
192
193
/// Summarizes metrics collected during a monitoring session
194
#[derive(Serialize, Deserialize, Debug, Clone)]
195
pub struct Summary {
196
    /// Total time elapsed in seconds
197
    pub total_time_secs: f64,
198
    /// Number of samples collected
199
    pub sample_count: usize,
200
    /// Maximum number of processes observed
201
    pub max_processes: usize,
202
    /// Maximum number of threads observed
203
    pub max_threads: usize,
204
    /// Cumulative disk read bytes
205
    pub total_disk_read_bytes: u64,
206
    /// Cumulative disk write bytes
207
    pub total_disk_write_bytes: u64,
208
    /// Cumulative network received bytes
209
    pub total_net_rx_bytes: u64,
210
    /// Cumulative network transmitted bytes
211
    pub total_net_tx_bytes: u64,
212
    /// Maximum memory RSS observed across all processes (in KB)
213
    pub peak_mem_rss_kb: u64,
214
    /// Average CPU usage (percent)
215
    pub avg_cpu_usage: f32,
216
}
217
218
impl Summary {
219
    /// Create a new empty summary
220
3
    pub fn new() -> Self {
221
3
        Self::default()
222
3
    }
223
224
    /// Create summary from a collection of individual metrics
225
4
    pub fn from_metrics(metrics: &[Metrics], elapsed_time: f64) -> Self {
226
4
        if metrics.is_empty() {
227
1
            return Self::new();
228
3
        }
229
3
230
3
        let mut total_cpu = 0.0;
231
3
        let mut max_threads = 0;
232
3
        let mut peak_mem_rss_kb = 0;
233
3
        let last_metrics = &metrics[metrics.len() - 1];
234
235
9
        for 
metric6
in metrics {
236
6
            total_cpu += metric.cpu_usage;
237
6
            max_threads = max_threads.max(metric.thread_count);
238
6
            peak_mem_rss_kb = peak_mem_rss_kb.max(metric.mem_rss_kb);
239
6
        }
240
241
        Self {
242
3
            total_time_secs: elapsed_time,
243
3
            sample_count: metrics.len(),
244
3
            max_processes: 1, // Single process monitoring
245
3
            max_threads,
246
3
            total_disk_read_bytes: last_metrics.disk_read_bytes,
247
3
            total_disk_write_bytes: last_metrics.disk_write_bytes,
248
3
            total_net_rx_bytes: last_metrics.net_rx_bytes,
249
3
            total_net_tx_bytes: last_metrics.net_tx_bytes,
250
3
            peak_mem_rss_kb,
251
3
            avg_cpu_usage: if metrics.is_empty() {
252
0
                0.0
253
            } else {
254
3
                total_cpu / metrics.len() as f32
255
            },
256
        }
257
4
    }
258
259
    /// Create summary from aggregated metrics
260
2
    pub fn from_aggregated_metrics(metrics: &[AggregatedMetrics], elapsed_time: f64) -> Self {
261
2
        if metrics.is_empty() {
262
1
            return Self::new();
263
1
        }
264
1
265
1
        let mut total_cpu = 0.0;
266
1
        let mut max_processes = 0;
267
1
        let mut max_threads = 0;
268
1
        let mut peak_mem_rss_kb = 0;
269
1
        let last_metrics = &metrics[metrics.len() - 1];
270
271
3
        for 
metric2
in metrics {
272
2
            total_cpu += metric.cpu_usage;
273
2
            max_processes = max_processes.max(metric.process_count);
274
2
            max_threads = max_threads.max(metric.thread_count);
275
2
            peak_mem_rss_kb = peak_mem_rss_kb.max(metric.mem_rss_kb);
276
2
        }
277
278
        Self {
279
1
            total_time_secs: elapsed_time,
280
1
            sample_count: metrics.len(),
281
1
            max_processes,
282
1
            max_threads,
283
1
            total_disk_read_bytes: last_metrics.disk_read_bytes,
284
1
            total_disk_write_bytes: last_metrics.disk_write_bytes,
285
1
            total_net_rx_bytes: last_metrics.net_rx_bytes,
286
1
            total_net_tx_bytes: last_metrics.net_tx_bytes,
287
1
            peak_mem_rss_kb,
288
1
            avg_cpu_usage: if metrics.is_empty() {
289
0
                0.0
290
            } else {
291
1
                total_cpu / metrics.len() as f32
292
            },
293
        }
294
2
    }
295
}
296
297
impl Default for Summary {
298
5
    fn default() -> Self {
299
5
        Self {
300
5
            total_time_secs: 0.0,
301
5
            sample_count: 0,
302
5
            max_processes: 0,
303
5
            max_threads: 0,
304
5
            total_disk_read_bytes: 0,
305
5
            total_disk_write_bytes: 0,
306
5
            total_net_rx_bytes: 0,
307
5
            total_net_tx_bytes: 0,
308
5
            peak_mem_rss_kb: 0,
309
5
            avg_cpu_usage: 0.0,
310
5
        }
311
5
    }
312
}
313
314
#[cfg(test)]
315
mod tests {
316
    use super::*;
317
318
    #[test]
319
0
    fn test_process_metadata_new() {
320
0
        let pid = 12345;
321
0
        let cmd = vec!["test".to_string(), "command".to_string()];
322
0
        let executable = "/usr/bin/test".to_string();
323
0
324
0
        let metadata = ProcessMetadata::new(pid, cmd.clone(), executable.clone());
325
0
326
0
        assert_eq!(metadata.pid, pid);
327
0
        assert_eq!(metadata.cmd, cmd);
328
0
        assert_eq!(metadata.executable, executable);
329
0
        assert!(metadata.t0_ms > 0);
330
0
    }
331
332
    #[test]
333
1
    fn test_metrics_new() {
334
1
        let metrics = Metrics::new();
335
1
336
1
        assert!(metrics.ts_ms > 0);
337
1
        assert_eq!(metrics.cpu_usage, 0.0);
338
1
        assert_eq!(metrics.mem_rss_kb, 0);
339
1
        assert_eq!(metrics.mem_vms_kb, 0);
340
1
        assert_eq!(metrics.disk_read_bytes, 0);
341
1
        assert_eq!(metrics.disk_write_bytes, 0);
342
1
        assert_eq!(metrics.net_rx_bytes, 0);
343
1
        assert_eq!(metrics.net_tx_bytes, 0);
344
1
        assert_eq!(metrics.thread_count, 0);
345
1
        assert_eq!(metrics.uptime_secs, 0);
346
1
        assert_eq!(metrics.cpu_core, None);
347
1
    }
348
349
    #[test]
350
1
    fn test_metrics_default() {
351
1
        let metrics = Metrics::default();
352
1
        assert!(metrics.ts_ms > 0);
353
1
        assert_eq!(metrics.cpu_usage, 0.0);
354
1
    }
355
356
    #[test]
357
1
    fn test_aggregated_metrics_from_empty_metrics() {
358
1
        let metrics = vec![];
359
1
        let aggregated = AggregatedMetrics::from_metrics(&metrics);
360
1
361
1
        assert_eq!(aggregated.cpu_usage, 0.0);
362
1
        assert_eq!(aggregated.process_count, 0);
363
1
        assert_eq!(aggregated.thread_count, 0);
364
1
        assert_eq!(aggregated.uptime_secs, 0);
365
1
    }
366
367
    #[test]
368
1
    fn test_aggregated_metrics_from_single_metric() {
369
1
        let mut metric = Metrics::new();
370
1
        metric.cpu_usage = 25.5;
371
1
        metric.mem_rss_kb = 1024;
372
1
        metric.mem_vms_kb = 2048;
373
1
        metric.disk_read_bytes = 512;
374
1
        metric.disk_write_bytes = 256;
375
1
        metric.net_rx_bytes = 128;
376
1
        metric.net_tx_bytes = 64;
377
1
        metric.thread_count = 4;
378
1
        metric.uptime_secs = 60;
379
1
380
1
        let metrics = vec![metric];
381
1
        let aggregated = AggregatedMetrics::from_metrics(&metrics);
382
1
383
1
        assert_eq!(aggregated.cpu_usage, 25.5);
384
1
        assert_eq!(aggregated.mem_rss_kb, 1024);
385
1
        assert_eq!(aggregated.mem_vms_kb, 2048);
386
1
        assert_eq!(aggregated.disk_read_bytes, 512);
387
1
        assert_eq!(aggregated.disk_write_bytes, 256);
388
1
        assert_eq!(aggregated.net_rx_bytes, 128);
389
1
        assert_eq!(aggregated.net_tx_bytes, 64);
390
1
        assert_eq!(aggregated.thread_count, 4);
391
1
        assert_eq!(aggregated.process_count, 1);
392
1
        assert_eq!(aggregated.uptime_secs, 60);
393
1
    }
394
395
    #[test]
396
1
    fn test_aggregated_metrics_from_multiple_metrics() {
397
1
        let mut metric1 = Metrics::new();
398
1
        metric1.cpu_usage = 10.0;
399
1
        metric1.mem_rss_kb = 500;
400
1
        metric1.thread_count = 2;
401
1
        metric1.uptime_secs = 30;
402
1
403
1
        let mut metric2 = Metrics::new();
404
1
        metric2.cpu_usage = 15.0;
405
1
        metric2.mem_rss_kb = 750;
406
1
        metric2.thread_count = 3;
407
1
        metric2.uptime_secs = 60;
408
1
409
1
        let metrics = vec![metric1, metric2];
410
1
        let aggregated = AggregatedMetrics::from_metrics(&metrics);
411
1
412
1
        assert_eq!(aggregated.cpu_usage, 25.0);
413
1
        assert_eq!(aggregated.mem_rss_kb, 1250);
414
1
        assert_eq!(aggregated.thread_count, 5);
415
1
        assert_eq!(aggregated.process_count, 2);
416
1
        assert_eq!(aggregated.uptime_secs, 60); // Max uptime
417
1
    }
418
419
    #[test]
420
1
    fn test_aggregated_metrics_default() {
421
1
        let aggregated = AggregatedMetrics::default();
422
1
423
1
        assert!(aggregated.ts_ms > 0);
424
1
        assert_eq!(aggregated.cpu_usage, 0.0);
425
1
        assert_eq!(aggregated.mem_rss_kb, 0);
426
1
        assert_eq!(aggregated.process_count, 0);
427
1
        assert_eq!(aggregated.thread_count, 0);
428
1
        assert_eq!(aggregated.uptime_secs, 0);
429
1
        assert!(aggregated.ebpf.is_none());
430
1
    }
431
432
    #[test]
433
1
    fn test_summary_new() {
434
1
        let summary = Summary::new();
435
1
436
1
        assert_eq!(summary.total_time_secs, 0.0);
437
1
        assert_eq!(summary.sample_count, 0);
438
1
        assert_eq!(summary.max_processes, 0);
439
1
        assert_eq!(summary.max_threads, 0);
440
1
        assert_eq!(summary.total_disk_read_bytes, 0);
441
1
        assert_eq!(summary.total_disk_write_bytes, 0);
442
1
        assert_eq!(summary.total_net_rx_bytes, 0);
443
1
        assert_eq!(summary.total_net_tx_bytes, 0);
444
1
        assert_eq!(summary.peak_mem_rss_kb, 0);
445
1
        assert_eq!(summary.avg_cpu_usage, 0.0);
446
1
    }
447
448
    #[test]
449
1
    fn test_summary_default() {
450
1
        let summary = Summary::default();
451
1
        assert_eq!(summary.total_time_secs, 0.0);
452
1
        assert_eq!(summary.sample_count, 0);
453
1
    }
454
455
    #[test]
456
1
    fn test_summary_from_empty_metrics() {
457
1
        let metrics = vec![];
458
1
        let summary = Summary::from_metrics(&metrics, 10.0);
459
1
460
1
        assert_eq!(summary.total_time_secs, 0.0);
461
1
        assert_eq!(summary.sample_count, 0);
462
1
        assert_eq!(summary.avg_cpu_usage, 0.0);
463
1
    }
464
465
    #[test]
466
1
    fn test_summary_from_metrics() {
467
1
        let mut metric1 = Metrics::new();
468
1
        metric1.cpu_usage = 20.0;
469
1
        metric1.mem_rss_kb = 1000;
470
1
        metric1.disk_read_bytes = 500;
471
1
        metric1.disk_write_bytes = 250;
472
1
        metric1.net_rx_bytes = 100;
473
1
        metric1.net_tx_bytes = 50;
474
1
        metric1.thread_count = 4;
475
1
476
1
        let mut metric2 = Metrics::new();
477
1
        metric2.cpu_usage = 30.0;
478
1
        metric2.mem_rss_kb = 1500;
479
1
        metric2.disk_read_bytes = 1000;
480
1
        metric2.disk_write_bytes = 500;
481
1
        metric2.net_rx_bytes = 200;
482
1
        metric2.net_tx_bytes = 100;
483
1
        metric2.thread_count = 6;
484
1
485
1
        let metrics = vec![metric1, metric2];
486
1
        let summary = Summary::from_metrics(&metrics, 15.5);
487
1
488
1
        assert_eq!(summary.total_time_secs, 15.5);
489
1
        assert_eq!(summary.sample_count, 2);
490
1
        assert_eq!(summary.max_processes, 1);
491
1
        assert_eq!(summary.max_threads, 6);
492
1
        assert_eq!(summary.total_disk_read_bytes, 1000);
493
1
        assert_eq!(summary.total_disk_write_bytes, 500);
494
1
        assert_eq!(summary.total_net_rx_bytes, 200);
495
1
        assert_eq!(summary.total_net_tx_bytes, 100);
496
1
        assert_eq!(summary.peak_mem_rss_kb, 1500);
497
1
        assert_eq!(summary.avg_cpu_usage, 25.0);
498
1
    }
499
500
    #[test]
501
1
    fn test_summary_from_empty_aggregated_metrics() {
502
1
        let metrics = vec![];
503
1
        let summary = Summary::from_aggregated_metrics(&metrics, 10.0);
504
1
505
1
        assert_eq!(summary.total_time_secs, 0.0);
506
1
        assert_eq!(summary.sample_count, 0);
507
1
        assert_eq!(summary.avg_cpu_usage, 0.0);
508
1
    }
509
510
    #[test]
511
1
    fn test_summary_from_aggregated_metrics() {
512
1
        let mut metric1 = AggregatedMetrics::default();
513
1
        metric1.cpu_usage = 40.0;
514
1
        metric1.mem_rss_kb = 2000;
515
1
        metric1.disk_read_bytes = 800;
516
1
        metric1.disk_write_bytes = 400;
517
1
        metric1.net_rx_bytes = 150;
518
1
        metric1.net_tx_bytes = 75;
519
1
        metric1.thread_count = 8;
520
1
        metric1.process_count = 2;
521
1
522
1
        let mut metric2 = AggregatedMetrics::default();
523
1
        metric2.cpu_usage = 60.0;
524
1
        metric2.mem_rss_kb = 3000;
525
1
        metric2.disk_read_bytes = 1600;
526
1
        metric2.disk_write_bytes = 800;
527
1
        metric2.net_rx_bytes = 300;
528
1
        metric2.net_tx_bytes = 150;
529
1
        metric2.thread_count = 12;
530
1
        metric2.process_count = 3;
531
1
532
1
        let metrics = vec![metric1, metric2];
533
1
        let summary = Summary::from_aggregated_metrics(&metrics, 25.0);
534
1
535
1
        assert_eq!(summary.total_time_secs, 25.0);
536
1
        assert_eq!(summary.sample_count, 2);
537
1
        assert_eq!(summary.max_processes, 3);
538
1
        assert_eq!(summary.max_threads, 12);
539
1
        assert_eq!(summary.total_disk_read_bytes, 1600);
540
1
        assert_eq!(summary.total_disk_write_bytes, 800);
541
1
        assert_eq!(summary.total_net_rx_bytes, 300);
542
1
        assert_eq!(summary.total_net_tx_bytes, 150);
543
1
        assert_eq!(summary.peak_mem_rss_kb, 3000);
544
1
        assert_eq!(summary.avg_cpu_usage, 50.0);
545
1
    }
546
547
    #[test]
548
1
    fn test_serialization() {
549
1
        let metadata = ProcessMetadata::new(123, vec!["test".to_string()], "test".to_string());
550
1
        let json = serde_json::to_string(&metadata).unwrap();
551
1
        let deserialized: ProcessMetadata = serde_json::from_str(&json).unwrap();
552
1
        assert_eq!(metadata.pid, deserialized.pid);
553
1
        assert_eq!(metadata.cmd, deserialized.cmd);
554
555
1
        let metrics = Metrics::new();
556
1
        let json = serde_json::to_string(&metrics).unwrap();
557
1
        let deserialized: Metrics = serde_json::from_str(&json).unwrap();
558
1
        assert_eq!(metrics.cpu_usage, deserialized.cpu_usage);
559
560
1
        let aggregated = AggregatedMetrics::default();
561
1
        let json = serde_json::to_string(&aggregated).unwrap();
562
1
        let deserialized: AggregatedMetrics = serde_json::from_str(&json).unwrap();
563
1
        assert_eq!(aggregated.cpu_usage, deserialized.cpu_usage);
564
565
1
        let summary = Summary::default();
566
1
        let json = serde_json::to_string(&summary).unwrap();
567
1
        let deserialized: Summary = serde_json::from_str(&json).unwrap();
568
1
        assert_eq!(summary.avg_cpu_usage, deserialized.avg_cpu_usage);
569
1
    }
570
571
    #[test]
572
1
    fn test_child_process_metrics() {
573
1
        let mut metrics = Metrics::new();
574
1
        metrics.cpu_usage = 15.0;
575
1
576
1
        let child = ChildProcessMetrics {
577
1
            pid: 456,
578
1
            command: "child_process".to_string(),
579
1
            metrics,
580
1
        };
581
1
582
1
        assert_eq!(child.pid, 456);
583
1
        assert_eq!(child.command, "child_process");
584
1
        assert_eq!(child.metrics.cpu_usage, 15.0);
585
1
    }
586
587
    #[test]
588
1
    fn test_process_tree_metrics() {
589
1
        let parent = Some(Metrics::new());
590
1
        let children = vec![ChildProcessMetrics {
591
1
            pid: 456,
592
1
            command: "child".to_string(),
593
1
            metrics: Metrics::new(),
594
1
        }];
595
1
        let aggregated = Some(AggregatedMetrics::default());
596
1
597
1
        let tree_metrics = ProcessTreeMetrics {
598
1
            ts_ms: 1234567890,
599
1
            parent,
600
1
            children,
601
1
            aggregated,
602
1
        };
603
1
604
1
        assert_eq!(tree_metrics.ts_ms, 1234567890);
605
1
        assert!(tree_metrics.parent.is_some());
606
1
        assert_eq!(tree_metrics.children.len(), 1);
607
1
        assert!(tree_metrics.aggregated.is_some());
608
1
    }
609
}