use crate::monitor::performance_monitor::{MetricType, ThresholdSeverity, ThresholdViolation};
use std::collections::BTreeMap;
use std::collections::HashMap;
use std::collections::VecDeque;
use std::hash::Hash;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use std::time::SystemTime;
#[derive(Debug, Clone)]
pub struct AlertRule {
pub id: String,
pub name: String,
pub description: String,
pub metric_type: MetricType,
pub condition: AlertCondition,
pub severity: AlertSeverity,
pub enabled: bool,
pub tags: HashMap<String, String>,
pub silence_period: Duration,
}
#[derive(Debug, Clone)]
pub enum AlertCondition {
GreaterThan(f64),
LessThan(f64),
Equals(f64),
OutOfRange(f64, f64),
TrendAnomaly(f64),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum AlertSeverity {
Info,
Warning,
Critical,
}
#[derive(Debug, Clone)]
pub struct AlertInstance {
pub id: String,
pub rule_id: String,
pub message: String,
pub severity: AlertSeverity,
pub triggered_at: u64,
pub resolved_at: Option<u64>,
pub data: AlertData,
pub status: AlertStatus,
}
#[derive(Debug, Clone)]
pub struct AlertData {
pub current_value: f64,
pub threshold: f64,
pub metric_type: MetricType,
pub tags: HashMap<String, String>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum AlertStatus {
Firing,
Resolved,
Silenced,
}
#[derive(Debug, Clone)]
pub struct NotificationChannel {
pub id: String,
pub name: String,
pub channel_type: NotificationType,
pub config: HashMap<String, String>,
pub enabled: bool,
}
#[derive(Debug, Clone)]
pub enum NotificationType {
Webhook,
Email,
Sms,
Slack,
Discord,
}
#[derive(Debug, Clone)]
pub struct NotificationMessage {
pub title: String,
pub content: String,
pub severity: AlertSeverity,
pub tags: HashMap<String, String>,
pub timestamp: u64,
}
#[derive(Debug, Clone)]
pub struct AlertStats {
pub total_alerts: u64,
pub active_alerts: u64,
pub resolved_alerts: u64,
pub silenced_alerts: u64,
pub notification_count: u64,
pub last_alert_at: Option<u64>,
}
#[derive(Debug, Clone)]
pub struct AlertSystemConfig {
pub max_history: usize,
pub default_silence_period: Duration,
pub enable_notifications: bool,
pub notification_retry_count: u32,
pub notification_retry_interval: Duration,
}
#[derive(Debug)]
pub struct AlertSystem {
config: AlertSystemConfig,
rules: Arc<Mutex<HashMap<String, AlertRule>>>,
active_alerts: Arc<Mutex<HashMap<String, AlertInstance>>>,
alert_history: Arc<Mutex<VecDeque<AlertInstance>>>,
notification_channels: Arc<Mutex<HashMap<String, NotificationChannel>>>,
silence_rules: Arc<Mutex<HashMap<String, SilenceRule>>>,
stats: Arc<Mutex<AlertStats>>,
}
#[derive(Debug, Clone)]
pub struct SilenceRule {
pub id: String,
pub name: String,
pub start_time: u64,
pub end_time: u64,
pub match_tags: HashMap<String, String>,
pub created_at: u64,
}
#[derive(Debug, Clone)]
pub struct NotificationResult {
pub success: bool,
pub error: Option<String>,
pub sent_at: u64,
}
impl AlertSystem {
pub fn new(config: AlertSystemConfig) -> Self {
Self {
config,
rules: Arc::new(Mutex::new(HashMap::new())),
active_alerts: Arc::new(Mutex::new(HashMap::new())),
alert_history: Arc::new(Mutex::new(VecDeque::new())),
notification_channels: Arc::new(Mutex::new(HashMap::new())),
silence_rules: Arc::new(Mutex::new(HashMap::new())),
stats: Arc::new(Mutex::new(AlertStats {
total_alerts: 0,
active_alerts: 0,
resolved_alerts: 0,
silenced_alerts: 0,
notification_count: 0,
last_alert_at: None,
})),
}
}
pub fn with_default_config() -> Self {
let config: _ = AlertSystemConfig {
max_history: 10000,
default_silence_period: Duration::from_secs(300), enable_notifications: true,
notification_retry_count: 3,
notification_retry_interval: Duration::from_secs(30),
};
Self::new(config)
}
pub fn add_rule(&self, rule: AlertRule) -> Result<(), String> {
let mut rules = self.rules.lock().map_err(|e| e.to_string())?;
rules.insert(rule.id.clone(), rule);
Ok(())
}
pub fn update_rule(&self, rule: AlertRule) -> Result<(), String> {
let mut rules = self.rules.lock().map_err(|e| e.to_string())?;
if !rules.contains_key(&rule.id) {
return Err(format!("Rule '{}' not found", rule.id));
}
rules.insert(rule.id.clone(), rule);
Ok(())
}
pub fn delete_rule(&self, rule_id: &str) -> Result<(), String> {
let mut rules = self.rules.lock().map_err(|e| e.to_string())?;
rules.remove(rule_id);
Ok(())
}
pub fn get_rules(&self) -> Result<Vec<AlertRule>, String> {
let rules: _ = self.rules.lock().map_err(|e| e.to_string())?;
Ok(rules.values().cloned().collect())
}
pub fn add_silence_rule(&self, silence_rule: SilenceRule) -> Result<(), String> {
let mut silence_rules = self.silence_rules.lock().map_err(|e| e.to_string())?;
silence_rules.insert(silence_rule.id.clone(), silence_rule);
Ok(())
}
pub fn delete_silence_rule(&self, rule_id: &str) -> Result<(), String> {
let mut silence_rules = self.silence_rules.lock().map_err(|e| e.to_string())?;
silence_rules.remove(rule_id);
Ok(())
}
fn should_silence(&self, tags: &HashMap<String, String>) -> bool {
let silence_rules: _ = self.silence_rules.lock().unwrap();
let current_time: _ = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
for silence_rule in silence_rules.values() {
if current_time >= silence_rule.start_time && current_time <= silence_rule.end_time {
let mut matches = true;
for (key, value) in &silence_rule.match_tags {
if tags.get(key) != Some(value) {
matches = false;
break;
}
}
if matches {
return true;
}
}
}
false
}
pub fn handle_threshold_violations(
&self,
violations: Vec<ThresholdViolation>,
) -> Result<Vec<AlertInstance>, String> {
let rules: _ = self.rules.lock().map_err(|e| e.to_string())?;
let mut active_alerts = self.active_alerts.lock().map_err(|e| e.to_string())?;
let mut alert_history = self.alert_history.lock().map_err(|e| e.to_string())?;
let mut stats = self.stats.lock().map_err(|e| e.to_string())?;
let mut triggered_alerts = Vec::new();
let current_time: _ = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
for violation in violations {
let matching_rules: Vec<AlertRule> = rules
.values()
.filter(|rule| rule.enabled && rule.metric_type == violation.metric_type)
.cloned()
.collect();
for rule in matching_rules {
if !self.evaluate_condition(&rule.condition, violation.current_value) {
continue;
}
if self.should_silence(&HashMap::new()) {
continue;
}
let alert_id: _ = format!("{}_{}", rule.id, current_time);
let existing_alert: _ = active_alerts
.values()
.find(|alert| alert.rule_id == rule.id && alert.status == AlertStatus::Firing);
if existing_alert.is_some() {
continue; }
let alert_instance: _ = AlertInstance {
id: alert_id.clone(),
rule_id: rule.id.clone(),
message: self.generate_alert_message(&rule, violation.current_value),
severity: rule.severity.clone(),
triggered_at: current_time,
resolved_at: None,
data: AlertData {
current_value: violation.current_value,
threshold: violation.threshold_value,
metric_type: violation.metric_type.clone(),
tags: HashMap::new(),
},
status: AlertStatus::Firing,
};
active_alerts.insert(alert_id.clone(), alert_instance.clone());
triggered_alerts.push(alert_instance.clone());
stats.total_alerts += 1;
stats.active_alerts += 1;
stats.last_alert_at = Some(current_time);
if self.config.enable_notifications {
self.send_notifications(&alert_instance)?;
}
}
}
for alert in &triggered_alerts {
alert_history.push_back(alert.clone());
while alert_history.len() > self.config.max_history {
alert_history.pop_front();
}
}
Ok(triggered_alerts)
}
fn evaluate_condition(&self, condition: &AlertCondition, value: f64) -> bool {
match condition {
AlertCondition::GreaterThan(threshold) => value > *threshold,
AlertCondition::LessThan(threshold) => value < *threshold,
AlertCondition::Equals(value_eq) => (value - *value_eq).abs() < f64::EPSILON,
AlertCondition::OutOfRange(min, max) => value < *min || value > *max,
AlertCondition::TrendAnomaly(_) => {
false
}
}
}
fn generate_alert_message(&self, rule: &AlertRule, current_value: f64) -> String {
format!(
"Alert: {} - Current value: {:.2}, Threshold: {:.2}",
rule.name,
current_value,
match &rule.condition {
AlertCondition::GreaterThan(t) => *t,
AlertCondition::LessThan(t) => *t,
AlertCondition::Equals(t) => *t,
AlertCondition::OutOfRange(_, t) => *t,
AlertCondition::TrendAnomaly(t) => *t,
}
)
}
pub fn resolve_alert(&self, alert_id: &str) -> Result<(), String> {
let mut active_alerts = self.active_alerts.lock().map_err(|e| e.to_string())?;
let mut alert_history = self.alert_history.lock().map_err(|e| e.to_string())?;
let mut stats = self.stats.lock().map_err(|e| e.to_string())?;
if let Some(mut alert) = active_alerts.remove(alert_id) {
alert.status = AlertStatus::Resolved;
alert.resolved_at = Some(
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs(),
);
alert_history.push_back(alert.clone());
stats.active_alerts = stats.active_alerts.saturating_sub(1);
stats.resolved_alerts += 1;
Ok(())
} else {
Err(format!("Alert '{}' not found", alert_id))
}
}
pub fn get_active_alerts(&self) -> Result<Vec<AlertInstance>, String> {
let active_alerts: _ = self.active_alerts.lock().map_err(|e| e.to_string())?;
Ok(active_alerts.values().cloned().collect())
}
pub fn get_alert_history(&self, limit: Option<usize>) -> Result<Vec<AlertInstance>, String> {
let alert_history: _ = self.alert_history.lock().map_err(|e| e.to_string())?;
let mut history = alert_history.iter().cloned().collect::<Vec<_>>();
history.sort_by(|a, b| b.triggered_at.cmp(&a.triggered_at));
if let Some(limit) = limit {
history.truncate(limit);
}
Ok(history)
}
pub fn add_notification_channel(&self, channel: NotificationChannel) -> Result<(), String> {
let mut notification_channels = self
.notification_channels
.lock()
.map_err(|e| e.to_string())?;
notification_channels.insert(channel.id.clone(), channel);
Ok(())
}
fn send_notifications(&self, alert: &AlertInstance) -> Result<(), String> {
let notification_channels: _ = self
.notification_channels
.lock()
.map_err(|e| e.to_string())?;
let mut stats = self.stats.lock().map_err(|e| e.to_string())?;
let message: _ = NotificationMessage {
title: format!("Amber Alert: {}", alert.severity.as_str()),
content: alert.message.clone(),
severity: alert.severity.clone(),
tags: alert.data.tags.clone(),
timestamp: alert.triggered_at,
};
for channel in notification_channels.values() {
if channel.enabled {
self.simulate_notification_send(channel, &message)?;
stats.notification_count += 1;
}
}
Ok(())
}
fn simulate_notification_send(
&self,
channel: &NotificationChannel,
message: &NotificationMessage,
) -> Result<NotificationResult, String> {
Ok(NotificationResult {
success: true,
error: None,
sent_at: SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs(),
})
}
pub fn get_stats(&self) -> Result<AlertStats, String> {
let stats: _ = self.stats.lock().map_err(|e| e.to_string())?;
Ok(stats.clone())
}
pub fn cleanup_expired_alerts(&self) -> Result<u64, String> {
let mut active_alerts = self.active_alerts.lock().map_err(|e| e.to_string())?;
let mut alert_history = self.alert_history.lock().map_err(|e| e.to_string())?;
let current_time: _ = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
let silence_period: _ = self.config.default_silence_period.as_secs();
let mut cleaned_count = 0;
let silence_rule_tags: HashMap<String, String> = HashMap::new();
active_alerts.retain(|_, alert| {
if alert.status == AlertStatus::Silenced {
if current_time - alert.triggered_at > silence_period {
cleaned_count += 1;
return false;
}
}
true
});
while alert_history.len() > self.config.max_history {
alert_history.pop_front();
cleaned_count += 1;
}
Ok(cleaned_count)
}
}
impl AlertSeverity {
pub fn as_str(&self) -> &'static str {
match self {
AlertSeverity::Info => "INFO",
AlertSeverity::Warning => "WARNING",
AlertSeverity::Critical => "CRITICAL",
}
}
pub fn as_u8(&self) -> u8 {
match self {
AlertSeverity::Info => 1,
AlertSeverity::Warning => 2,
AlertSeverity::Critical => 3,
}
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_alert_system_creation() {
let alert_system: _ = AlertSystem::with_default_config();
assert!(alert_system.get_stats().is_ok());
}
#[test]
fn test_add_alert_rule() {
let alert_system: _ = AlertSystem::with_default_config();
let rule: _ = AlertRule {
id: "test_rule".to_string(),
name: "Test Rule".to_string(),
description: "Test description".to_string(),
metric_type: MetricType::CpuUsage,
condition: AlertCondition::GreaterThan(80.0),
severity: AlertSeverity::Warning,
enabled: true,
tags: HashMap::new(),
silence_period: Duration::from_secs(300),
};
assert!(alert_system.add_rule(rule).is_ok());
let rules: _ = alert_system.get_rules().unwrap();
assert_eq!(rules.len(), 1);
}
#[test]
fn test_handle_threshold_violations() {
let alert_system: _ = AlertSystem::with_default_config();
let rule: _ = AlertRule {
id: "cpu_rule".to_string(),
name: "CPU High".to_string(),
description: "CPU usage too high".to_string(),
metric_type: MetricType::CpuUsage,
condition: AlertCondition::GreaterThan(80.0),
severity: AlertSeverity::Critical,
enabled: true,
tags: HashMap::new(),
silence_period: Duration::from_secs(300),
};
alert_system.add_rule(rule).unwrap();
let violation: _ = ThresholdViolation {
metric_type: MetricType::CpuUsage,
current_value: 95.0,
threshold_value: 80.0,
severity: ThresholdSeverity::Critical,
timestamp: 1234567890,
};
let alerts: _ = alert_system
.handle_threshold_violations(vec![violation])
.unwrap();
assert_eq!(alerts.len(), 1);
assert_eq!(alerts[0].severity, AlertSeverity::Critical);
}
#[test]
fn test_resolve_alert() {
let alert_system: _ = AlertSystem::with_default_config();
let rule: _ = AlertRule {
id: "cpu_rule".to_string(),
name: "CPU High".to_string(),
description: "CPU usage too high".to_string(),
metric_type: MetricType::CpuUsage,
condition: AlertCondition::GreaterThan(80.0),
severity: AlertSeverity::Critical,
enabled: true,
tags: HashMap::new(),
silence_period: Duration::from_secs(300),
};
alert_system.add_rule(rule).unwrap();
let violation: _ = ThresholdViolation {
metric_type: MetricType::CpuUsage,
current_value: 95.0,
threshold_value: 80.0,
severity: ThresholdSeverity::Critical,
timestamp: 1234567890,
};
let alerts: _ = alert_system
.handle_threshold_violations(vec![violation])
.unwrap();
let alert_id: _ = &alerts[0].id;
assert!(alert_system.resolve_alert(alert_id).is_ok());
let active_alerts: _ = alert_system.get_active_alerts().unwrap();
assert_eq!(active_alerts.len(), 0);
}
#[test]
fn test_get_alert_history() {
let alert_system: _ = AlertSystem::with_default_config();
let rule: _ = AlertRule {
id: "cpu_rule".to_string(),
name: "CPU High".to_string(),
description: "CPU usage too high".to_string(),
metric_type: MetricType::CpuUsage,
condition: AlertCondition::GreaterThan(80.0),
severity: AlertSeverity::Critical,
enabled: true,
tags: HashMap::new(),
silence_period: Duration::from_secs(300),
};
alert_system.add_rule(rule).unwrap();
let violation: _ = ThresholdViolation {
metric_type: MetricType::CpuUsage,
current_value: 95.0,
threshold_value: 80.0,
severity: ThresholdSeverity::Critical,
timestamp: 1234567890,
};
alert_system
.handle_threshold_violations(vec![violation])
.unwrap();
let history: _ = alert_system.get_alert_history(None).unwrap();
assert_eq!(history.len(), 1);
}
#[test]
fn test_evaluate_condition() {
let alert_system: _ = AlertSystem::with_default_config();
let conditions: _ = vec![
(AlertCondition::GreaterThan(50.0), 60.0, true),
(AlertCondition::LessThan(50.0), 40.0, true),
(AlertCondition::Equals(50.0), 50.0, true),
(AlertCondition::OutOfRange(10.0, 90.0), 95.0, true),
];
for (condition, value, expected) in conditions {
assert_eq!(alert_system.evaluate_condition(&condition, value), expected);
}
}
#[test]
fn test_add_silence_rule() {
let alert_system: _ = AlertSystem::with_default_config();
let silence_rule: _ = SilenceRule {
id: "silence_1".to_string(),
name: "Silence Test".to_string(),
start_time: 1234567890,
end_time: 1234567890 + 3600,
match_tags: HashMap::new(),
created_at: 1234567890,
};
assert!(alert_system.add_silence_rule(silence_rule).is_ok());
}
#[test]
fn test_notification_channel() {
let alert_system: _ = AlertSystem::with_default_config();
let channel: _ = NotificationChannel {
id: "webhook_1".to_string(),
name: "Webhook".to_string(),
channel_type: NotificationType::Webhook,
config: HashMap::new(),
enabled: true,
};
assert!(alert_system.add_notification_channel(channel).is_ok());
}
#[test]
fn test_alert_severity() {
assert_eq!(AlertSeverity::Info.as_str(), "INFO");
assert_eq!(AlertSeverity::Warning.as_str(), "WARNING");
assert_eq!(AlertSeverity::Critical.as_str(), "CRITICAL");
assert_eq!(AlertSeverity::Info.as_u8(), 1);
assert_eq!(AlertSeverity::Warning.as_u8(), 2);
assert_eq!(AlertSeverity::Critical.as_u8(), 3);
}
}