scirs2_metrics/serialization/
comparison.rs1use chrono::Duration;
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9use super::{MetricCollection, MetricResult};
10use crate::error::Result;
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct MetricComparison {
17 pub name: String,
19 pub value1: f64,
21 pub value2: f64,
23 pub absolute_diff: f64,
25 pub relative_diff: f64,
27 pub is_significant: bool,
29 pub threshold: Option<f64>,
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct CollectionComparison {
38 pub name1: String,
40 pub name2: String,
42 pub metric_comparisons: Vec<MetricComparison>,
44 pub summary: ComparisonSummary,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct ComparisonSummary {
53 pub total_metrics: usize,
55 pub improved: usize,
57 pub degraded: usize,
59 pub unchanged: usize,
61 pub only_in_first: usize,
63 pub only_in_second: usize,
65 pub avg_absolute_diff: f64,
67 pub avg_relative_diff: f64,
69}
70
71#[allow(dead_code)]
83pub fn compare_metrics(
84 metric1: &MetricResult,
85 metric2: &MetricResult,
86 threshold: Option<f64>,
87) -> MetricComparison {
88 let value1 = metric1.value;
89 let value2 = metric2.value;
90
91 let absolute_diff = value2 - value1;
92 let relative_diff = if value1 != 0.0 {
93 absolute_diff / value1
94 } else if value2 == 0.0 {
95 0.0
96 } else {
97 1.0
98 };
99
100 let is_significant = if let Some(t) = threshold {
101 relative_diff.abs() > t
102 } else {
103 false
104 };
105
106 MetricComparison {
107 name: metric1.name.clone(),
108 value1,
109 value2,
110 absolute_diff,
111 relative_diff,
112 is_significant,
113 threshold,
114 }
115}
116
117#[allow(dead_code)]
129pub fn compare_collections(
130 collection1: &MetricCollection,
131 collection2: &MetricCollection,
132 threshold: Option<f64>,
133) -> CollectionComparison {
134 let mut metrics1_map = HashMap::new();
136 for metric in &collection1.metrics {
137 metrics1_map.insert(metric.name.clone(), metric);
138 }
139
140 let mut metrics2_map = HashMap::new();
141 for metric in &collection2.metrics {
142 metrics2_map.insert(metric.name.clone(), metric);
143 }
144
145 let mut metric_comparisons = Vec::new();
147
148 let mut improved = 0;
149 let mut degraded = 0;
150 let mut unchanged = 0;
151 let mut only_in_first = 0;
152 let mut only_in_second = 0;
153
154 let mut total_absolute_diff = 0.0;
155 let mut total_relative_diff = 0.0;
156
157 for metric1 in &collection1.metrics {
159 if let Some(metric2) = metrics2_map.get(&metric1.name) {
160 let comparison = compare_metrics(metric1, metric2, threshold);
162
163 if comparison.absolute_diff > 0.0 {
165 improved += 1;
166 } else if comparison.absolute_diff < 0.0 {
167 degraded += 1;
168 } else {
169 unchanged += 1;
170 }
171
172 total_absolute_diff += comparison.absolute_diff.abs();
173 total_relative_diff += comparison.relative_diff.abs();
174
175 metric_comparisons.push(comparison);
176 } else {
177 only_in_first += 1;
179 }
180 }
181
182 for metric2 in &collection2.metrics {
184 if !metrics1_map.contains_key(&metric2.name) {
185 only_in_second += 1;
186 }
187 }
188
189 let compared_count = metric_comparisons.len();
191 let avg_absolute_diff = if compared_count > 0 {
192 total_absolute_diff / compared_count as f64
193 } else {
194 0.0
195 };
196
197 let avg_relative_diff = if compared_count > 0 {
198 total_relative_diff / compared_count as f64
199 } else {
200 0.0
201 };
202
203 let summary = ComparisonSummary {
205 total_metrics: compared_count,
206 improved,
207 degraded,
208 unchanged,
209 only_in_first,
210 only_in_second,
211 avg_absolute_diff,
212 avg_relative_diff,
213 };
214
215 CollectionComparison {
216 name1: collection1.name.clone(),
217 name2: collection2.name.clone(),
218 metric_comparisons,
219 summary,
220 }
221}
222
223#[allow(dead_code)]
235pub fn find_significant_differences(
236 collection1: &MetricCollection,
237 collection2: &MetricCollection,
238 threshold: f64,
239) -> Vec<MetricComparison> {
240 let comparison = compare_collections(collection1, collection2, Some(threshold));
241
242 comparison
243 .metric_comparisons
244 .into_iter()
245 .filter(|comp| comp.is_significant)
246 .collect()
247}
248
249#[allow(dead_code)]
261pub fn combine_collections(
262 collections: &[MetricCollection],
263 name: &str,
264 description: Option<&str>,
265) -> MetricCollection {
266 let mut combined = MetricCollection::new(name, description);
267
268 for collection in collections {
269 for metric in &collection.metrics {
270 let mut metadata = metric.metadata.clone().unwrap_or_default();
272
273 if metadata.additional_metadata.is_none() {
274 metadata.additional_metadata = Some(HashMap::new());
275 }
276
277 if let Some(ref mut additional) = metadata.additional_metadata {
278 additional.insert("source_collection".to_string(), collection.name.clone());
279 additional.insert(
280 "source_timestamp".to_string(),
281 collection.created_at.to_string(),
282 );
283 }
284
285 let new_metric = MetricResult {
286 name: metric.name.clone(),
287 value: metric.value,
288 additional_values: metric.additional_values.clone(),
289 timestamp: metric.timestamp,
290 metadata: Some(metadata),
291 };
292
293 combined.add_metric(new_metric);
294 }
295 }
296
297 combined
298}
299
300#[allow(dead_code)]
311pub fn filter_metrics<F>(_collection: &MetricCollection, filterfn: F) -> MetricCollection
312where
313 F: Fn(&MetricResult) -> bool,
314{
315 let mut filtered = MetricCollection::new(
316 &format!("{} (filtered)", _collection.name),
317 _collection.description.as_deref(),
318 );
319
320 for metric in &_collection.metrics {
321 if filterfn(metric) {
322 filtered.add_metric(metric.clone());
323 }
324 }
325
326 filtered
327}
328
329#[allow(dead_code)]
340pub fn filter_by_name(collection: &MetricCollection, pattern: &str) -> MetricCollection {
341 filter_metrics(collection, |metric| metric.name.contains(pattern))
342}
343
344#[allow(dead_code)]
356pub fn filter_by_time_range(
357 collection: &MetricCollection,
358 start_age: Duration,
359 end_age: Duration,
360) -> Result<MetricCollection> {
361 let now = chrono::Utc::now();
362
363 Ok(filter_metrics(collection, |metric| {
364 let _age = now - metric.timestamp;
365 _age >= end_age && _age <= start_age
366 }))
367}