Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
use super::funejson::Value; ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
fn check_counts(target_basis_points: i64, total_events: i64, bad_events: 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);
}
}
// Non-negative operands only, so half-up needs no sign handling.
fn half_up(n: i128, d: i128) -> i128 {
(2 * n + d) / (2 * d)
}
/// The error budget of an SLO over one window: how many bad events it
/// allows, how many are left, and how much of it is spent. Exact (i128)
/// arithmetic throughout.
///
/// # Panics
/// Panics on a target outside 1..=9999, negative counts, or more bad events than events.
pub fn error_budget(target_basis_points: i64, total_events: i64, bad_events: i64) -> ErrorBudget {
check_counts(target_basis_points, total_events, bad_events);
let total = total_events as i128;
let bad = bad_events as i128;
// The exact budget is budget_times_10000 / 10000 events; keep it as a fraction.
let budget_times_10000 = total * (10000 - target_basis_points as i128);
let allowed_bad = (budget_times_10000 / 10000) as i64;
let consumed = if total == 0 { 0 } else { half_up(bad * 10000 * 10000, budget_times_10000) as i64 };
ErrorBudget {
allowed_bad,
remaining_bad: allowed_bad - bad_events,
consumed_basis_points: consumed,
remaining_basis_points: 10000 - consumed,
exhausted: bad * 10000 > budget_times_10000,
}
}
pub fn error_budget_to_value(b: &ErrorBudget) -> Value {
Value::obj(vec![
("allowedBad", Value::Int(b.allowed_bad)),
("remainingBad", Value::Int(b.remaining_bad)),
("consumedBasisPoints", Value::Int(b.consumed_basis_points)),
("remainingBasisPoints", Value::Int(b.remaining_basis_points)),
("exhausted", Value::Bool(b.exhausted)),
])
}
// 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 {
match v {
Value::Int(i) => *i,
Value::Float(f) => panic!("{} must be a whole number {} received {}", what, if what == "targetBasisPoints" { "from 1 to 9999 (10000 leaves no error budget)," } else { "of at least 0," }, f),
_ => panic!("{} must be a whole number", what),
}
}
pub fn fune_vector(args: &[Value]) -> Value {
error_budget_to_value(&error_budget(
whole(&args[0], "targetBasisPoints"),
whole(&args[1], "totalEvents"),
whole(&args[2], "badEvents"),
))
}