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::math_round_div::round_div; ← from math.round-div ^1.0.0 · built alongside by fune
use super::stats_percentile::percentile; ← from stats.percentile ^2.0.0 · built alongside by fune
/// Summarise a run of probes the way ping does, in whole microseconds.
///
/// Integer microseconds rather than float milliseconds, so every language
/// gives the same answer and a vector can compare exactly. A lost probe counts
/// towards loss and is skipped (not a break) when measuring jitter.
///
/// # Panics
/// Panics on an empty list or a negative sample.
pub fn summarise_latency(samples_us: &[Option<i64>]) -> LatencySummary {
if samples_us.is_empty() {
panic!("samplesUs must not be empty");
}
let mut got: Vec<i64> = Vec::new();
for s in samples_us.iter().flatten() {
if *s < 0 {
panic!("samples must not be negative, received {}", s);
}
got.push(*s);
}
let sent = samples_us.len() as i64;
let received = got.len() as i64;
let lost = sent - received;
let loss = round_div(lost * 10000, sent, "half-up");
if received == 0 {
return LatencySummary {
sent,
received,
lost,
loss_basis_points: loss,
min_us: None,
avg_us: None,
max_us: None,
p95_us: None,
jitter_us: None,
};
}
let jitter = if received >= 2 {
let diffs: i64 = got.windows(2).map(|w| (w[1] - w[0]).abs()).sum();
Some(round_div(diffs, received - 1, "half-up"))
} else {
None
};
// Nearest-rank returns a sample that was really seen, so it is a whole number.
let floats: Vec<f64> = got.iter().map(|v| *v as f64).collect();
let p95 = percentile(&floats, 95.0, "nearest-rank", 0) as i64;
LatencySummary {
sent,
received,
lost,
loss_basis_points: loss,
min_us: got.iter().min().copied(),
avg_us: Some(round_div(got.iter().sum(), received, "half-up")),
max_us: got.iter().max().copied(),
p95_us: Some(p95),
jitter_us: jitter,
}
}
fn opt(v: Option<i64>) -> Value {
match v {
Some(n) => Value::Int(n),
None => Value::Null,
}
}
pub fn latency_summary_to_value(s: &LatencySummary) -> Value {
Value::obj(vec![
("sent", Value::Int(s.sent)),
("received", Value::Int(s.received)),
("lost", Value::Int(s.lost)),
("lossBasisPoints", Value::Int(s.loss_basis_points)),
("minUs", opt(s.min_us)),
("avgUs", opt(s.avg_us)),
("maxUs", opt(s.max_us)),
("p95Us", opt(s.p95_us)),
("jitterUs", opt(s.jitter_us)),
])
}
/// Read a LatencySummary back from JSON, for capabilities that take one.
pub fn latency_summary_from_value(v: &Value) -> LatencySummary {
let o = |k: &str| {
let x = v.get(k);
if x.is_null() {
None
} else {
Some(x.as_i64())
}
};
LatencySummary {
sent: v.get("sent").as_i64(),
received: v.get("received").as_i64(),
lost: v.get("lost").as_i64(),
loss_basis_points: v.get("lossBasisPoints").as_i64(),
min_us: o("minUs"),
avg_us: o("avgUs"),
max_us: o("maxUs"),
p95_us: o("p95Us"),
jitter_us: o("jitterUs"),
}
}
pub fn fune_vector(args: &[Value]) -> Value {
let samples: Vec<Option<i64>> = args[0]
.as_arr()
.iter()
.map(|v| match v {
Value::Null => None,
Value::Int(n) => Some(*n),
Value::Float(f) if f.fract() == 0.0 && f.is_finite() => Some(*f as i64),
other => panic!(
"each sample must be a whole number of microseconds or null, received {}",
other
),
})
.collect();
latency_summary_to_value(&summarise_latency(&samples))
}