pub mod budgets;
pub mod objectives;
pub mod reports;
use crate::error::Result;
use chrono::{DateTime, Duration, Utc};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Slo {
pub name: String,
pub description: String,
pub target: f64,
pub sli_query: String,
pub time_window: TimeWindow,
pub error_budget: ErrorBudget,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum TimeWindow {
Rolling(Duration),
Calendar(CalendarWindow),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum CalendarWindow {
Daily,
Weekly,
Monthly,
Quarterly,
Yearly,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ErrorBudget {
pub total: f64,
pub consumed: f64,
pub remaining: f64,
pub burn_rate: f64,
}
impl ErrorBudget {
pub fn from_target(target: f64) -> Self {
let total = 100.0 - target;
Self {
total,
consumed: 0.0,
remaining: total,
burn_rate: 0.0,
}
}
pub fn update(&mut self, consumed: f64) {
self.consumed = consumed;
self.remaining = self.total - consumed;
}
pub fn calculate_burn_rate(&mut self, window_hours: f64) {
if window_hours > 0.0 {
self.burn_rate = self.consumed / window_hours;
}
}
pub fn is_exhausted(&self) -> bool {
self.remaining <= 0.0
}
pub fn exhaustion_percentage(&self) -> f64 {
if self.total > 0.0 {
(self.consumed / self.total) * 100.0
} else {
0.0
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SloStatus {
pub name: String,
pub achievement: f64,
pub target: f64,
pub is_met: bool,
pub error_budget: ErrorBudget,
pub evaluated_at: DateTime<Utc>,
}
impl SloStatus {
pub fn new(name: String, achievement: f64, target: f64, error_budget: ErrorBudget) -> Self {
Self {
name,
achievement,
target,
is_met: achievement >= target,
error_budget,
evaluated_at: Utc::now(),
}
}
}
pub struct SloMonitor {
slos: Vec<Slo>,
}
impl SloMonitor {
pub fn new() -> Self {
Self { slos: Vec::new() }
}
pub fn add_slo(&mut self, slo: Slo) {
self.slos.push(slo);
}
pub fn slos(&self) -> &[Slo] {
&self.slos
}
pub fn evaluate_all(&self) -> Result<Vec<SloStatus>> {
Ok(Vec::new())
}
}
impl Default for SloMonitor {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_budget() {
let mut budget = ErrorBudget::from_target(99.9);
assert!((budget.total - 0.1).abs() < 1e-10);
assert!((budget.remaining - 0.1).abs() < 1e-10);
budget.update(0.05);
assert_eq!(budget.consumed, 0.05);
assert!((budget.remaining - 0.05).abs() < 1e-10);
assert!(!budget.is_exhausted());
budget.update(0.15);
assert!(budget.is_exhausted());
}
#[test]
fn test_slo_status() {
let budget = ErrorBudget::from_target(99.9);
let status = SloStatus::new("test_slo".to_string(), 99.95, 99.9, budget);
assert!(status.is_met);
assert_eq!(status.achievement, 99.95);
}
}