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())
}