Functional Weave
Code in Python

monitor.error-budget@1.0.0

impl/rust.rs

2,988 bytes · the Rust implementation · view raw

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"),
    ))
}