use super::funejson::Value; /// How fast an SLO's error budget is burning, in thousandths: the window's /// error rate divided by the error rate the SLO allows. 1000 spends exactly /// the budget over the SLO period; 14400 (14.4x) spends 2% of a 30-day /// budget in one hour. Exact i128 arithmetic. /// /// # Panics /// Panics on a target outside 1..=9999, negative counts, or more bad events than events. pub fn burn_rate(target_basis_points: i64, total_events: i64, bad_events: i64) -> i64 { if !(1..=9999).contains(&target_basis_points) { panic!( "targetBasisPoints must be a whole number from 1 to 9999 (10000 leaves no error budget), received {}", target_basis_points ); } if total_events < 0 { panic!("totalEvents must be a whole number of at least 0, received {}", total_events); } if bad_events < 0 { panic!("badEvents must be a whole number of at least 0, received {}", bad_events); } if bad_events > total_events { panic!("badEvents must not exceed totalEvents: {} > {}", bad_events, total_events); } // An empty window has no error rate; 0 keeps an alert quiet rather than failing it. if total_events == 0 { return 0; } let n = bad_events as i128 * 10000 * 1000; let d = total_events as i128 * (10000 - target_basis_points) as i128; ((2 * n + d) / (2 * d)) as i64 } // JSON cannot tell 2 from 2.0, and Python refuses a float, so any non-Int is refused here too. fn whole(v: &Value, what: &str) -> i64 { let rule = if what == "targetBasisPoints" { "from 1 to 9999 (10000 leaves no error budget)," } else { "of at least 0," }; match v { Value::Int(i) => *i, Value::Float(f) => panic!("{} must be a whole number {} received {}", what, rule, f), _ => panic!("{} must be a whole number {}", what, rule), } } pub fn fune_vector(args: &[Value]) -> Value { Value::Int(burn_rate( whole(&args[0], "targetBasisPoints"), whole(&args[1], "totalEvents"), whole(&args[2], "badEvents"), )) }