Functional Weave
Code in Python

monitor.incidents@1.0.0

impl/rust.rs

3,891 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

/// The incidents in a check series. A run of consecutive bad checks is one
/// incident, from its first bad check to the first good check after it, but
/// only when it holds at least confirm_checks checks: a single failed probe is
/// noise, not an outage, and would otherwise page and skew MTTR.
///
/// # Panics
/// Panics on an incident status other than down or degraded, confirm_checks
/// below 1, an unknown or misordered check, or now before the last check.
pub fn find_incidents(checks: &[Check], incident_status: &str, confirm_checks: i64, now: i64) -> Vec<Incident> {
    if incident_status != "down" && incident_status != "degraded" {
        panic!("incidentStatus must be down or degraded, received {}", incident_status);
    }
    if confirm_checks < 1 {
        panic!("confirmChecks must be at least 1, received {}", confirm_checks);
    }
    for (i, c) in checks.iter().enumerate() {
        if !matches!(c.status.as_str(), "up" | "degraded" | "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 let Some(last) = checks.last() {
        if now < last.at {
            panic!("now {} is before the last check at {}", now, last.at);
        }
    }
    let bad = |s: &str| s == "down" || (incident_status == "degraded" && s == "degraded");
    let mut out = Vec::new();
    let mut i = 0;
    while i < checks.len() {
        if !bad(&checks[i].status) {
            i += 1;
            continue;
        }
        let start = checks[i].at;
        let mut count = 0i64;
        let mut worst = "degraded";
        while i < checks.len() && bad(&checks[i].status) {
            if checks[i].status == "down" {
                worst = "down";
            }
            count += 1;
            i += 1;
        }
        let end = if i < checks.len() { Some(checks[i].at) } else { None };
        if count >= confirm_checks {
            out.push(Incident {
                start,
                end,
                duration_seconds: end.unwrap_or(now) - start,
                worst: worst.to_string(),
                checks: count,
            });
        }
    }
    out
}

/// An `Incident` from its JSON form, for adapters of capabilities built on this one.
pub fn incident_from_value(v: &Value) -> Incident {
    let end = v.get("end");
    Incident {
        start: v.get("start").as_i64(),
        end: if end.is_null() { None } else { Some(end.as_i64()) },
        duration_seconds: v.get("durationSeconds").as_i64(),
        worst: v.get("worst").as_str().to_string(),
        checks: v.get("checks").as_i64(),
    }
}

pub fn incidents_from_value(v: &Value) -> Vec<Incident> {
    v.as_arr().iter().map(incident_from_value).collect()
}

pub fn incident_to_value(x: &Incident) -> Value {
    Value::obj(vec![
        ("start", Value::Int(x.start)),
        ("end", match x.end { Some(e) => Value::Int(e), None => Value::Null }),
        ("durationSeconds", Value::Int(x.duration_seconds)),
        ("worst", Value::str(&x.worst)),
        ("checks", Value::Int(x.checks)),
    ])
}

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

pub fn fune_vector(args: &[Value]) -> Value {
    let checks = checks_from_value(&args[0]);
    let confirm = match &args[2] {
        Value::Int(i) => *i,
        other => panic!("confirmChecks must be at least 1, received {:?}", other),
    };
    let now = whole(&args[3], "now must be a whole number of seconds");
    let out = find_incidents(&checks, args[1].as_str(), confirm, now);
    Value::Arr(out.iter().map(incident_to_value).collect())
}