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/summary.rs
Line
Count
Source
1
//! Summary generation utilities
2
//!
3
//! This module provides functionality for generating summaries from
4
//! collected metrics data, including reading from files.
5
6
use crate::error::{DenetError, Result};
7
use crate::monitor::metrics::{AggregatedMetrics, Metrics, Summary};
8
use std::fs::File;
9
use std::io::{BufRead, BufReader};
10
use std::path::Path;
11
12
/// Utilities for summary generation
13
pub struct SummaryGenerator;
14
15
impl SummaryGenerator {
16
    /// Generate summary from a JSON file containing metrics
17
1
    pub fn from_json_file<P: AsRef<Path>>(path: P) -> Result<Summary> {
18
1
        let file = File::open(&path)
?0
;
19
1
        let reader = BufReader::new(file);
20
1
21
1
        let mut metrics: Vec<Metrics> = Vec::new();
22
1
        let mut aggregated_metrics: Vec<AggregatedMetrics> = Vec::new();
23
24
2
        for line in 
reader.lines()1
{
25
2
            let line = line
?0
;
26
2
            let line = line.trim();
27
2
            if line.is_empty() {
28
0
                continue;
29
2
            }
30
31
            // Try to parse as different metric types
32
2
            if let Ok(metric) = serde_json::from_str::<Metrics>(line) {
33
2
                metrics.push(metric);
34
2
            } else if let Ok(
agg_metric0
) =
serde_json::from_str::<AggregatedMetrics>(line)0
{
35
0
                aggregated_metrics.push(agg_metric);
36
0
            } else {
37
                // Try parsing as a nested structure with "aggregated" field
38
0
                if let Ok(value) = serde_json::from_str::<serde_json::Value>(line) {
39
0
                    if let Some(agg) = value.get("aggregated") {
40
0
                        if let Ok(agg_metric) =
41
0
                            serde_json::from_value::<AggregatedMetrics>(agg.clone())
42
                        {
43
0
                            aggregated_metrics.push(agg_metric);
44
0
                            continue;
45
0
                        }
46
0
                    }
47
0
                }
48
                // If we can't parse the line, skip it
49
0
                continue;
50
            }
51
        }
52
53
1
        let elapsed_time = Self::calculate_elapsed_time(&metrics, &aggregated_metrics);
54
55
1
        let summary = if !aggregated_metrics.is_empty() {
56
0
            Summary::from_aggregated_metrics(&aggregated_metrics, elapsed_time)
57
1
        } else if !metrics.is_empty() {
58
1
            Summary::from_metrics(&metrics, elapsed_time)
59
        } else {
60
0
            return Err(DenetError::Other(
61
0
                "No valid metrics found in file".to_string(),
62
0
            ));
63
        };
64
65
1
        Ok(summary)
66
1
    }
67
68
    /// Generate summary from JSON strings
69
1
    pub fn from_json_strings(json_strings: &[String], elapsed_time: f64) -> Result<Summary> {
70
1
        let mut metrics: Vec<Metrics> = Vec::new();
71
1
        let mut aggregated_metrics: Vec<AggregatedMetrics> = Vec::new();
72
73
3
        for 
json_str2
in json_strings {
74
2
            if let Ok(metric) = serde_json::from_str::<Metrics>(json_str) {
75
2
                metrics.push(metric);
76
2
            } else if let Ok(
agg_metric0
) =
serde_json::from_str::<AggregatedMetrics>(json_str)0
{
77
0
                aggregated_metrics.push(agg_metric);
78
0
            } else {
79
                // Try parsing as nested structure
80
0
                if let Ok(value) = serde_json::from_str::<serde_json::Value>(json_str) {
81
0
                    if let Some(agg) = value.get("aggregated") {
82
0
                        if let Ok(agg_metric) =
83
0
                            serde_json::from_value::<AggregatedMetrics>(agg.clone())
84
0
                        {
85
0
                            aggregated_metrics.push(agg_metric);
86
0
                        }
87
0
                    }
88
0
                }
89
            }
90
        }
91
92
1
        let summary = if !aggregated_metrics.is_empty() {
93
0
            Summary::from_aggregated_metrics(&aggregated_metrics, elapsed_time)
94
1
        } else if !metrics.is_empty() {
95
1
            Summary::from_metrics(&metrics, elapsed_time)
96
        } else {
97
0
            Summary::new()
98
        };
99
100
1
        Ok(summary)
101
1
    }
102
103
    /// Calculate elapsed time from metrics collections
104
1
    fn calculate_elapsed_time(
105
1
        metrics: &[Metrics],
106
1
        aggregated_metrics: &[AggregatedMetrics],
107
1
    ) -> f64 {
108
1
        if !aggregated_metrics.is_empty() {
109
0
            let first = aggregated_metrics[0].ts_ms;
110
0
            let last = aggregated_metrics[aggregated_metrics.len() - 1].ts_ms;
111
0
            (last - first) as f64 / 1000.0
112
1
        } else if !metrics.is_empty() {
113
1
            let first = metrics[0].ts_ms;
114
1
            let last = metrics[metrics.len() - 1].ts_ms;
115
1
            (last - first) as f64 / 1000.0
116
        } else {
117
0
            0.0
118
        }
119
1
    }
120
}
121
122
#[cfg(test)]
123
mod tests {
124
    use super::*;
125
    use std::io::Write;
126
    use tempfile::NamedTempFile;
127
128
    #[test]
129
1
    fn test_summary_from_json_file() -> Result<()> {
130
1
        let mut temp_file = NamedTempFile::new()
?0
;
131
132
        // Write some test metrics
133
1
        writeln!(
134
1
            temp_file,
135
1
            r#"{{"ts_ms":1000,"cpu_usage":50.0,"mem_rss_kb":1024,"mem_vms_kb":2048,"disk_read_bytes":0,"disk_write_bytes":0,"net_rx_bytes":0,"net_tx_bytes":0,"thread_count":1,"uptime_secs":10,"cpu_core":null}}"#
136
1
        )
?0
;
137
1
        writeln!(
138
1
            temp_file,
139
1
            r#"{{"ts_ms":2000,"cpu_usage":75.0,"mem_rss_kb":1536,"mem_vms_kb":3072,"disk_read_bytes":100,"disk_write_bytes":200,"net_rx_bytes":50,"net_tx_bytes":75,"thread_count":2,"uptime_secs":20,"cpu_core":null}}"#
140
1
        )
?0
;
141
142
1
        temp_file.flush()
?0
;
143
144
1
        let summary = SummaryGenerator::from_json_file(temp_file.path())
?0
;
145
146
1
        assert_eq!(summary.sample_count, 2);
147
1
        assert_eq!(summary.total_time_secs, 1.0); // 2000-1000 = 1000ms = 1s
148
1
        assert_eq!(summary.avg_cpu_usage, 62.5); // (50 + 75) / 2
149
1
        assert_eq!(summary.peak_mem_rss_kb, 1536);
150
151
1
        Ok(())
152
1
    }
153
154
    #[test]
155
1
    fn test_summary_from_json_strings() -> Result<()> {
156
1
        let json_strings = vec![
157
1
            r#"{"ts_ms":1000,"cpu_usage":25.0,"mem_rss_kb":512,"mem_vms_kb":1024,"disk_read_bytes":0,"disk_write_bytes":0,"net_rx_bytes":0,"net_tx_bytes":0,"thread_count":1,"uptime_secs":5,"cpu_core":null}"#.to_string(),
158
1
            r#"{"ts_ms":2000,"cpu_usage":50.0,"mem_rss_kb":768,"mem_vms_kb":1536,"disk_read_bytes":50,"disk_write_bytes":100,"net_rx_bytes":25,"net_tx_bytes":50,"thread_count":1,"uptime_secs":10,"cpu_core":null}"#.to_string(),
159
1
        ];
160
161
1
        let summary = SummaryGenerator::from_json_strings(&json_strings, 1.0)
?0
;
162
163
1
        assert_eq!(summary.sample_count, 2);
164
1
        assert_eq!(summary.total_time_secs, 1.0);
165
1
        assert_eq!(summary.avg_cpu_usage, 37.5); // (25 + 50) / 2
166
1
        assert_eq!(summary.peak_mem_rss_kb, 768);
167
168
1
        Ok(())
169
1
    }
170
}