refactor: move business logic into app

* also move all at directory root crate

* all actors are into lib directory

* formatter is into system_monitor now
This commit is contained in:
2025-08-09 13:52:38 +02:00
parent 3bb8f39e34
commit 985ee54669
15 changed files with 67 additions and 68 deletions

View File

@@ -0,0 +1,54 @@
use crate::{Severity, SystemMonitor, 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: &SystemMonitor) -> String {
let metrics = monitor.get_metrics();
let mut result = "📊 **Rapport Système**\n\n```\n".to_string();
for (key, value) in &metrics {
result.push_str(&format!("{}: {:.1}%\n", key, value));
}
result.push_str("```\n\n*Surveillance automatique*");
result
}
}

View File

@@ -0,0 +1,40 @@
use sysinfo::{Disks, System};
pub fn get_cpu_usage(sys: &System, _: &Disks) -> f32 {
sys.global_cpu_usage()
}
pub 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
}
pub 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
}
pub 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
}

View File

@@ -0,0 +1,195 @@
use crate::resource_threshold::ResourceThreshold;
use std::collections::HashMap;
use sysinfo::{Disks, System};
mod get_info;
pub mod resource_threshold;
pub mod alert_formatter;
#[derive(Debug, Clone)]
pub struct ThresholdViolation {
pub metric_name: String,
pub current_value: f32,
pub threshold: f32,
pub severity: Severity,
}
#[derive(Debug, Clone, PartialEq, Copy)]
pub enum Severity {
Warning,
Critical,
}
pub struct SystemMonitor {
pub system: System,
pub disks: Disks,
pub thresholds: Vec<ResourceThreshold>,
}
impl SystemMonitor {
pub fn new(thresholds: Vec<ResourceThreshold>) -> Self {
let mut system_monitor = SystemMonitor {
system: System::new_all(),
disks: Disks::new_with_refreshed_list(),
thresholds,
};
system_monitor.refresh();
system_monitor
}
pub fn refresh(&mut self) {
self.system.refresh_all();
self.disks.refresh(true);
}
pub fn check_thresholds(&self) -> Vec<ThresholdViolation> {
let mut violations = Vec::new();
for threshold in &self.thresholds {
let current_value = threshold.get_value(&self.system, &self.disks);
// Check critical threshold first (highest priority)
if let Some(critical_threshold) = threshold.critical_threshold {
if current_value > critical_threshold {
violations.push(ThresholdViolation {
metric_name: threshold.name.clone(),
current_value,
threshold: critical_threshold,
severity: Severity::Critical,
});
continue; // Skip warning check if critical
}
}
// Check warning threshold
if let Some(warning_threshold) = threshold.warning_threshold {
if current_value > warning_threshold {
violations.push(ThresholdViolation {
metric_name: threshold.name.clone(),
current_value,
threshold: warning_threshold,
severity: Severity::Warning,
});
}
}
}
violations
}
/// Get current system metrics based on configured thresholds
pub fn get_metrics(&self) -> HashMap<String, f32> {
let mut metrics = HashMap::new();
for threshold in &self.thresholds {
let value = threshold.get_value(&self.system, &self.disks);
metrics.insert(threshold.name.clone(), value);
}
metrics
}
}
impl Default for SystemMonitor {
fn default() -> Self {
Self::new(resource_threshold::get_default_resource_thresholds())
}
}
#[cfg(test)]
mod tests {
use super::*;
struct TestCase {
name: &'static str,
value: f32,
warning_threshold: Option<f32>,
critical_threshold: Option<f32>,
expected_violation: Option<Severity>,
}
fn create_test_case() -> Vec<TestCase> {
vec![
TestCase {
name: "Everything is fine",
value: 1.0,
warning_threshold: Some(2.0),
critical_threshold: Some(3.0),
expected_violation: None,
},
TestCase {
name: "Warning threshold triggered",
value: 2.5,
warning_threshold: Some(2.0),
critical_threshold: Some(3.0),
expected_violation: Some(Severity::Warning),
},
TestCase {
name: "Critical threshold triggered",
value: 3.5,
warning_threshold: Some(1.0),
critical_threshold: Some(2.0),
expected_violation: Some(Severity::Critical),
},
TestCase {
name: "Warning threshold triggered with critical empty",
value: 3.5,
warning_threshold: Some(3.0),
critical_threshold: None,
expected_violation: Some(Severity::Warning),
},
TestCase {
name: "Critical threshold triggered with warning empty",
value: 3.5,
warning_threshold: None,
critical_threshold: Some(3.0),
expected_violation: Some(Severity::Critical),
},
]
}
#[test]
fn test_check_thresholds() {
// Iterate through each test case
for test_case in create_test_case() {
let mut monitor = SystemMonitor::default();
let mut threshold = ResourceThreshold::new(
test_case.name.to_string(),
move |_, _| test_case.value
);
// Apply thresholds conditionally
if let Some(warning) = test_case.warning_threshold {
threshold = threshold.with_warning_threshold(warning);
}
if let Some(critical) = test_case.critical_threshold {
threshold = threshold.with_critical_threshold(critical);
}
monitor.thresholds = vec![threshold];
let violations = monitor.check_thresholds();
if let Some(expected_severity) = test_case.expected_violation {
assert_eq!(
violations.len(),
1,
"Expected one violation for test case: {}",
test_case.name
);
assert_eq!(
violations[0].severity, expected_severity,
"Unexpected severity for test case: {}",
test_case.name
);
} else {
assert!(
violations.is_empty(),
"Expected no violations for test case: {}",
test_case.name
);
}
}
}
}

View File

@@ -0,0 +1,62 @@
use crate::get_info;
use sysinfo::{Disks, System};
pub struct ResourceThreshold {
pub name: String,
pub get_value_fn: Box<dyn Fn(&System, &Disks) -> f32>,
pub warning_threshold: Option<f32>,
pub critical_threshold: Option<f32>,
}
impl ResourceThreshold {
pub fn new<F>(name: String, get_value_fn: F) -> Self
where
F: Fn(&System, &Disks) -> f32 + 'static,
{
Self {
name,
get_value_fn: Box::new(get_value_fn),
warning_threshold: None,
critical_threshold: None,
}
}
pub fn with_warning_threshold(mut self, warning_threshold: f32) -> Self {
self.warning_threshold = Some(warning_threshold);
self
}
pub fn with_critical_threshold(mut self, critical_threshold: f32) -> Self {
self.critical_threshold = Some(critical_threshold);
self
}
pub fn get_value(&self, sys: &System, disks: &Disks) -> f32 {
(self.get_value_fn)(sys, disks)
}
}
pub fn get_default_resource_thresholds() -> Vec<ResourceThreshold> {
vec![
ResourceThreshold::new("CPU Usage".to_string(), |sys, disks| {
get_info::get_cpu_usage(sys, disks)
})
.with_warning_threshold(80.0)
.with_critical_threshold(95.0),
ResourceThreshold::new("Memory Usage".to_string(), |sys, disks| {
get_info::get_memory_usage(sys, disks)
})
.with_warning_threshold(85.0)
.with_critical_threshold(95.0),
ResourceThreshold::new("Swap Usage".to_string(), |sys, disks| {
get_info::get_swap_usage(sys, disks)
})
.with_warning_threshold(1.0)
.with_critical_threshold(25.0),
ResourceThreshold::new("Disk Usage".to_string(), |sys, disks| {
get_info::get_disk_usage(sys, disks)
})
.with_warning_threshold(80.0)
.with_critical_threshold(90.0),
]
}