use super::runner::RunOutcome;
use std::collections::BTreeMap;
#[derive(Default)]
pub(super) struct MetricsStore {
pub(super) monitors: BTreeMap<String, MonitorMetrics>,
}
#[derive(Default)]
pub(super) struct MonitorMetrics {
pub(super) runs_pass: u64,
pub(super) runs_fail: u64,
pub(super) runs_timeout: u64,
pub(super) last_success: bool,
pub(super) last_duration_s: f64,
pub(super) last_run_unix: i64,
pub(super) scenarios: BTreeMap<String, ScenarioMetrics>,
}
#[derive(Default)]
pub(super) struct ScenarioMetrics {
pub(super) passed: bool,
pub(super) agents: BTreeMap<String, AgentSample>,
}
pub(super) struct AgentSample {
pub(super) registered: bool,
pub(super) mos: Option<f64>,
pub(super) jitter_ms: Option<f64>,
pub(super) packet_loss_pct: Option<f64>,
pub(super) rtt_ms: Option<f64>,
}
impl MetricsStore {
pub(super) fn record(&mut self, monitor: &str, outcome: &RunOutcome, now_unix: i64) {
let m = self.monitors.entry(monitor.to_string()).or_default();
if outcome.passed {
m.runs_pass += 1;
} else {
m.runs_fail += 1;
}
if outcome.timed_out {
m.runs_timeout += 1;
}
m.last_success = outcome.passed;
m.last_duration_s = outcome.duration.as_secs_f64();
m.last_run_unix = now_unix;
m.scenarios = outcome
.scenarios
.iter()
.map(|s| {
let agents = s
.agents
.iter()
.map(|a| {
(
a.agent.clone(),
AgentSample {
registered: a.registered,
mos: a.mos,
jitter_ms: a.jitter_ms,
packet_loss_pct: a.packet_loss_pct,
rtt_ms: a.rtt_ms,
},
)
})
.collect();
(
s.name.clone(),
ScenarioMetrics {
passed: s.passed,
agents,
},
)
})
.collect();
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::serve::runner::{AgentMetric, ScenarioOutcome};
use std::time::Duration;
#[test]
fn record_counts_and_snapshots_last_run() {
let mut store = MetricsStore::default();
let outcome = RunOutcome {
passed: true,
error: None,
duration: Duration::from_millis(1500),
timed_out: false,
scenarios: vec![ScenarioOutcome {
name: "rejects".into(),
passed: true,
agents: vec![AgentMetric {
agent: "Caller".into(),
registered: true,
mos: Some(4.2),
jitter_ms: None,
packet_loss_pct: None,
rtt_ms: None,
}],
}],
};
store.record("smoke", &outcome, 1_700_000_000);
let m = &store.monitors["smoke"];
assert_eq!(m.runs_pass, 1);
assert_eq!(m.runs_fail, 0);
assert!(m.last_success);
let s = &m.scenarios["rejects"];
assert!(s.passed);
assert_eq!(s.agents["Caller"].mos, Some(4.2));
}
#[test]
fn record_replaces_scenarios_each_run() {
let mut store = MetricsStore::default();
let mk = |scenario: &str| RunOutcome {
passed: false,
error: None,
duration: Duration::from_millis(1),
timed_out: false,
scenarios: vec![ScenarioOutcome {
name: scenario.into(),
passed: false,
agents: vec![],
}],
};
store.record("m", &mk("first"), 1);
store.record("m", &mk("second"), 2);
let m = &store.monitors["m"];
assert_eq!(m.runs_fail, 2);
assert!(!m.scenarios.contains_key("first"));
assert!(m.scenarios.contains_key("second"));
}
}