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::net_latency_summary::{latency_summary_from_value, LatencySummary}; ← from net.latency-summary ^1.0.0 · built alongside by fune
// Integer arithmetic only, so the reason text is identical in every language.
fn percent(basis_points: i64) -> String {
format!("{}.{:02}%", basis_points / 100, basis_points % 100)
}
fn ms(us: i64) -> String {
format!("{}.{:03} ms", us / 1000, us % 1000)
}
/// Up, degraded or down, and every reason, from one latency summary.
///
/// Down wins outright: once a device is down, its p95 and jitter are beside
/// the point. Otherwise every threshold crossed is listed, loss first, so an
/// alert says all that is wrong at once.
///
/// # Panics
/// Panics on an inconsistent summary or thresholds out of range.
pub fn device_status(summary: &LatencySummary, thresholds: &StatusThresholds) -> StatusResult {
let (sent, received, lost) = (summary.sent, summary.received, summary.lost);
if sent < 1 || received < 0 || lost < 0 || received + lost != sent {
panic!(
"summary is inconsistent: sent {}, received {}, lost {}",
sent, received, lost
);
}
let deg_loss = thresholds.degraded_loss_basis_points;
let down_loss = thresholds.down_loss_basis_points;
let p95_limit = thresholds.degraded_p95_us;
let jitter_limit = thresholds.degraded_jitter_us;
if !(1..=10000).contains(&down_loss) {
panic!("downLossBasisPoints must be 1 to 10000, received {}", down_loss);
}
if deg_loss < 1 || deg_loss > down_loss {
panic!(
"degradedLossBasisPoints must be 1 to downLossBasisPoints ({}), received {}",
down_loss, deg_loss
);
}
if p95_limit < 1 {
panic!("degradedP95Us must be at least 1, received {}", p95_limit);
}
if let Some(j) = jitter_limit {
if j < 1 {
panic!("degradedJitterUs must be null or at least 1, received {}", j);
}
}
if received == 0 {
return StatusResult {
status: "down".to_string(),
reasons: vec![format!("no replies: {} of {} probes lost", lost, sent)],
};
}
let loss = summary.loss_basis_points;
if loss >= down_loss {
return StatusResult {
status: "down".to_string(),
reasons: vec![format!(
"loss {} at or above the down threshold {}",
percent(loss),
percent(down_loss)
)],
};
}
let mut reasons: Vec<String> = Vec::new();
if loss >= deg_loss {
reasons.push(format!("loss {} at or above {}", percent(loss), percent(deg_loss)));
}
if let Some(p95) = summary.p95_us {
if p95 >= p95_limit {
reasons.push(format!("p95 {} at or above {}", ms(p95), ms(p95_limit)));
}
}
if let (Some(limit), Some(jitter)) = (jitter_limit, summary.jitter_us) {
if jitter >= limit {
reasons.push(format!("jitter {} at or above {}", ms(jitter), ms(limit)));
}
}
let status = if reasons.is_empty() { "up" } else { "degraded" };
StatusResult {
status: status.to_string(),
reasons,
}
}
pub fn status_result_to_value(r: &StatusResult) -> Value {
Value::obj(vec![
("status", Value::str(&r.status)),
(
"reasons",
Value::Arr(r.reasons.iter().map(|s| Value::str(s)).collect()),
),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
let summary = latency_summary_from_value(&args[0]);
let t = &args[1];
let jitter = t.get("degradedJitterUs");
let thresholds = StatusThresholds {
degraded_loss_basis_points: t.get("degradedLossBasisPoints").as_i64(),
down_loss_basis_points: t.get("downLossBasisPoints").as_i64(),
degraded_p95_us: t.get("degradedP95Us").as_i64(),
degraded_jitter_us: if jitter.is_null() { None } else { Some(jitter.as_i64()) },
};
status_result_to_value(&device_status(&summary, &thresholds))
}