use super::*;
use crate::assert::derive_phase_metrics;
use crate::stats::{MetricKind, metric_def};
use crate::workload::PhaseSlice;
use crate::workload::schbench::plat::{Pct, PlatStats};
use crate::workload::schbench::run::SchbenchPhaseStats;
fn wakeup_hist() -> PlatStats {
let mut h = PlatStats::default();
for _ in 0..99 {
h.add_lat(10);
}
h.add_lat(10_000);
h
}
fn sps(loop_count: u64, msg_pc: u64, worker_pc: u64) -> SchbenchPhaseStats {
SchbenchPhaseStats {
wakeup: wakeup_hist(),
request: PlatStats::default(),
rps: PlatStats::default(),
msg_run_delay_ns: 50_000,
msg_pcount: msg_pc,
worker_run_delay_ns: 80_000,
worker_pcount: worker_pc,
loop_count,
}
}
fn slice_with(schbench: Option<SchbenchPhaseStats>) -> PhaseSlice {
PhaseSlice {
schbench,
..Default::default()
}
}
#[test]
fn phase_cgroup_merge_pools_schbench_carrier() {
let mut sa = sps(10, 5, 0);
sa.rps.add_lat(700);
let mut sb = sps(20, 3, 4);
sb.rps.add_lat(900);
let a = phase_slice_to_cgroup_stats(&slice_with(Some(sa)), None);
let b = phase_slice_to_cgroup_stats(&slice_with(Some(sb)), None);
let merged = PhaseCgroupStats::merge(a, b)
.schbench
.expect("both Some -> Some");
assert_eq!(merged.wakeup.sample_count(), 200);
assert_eq!(merged.msg_run_delay_ns, 100_000);
assert_eq!(merged.msg_pcount, 8);
assert_eq!(merged.worker_run_delay_ns, 160_000);
assert_eq!(merged.worker_pcount, 4);
assert_eq!(merged.loop_count, 30);
assert_eq!(merged.rps.sample_count(), 2);
let r = merged.rps.percentiles();
assert_eq!(r.min, 700, "merged rps min = lower operand's extreme");
assert_eq!(r.max, 900, "merged rps max = higher operand's extreme");
}
#[test]
fn phase_cgroup_merge_schbench_option_or() {
let some = || phase_slice_to_cgroup_stats(&slice_with(Some(sps(7, 1, 1))), None);
let none = || phase_slice_to_cgroup_stats(&slice_with(None), None);
assert!(
PhaseCgroupStats::merge(some(), none()).schbench.is_some(),
"Some+None -> Some"
);
assert!(
PhaseCgroupStats::merge(none(), some()).schbench.is_some(),
"None+Some -> Some"
);
assert!(
PhaseCgroupStats::merge(none(), none()).schbench.is_none(),
"None+None (non-schbench cgroup) -> None"
);
}
#[test]
fn derive_writes_perphase_scalars_with_absent_guards() {
let pc = PhaseCgroupStats {
schbench: Some(sps(42, 5, 0)), ..Default::default()
};
let mut bucket = PhaseBucket::default();
bucket.per_cgroup.insert("cg".to_string(), pc);
derive_phase_metrics(std::slice::from_mut(&mut bucket));
assert_eq!(bucket.metrics.get("wakeup_p50_latency_us"), Some(&10.0));
assert_eq!(bucket.metrics.get("wakeup_p99_latency_us"), Some(&10.0));
assert!(*bucket.metrics.get("wakeup_p999_latency_us").unwrap() >= 10_000.0);
assert_eq!(bucket.metrics.get("wakeup_min_latency_us"), Some(&10.0));
assert_eq!(bucket.metrics.get("wakeup_max_latency_us"), Some(&10_000.0));
assert!(!bucket.metrics.contains_key("request_p99_latency_us"));
assert!(!bucket.metrics.contains_key("request_min_latency_us"));
assert!(!bucket.metrics.contains_key("request_max_latency_us"));
assert!(!bucket.metrics.contains_key("rps_p20"));
assert!(!bucket.metrics.contains_key("rps_min"));
assert_eq!(bucket.metrics.get("sched_delay_msg_us"), Some(&10.0));
assert!(!bucket.metrics.contains_key("sched_delay_worker_us"));
assert_eq!(bucket.metrics.get("schbench_loop_count"), Some(&42.0));
}
#[test]
fn derive_is_noop_for_non_schbench_phase() {
let mut bucket = PhaseBucket::default();
bucket
.per_cgroup
.insert("cg".to_string(), PhaseCgroupStats::default());
derive_phase_metrics(std::slice::from_mut(&mut bucket));
assert!(
bucket.metrics.is_empty(),
"no schbench carrier -> no schbench keys"
);
}
#[test]
fn derive_pools_schbench_across_cgroups() {
let mk = || PhaseCgroupStats {
schbench: Some(sps(10, 5, 5)),
..Default::default()
};
let mut bucket = PhaseBucket::default();
bucket.per_cgroup.insert("cg_a".to_string(), mk());
bucket.per_cgroup.insert("cg_b".to_string(), mk());
derive_phase_metrics(std::slice::from_mut(&mut bucket));
assert_eq!(bucket.metrics.get("schbench_loop_count"), Some(&20.0));
assert_eq!(bucket.metrics.get("sched_delay_msg_us"), Some(&10.0));
assert_eq!(bucket.metrics.get("wakeup_p99_latency_us"), Some(&10.0));
}
#[test]
fn derive_rps_distribution_values_and_absent_guard() {
let mut rps = PlatStats::default();
for _ in 0..20 {
rps.add_lat(100);
}
for _ in 0..30 {
rps.add_lat(2_000);
}
for _ in 0..50 {
rps.add_lat(40_000);
}
let r = rps.percentiles();
assert!(
r.value_at(Pct::P20) < r.value_at(Pct::P90),
"spanning distribution yields distinct percentiles (transpose-detecting)"
);
let mut carrier = sps(10, 5, 3);
carrier.rps = rps;
let pc = PhaseCgroupStats {
schbench: Some(carrier),
..Default::default()
};
let mut bucket = PhaseBucket::default();
bucket.per_cgroup.insert("cg".to_string(), pc);
derive_phase_metrics(std::slice::from_mut(&mut bucket));
assert_eq!(
bucket.metrics.get("rps_p20"),
Some(&(r.value_at(Pct::P20) as f64))
);
assert_eq!(
bucket.metrics.get("rps_p50"),
Some(&(r.value_at(Pct::P50) as f64))
);
assert_eq!(
bucket.metrics.get("rps_p90"),
Some(&(r.value_at(Pct::P90) as f64))
);
assert_eq!(bucket.metrics.get("rps_min"), Some(&100.0));
assert_eq!(bucket.metrics.get("rps_max"), Some(&40_000.0));
}
#[test]
fn derive_pools_rps_across_cgroups() {
let mk = |lo: u32, hi: u32| {
let mut c = sps(10, 5, 5);
c.rps.add_lat(lo);
c.rps.add_lat(hi);
PhaseCgroupStats {
schbench: Some(c),
..Default::default()
}
};
let mut bucket = PhaseBucket::default();
bucket.per_cgroup.insert("cg_a".to_string(), mk(100, 200));
bucket
.per_cgroup
.insert("cg_b".to_string(), mk(10_000, 20_000));
derive_phase_metrics(std::slice::from_mut(&mut bucket));
assert_eq!(bucket.metrics.get("rps_min"), Some(&100.0));
assert_eq!(bucket.metrics.get("rps_max"), Some(&20_000.0));
}
#[test]
fn derive_request_min_max_value_pins() {
let mut carrier = sps(5, 1, 1);
let mut req = PlatStats::default();
for _ in 0..50 {
req.add_lat(30);
}
req.add_lat(9_000);
carrier.request = req;
let pc = PhaseCgroupStats {
schbench: Some(carrier),
..Default::default()
};
let mut bucket = PhaseBucket::default();
bucket.per_cgroup.insert("cg".to_string(), pc);
derive_phase_metrics(std::slice::from_mut(&mut bucket));
assert_eq!(bucket.metrics.get("request_min_latency_us"), Some(&30.0));
assert_eq!(bucket.metrics.get("request_max_latency_us"), Some(&9_000.0));
}
#[test]
fn phase_cgroup_schbench_serde_roundtrips() {
let mut carrier = sps(42, 5, 3);
let mut rps = PlatStats::default();
for v in [700u32, 720, 740] {
rps.add_lat(v);
}
carrier.rps = rps;
let pc = PhaseCgroupStats {
schbench: Some(carrier),
..Default::default()
};
let json = serde_json::to_string(&pc).expect("serialize PhaseCgroupStats");
let back: PhaseCgroupStats = serde_json::from_str(&json).expect("deserialize");
assert_eq!(back, pc, "PhaseCgroupStats roundtrips incl. schbench");
let s = back.schbench.expect("schbench survives the roundtrip");
assert_eq!(s.wakeup.sample_count(), 100);
assert_eq!(
s.rps.sample_count(),
3,
"the widened per-phase rps field roundtrips"
);
assert_eq!(s.loop_count, 42);
assert_eq!(s.msg_pcount, 5);
assert_eq!(s.worker_pcount, 3);
let bytes = postcard::to_stdvec(&pc).expect("serialize postcard");
let back2: PhaseCgroupStats = postcard::from_bytes(&bytes).expect("deserialize postcard");
assert_eq!(back2, pc, "postcard-roundtrips with an EMPTY metrics map");
assert!(
back2.schbench.is_some(),
"schbench survives the postcard roundtrip (no empty-metrics-field desync)"
);
let mut pc2 = pc.clone();
pc2.metrics.insert("schbench_loop_count".to_string(), 42.0);
pc2.metrics
.insert("wakeup_p99_latency_us".to_string(), 10.0);
let bytes2 = postcard::to_stdvec(&pc2).expect("serialize postcard");
let back3: PhaseCgroupStats = postcard::from_bytes(&bytes2).expect("deserialize postcard");
assert_eq!(
back3, pc2,
"populated per-cgroup metrics postcard-roundtrip"
);
assert_eq!(back3.metrics.get("schbench_loop_count"), Some(&42.0));
assert!(
back3.schbench.is_some(),
"schbench survives with a populated metrics map"
);
}
#[test]
fn derive_populates_per_cgroup_metrics_and_pooled_matches_repool() {
let mut bucket = PhaseBucket::default();
bucket.per_cgroup.insert(
"cg_a".to_string(),
PhaseCgroupStats {
schbench: Some(sps(10, 5, 5)),
..Default::default()
},
);
bucket.per_cgroup.insert(
"cg_b".to_string(),
PhaseCgroupStats {
schbench: Some(sps(30, 3, 3)),
..Default::default()
},
);
derive_phase_metrics(std::slice::from_mut(&mut bucket));
assert_eq!(
bucket.per_cgroup["cg_a"].get("schbench_loop_count"),
Some(10.0)
);
assert_eq!(
bucket.per_cgroup["cg_b"].get("schbench_loop_count"),
Some(30.0)
);
assert_eq!(bucket.get("schbench_loop_count"), Some(40.0));
assert_eq!(
bucket.per_cgroup["cg_a"].get("wakeup_p99_latency_us"),
Some(10.0)
);
assert_eq!(bucket.get("wakeup_p99_latency_us"), Some(10.0));
assert_eq!(
bucket.per_cgroup["cg_b"].expect_metric("schbench_loop_count"),
30.0
);
let mut bucket2 = PhaseBucket::default();
bucket2
.per_cgroup
.insert("cg".to_string(), PhaseCgroupStats::default());
derive_phase_metrics(std::slice::from_mut(&mut bucket2));
assert!(
bucket2.per_cgroup["cg"]
.get("schbench_loop_count")
.is_none()
);
}
#[test]
fn derive_mixed_schbench_and_plain_cgroups_pool_undiluted() {
let mut bucket = PhaseBucket::default();
bucket.per_cgroup.insert(
"cg_sch".to_string(),
PhaseCgroupStats {
schbench: Some(sps(40, 5, 5)),
..Default::default()
},
);
bucket
.per_cgroup
.insert("cg_plain".to_string(), PhaseCgroupStats::default());
derive_phase_metrics(std::slice::from_mut(&mut bucket));
assert_eq!(
bucket.get("schbench_loop_count"),
Some(40.0),
"pool is the schbench carrier alone, undiluted by the plain cgroup"
);
assert_eq!(
bucket.per_cgroup["cg_sch"].get("schbench_loop_count"),
Some(40.0)
);
assert!(
bucket.per_cgroup["cg_plain"]
.get("schbench_loop_count")
.is_none(),
"the plain (non-schbench) carrier gets no schbench keys"
);
assert_eq!(
bucket.per_cgroup["cg_plain"].get("migration_ratio"),
Some(0.0),
"the plain carrier still gets the always-present non-schbench migration_ratio"
);
}
#[test]
fn derive_pooled_percentile_is_the_combined_histogram() {
let mk = |a: u32, b: u32| {
let mut s = sps(1, 5, 5);
let mut w = PlatStats::default();
for _ in 0..50 {
w.add_lat(a);
}
for _ in 0..50 {
w.add_lat(b);
}
s.wakeup = w;
s
};
let mut bucket = PhaseBucket::default();
bucket.per_cgroup.insert(
"cg_a".to_string(),
PhaseCgroupStats {
schbench: Some(mk(10, 100)),
..Default::default()
},
);
bucket.per_cgroup.insert(
"cg_b".to_string(),
PhaseCgroupStats {
schbench: Some(mk(1_000, 10_000)),
..Default::default()
},
);
derive_phase_metrics(std::slice::from_mut(&mut bucket));
let pooled_p50 = bucket.get("wakeup_p50_latency_us").expect("pooled p50");
let a_p50 = bucket.per_cgroup["cg_a"]
.get("wakeup_p50_latency_us")
.expect("cg_a p50");
let b_p50 = bucket.per_cgroup["cg_b"]
.get("wakeup_p50_latency_us")
.expect("cg_b p50");
assert!(
a_p50 < pooled_p50 && pooled_p50 < b_p50,
"pooled p50 {pooled_p50} must be strictly between cg_a {a_p50} and cg_b {b_p50} \
(the combined-histogram median, not a per-cgroup value or an average)"
);
}
#[test]
fn derive_per_cgroup_absent_discipline() {
let mut bucket = PhaseBucket::default();
bucket.per_cgroup.insert(
"cg".to_string(),
PhaseCgroupStats {
schbench: Some(sps(7, 5, 0)), ..Default::default()
},
);
derive_phase_metrics(std::slice::from_mut(&mut bucket));
let pc = &bucket.per_cgroup["cg"];
assert_eq!(
pc.get("wakeup_p99_latency_us"),
Some(10.0),
"wakeup present per-cgroup"
);
assert_eq!(pc.get("schbench_loop_count"), Some(7.0));
assert!(
pc.get("request_p99_latency_us").is_none(),
"empty request -> absent per-cgroup"
);
assert!(
pc.get("rps_p20").is_none(),
"empty rps -> absent per-cgroup"
);
assert!(
pc.get("sched_delay_worker_us").is_none(),
"worker_pcount 0 -> absent per-cgroup (no false 0)"
);
}
#[test]
fn strip_phase_cgroup_samples_drops_schbench_carrier() {
let mut carrier = sps(7, 1, 1); for v in [700u32, 720, 740] {
carrier.rps.add_lat(v); }
let pc = PhaseCgroupStats {
schbench: Some(carrier),
wake_latencies_ns: vec![1, 2], ..Default::default()
};
let mut bucket = PhaseBucket::default();
bucket.per_cgroup.insert("cg".to_string(), pc);
let mut r = crate::assert::AssertResult::pass();
r.stats.phases = vec![bucket];
let dropped = r.strip_phase_cgroup_samples();
let cg = &r.stats.phases[0].per_cgroup["cg"];
assert!(cg.schbench.is_none(), "schbench carrier dropped to None");
assert!(cg.stripped, "carrier flagged stripped");
assert_eq!(
dropped,
2 + 100 + 3,
"dropped counts generic + schbench wakeup + rps histogram samples"
);
assert!(r.is_pass(), "verdict preserved (no PASS->FAIL flip)");
}
#[test]
fn schbench_perphase_metrics_registered() {
for key in [
"wakeup_p50_latency_us",
"wakeup_p90_latency_us",
"wakeup_p99_latency_us",
"wakeup_p999_latency_us",
"wakeup_min_latency_us",
"wakeup_max_latency_us",
"request_p50_latency_us",
"request_p90_latency_us",
"request_p99_latency_us",
"request_p999_latency_us",
"request_min_latency_us",
"request_max_latency_us",
"sched_delay_msg_us",
"sched_delay_worker_us",
"schbench_loop_count",
"rps_p20",
"rps_p50",
"rps_p90",
"rps_min",
"rps_max",
] {
let def = metric_def(key).unwrap_or_else(|| panic!("{key} must be registered"));
assert!(
matches!(def.kind, MetricKind::PerPhase),
"{key} kind is PerPhase"
);
assert!(def.kind.is_derived(), "{key} is is_derived");
}
use crate::test_support::Polarity;
assert!(
matches!(
metric_def("wakeup_p99_latency_us").unwrap().polarity,
Polarity::LowerBetter
),
"latency is LowerBetter"
);
assert!(
matches!(
metric_def("sched_delay_worker_us").unwrap().polarity,
Polarity::LowerBetter
),
"sched-delay is LowerBetter"
);
assert!(
matches!(
metric_def("schbench_loop_count").unwrap().polarity,
Polarity::HigherBetter
),
"loop_count is HigherBetter"
);
assert!(
matches!(
metric_def("wakeup_min_latency_us").unwrap().polarity,
Polarity::LowerBetter
),
"latency min is LowerBetter"
);
assert!(
matches!(
metric_def("wakeup_max_latency_us").unwrap().polarity,
Polarity::LowerBetter
),
"latency max is LowerBetter"
);
assert!(
matches!(
metric_def("rps_p50").unwrap().polarity,
Polarity::HigherBetter
),
"rps percentiles are HigherBetter"
);
assert!(
matches!(
metric_def("rps_min").unwrap().polarity,
Polarity::HigherBetter
),
"rps_min inverts latency-min polarity (HigherBetter)"
);
assert!(
matches!(
metric_def("rps_max").unwrap().polarity,
Polarity::HigherBetter
),
"rps_max inverts latency-max polarity (HigherBetter)"
);
}