#![allow(dead_code)]
use super::{
config::{AlertSeverity, ComparisonType},
event_system::ScirS2Event,
};
use std::collections::HashMap;
use std::time::{Duration, Instant};
#[derive(Debug)]
pub struct ScirS2MonitoringSystem {
pub real_time_metrics: HashMap<String, f64>,
pub historical_data: Vec<MonitoringDataPoint>,
pub alert_conditions: Vec<AlertCondition>,
pub active_alerts: Vec<ActiveAlert>,
config: MonitoringConfig,
retention: DataRetentionSettings,
aggregators: HashMap<String, MetricAggregator>,
}
#[derive(Debug, Clone)]
pub struct MonitoringConfig {
pub enabled: bool,
pub collection_interval: Duration,
pub alert_check_interval: Duration,
pub max_historical_points: usize,
pub enable_aggregation: bool,
pub alert_throttle_duration: Duration,
}
#[derive(Debug, Clone)]
pub struct DataRetentionSettings {
pub raw_data_retention: Duration,
pub aggregated_data_retention: Duration,
pub compression_threshold: usize,
pub auto_cleanup: bool,
}
#[derive(Debug, Clone)]
pub struct MonitoringDataPoint {
pub timestamp: Instant,
pub metric_name: String,
pub value: f64,
pub context: HashMap<String, String>,
pub source: String,
}
#[derive(Debug, Clone)]
pub struct AlertCondition {
pub id: String,
pub metric_name: String,
pub threshold: f64,
pub comparison: ComparisonType,
pub severity: AlertSeverity,
pub description: String,
pub enabled: bool,
pub cooldown: Duration,
pub last_triggered: Option<Instant>,
}
#[derive(Debug, Clone)]
pub struct ActiveAlert {
pub condition: AlertCondition,
pub triggered_at: Instant,
pub current_value: f64,
pub acknowledged: bool,
pub acknowledged_at: Option<Instant>,
pub count: u32,
}
#[derive(Debug, Clone)]
pub struct MetricAggregator {
pub name: String,
pub values: Vec<f64>,
pub window_size: usize,
pub stats: AggregationStats,
pub last_update: Instant,
}
#[derive(Debug, Clone)]
pub struct AggregationStats {
pub min: f64,
pub max: f64,
pub avg: f64,
pub std_dev: f64,
pub p95: f64,
pub p99: f64,
pub count: usize,
}
#[derive(Debug, Clone)]
pub struct DashboardData {
pub current_metrics: HashMap<String, f64>,
pub alert_summary: AlertSummary,
pub top_metrics: Vec<TopMetric>,
pub health_score: f64,
pub trends: HashMap<String, TrendAnalysis>,
}
#[derive(Debug, Clone)]
pub struct AlertSummary {
pub total_active: usize,
pub by_severity: HashMap<String, usize>,
pub recent_activity: Vec<RecentAlertActivity>,
}
#[derive(Debug, Clone)]
pub struct TopMetric {
pub name: String,
pub value: f64,
pub change: f64,
pub activity: f64,
}
#[derive(Debug, Clone)]
pub struct RecentAlertActivity {
pub condition_id: String,
pub activity_type: AlertActivityType,
pub timestamp: Instant,
pub value: f64,
}
#[derive(Debug, Clone)]
pub enum AlertActivityType {
Triggered,
Resolved,
Acknowledged,
Escalated,
}
#[derive(Debug, Clone)]
pub struct TrendAnalysis {
pub direction: TrendDirection,
pub strength: f64,
pub confidence: f64,
pub predicted_value: Option<f64>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum TrendDirection {
Increasing,
Decreasing,
Stable,
Volatile,
}
impl ScirS2MonitoringSystem {
pub fn new() -> Self {
Self {
real_time_metrics: HashMap::new(),
historical_data: Vec::new(),
alert_conditions: Vec::new(),
active_alerts: Vec::new(),
config: MonitoringConfig::default(),
retention: DataRetentionSettings::default(),
aggregators: HashMap::new(),
}
}
pub fn update_metric(&mut self, name: String, value: f64) {
self.update_metric_with_context(name, value, HashMap::new());
}
pub fn update_metric_with_context(
&mut self,
name: String,
value: f64,
context: HashMap<String, String>,
) {
let now = Instant::now();
self.real_time_metrics.insert(name.clone(), value);
let data_point = MonitoringDataPoint {
timestamp: now,
metric_name: name.clone(),
value,
context,
source: "scirs2_integration".to_string(),
};
self.historical_data.push(data_point);
if self.historical_data.len() > self.config.max_historical_points {
self.historical_data.remove(0);
}
if self.config.enable_aggregation {
self.update_aggregator(&name, value, now);
}
}
pub fn add_alert_condition(&mut self, condition: AlertCondition) {
self.alert_conditions.push(condition);
}
pub fn remove_alert_condition(&mut self, condition_id: &str) {
self.alert_conditions.retain(|c| c.id != condition_id);
self.active_alerts
.retain(|a| a.condition.id != condition_id);
}
pub fn check_alerts(&mut self) {
let now = Instant::now();
let mut alerts_to_trigger = Vec::new();
let mut conditions_to_update = Vec::new();
for (index, condition) in self.alert_conditions.iter().enumerate() {
if !condition.enabled {
continue;
}
if let Some(last_triggered) = condition.last_triggered {
if now.duration_since(last_triggered) < condition.cooldown {
continue;
}
}
if let Some(¤t_value) = self.real_time_metrics.get(&condition.metric_name) {
let alert_triggered = match condition.comparison {
ComparisonType::GreaterThan => current_value > condition.threshold,
ComparisonType::LessThan => current_value < condition.threshold,
ComparisonType::Equal => {
(current_value - condition.threshold).abs() < f64::EPSILON
}
ComparisonType::NotEqual => {
(current_value - condition.threshold).abs() > f64::EPSILON
}
};
if alert_triggered {
alerts_to_trigger.push((condition.clone(), current_value));
conditions_to_update.push(index);
}
}
}
for (condition, current_value) in alerts_to_trigger {
self.trigger_alert(condition, current_value, now);
}
for index in conditions_to_update {
self.alert_conditions[index].last_triggered = Some(now);
}
self.check_alert_resolution();
}
pub fn process_event(&mut self, event: &ScirS2Event) {
let _now = Instant::now();
match event {
ScirS2Event::Allocation {
size, allocator, ..
} => {
self.update_metric(format!("{}_allocations", allocator), 1.0);
self.update_metric(format!("{}_allocated_bytes", allocator), *size as f64);
}
ScirS2Event::MemoryPressure {
level,
available_memory,
..
} => {
let pressure_value = match level {
crate::memory_profiler::allocation::PressureLevel::None => 0.0,
crate::memory_profiler::allocation::PressureLevel::Low => 0.25,
crate::memory_profiler::allocation::PressureLevel::Medium => 0.5,
crate::memory_profiler::allocation::PressureLevel::High => 0.75,
crate::memory_profiler::allocation::PressureLevel::Critical => 1.0,
};
self.update_metric("memory_pressure".to_string(), pressure_value);
self.update_metric("available_memory".to_string(), *available_memory as f64);
}
ScirS2Event::PoolUtilizationChange {
pool_id,
new_utilization,
..
} => {
self.update_metric(format!("{}_utilization", pool_id), *new_utilization);
}
ScirS2Event::PerformanceDegradation {
allocator,
degradation_amount,
..
} => {
self.update_metric(
format!("{}_performance_degradation", allocator),
*degradation_amount,
);
}
_ => {}
}
}
pub fn get_dashboard_data(&self) -> DashboardData {
DashboardData {
current_metrics: self.real_time_metrics.clone(),
alert_summary: self.get_alert_summary(),
top_metrics: self.get_top_metrics(),
health_score: self.calculate_health_score(),
trends: self.analyze_trends(),
}
}
pub fn get_metric_history(
&self,
metric_name: &str,
duration: Duration,
) -> Vec<MonitoringDataPoint> {
let cutoff = Instant::now() - duration;
self.historical_data
.iter()
.filter(|point| point.metric_name == metric_name && point.timestamp > cutoff)
.cloned()
.collect()
}
pub fn acknowledge_alert(&mut self, condition_id: &str) {
let now = Instant::now();
for alert in &mut self.active_alerts {
if alert.condition.id == condition_id {
alert.acknowledged = true;
alert.acknowledged_at = Some(now);
}
}
}
pub fn clear_acknowledged_alerts(&mut self) {
self.active_alerts.retain(|alert| !alert.acknowledged);
}
pub fn get_metric_stats(&self, metric_name: &str) -> Option<&AggregationStats> {
self.aggregators.get(metric_name).map(|agg| &agg.stats)
}
pub fn update_config(&mut self, config: MonitoringConfig) {
self.config = config;
}
pub fn cleanup_old_data(&mut self) {
if !self.retention.auto_cleanup {
return;
}
let now = Instant::now();
let cutoff = now - self.retention.raw_data_retention;
self.historical_data
.retain(|point| point.timestamp > cutoff);
self.active_alerts.retain(|alert| {
if alert.acknowledged {
if let Some(ack_time) = alert.acknowledged_at {
now.duration_since(ack_time) < Duration::from_secs(7 * 24 * 60 * 60)
} else {
true
}
} else {
true
}
});
}
fn update_aggregator(&mut self, name: &str, value: f64, timestamp: Instant) {
let aggregator =
self.aggregators
.entry(name.to_string())
.or_insert_with(|| MetricAggregator {
name: name.to_string(),
values: Vec::new(),
window_size: 1000,
stats: AggregationStats::default(),
last_update: timestamp,
});
aggregator.values.push(value);
aggregator.last_update = timestamp;
if aggregator.values.len() > aggregator.window_size {
aggregator.values.remove(0);
}
aggregator.update_stats();
}
fn trigger_alert(&mut self, condition: AlertCondition, value: f64, timestamp: Instant) {
if let Some(existing_alert) = self
.active_alerts
.iter_mut()
.find(|a| a.condition.id == condition.id)
{
existing_alert.count += 1;
existing_alert.current_value = value;
existing_alert.triggered_at = timestamp;
} else {
let alert = ActiveAlert {
condition,
triggered_at: timestamp,
current_value: value,
acknowledged: false,
acknowledged_at: None,
count: 1,
};
self.active_alerts.push(alert);
}
}
fn check_alert_resolution(&mut self) {
let mut resolved_alerts = Vec::new();
for (index, alert) in self.active_alerts.iter().enumerate() {
if let Some(¤t_value) = self.real_time_metrics.get(&alert.condition.metric_name) {
let alert_resolved = match alert.condition.comparison {
ComparisonType::GreaterThan => current_value <= alert.condition.threshold,
ComparisonType::LessThan => current_value >= alert.condition.threshold,
ComparisonType::Equal => {
(current_value - alert.condition.threshold).abs() > f64::EPSILON
}
ComparisonType::NotEqual => {
(current_value - alert.condition.threshold).abs() < f64::EPSILON
}
};
if alert_resolved {
resolved_alerts.push(index);
}
}
}
for index in resolved_alerts.into_iter().rev() {
self.active_alerts.remove(index);
}
}
fn get_alert_summary(&self) -> AlertSummary {
let mut by_severity = HashMap::new();
for alert in &self.active_alerts {
let severity_str = format!("{:?}", alert.condition.severity);
*by_severity.entry(severity_str).or_insert(0) += 1;
}
let recent_activity = self
.active_alerts
.iter()
.take(10)
.map(|alert| RecentAlertActivity {
condition_id: alert.condition.id.clone(),
activity_type: AlertActivityType::Triggered,
timestamp: alert.triggered_at,
value: alert.current_value,
})
.collect();
AlertSummary {
total_active: self.active_alerts.len(),
by_severity,
recent_activity,
}
}
fn get_top_metrics(&self) -> Vec<TopMetric> {
let mut metrics: Vec<_> = self
.real_time_metrics
.iter()
.map(|(name, &value)| {
let change = self.calculate_metric_change(name);
let activity = self.calculate_metric_activity(name);
TopMetric {
name: name.clone(),
value,
change,
activity,
}
})
.collect();
metrics.sort_by(|a, b| {
b.activity
.partial_cmp(&a.activity)
.unwrap_or(std::cmp::Ordering::Equal)
});
metrics.truncate(10);
metrics
}
fn calculate_health_score(&self) -> f64 {
let mut score = 1.0;
let alert_penalty = self.active_alerts.len() as f64 * 0.1;
score -= alert_penalty;
let negative_trends = self
.analyze_trends()
.values()
.filter(|trend| trend.direction == TrendDirection::Decreasing)
.count() as f64;
score -= negative_trends * 0.05;
score.max(0.0)
}
fn analyze_trends(&self) -> HashMap<String, TrendAnalysis> {
let mut trends = HashMap::new();
for (metric_name, _) in &self.real_time_metrics {
if let Some(trend) = self.analyze_metric_trend(metric_name) {
trends.insert(metric_name.clone(), trend);
}
}
trends
}
fn analyze_metric_trend(&self, metric_name: &str) -> Option<TrendAnalysis> {
let recent_points: Vec<_> = self
.historical_data
.iter()
.filter(|point| point.metric_name == metric_name)
.rev()
.take(20)
.collect();
if recent_points.len() < 5 {
return None;
}
let values: Vec<f64> = recent_points.iter().map(|p| p.value).collect();
let (slope, confidence) = self.calculate_linear_trend(&values);
let direction = if slope > 0.1 {
TrendDirection::Increasing
} else if slope < -0.1 {
TrendDirection::Decreasing
} else {
TrendDirection::Stable
};
Some(TrendAnalysis {
direction,
strength: slope.abs(),
confidence,
predicted_value: None, })
}
fn calculate_linear_trend(&self, values: &[f64]) -> (f64, f64) {
let n = values.len() as f64;
let x_sum: f64 = (0..values.len()).map(|i| i as f64).sum();
let y_sum: f64 = values.iter().sum();
let xy_sum: f64 = values.iter().enumerate().map(|(i, &y)| i as f64 * y).sum();
let x2_sum: f64 = (0..values.len()).map(|i| (i as f64).powi(2)).sum();
let slope = (n * xy_sum - x_sum * y_sum) / (n * x2_sum - x_sum.powi(2));
let confidence = 0.8;
(slope, confidence)
}
fn calculate_metric_change(&self, metric_name: &str) -> f64 {
let recent_points: Vec<_> = self
.historical_data
.iter()
.filter(|point| point.metric_name == metric_name)
.rev()
.take(2)
.collect();
if recent_points.len() == 2 {
recent_points[0].value - recent_points[1].value
} else {
0.0
}
}
fn calculate_metric_activity(&self, metric_name: &str) -> f64 {
let recent_points: Vec<_> = self
.historical_data
.iter()
.filter(|point| point.metric_name == metric_name)
.rev()
.take(10)
.map(|p| p.value)
.collect();
if recent_points.len() < 2 {
return 0.0;
}
let mean = recent_points.iter().sum::<f64>() / recent_points.len() as f64;
let variance = recent_points
.iter()
.map(|&x| (x - mean).powi(2))
.sum::<f64>()
/ recent_points.len() as f64;
variance.sqrt().min(1.0)
}
}
impl Default for MonitoringConfig {
fn default() -> Self {
Self {
enabled: true,
collection_interval: Duration::from_secs(5),
alert_check_interval: Duration::from_secs(10),
max_historical_points: 10000,
enable_aggregation: true,
alert_throttle_duration: Duration::from_secs(60),
}
}
}
impl Default for DataRetentionSettings {
fn default() -> Self {
Self {
raw_data_retention: Duration::from_secs(86400), aggregated_data_retention: Duration::from_secs(86400 * 30), compression_threshold: 1000,
auto_cleanup: true,
}
}
}
impl Default for AggregationStats {
fn default() -> Self {
Self {
min: f64::INFINITY,
max: f64::NEG_INFINITY,
avg: 0.0,
std_dev: 0.0,
p95: 0.0,
p99: 0.0,
count: 0,
}
}
}
impl MetricAggregator {
fn update_stats(&mut self) {
if self.values.is_empty() {
return;
}
let mut sorted_values = self.values.clone();
sorted_values.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
self.stats.count = self.values.len();
self.stats.min = sorted_values[0];
self.stats.max = sorted_values[sorted_values.len() - 1];
self.stats.avg = self.values.iter().sum::<f64>() / self.values.len() as f64;
let variance = self
.values
.iter()
.map(|&x| (x - self.stats.avg).powi(2))
.sum::<f64>()
/ self.values.len() as f64;
self.stats.std_dev = variance.sqrt();
if self.stats.count >= 20 {
let p95_index = (self.stats.count as f64 * 0.95) as usize;
let p99_index = (self.stats.count as f64 * 0.99) as usize;
self.stats.p95 = sorted_values[p95_index.min(sorted_values.len() - 1)];
self.stats.p99 = sorted_values[p99_index.min(sorted_values.len() - 1)];
}
}
}
trait DurationExt {
fn from_days(days: u64) -> Duration;
}
impl DurationExt for Duration {
fn from_days(days: u64) -> Duration {
Duration::from_secs(days * 24 * 3600)
}
}