refactor(system_monitor): move metrics collection and alerting to dedicated modules
* node_notifier doesn't need sysinfo anymore. * implement AlertFormatter in node_notifier and its own file. * implement SystemMonitor, ResourceThreshold, ThresholdViolation, and Severity in system_monitor. * implement parametrized tests for SystemMonitor.
This commit is contained in:
9
node_notifier/Cargo.lock
generated
9
node_notifier/Cargo.lock
generated
@@ -644,7 +644,7 @@ dependencies = [
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sysinfo",
|
||||
"system_monitor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1093,6 +1093,13 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system_monitor"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"sysinfo",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.20.0"
|
||||
|
||||
@@ -8,5 +8,5 @@ dotenvy = "0.15.7"
|
||||
reqwest = { version = "0.12.22", features = ["blocking", "json"] }
|
||||
serde = "1.0.219"
|
||||
serde_json = "1.0.140"
|
||||
sysinfo = "0.35.2"
|
||||
discord_client = { path = "../discord_client" }
|
||||
system_monitor = { path = "../system_monitor" }
|
||||
|
||||
52
node_notifier/src/alert_formatter.rs
Normal file
52
node_notifier/src/alert_formatter.rs
Normal file
@@ -0,0 +1,52 @@
|
||||
use system_monitor::{Severity,ThresholdViolation};
|
||||
|
||||
pub struct AlertFormatter;
|
||||
|
||||
impl AlertFormatter {
|
||||
pub fn format_violation(&self, violation: &ThresholdViolation) -> String {
|
||||
let emoji = match violation.severity {
|
||||
Severity::Warning => "⚠️",
|
||||
Severity::Critical => "🚨",
|
||||
};
|
||||
|
||||
let level = match violation.severity {
|
||||
Severity::Warning => "ALERTE",
|
||||
Severity::Critical => "CRITIQUE",
|
||||
};
|
||||
|
||||
format!(
|
||||
"{} **{}** - {}\n\n**Valeur actuelle:** {:.2}%\n**Seuil:** {:.2}%",
|
||||
emoji,
|
||||
level,
|
||||
self.get_message_for_metric(&violation.metric_name, &violation.severity),
|
||||
violation.current_value,
|
||||
violation.threshold
|
||||
)
|
||||
}
|
||||
|
||||
fn get_message_for_metric(&self, metric: &str, severity: &Severity) -> String {
|
||||
match (metric, severity) {
|
||||
("CPU", Severity::Warning) => "Utilisation CPU élevée détectée".to_string(),
|
||||
("CPU", Severity::Critical) => "CPU en surchauffe - Intervention requise !".to_string(),
|
||||
("Memory", Severity::Warning) => "Utilisation mémoire élevée".to_string(),
|
||||
("Memory", Severity::Critical) => "Mémoire saturée - Risque de crash !".to_string(),
|
||||
("Swap", Severity::Warning) => "Utilisation swap élevée".to_string(),
|
||||
("Swap", Severity::Critical) => "Swap saturé - Performance dégradée !".to_string(),
|
||||
("Disk", Severity::Warning) => "Espace disque faible".to_string(),
|
||||
("Disk", Severity::Critical) => "Disque presque plein - Nettoyage urgent !".to_string(),
|
||||
(metric, _) => format!("Problème détecté sur {metric}"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn format_summary(&self, monitor: &system_monitor::SystemMonitor) -> String {
|
||||
let metrics = monitor.get_metrics();
|
||||
|
||||
format!(
|
||||
"📊 **Rapport Système**\n\n```\nCPU: {:.1}%\nMémoire: {:.1}%\nSwap: {:.1}%\nDisque: {:.1}%\n```\n\n*Surveillance automatique*",
|
||||
metrics.get("CPU").unwrap_or(&0.0),
|
||||
metrics.get("Memory").unwrap_or(&0.0),
|
||||
metrics.get("Swap").unwrap_or(&0.0),
|
||||
metrics.get("Disk").unwrap_or(&0.0)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,101 +1,8 @@
|
||||
use discord_client::DiscordNotifier;
|
||||
use dotenvy::dotenv;
|
||||
use sysinfo::{Disks, System};
|
||||
use system_monitor::SystemMonitor;
|
||||
|
||||
struct ResourceThreshold {
|
||||
name: String,
|
||||
get_usage_fn: fn(&System, &Disks) -> f32,
|
||||
threshold: f32,
|
||||
value: f32,
|
||||
messagef: String,
|
||||
}
|
||||
|
||||
impl ResourceThreshold {
|
||||
fn check(&mut self, sys: &System, disks: &Disks) -> Option<String> {
|
||||
self.value = (self.get_usage_fn)(sys, disks);
|
||||
if self.value > self.threshold {
|
||||
Some(
|
||||
format!(
|
||||
"{}: {:.2}% - {}",
|
||||
self.name, self.value, self.messagef
|
||||
)
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_cpu_usage(sys: &System, _: &Disks) -> f32 {
|
||||
sys.global_cpu_usage()
|
||||
}
|
||||
|
||||
fn get_memory_usage(sys: &System, _: &Disks) -> f32 {
|
||||
let total_memory = sys.total_memory() as f32;
|
||||
let used_memory = sys.used_memory() as f32;
|
||||
|
||||
used_memory / total_memory * 100.0
|
||||
}
|
||||
|
||||
fn get_swap_usage(sys: &System, _: &Disks) -> f32 {
|
||||
let total_swap = sys.total_swap() as f32;
|
||||
let used_swap = sys.used_swap() as f32;
|
||||
|
||||
used_swap / total_swap * 100.0
|
||||
}
|
||||
|
||||
fn get_disk_usage(_: &System, disks: &Disks) -> f32 {
|
||||
let mut total_used_space = 0.0;
|
||||
let mut total_space = 0.0;
|
||||
|
||||
for disk in disks {
|
||||
if let Some(mount_point) = disk.mount_point().to_str() {
|
||||
if mount_point == "/" || mount_point == "/home" {
|
||||
total_used_space += disk.total_space() as f32 - disk.available_space() as f32;
|
||||
total_space += disk.total_space() as f32;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if total_space == 0.0 {
|
||||
return 0.0; // Avoid division by zero
|
||||
}
|
||||
|
||||
(total_used_space / total_space) * 100.0
|
||||
}
|
||||
|
||||
fn get_resource_thresholds() -> Vec<ResourceThreshold> {
|
||||
vec![
|
||||
ResourceThreshold {
|
||||
name: "CPU".to_string(),
|
||||
get_usage_fn: get_cpu_usage,
|
||||
threshold: 80.0,
|
||||
value: 0.0,
|
||||
messagef: "High CPU usage detected".to_string(),
|
||||
},
|
||||
ResourceThreshold {
|
||||
name: "Memory".to_string(),
|
||||
get_usage_fn: get_memory_usage,
|
||||
threshold: 80.0,
|
||||
value: 0.0,
|
||||
messagef: "High memory usage detected".to_string(),
|
||||
},
|
||||
ResourceThreshold {
|
||||
name: "Swap".to_string(),
|
||||
get_usage_fn: get_swap_usage,
|
||||
threshold: 80.0,
|
||||
value: 0.0,
|
||||
messagef: "High swap usage detected".to_string(),
|
||||
},
|
||||
ResourceThreshold {
|
||||
name: "Disk".to_string(),
|
||||
get_usage_fn: get_disk_usage,
|
||||
threshold: 80.0,
|
||||
value: 0.0,
|
||||
messagef: "High disk usage detected".to_string(),
|
||||
},
|
||||
]
|
||||
}
|
||||
mod alert_formatter;
|
||||
|
||||
fn main() {
|
||||
dotenv().ok();
|
||||
@@ -106,29 +13,22 @@ fn main() {
|
||||
"https://cdn.shopify.com/s/files/1/0262/1423/6212/files/Lord_of_the_Rings_eye_of_Sauron_-_Ghtic.com_-_Blog.png?v=1579680018".to_string(),
|
||||
);
|
||||
|
||||
let mut sys = System::new_all();
|
||||
let mut disks: Disks = Disks::new_with_refreshed_list();
|
||||
sys.refresh_all();
|
||||
disks.refresh(true);
|
||||
let monitor =
|
||||
SystemMonitor::new(system_monitor::resource_threshold::get_default_resource_thresholds());
|
||||
let formatter = alert_formatter::AlertFormatter;
|
||||
|
||||
let mut thresholds = get_resource_thresholds();
|
||||
|
||||
for threshold in &mut thresholds {
|
||||
if let Some(message) = threshold.check(&sys, &disks) {
|
||||
notifier.send_notification(&message).expect("Failed to send notification");
|
||||
}
|
||||
// Check for threshold violations and send alerts
|
||||
let violations = monitor.check_thresholds();
|
||||
for violation in violations {
|
||||
let message = formatter.format_violation(&violation);
|
||||
notifier
|
||||
.send_notification(&message)
|
||||
.expect("Failed to send notification");
|
||||
}
|
||||
|
||||
// Send a final notification with all system information
|
||||
let mut final_message = String::new();
|
||||
for threshold in thresholds {
|
||||
final_message.push_str(&format!(
|
||||
"{} usage: {:.2}%\n",
|
||||
threshold.name, threshold.value
|
||||
));
|
||||
}
|
||||
|
||||
let summary = formatter.format_summary(&monitor);
|
||||
notifier
|
||||
.send_notification(&final_message)
|
||||
.send_notification(&summary)
|
||||
.expect("Failed to send final notification");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user