Functional Weave
Code in Python

monitor.uptime@1.0.0

impl/rust.rs

3,507 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
use super::monitor_check_status::{checks_from_value, Check};  ← from monitor.check-status ^1.0.0 · built alongside by fune

/// Seconds up, degraded, down and unknown over [from, to). Each check's status
/// holds from its `at` until the next check, but for at most max_gap_seconds:
/// a monitor that stopped reporting has not proved the target was up.
///
/// # Panics
/// Panics when from is after to, max_gap_seconds is below 1, the checks are
/// not strictly ascending, or a status is unknown.
pub fn uptime(checks: &[Check], from: i64, to: i64, max_gap_seconds: i64) -> UptimeReport {
    if from > to {
        panic!("from must not be after to: {} > {}", from, to);
    }
    if max_gap_seconds < 1 {
        panic!("maxGapSeconds must be a whole number of at least 1, received {}", max_gap_seconds);
    }
    let (mut up, mut degraded, mut down) = (0i64, 0i64, 0i64);
    let (mut count, mut down_count) = (0i64, 0i64);
    for (i, c) in checks.iter().enumerate() {
        if c.status != "up" && c.status != "degraded" && c.status != "down" {
            panic!("unknown check status: {}", c.status);
        }
        if i > 0 && c.at <= checks[i - 1].at {
            panic!("checks must be in strictly ascending time order: {} follows {}", c.at, checks[i - 1].at);
        }
        if c.at >= from && c.at < to {
            count += 1;
            if c.status == "down" {
                down_count += 1;
            }
        }
        // The span this check vouches for, clipped to the window.
        let mut end = c.at.saturating_add(max_gap_seconds);
        if i + 1 < checks.len() && checks[i + 1].at < end {
            end = checks[i + 1].at;
        }
        let s = c.at.max(from);
        let e = end.min(to);
        if e > s {
            match c.status.as_str() {
                "up" => up += e - s,
                "degraded" => degraded += e - s,
                _ => down += e - s,
            }
        }
    }
    let known = up + degraded + down;
    // Floored in i128: never 100.00% while a down second exists.
    let uptime_basis_points = if known == 0 {
        None
    } else {
        Some(((up + degraded) as i128 * 10000 / known as i128) as i64)
    };
    UptimeReport {
        up_seconds: up,
        degraded_seconds: degraded,
        down_seconds: down,
        unknown_seconds: to - from - known,
        uptime_basis_points,
        checks: count,
        down_checks: down_count,
    }
}

pub fn uptime_report_to_value(r: &UptimeReport) -> Value {
    Value::obj(vec![
        ("upSeconds", Value::Int(r.up_seconds)),
        ("degradedSeconds", Value::Int(r.degraded_seconds)),
        ("downSeconds", Value::Int(r.down_seconds)),
        ("unknownSeconds", Value::Int(r.unknown_seconds)),
        ("uptimeBasisPoints", r.uptime_basis_points.map(Value::Int).unwrap_or(Value::Null)),
        ("checks", Value::Int(r.checks)),
        ("downChecks", Value::Int(r.down_checks)),
    ])
}

fn whole(v: &Value, msg: &str) -> i64 {
    match v {
        Value::Int(i) => *i,
        other => panic!("{}{}", msg, other),
    }
}

pub fn fune_vector(args: &[Value]) -> Value {
    let checks = checks_from_value(&args[0]);
    let from = match &args[1] { Value::Int(i) => *i, _ => panic!("from and to must be whole seconds") };
    let to = match &args[2] { Value::Int(i) => *i, _ => panic!("from and to must be whole seconds") };
    let gap = whole(&args[3], "maxGapSeconds must be a whole number of at least 1, received ");
    uptime_report_to_value(&uptime(&checks, from, to, gap))
}