/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 | | } |