1use crate::alert_storage::{AlertStorage, StoredAlert};
2use crate::alert_system::{AlertSeverity};
3use chrono::{DateTime, Utc, Duration, Timelike, Datelike};
4use serde::{Serialize, Deserialize};
5use std::collections::{HashMap, HashSet};
6use std::str::FromStr;
7
8impl FromStr for AlertSeverity {
9 type Err = String;
10
11 fn from_str(s: &str) -> Result<Self, Self::Err> {
12 match s.to_lowercase().as_str() {
13 "critical" => Ok(AlertSeverity::Critical),
14 "warning" => Ok(AlertSeverity::Warning),
15 "info" => Ok(AlertSeverity::Info),
16 _ => Err(format!("Invalid severity: {}", s))
17 }
18 }
19}
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct AlertAnalysis {
23 pub time_range: TimeRange,
24 pub total_alerts: usize,
25 pub severity_distribution: HashMap<AlertSeverity, usize>,
26 pub resolution_metrics: ResolutionMetrics,
27 pub patterns: Vec<AlertPattern>,
28 pub recommendations: Vec<AlertRecommendation>,
29}
30
31#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct TimeRange {
33 pub start: DateTime<Utc>,
34 pub end: DateTime<Utc>,
35 #[serde(with = "duration_serde")]
36 pub duration: Duration,
37}
38
39mod duration_serde {
40 use serde::{Deserialize, Deserializer, Serialize, Serializer};
41 use chrono::Duration;
42
43 pub fn serialize<S>(duration: &Duration, serializer: S) -> Result<S::Ok, S::Error>
44 where
45 S: Serializer,
46 {
47 duration.num_seconds().serialize(serializer)
48 }
49
50 pub fn deserialize<'de, D>(deserializer: D) -> Result<Duration, D::Error>
51 where
52 D: Deserializer<'de>,
53 {
54 let secs = i64::deserialize(deserializer)?;
55 Ok(Duration::seconds(secs))
56 }
57}
58
59#[derive(Debug, Clone, Serialize, Deserialize)]
60pub struct ResolutionMetrics {
61 #[serde(with = "duration_serde")]
62 pub avg_resolution_time: Duration,
63 pub resolution_rate: f64,
64 pub unresolved_critical: usize,
65}
66
67#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct AlertPattern {
69 pub pattern_type: PatternType,
70 pub frequency: usize,
71 pub confidence: f64,
72 pub affected_metrics: Vec<String>,
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize)]
76pub enum PatternType {
77 Periodic,
78 Cascading,
79 Correlated,
80 Seasonal,
81}
82
83#[derive(Debug, Clone, Serialize, Deserialize)]
84pub struct AlertRecommendation {
85 pub recommendation_type: RecommendationType,
86 pub description: String,
87 pub priority: u8,
88 pub estimated_impact: f64,
89}
90
91#[derive(Debug, Clone, Serialize, Deserialize)]
92pub enum RecommendationType {
93 ThresholdAdjustment,
94 MonitoringEnhancement,
95 AutoRemediation,
96 ProcessImprovement,
97}
98
99pub struct AlertAnalyzer {
100 storage: AlertStorage,
101 config: AnalyzerConfig,
102}
103
104#[derive(Clone)]
105pub struct AnalyzerConfig {
106 pub analysis_window: Duration,
107 pub pattern_detection_threshold: f64,
108 pub min_correlation_strength: f64,
109}
110
111impl AlertAnalyzer {
112 pub fn new(storage: AlertStorage, config: AnalyzerConfig) -> Self {
113 Self { storage, config }
114 }
115
116 pub async fn analyze_alerts(&self, time_range: TimeRange) -> Result<AlertAnalysis, Box<dyn std::error::Error>> {
117 let alerts = self.storage.get_alerts_in_range(time_range.start, time_range.end).await?;
118
119 let analysis = AlertAnalysis {
120 time_range,
121 total_alerts: alerts.len(),
122 severity_distribution: self.calculate_severity_distribution(&alerts),
123 resolution_metrics: self.calculate_resolution_metrics(&alerts),
124 patterns: self.detect_patterns(&alerts),
125 recommendations: self.generate_recommendations(&alerts),
126 };
127
128 Ok(analysis)
129 }
130
131 fn calculate_severity_distribution(&self, alerts: &[StoredAlert]) -> HashMap<AlertSeverity, usize> {
132 let mut distribution = HashMap::new();
133
134 for alert in alerts {
135 let severity = AlertSeverity::from_str(&alert.severity)
136 .unwrap_or(AlertSeverity::Info);
137 *distribution.entry(severity).or_insert(0) += 1;
138 }
139
140 distribution
141 }
142
143 fn calculate_resolution_metrics(&self, alerts: &[StoredAlert]) -> ResolutionMetrics {
144 let mut total_resolution_time = Duration::zero();
145 let mut resolved_count = 0;
146 let mut unresolved_critical = 0;
147
148 for alert in alerts {
149 if let Some(resolved_at) = alert.resolved_at {
150 let resolution_time = resolved_at - alert.created_at;
151 total_resolution_time = total_resolution_time + resolution_time;
152 resolved_count += 1;
153 } else if alert.severity == AlertSeverity::Critical.to_string() {
154 unresolved_critical += 1;
155 }
156 }
157
158 let avg_resolution_time = if resolved_count > 0 {
159 total_resolution_time / resolved_count as i32
160 } else {
161 Duration::zero()
162 };
163
164 let resolution_rate = resolved_count as f64 / alerts.len() as f64;
165
166 ResolutionMetrics {
167 avg_resolution_time,
168 resolution_rate,
169 unresolved_critical,
170 }
171 }
172
173 fn detect_patterns(&self, alerts: &[StoredAlert]) -> Vec<AlertPattern> {
174 let mut patterns = Vec::new();
175
176 if let Some(pattern) = self.detect_periodic_pattern(alerts) {
177 patterns.push(pattern);
178 }
179
180 if let Some(pattern) = self.detect_cascade_pattern(alerts) {
181 patterns.push(pattern);
182 }
183
184 patterns.extend(self.detect_correlated_patterns(alerts));
185
186 if let Some(pattern) = self.detect_seasonal_pattern(alerts) {
187 patterns.push(pattern);
188 }
189
190 patterns
191 }
192
193 fn detect_periodic_pattern(&self, alerts: &[StoredAlert]) -> Option<AlertPattern> {
194 let mut timeline = alerts.iter()
195 .map(|a| (a.created_at, a.metric_name.clone()))
196 .collect::<Vec<_>>();
197 timeline.sort_by_key(|k| k.0);
198
199 let intervals = vec![
200 chrono::Duration::hours(1),
201 chrono::Duration::hours(6),
202 chrono::Duration::hours(12),
203 chrono::Duration::hours(24),
204 ];
205
206 let mut best_period = None;
207 let mut best_confidence = 0.0;
208
209 for interval in intervals {
210 if let Some((period, confidence)) = self.analyze_periodicity(&timeline, interval) {
211 if confidence > best_confidence && confidence > self.config.pattern_detection_threshold {
212 best_period = Some(period);
213 best_confidence = confidence;
214 }
215 }
216 }
217
218 best_period.map(|period| AlertPattern {
219 pattern_type: PatternType::Periodic,
220 frequency: period.num_hours() as usize,
221 confidence: best_confidence,
222 affected_metrics: self.get_affected_metrics(alerts),
223 })
224 }
225
226 fn analyze_periodicity(
227 &self,
228 timeline: &[(DateTime<Utc>, String)],
229 interval: Duration
230 ) -> Option<(Duration, f64)> {
231 let mut interval_counts = HashMap::new();
232 let mut prev_time = None;
233
234 for (time, _) in timeline {
235 if let Some(prev) = prev_time {
236 let diff: Duration = *time - prev;
237 let normalized_diff = Duration::hours(
238 (diff.num_seconds() as f64 / interval.num_seconds() as f64).round() as i64
239 );
240
241 *interval_counts.entry(normalized_diff).or_insert(0) += 1;
242 }
243 prev_time = Some(*time);
244 }
245
246 interval_counts.iter()
247 .max_by_key(|(_, &count)| count)
248 .map(|(period, count)| {
249 let total_intervals = interval_counts.values().sum::<i32>();
250 let confidence = *count as f64 / total_intervals as f64;
251 (*period, confidence)
252 })
253 }
254
255 fn detect_cascade_pattern(&self, alerts: &[StoredAlert]) -> Option<AlertPattern> {
256 let mut timeline = alerts.iter()
257 .map(|a| (a.created_at, a.metric_name.clone()))
258 .collect::<Vec<_>>();
259 timeline.sort_by_key(|k| k.0);
260
261 let window_size = chrono::Duration::minutes(5);
262 let mut cascade_groups = Vec::new();
263 let mut current_group = Vec::new();
264
265 for i in 0..timeline.len() {
266 if current_group.is_empty() {
267 current_group.push(timeline[i].clone());
268 continue;
269 }
270
271 let time_diff = timeline[i].0 - current_group.last().unwrap().0;
272 if time_diff <= window_size {
273 current_group.push(timeline[i].clone());
274 } else {
275 if current_group.len() >= 3 {
276 cascade_groups.push(current_group.clone());
277 }
278 current_group.clear();
279 current_group.push(timeline[i].clone());
280 }
281 }
282
283 if current_group.len() >= 3 {
284 cascade_groups.push(current_group);
285 }
286
287 if let Some(largest_cascade) = cascade_groups.iter().max_by_key(|g| g.len()) {
288 if largest_cascade.len() >= 3 {
289 let affected_metrics = largest_cascade.iter()
290 .map(|(_, metric)| metric.clone())
291 .collect();
292
293 Some(AlertPattern {
294 pattern_type: PatternType::Cascading,
295 frequency: largest_cascade.len(),
296 confidence: largest_cascade.len() as f64 / alerts.len() as f64,
297 affected_metrics,
298 })
299 } else {
300 None
301 }
302 } else {
303 None
304 }
305 }
306
307 fn detect_correlated_patterns(&self, alerts: &[StoredAlert]) -> Vec<AlertPattern> {
308 let mut patterns = Vec::new();
309 let mut metric_groups = HashMap::new();
310
311 let window_size = chrono::Duration::minutes(15);
312 for alert in alerts {
313 let window_start = alert.created_at.timestamp() / window_size.num_seconds();
314 metric_groups
315 .entry(window_start)
316 .or_insert_with(HashSet::new)
317 .insert(alert.metric_name.clone());
318 }
319
320 let mut co_occurrences = HashMap::new();
321 for metrics in metric_groups.values() {
322 for m1 in metrics.iter() {
323 for m2 in metrics.iter() {
324 if m1 < m2 {
325 *co_occurrences.entry((m1.clone(), m2.clone())).or_insert(0) += 1;
326 }
327 }
328 }
329 }
330
331 let min_occurrences = (metric_groups.len() as f64 * self.config.min_correlation_strength) as usize;
332 let mut correlated_metrics = HashSet::new();
333
334 for ((m1, m2), count) in co_occurrences {
335 if count >= min_occurrences {
336 let confidence = count as f64 / metric_groups.len() as f64;
337
338 if !correlated_metrics.contains(&m1) && !correlated_metrics.contains(&m2) {
339 patterns.push(AlertPattern {
340 pattern_type: PatternType::Correlated,
341 frequency: count,
342 confidence,
343 affected_metrics: vec![m1.clone(), m2.clone()],
344 });
345 correlated_metrics.insert(m1);
346 correlated_metrics.insert(m2);
347 }
348 }
349 }
350
351 patterns
352 }
353
354 fn detect_seasonal_pattern(&self, alerts: &[StoredAlert]) -> Option<AlertPattern> {
355 let mut hourly_distribution = vec![0; 24];
356 let mut daily_distribution = vec![0; 7];
357
358 for alert in alerts {
359 let hour = alert.created_at.hour() as usize;
360 let day = alert.created_at.weekday().num_days_from_monday() as usize;
361
362 hourly_distribution[hour] += 1;
363 daily_distribution[day] += 1;
364 }
365
366 let hourly_variance = self.calculate_distribution_variance(&hourly_distribution);
367 let daily_variance = self.calculate_distribution_variance(&daily_distribution);
368
369 if hourly_variance > self.config.pattern_detection_threshold {
370 Some(AlertPattern {
371 pattern_type: PatternType::Seasonal,
372 frequency: self.find_peak_frequency(&hourly_distribution),
373 confidence: hourly_variance,
374 affected_metrics: self.get_affected_metrics(alerts),
375 })
376 } else if daily_variance > self.config.pattern_detection_threshold {
377 Some(AlertPattern {
378 pattern_type: PatternType::Seasonal,
379 frequency: self.find_peak_frequency(&daily_distribution) * 24,
380 confidence: daily_variance,
381 affected_metrics: self.get_affected_metrics(alerts),
382 })
383 } else {
384 None
385 }
386 }
387
388 fn calculate_distribution_variance(&self, distribution: &[i32]) -> f64 {
389 let mean = distribution.iter().sum::<i32>() as f64 / distribution.len() as f64;
390 let variance = distribution.iter()
391 .map(|&x| {
392 let diff = x as f64 - mean;
393 diff * diff
394 })
395 .sum::<f64>() / distribution.len() as f64;
396
397 variance.sqrt() / mean
398 }
399
400 fn find_peak_frequency(&self, distribution: &[i32]) -> usize {
401 let mut max_val = 0;
402 let mut max_idx = 0;
403
404 for (idx, &val) in distribution.iter().enumerate() {
405 if val > max_val {
406 max_val = val;
407 max_idx = idx;
408 }
409 }
410
411 max_idx
412 }
413
414 fn get_affected_metrics(&self, alerts: &[StoredAlert]) -> Vec<String> {
415 let mut metrics = std::collections::HashSet::new();
416 for alert in alerts {
417 metrics.insert(alert.metric_name.clone());
418 }
419 metrics.into_iter().collect()
420 }
421
422 fn generate_recommendations(&self, alerts: &[StoredAlert]) -> Vec<AlertRecommendation> {
423 let mut recommendations = Vec::new();
424
425 if let Some(rec) = self.recommend_threshold_adjustments(alerts) {
426 recommendations.push(rec);
427 }
428
429 if let Some(rec) = self.recommend_monitoring_improvements(alerts) {
430 recommendations.push(rec);
431 }
432
433 if let Some(rec) = self.recommend_auto_remediation(alerts) {
434 recommendations.push(rec);
435 }
436
437 recommendations
438 }
439
440 fn recommend_auto_remediation(&self, alerts: &[StoredAlert]) -> Option<AlertRecommendation> {
441 let auto_resolvable_count = alerts.iter()
442 .filter(|a| self.is_auto_resolvable(a))
443 .count();
444
445 let total_alerts = alerts.len();
446 if auto_resolvable_count > total_alerts / 3 {
447 Some(AlertRecommendation {
448 recommendation_type: RecommendationType::AutoRemediation,
449 description: "Implement automatic resolution for common alert patterns".to_string(),
450 priority: 8,
451 estimated_impact: 0.7,
452 })
453 } else {
454 None
455 }
456 }
457
458 fn is_auto_resolvable(&self, alert: &StoredAlert) -> bool {
459 match alert.severity.as_str() {
460 "Info" | "Warning" => true,
461 "Critical" => false,
462 _ => false,
463 }
464 }
465
466 fn recommend_threshold_adjustments(&self, alerts: &[StoredAlert]) -> Option<AlertRecommendation> {
467 let mut threshold_alerts = 0;
468 for alert in alerts {
469 if alert.current_value > alert.threshold * 0.9
470 && alert.current_value < alert.threshold * 1.1 {
471 threshold_alerts += 1;
472 }
473 }
474
475 if threshold_alerts > alerts.len() / 4 {
476 Some(AlertRecommendation {
477 recommendation_type: RecommendationType::ThresholdAdjustment,
478 description: "Consider adjusting thresholds based on recent alert patterns".to_string(),
479 priority: 7,
480 estimated_impact: 0.6,
481 })
482 } else {
483 None
484 }
485 }
486
487 fn recommend_monitoring_improvements(&self, alerts: &[StoredAlert]) -> Option<AlertRecommendation> {
488 let mut metric_frequencies = HashMap::new();
489 for alert in alerts {
490 *metric_frequencies.entry(&alert.metric_name).or_insert(0) += 1;
491 }
492
493 let high_frequency_metrics = metric_frequencies.iter()
494 .filter(|(_, &count)| count > alerts.len() / 10)
495 .map(|(metric, _)| (*metric).clone())
496 .collect::<Vec<String>>();
497
498 if !high_frequency_metrics.is_empty() {
499 Some(AlertRecommendation {
500 recommendation_type: RecommendationType::MonitoringEnhancement,
501 description: format!(
502 "Enhance monitoring for metrics: {}",
503 high_frequency_metrics.join(", ")
504 ),
505 priority: 6,
506 estimated_impact: 0.5,
507 })
508 } else {
509 None
510 }
511 }
512
513 #[allow(dead_code)]
514 fn analyze_alert_similarity(&self, a1: &StoredAlert, a2: &StoredAlert) -> f64 {
515 let time_diff = (a1.created_at - a2.created_at).num_seconds().abs() as f64;
516 let time_similarity = (-time_diff / 3600.0).exp();
517
518 let metric_similarity = if a1.metric_name == a2.metric_name { 1.0 } else { 0.0 };
519 let severity_similarity = if a1.severity == a2.severity { 1.0 } else { 0.0 };
520
521 let threshold_diff = (a1.threshold - a2.threshold).abs();
522 let threshold_similarity = (-threshold_diff / a1.threshold).exp();
523
524 0.4 * time_similarity +
525 0.3 * metric_similarity +
526 0.2 * severity_similarity +
527 0.1 * threshold_similarity
528 }
529}