leptos_sync_core/reliability/monitoring/
metrics.rs1use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5use std::time::{SystemTime, UNIX_EPOCH};
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct Metric {
10 pub name: String,
12 pub value: f64,
14 pub timestamp: u64,
16 pub tags: HashMap<String, String>,
18}
19
20impl Metric {
21 pub fn new(name: String, value: f64) -> Self {
23 Self {
24 name,
25 value,
26 timestamp: SystemTime::now()
27 .duration_since(UNIX_EPOCH)
28 .unwrap()
29 .as_secs(),
30 tags: HashMap::new(),
31 }
32 }
33
34 pub fn with_tags(mut self, tags: HashMap<String, String>) -> Self {
36 self.tags = tags;
37 self
38 }
39
40 pub fn with_timestamp(mut self, timestamp: u64) -> Self {
42 self.timestamp = timestamp;
43 self
44 }
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct TimeRange {
50 pub start: u64,
52 pub end: u64,
54}
55
56impl TimeRange {
57 pub fn new(start: u64, end: u64) -> Self {
59 Self { start, end }
60 }
61
62 pub fn last_seconds(seconds: u64) -> Self {
64 let now = SystemTime::now()
65 .duration_since(UNIX_EPOCH)
66 .unwrap()
67 .as_secs();
68 Self {
69 start: now - seconds,
70 end: now,
71 }
72 }
73
74 pub fn contains(&self, timestamp: u64) -> bool {
76 timestamp >= self.start && timestamp <= self.end
77 }
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize)]
82pub enum AggregationType {
83 Sum,
85 Average,
87 Min,
89 Max,
91 Count,
93 Latest,
95}
96
97#[derive(Debug, Clone, Serialize, Deserialize)]
99pub struct AggregatedMetric {
100 pub name: String,
102 pub value: f64,
104 pub aggregation_type: AggregationType,
106 pub time_range: TimeRange,
108 pub sample_count: usize,
110}
111
112#[derive(Debug, Clone)]
114pub struct MetricsCollector {
115 metrics: HashMap<String, Vec<Metric>>,
117 max_metrics_per_name: usize,
119 auto_cleanup: bool,
121 max_metric_age: u64,
123}
124
125impl MetricsCollector {
126 pub fn new() -> Self {
128 Self {
129 metrics: HashMap::new(),
130 max_metrics_per_name: 1000,
131 auto_cleanup: true,
132 max_metric_age: 3600, }
134 }
135
136 pub fn with_config(config: MetricsConfig) -> Self {
138 Self {
139 metrics: HashMap::new(),
140 max_metrics_per_name: config.max_metrics_per_name,
141 auto_cleanup: config.auto_cleanup,
142 max_metric_age: config.max_metric_age,
143 }
144 }
145
146 pub fn record(&mut self, metric: Metric) {
148 let name = metric.name.clone();
149
150 self.metrics.entry(name.clone()).or_insert_with(Vec::new).push(metric);
152
153 if self.auto_cleanup {
155 self.cleanup_old_metrics(&name);
156 }
157
158 if let Some(metrics) = self.metrics.get_mut(&name) {
160 if metrics.len() > self.max_metrics_per_name {
161 metrics.drain(0..metrics.len() - self.max_metrics_per_name);
162 }
163 }
164 }
165
166 pub fn get_metrics(&self, name: &str, time_range: &TimeRange) -> Vec<&Metric> {
168 self.metrics
169 .get(name)
170 .map(|metrics| {
171 metrics
172 .iter()
173 .filter(|m| time_range.contains(m.timestamp))
174 .collect()
175 })
176 .unwrap_or_default()
177 }
178
179 pub fn get_all_metrics(&self, name: &str) -> Vec<&Metric> {
181 self.metrics
182 .get(name)
183 .map(|metrics| metrics.iter().collect())
184 .unwrap_or_default()
185 }
186
187 pub fn aggregate_metrics(
189 &self,
190 name: &str,
191 time_range: &TimeRange,
192 aggregation_type: AggregationType,
193 ) -> Option<AggregatedMetric> {
194 let metrics = self.get_metrics(name, time_range);
195
196 if metrics.is_empty() {
197 return None;
198 }
199
200 let value = match aggregation_type {
201 AggregationType::Sum => metrics.iter().map(|m| m.value).sum(),
202 AggregationType::Average => {
203 let sum: f64 = metrics.iter().map(|m| m.value).sum();
204 sum / metrics.len() as f64
205 }
206 AggregationType::Min => metrics.iter().map(|m| m.value).fold(f64::INFINITY, f64::min),
207 AggregationType::Max => metrics.iter().map(|m| m.value).fold(f64::NEG_INFINITY, f64::max),
208 AggregationType::Count => metrics.len() as f64,
209 AggregationType::Latest => metrics.last().unwrap().value,
210 };
211
212 Some(AggregatedMetric {
213 name: name.to_string(),
214 value,
215 aggregation_type,
216 time_range: time_range.clone(),
217 sample_count: metrics.len(),
218 })
219 }
220
221 pub fn get_metric_names(&self) -> Vec<String> {
223 self.metrics.keys().cloned().collect()
224 }
225
226 pub fn clear(&mut self) {
228 self.metrics.clear();
229 }
230
231 pub fn clear_metrics(&mut self, name: &str) {
233 self.metrics.remove(name);
234 }
235
236 fn cleanup_old_metrics(&mut self, name: &str) {
238 let cutoff_time = SystemTime::now()
239 .duration_since(UNIX_EPOCH)
240 .unwrap()
241 .as_secs()
242 - self.max_metric_age;
243
244 if let Some(metrics) = self.metrics.get_mut(name) {
245 metrics.retain(|m| m.timestamp >= cutoff_time);
246 }
247 }
248
249 pub fn total_metrics_count(&self) -> usize {
251 self.metrics.values().map(|v| v.len()).sum()
252 }
253
254 pub fn unique_metric_count(&self) -> usize {
256 self.metrics.len()
257 }
258}
259
260impl Default for MetricsCollector {
261 fn default() -> Self {
262 Self::new()
263 }
264}
265
266#[derive(Debug, Clone, Serialize, Deserialize)]
268pub struct MetricsConfig {
269 pub max_metrics_per_name: usize,
271 pub auto_cleanup: bool,
273 pub max_metric_age: u64,
275}
276
277impl Default for MetricsConfig {
278 fn default() -> Self {
279 Self {
280 max_metrics_per_name: 1000,
281 auto_cleanup: true,
282 max_metric_age: 3600, }
284 }
285}
286
287#[cfg(test)]
288mod tests {
289 use super::*;
290
291 #[test]
292 fn test_metric_creation() {
293 let metric = Metric::new("test_metric".to_string(), 42.0);
294 assert_eq!(metric.name, "test_metric");
295 assert_eq!(metric.value, 42.0);
296 assert!(!metric.tags.is_empty() || metric.tags.is_empty()); }
298
299 #[test]
300 fn test_metric_with_tags() {
301 let mut tags = HashMap::new();
302 tags.insert("service".to_string(), "api".to_string());
303 tags.insert("version".to_string(), "1.0".to_string());
304
305 let metric = Metric::new("test_metric".to_string(), 42.0).with_tags(tags.clone());
306 assert_eq!(metric.tags, tags);
307 }
308
309 #[test]
310 fn test_time_range() {
311 let range = TimeRange::new(1000, 2000);
312 assert!(range.contains(1500));
313 assert!(!range.contains(500));
314 assert!(!range.contains(2500));
315 }
316
317 #[test]
318 fn test_metrics_collector() {
319 let mut collector = MetricsCollector::new();
320
321 collector.record(Metric::new("cpu_usage".to_string(), 75.0));
323 collector.record(Metric::new("memory_usage".to_string(), 60.0));
324 collector.record(Metric::new("cpu_usage".to_string(), 80.0));
325
326 assert_eq!(collector.unique_metric_count(), 2);
328 assert_eq!(collector.total_metrics_count(), 3);
329
330 let cpu_metrics = collector.get_all_metrics("cpu_usage");
332 assert_eq!(cpu_metrics.len(), 2);
333
334 let time_range = TimeRange::last_seconds(3600);
336 let avg_cpu = collector.aggregate_metrics("cpu_usage", &time_range, AggregationType::Average);
337 assert!(avg_cpu.is_some());
338 assert_eq!(avg_cpu.unwrap().value, 77.5); }
340
341 #[test]
342 fn test_aggregation_types() {
343 let mut collector = MetricsCollector::new();
344
345 collector.record(Metric::new("test".to_string(), 10.0));
347 collector.record(Metric::new("test".to_string(), 20.0));
348 collector.record(Metric::new("test".to_string(), 30.0));
349
350 let time_range = TimeRange::last_seconds(3600);
351
352 let sum = collector.aggregate_metrics("test", &time_range, AggregationType::Sum);
354 assert_eq!(sum.unwrap().value, 60.0);
355
356 let avg = collector.aggregate_metrics("test", &time_range, AggregationType::Average);
357 assert_eq!(avg.unwrap().value, 20.0);
358
359 let min = collector.aggregate_metrics("test", &time_range, AggregationType::Min);
360 assert_eq!(min.unwrap().value, 10.0);
361
362 let max = collector.aggregate_metrics("test", &time_range, AggregationType::Max);
363 assert_eq!(max.unwrap().value, 30.0);
364
365 let count = collector.aggregate_metrics("test", &time_range, AggregationType::Count);
366 assert_eq!(count.unwrap().value, 3.0);
367 }
368}