#![allow(clippy::unwrap_used, clippy::expect_used)]
use std::collections::BTreeMap;
use std::time::Duration;
use chronix::chronix_analytics::anomaly::DetectorType;
use chronix::chronix_analytics::{
AlertAction, AlertConfig, AlertEngine, StreamingAnomalyConfig, StreamingAnomalyEngine,
};
use chronix::prelude::*;
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("─── 1. Streaming Anomaly Engine ───");
let anomaly_engine = StreamingAnomalyEngine::new();
anomaly_engine.enable(
StreamingAnomalyConfig::new("cpu", DetectorType::ZScore, 2.5)
.with_field("usage")
.with_min_fit_points(20)
.with_max_buffer_size(5000),
)?;
println!(" Enabled Z-Score detector for cpu.usage (threshold=2.5)");
println!("\n─── 2. Alert Engine ───");
let alert_engine = AlertEngine::new();
alert_engine.register(
AlertConfig::new("cpu_critical", "cpu", 0.8)
.with_cooldown(Duration::from_secs(60))
.with_actions(vec![AlertAction::Log, AlertAction::Metric]),
);
alert_engine.register(
AlertConfig::new("cpu_warning", "cpu", 0.5)
.with_cooldown(Duration::from_secs(30))
.with_action(AlertAction::Log),
);
println!(" Registered {} alert rules", alert_engine.alert_count());
println!("\n─── 3. Processing writes ───");
let base_ts = 1_700_000_000_000_000_000_i64;
let tags: BTreeMap<String, String> = [("host".into(), "web-1".into())].into_iter().collect();
println!(" Phase 1: 40 normal points (20-30 range)...");
for i in 0..40 {
let value = 25.0 + (i as f64 * 0.5).sin() * 5.0;
let mut fields = BTreeMap::new();
fields.insert("usage".into(), FieldValue::from(value));
let scored =
anomaly_engine.process_write("cpu", &tags, &fields, base_ts + i * 1_000_000_000);
if let Some(anomaly) = scored
&& anomaly.score.is_anomaly
{
println!(
" ⚠️ ts={} value={:.1} score={:.3}",
anomaly.score.timestamp, anomaly.score.value, anomaly.score.score
);
}
}
println!(" Phase 2: 5 anomalous spikes (90-110 range)...");
let mut total_alerts = Vec::new();
for j in 0..5 {
let i = 40 + j;
let spike_value = 95.0 + j as f64 * 4.0;
let mut fields = BTreeMap::new();
fields.insert("usage".into(), FieldValue::from(spike_value));
let ts = base_ts + i * 1_000_000_000;
let scored = anomaly_engine.process_write("cpu", &tags, &fields, ts);
if let Some(ref anomaly) = scored {
let icon = if anomaly.score.is_anomaly {
"⚠️ "
} else {
" "
};
println!(
" {icon}ts={ts} value={spike_value:.1} score={:.3} anomaly={}",
anomaly.score.score, anomaly.score.is_anomaly
);
let fired = alert_engine.evaluate("cpu", &tags, anomaly.score.score, ts);
total_alerts.extend(fired);
}
}
println!(" Phase 3: 5 recovery points (25 range)...");
for k in 0..5 {
let i = 45 + k;
let mut fields = BTreeMap::new();
fields.insert("usage".into(), FieldValue::from(25.0));
anomaly_engine.process_write("cpu", &tags, &fields, base_ts + i * 1_000_000_000);
}
println!("\n─── 4. Fired alerts ───");
println!(" Total alerts fired: {}", total_alerts.len());
for alert in &total_alerts {
println!(
" 🔔 rule={} measurement={} score={:.3} threshold={:.2} ts={}",
alert.alert_id, alert.measurement, alert.score, alert.threshold, alert.timestamp
);
}
println!("\n─── 5. Alert history ───");
let history = alert_engine.history();
println!(" History entries: {}", history.len());
println!("\n─── 6. Engine management ───");
println!(
" cpu detector enabled: {}",
anomaly_engine.is_enabled("cpu")
);
anomaly_engine.disable("cpu");
println!(" After disable: {}", anomaly_engine.is_enabled("cpu"));
alert_engine.unregister("cpu_warning");
println!(
" Alert rules after unregister: {}",
alert_engine.alert_count()
);
println!("\n✅ Done");
Ok(())
}