use chrono::{DateTime, Utc};
use parking_lot::RwLock;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
use super::{AlertInstance, AlertLevel};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AlertGroup {
pub id: String,
pub labels: HashMap<String, String>,
pub alerts: Vec<String>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub max_severity: AlertLevel,
}
impl AlertGroup {
pub fn new(labels: HashMap<String, String>) -> Self {
let label_str: String = {
let mut sorted: Vec<_> = labels.iter().collect();
sorted.sort_by_key(|(k, _)| *k);
sorted
.iter()
.map(|(k, v)| format!("{}={}", k, v))
.collect::<Vec<_>>()
.join(",")
};
let id = format!("group:{}", label_str);
Self {
id,
labels,
alerts: Vec::new(),
created_at: Utc::now(),
updated_at: Utc::now(),
max_severity: AlertLevel::Info,
}
}
pub fn add_alert(&mut self, alert_id: String, severity: AlertLevel) {
if !self.alerts.contains(&alert_id) {
self.alerts.push(alert_id);
}
if severity > self.max_severity {
self.max_severity = severity;
}
self.updated_at = Utc::now();
}
pub fn remove_alert(&mut self, alert_id: &str) {
self.alerts.retain(|id| id != alert_id);
self.updated_at = Utc::now();
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.alerts.is_empty()
}
#[must_use]
pub fn count(&self) -> usize {
self.alerts.len()
}
}
pub struct AlertGrouper {
groups: Arc<RwLock<HashMap<String, AlertGroup>>>,
group_by_keys: Vec<String>,
}
impl AlertGrouper {
pub fn new() -> Self {
Self {
groups: Arc::new(RwLock::new(HashMap::new())),
group_by_keys: vec!["alertname".to_string(), "severity".to_string()],
}
}
pub fn with_keys(keys: Vec<String>) -> Self {
Self {
groups: Arc::new(RwLock::new(HashMap::new())),
group_by_keys: keys,
}
}
fn get_group_labels(&self, alert: &AlertInstance) -> HashMap<String, String> {
let mut labels = HashMap::new();
for key in &self.group_by_keys {
if let Some(value) = alert.labels.get(key) {
labels.insert(key.clone(), value.clone());
}
}
labels
}
pub fn add_alert(&self, alert: &AlertInstance) -> String {
let group_labels = self.get_group_labels(alert);
let mut groups = self.groups.write();
let group_id = {
let mut sorted: Vec<_> = group_labels.iter().collect();
sorted.sort_by_key(|(k, _)| *k);
let label_str: String = sorted
.iter()
.map(|(k, v)| format!("{}={}", k, v))
.collect::<Vec<_>>()
.join(",");
format!("group:{}", label_str)
};
let group = groups
.entry(group_id.clone())
.or_insert_with(|| AlertGroup::new(group_labels));
group.add_alert(alert.id.clone(), alert.level);
group_id
}
pub fn remove_alert(&self, alert: &AlertInstance) {
let mut groups = self.groups.write();
let group_labels = self.get_group_labels(alert);
let group_id = {
let mut sorted: Vec<_> = group_labels.iter().collect();
sorted.sort_by_key(|(k, _)| *k);
let label_str: String = sorted
.iter()
.map(|(k, v)| format!("{}={}", k, v))
.collect::<Vec<_>>()
.join(",");
format!("group:{}", label_str)
};
if let Some(group) = groups.get_mut(&group_id) {
group.remove_alert(&alert.id);
if group.is_empty() {
groups.remove(&group_id);
}
}
}
pub fn get_groups(&self) -> Vec<AlertGroup> {
self.groups.read().values().cloned().collect()
}
pub fn get_group(&self, group_id: &str) -> Option<AlertGroup> {
self.groups.read().get(group_id).cloned()
}
}
impl Default for AlertGrouper {
fn default() -> Self {
Self::new()
}
}