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