#![allow(clippy::unwrap_used, clippy::expect_used)]
use std::sync::Arc;
use std::time::Duration;
use chronix::chronix_analytics::ContinuousForecastConfig;
use chronix::chronix_analytics::forecast::ModelType;
use chronix::prelude::*;
use chronix::{Chronix, Pipeline, PipelineConfig, fields, tags};
use chronix_security::audit::{AuditAction, AuditDecision, AuditEvent};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let dir = tempfile::tempdir()?;
let config = ChronixConfig::builder().data_dir(dir.path()).build()?;
let db = Arc::new(Chronix::open(config)?);
let pipeline_config = PipelineConfig {
signal_store_capacity: 10_000,
enable_log_delivery: true,
enable_metric_delivery: true,
audit_memory_capacity: 1_000,
forecast_horizon: 5,
..PipelineConfig::default()
};
let pipeline = Arc::new(Pipeline::with_config(pipeline_config));
println!("✅ Pipeline created");
println!("\n─── 2. Creating triggers via Signal SQL ───");
let create_result = pipeline
.execute_signal_sql("CREATE TRIGGER high_cpu ON cpu_usage WHEN value > 90.0 FOR 1m");
println!(" CREATE TRIGGER result: {create_result:?}");
let show_result = pipeline.execute_signal_sql("SHOW TRIGGERS");
println!(" SHOW TRIGGERS result: {show_result:?}");
pipeline.forecast_engine().enable(
ContinuousForecastConfig::new("cpu_usage", ModelType::Ses)
.with_field("value")
.with_min_fit_points(20)
.with_update_interval(20),
)?;
println!(" Continuous forecast enabled for cpu_usage (SES, refit every 20 points)");
let listener_handle = pipeline.spawn_cdc_listener(db.event_bus());
println!("\n✅ CDC listener spawned");
let base_ts = 1_700_000_000_000_000_000_i64;
println!("\n─── 4. Writing normal + anomalous data ───");
let key = SeriesKey::new("cpu_usage", tags! { "host" => "prod-1", "dc" => "us-east" })?;
for i in 0..50 {
let value = 45.0 + (i as f64 * 0.1).sin() * 10.0; let point = Point::new(
key.clone(),
fields! { "value" => value },
base_ts + i * 1_000_000_000,
)?;
db.insert(&point)?;
}
println!(" Wrote 50 normal points (35-55 range)");
for i in 50..55 {
let point = Point::new(
key.clone(),
fields! { "value" => 98.0 + (i - 50) as f64 }, base_ts + i * 1_000_000_000,
)?;
db.insert(&point)?;
}
println!(" Wrote 5 anomalous points (98-103 range)");
tokio::time::sleep(Duration::from_millis(200)).await;
println!("\n─── 5. Pipeline state ───");
println!(" Signals in store: {}", pipeline.signal_store().len());
println!(
" Trigger engine rules: {}",
pipeline.trigger_engine().trigger_count()
);
let forecast_cache = pipeline.forecast_cache();
println!(" Forecasts cached: {}", forecast_cache.len());
println!("\n─── 6. Audit trail ───");
for signal in pipeline.signal_store().all() {
pipeline.audit(
AuditEvent::new(
"on-call-router",
AuditAction::Custom("paged".into()),
format!("cpu_usage/{}", signal.trigger_name),
AuditDecision::Allow,
)
.with_metadata("value", format!("{:.1}", signal.value)),
);
}
if let Some(audit_sink) = pipeline.audit_sink() {
let events = audit_sink.events();
println!(" Audit events: {}", events.len());
for event in events.iter().take(3) {
println!(" - {:?}", event);
}
}
listener_handle.abort();
db.close()?;
println!("\n✅ Done");
Ok(())
}