use super::*;
#[test]
fn build_phase_buckets_with_stimulus_synthesized_inverted_window_folds_nothing() {
use crate::monitor::{CpuSnapshot, MonitorReport, MonitorSample};
use crate::scenario::snapshot::DrainedSnapshotEntry;
use crate::timeline::StimulusEvent;
let cpu = |nr: u32| CpuSnapshot {
nr_running: nr,
..Default::default()
};
let mon = MonitorReport {
samples: vec![MonitorSample::new(1500, vec![cpu(6), cpu(2)])],
..Default::default()
};
let samples = SampleSeries::from_drained_typed(Vec::<DrainedSnapshotEntry>::new(), Some(mon));
let ev = |elapsed_ms: u64, k: u16, is_step_end: bool| StimulusEvent {
elapsed_ms,
label: format!("Step{}[{k}]", if is_step_end { "End" } else { "Start" }),
op_kind: None,
detail: None,
total_iterations: Some(0),
step_index: Some(k),
is_terminal: false,
is_step_end,
};
let stimulus = vec![ev(2000, 1, false), ev(1000, 1, true)];
let phases = crate::assert::build_phase_buckets_with_stimulus(&samples, &stimulus);
let step1 = phases
.iter()
.find(|p| p.step_index == 1)
.expect("synthesized step 1 bucket present despite inverted window");
assert!(
step1.start_ms > step1.end_ms,
"window is inverted: start {} end {}",
step1.start_ms,
step1.end_ms,
);
assert!(
!step1.metrics.contains_key("avg_imbalance_ratio"),
"an inverted window folds no monitor samples; got {:?}",
step1.metrics,
);
}
#[test]
fn build_phase_buckets_with_stimulus_synthesized_end_ms_clamps_to_terminal() {
use crate::monitor::{CpuSnapshot, MonitorReport, MonitorSample};
use crate::scenario::snapshot::DrainedSnapshotEntry;
use crate::timeline::StimulusEvent;
let cpu = |nr: u32| CpuSnapshot {
nr_running: nr,
..Default::default()
};
let mon = MonitorReport {
samples: vec![
MonitorSample::new(1000, vec![cpu(2), cpu(2)]),
MonitorSample::new(4000, vec![cpu(10), cpu(2)]),
],
..Default::default()
};
let samples = SampleSeries::from_drained_typed(Vec::<DrainedSnapshotEntry>::new(), Some(mon));
let start = StimulusEvent {
elapsed_ms: 1000,
label: "StepStart[1]".to_string(),
op_kind: None,
detail: None,
total_iterations: None,
step_index: Some(1),
is_terminal: false,
is_step_end: false,
};
let terminal = StimulusEvent {
elapsed_ms: 3000,
label: "ScenarioEnd".to_string(),
op_kind: None,
detail: None,
total_iterations: None,
step_index: None,
is_terminal: true,
is_step_end: false,
};
let phases = crate::assert::build_phase_buckets_with_stimulus(&samples, &[start, terminal]);
let step1 = phases
.iter()
.find(|p| p.step_index == 1)
.expect("synthesized step 1 bucket present");
assert_eq!(
step1.end_ms, 3000,
"end_ms clamps to the terminal (3000), not u64::MAX",
);
assert_eq!(
step1.metrics.get("avg_imbalance_ratio").copied(),
Some(1.0),
"only the in-window pre-terminal sample folds (imbalance 1.0); the \
post-terminal sample (5.0) is excluded — avg is 1.0, not 3.0",
);
}
#[test]
fn by_stimulus_phase_separates_what_by_stamped_phase_collapses() {
use crate::scenario::sample::SampleSeries;
use crate::scenario::snapshot::{DrainedSnapshotEntry, MissingStatsReason};
use crate::timeline::StimulusEvent;
let mk = |tag: &str, offset_ms: u64| DrainedSnapshotEntry {
tag: tag.to_string(),
report: fixture_report(),
stats: Err(MissingStatsReason::NoSchedulerBinary),
elapsed_ms: Some(9_000),
boundary_offset_ms: Some(offset_ms),
step_index: Some(3),
};
let drained = vec![
mk("p0", 500), mk("p1", 1_500), mk("p2", 2_500), mk("p3", 3_500), ];
let samples = SampleSeries::from_drained_typed(drained, None);
let stim = |elapsed_ms: u64, k: u16| StimulusEvent {
elapsed_ms,
label: format!("StepStart[{k}]"),
op_kind: None,
detail: None,
total_iterations: None,
step_index: Some(k),
is_terminal: false,
is_step_end: false,
};
let stimulus = vec![stim(1000, 1), stim(2000, 2), stim(3000, 3)];
let stamped = samples.by_stamped_phase();
assert_eq!(stamped.keys().copied().collect::<Vec<_>>(), vec![3]);
assert_eq!(stamped[&3].len(), 4, "stamped grouping collapses the burst");
let by_stim = samples.by_stimulus_phase(&stimulus);
assert_eq!(
by_stim.keys().copied().collect::<Vec<_>>(),
vec![0, 1, 2, 3]
);
for k in [0u16, 1, 2, 3] {
assert_eq!(
by_stim[&k].len(),
1,
"phase {k} should hold exactly one sample"
);
}
}
#[test]
fn build_phase_buckets_with_stimulus_none_offset_falls_back_to_stamped_step() {
use crate::scenario::snapshot::{DrainedSnapshotEntry, MissingStatsReason};
use crate::timeline::StimulusEvent;
let mk = |tag: &str, step: u16| DrainedSnapshotEntry {
tag: tag.to_string(),
report: fixture_report(),
stats: Err(MissingStatsReason::NoSchedulerBinary),
elapsed_ms: Some(100),
boundary_offset_ms: None,
step_index: Some(step),
};
let drained = vec![mk("periodic_000", 1), mk("periodic_001", 2)];
let samples = SampleSeries::from_drained_typed(drained, None);
let stimulus = vec![StimulusEvent {
elapsed_ms: 0,
label: "StepStart[5]".to_string(),
op_kind: None,
detail: None,
total_iterations: None,
step_index: Some(5),
is_terminal: false,
is_step_end: false,
}];
let phases = crate::assert::build_phase_buckets_with_stimulus(&samples, &stimulus);
let idxs: Vec<u16> = phases.iter().map(|p| p.step_index).collect();
assert_eq!(
idxs,
vec![1, 2, 5],
"None-offset captures keep their stamped step_index (1, 2), NOT \
remapped to the step-5 stimulus; step 5's StepStart synthesizes its \
own capture-free bucket; got {idxs:?}",
);
let count = |k: u16| {
phases
.iter()
.find(|p| p.step_index == k)
.map(|p| p.sample_count)
};
assert_eq!(
count(1),
Some(1),
"capture stamped 1 stays in its own bucket"
);
assert_eq!(
count(2),
Some(1),
"capture stamped 2 stays in its own bucket"
);
assert_eq!(
count(5),
Some(0),
"step 5 is the synthesized capture-free bucket"
);
}
#[test]
fn build_phase_buckets_with_stimulus_iteration_rate_attaches_by_step_not_interior_window() {
use crate::scenario::snapshot::{DrainedSnapshotEntry, MissingStatsReason};
use crate::timeline::StimulusEvent;
let mk = |tag: &str, offset_ms: u64| DrainedSnapshotEntry {
tag: tag.to_string(),
report: fixture_report(),
stats: Err(MissingStatsReason::NoSchedulerBinary),
elapsed_ms: Some(9_000),
boundary_offset_ms: Some(offset_ms),
step_index: Some(9),
};
let drained = vec![
mk("periodic_000", 1_500), mk("periodic_001", 2_500), ];
let samples = SampleSeries::from_drained_typed(drained, None);
let stim = |elapsed_ms: u64, k: u16, iters: u64| StimulusEvent {
elapsed_ms,
label: format!("StepStart[{k}]"),
op_kind: None,
detail: None,
total_iterations: Some(iters),
step_index: Some(k),
is_terminal: false,
is_step_end: false,
};
let stimulus = vec![stim(1000, 1, 0), stim(2000, 2, 1000), stim(3000, 3, 3000)];
let phases = crate::assert::build_phase_buckets_with_stimulus(&samples, &stimulus);
let step1 = phases
.iter()
.find(|p| p.step_index == 1)
.expect("Step[0] bucket present");
assert_eq!(
step1.metrics.get("iteration_rate").copied(),
Some(1000.0),
"step 1 iteration_rate must attach by step_index to the interior \
bucket; got {:?} (start_ms={}, end_ms={})",
step1.metrics.get("iteration_rate"),
step1.start_ms,
step1.end_ms,
);
let step2 = phases
.iter()
.find(|p| p.step_index == 2)
.expect("Step[1] bucket present");
assert_eq!(
step2.metrics.get("iteration_rate").copied(),
Some(2000.0),
"step 2 iteration_rate must attach by step_index; got {:?}",
step2.metrics.get("iteration_rate"),
);
}
#[test]
fn build_phase_buckets_with_stimulus_pairs_step_local_for_respawned_workers() {
use crate::scenario::snapshot::{DrainedSnapshotEntry, MissingStatsReason};
use crate::timeline::StimulusEvent;
let mk = |tag: &str, offset_ms: u64| DrainedSnapshotEntry {
tag: tag.to_string(),
report: fixture_report(),
stats: Err(MissingStatsReason::NoSchedulerBinary),
elapsed_ms: Some(9_000),
boundary_offset_ms: Some(offset_ms),
step_index: Some(9),
};
let drained = vec![
mk("periodic_000", 1_500), mk("periodic_001", 2_500), ];
let samples = SampleSeries::from_drained_typed(drained, None);
let start = |elapsed_ms: u64, k: u16, iters: u64| StimulusEvent {
elapsed_ms,
label: format!("StepStart[{k}]"),
op_kind: None,
detail: None,
total_iterations: Some(iters),
step_index: Some(k),
is_terminal: false,
is_step_end: false,
};
let end = |elapsed_ms: u64, k: u16, iters: u64| StimulusEvent {
elapsed_ms,
label: format!("StepEnd[{k}]"),
op_kind: None,
detail: None,
total_iterations: Some(iters),
step_index: Some(k),
is_terminal: false,
is_step_end: true,
};
let stimulus = vec![
start(1_000, 1, 0),
end(2_000, 1, 10_000),
start(2_100, 2, 0),
end(3_100, 2, 5_000),
StimulusEvent::terminal(3_200, 5_000),
];
let phases = crate::assert::build_phase_buckets_with_stimulus(&samples, &stimulus);
let step1 = phases
.iter()
.find(|p| p.step_index == 1)
.expect("step 1 bucket present");
let step2 = phases
.iter()
.find(|p| p.step_index == 2)
.expect("step 2 bucket present");
assert_eq!(
step1.metrics.get("iteration_rate").copied(),
Some(10_000.0),
"step 1 must report its step-local StepStart->StepEnd rate, not the \
cross-step 0->0 delta (the old cross-step silent None); got {:?}",
step1.metrics.get("iteration_rate"),
);
assert_eq!(
step2.metrics.get("iteration_rate").copied(),
Some(5_000.0),
"step 2 (respawned workers) must report its step-local rate; got {:?}",
step2.metrics.get("iteration_rate"),
);
}
#[test]
fn build_phase_buckets_with_stimulus_step_local_wins_over_persistent_cross_step() {
use crate::scenario::snapshot::{DrainedSnapshotEntry, MissingStatsReason};
use crate::timeline::StimulusEvent;
let mk = |tag: &str, offset_ms: u64| DrainedSnapshotEntry {
tag: tag.to_string(),
report: fixture_report(),
stats: Err(MissingStatsReason::NoSchedulerBinary),
elapsed_ms: Some(9_000),
boundary_offset_ms: Some(offset_ms),
step_index: Some(9),
};
let drained = vec![mk("periodic_000", 1_500), mk("periodic_001", 2_500)];
let samples = SampleSeries::from_drained_typed(drained, None);
let start = |elapsed_ms: u64, k: u16, iters: u64| StimulusEvent {
elapsed_ms,
label: format!("StepStart[{k}]"),
op_kind: None,
detail: None,
total_iterations: Some(iters),
step_index: Some(k),
is_terminal: false,
is_step_end: false,
};
let end = |elapsed_ms: u64, k: u16, iters: u64| StimulusEvent {
elapsed_ms,
label: format!("StepEnd[{k}]"),
op_kind: None,
detail: None,
total_iterations: Some(iters),
step_index: Some(k),
is_terminal: false,
is_step_end: true,
};
let stimulus = vec![
start(1_000, 1, 0),
end(2_000, 1, 10_000),
start(2_100, 2, 10_500),
end(3_100, 2, 15_500),
StimulusEvent::terminal(3_200, 15_500),
];
let phases = crate::assert::build_phase_buckets_with_stimulus(&samples, &stimulus);
let step1 = phases
.iter()
.find(|p| p.step_index == 1)
.expect("step 1 bucket present");
let step2 = phases
.iter()
.find(|p| p.step_index == 2)
.expect("step 2 bucket present");
assert_eq!(
step1.metrics.get("iteration_rate").copied(),
Some(10_000.0),
"step-local rate must win over the 5000/s persistent cross-step \
delta (the is_step_end guard skips the cross-step pair); got {:?}",
step1.metrics.get("iteration_rate"),
);
assert_eq!(
step2.metrics.get("iteration_rate").copied(),
Some(5_000.0),
"step 2 must report its own start-to-end-of-hold rate; got {:?}",
step2.metrics.get("iteration_rate"),
);
}
#[test]
fn build_phase_buckets_with_stimulus_stalled_step_reports_measured_zero() {
use crate::scenario::snapshot::{DrainedSnapshotEntry, MissingStatsReason};
use crate::timeline::StimulusEvent;
let mk = |tag: &str, offset_ms: u64| DrainedSnapshotEntry {
tag: tag.to_string(),
report: fixture_report(),
stats: Err(MissingStatsReason::NoSchedulerBinary),
elapsed_ms: Some(9_000),
boundary_offset_ms: Some(offset_ms),
step_index: Some(9),
};
let drained = vec![mk("periodic_000", 1_500), mk("periodic_001", 2_500)];
let samples = SampleSeries::from_drained_typed(drained, None);
let start = |elapsed_ms: u64, k: u16, iters: u64| StimulusEvent {
elapsed_ms,
label: format!("StepStart[{k}]"),
op_kind: None,
detail: None,
total_iterations: Some(iters),
step_index: Some(k),
is_terminal: false,
is_step_end: false,
};
let end = |elapsed_ms: u64, k: u16, iters: u64| StimulusEvent {
elapsed_ms,
label: format!("StepEnd[{k}]"),
op_kind: None,
detail: None,
total_iterations: Some(iters),
step_index: Some(k),
is_terminal: false,
is_step_end: true,
};
let stimulus = vec![
start(1_000, 1, 0),
end(2_000, 1, 0),
start(2_100, 2, 500),
end(3_100, 2, 1_500),
StimulusEvent::terminal(3_200, 1_500),
];
let phases = crate::assert::build_phase_buckets_with_stimulus(&samples, &stimulus);
let step1 = phases
.iter()
.find(|p| p.step_index == 1)
.expect("step 1 bucket present");
let step2 = phases
.iter()
.find(|p| p.step_index == 2)
.expect("step 2 bucket present");
assert_eq!(
step1.metrics.get("iteration_rate").copied(),
Some(0.0),
"a stalled step reports measured-zero throughput, not the leaked \
5000/s teardown gap rate from the StepEnd[1] -> StepStart[2] pair; \
got {:?}",
step1.metrics.get("iteration_rate"),
);
assert_eq!(
step2.metrics.get("iteration_rate").copied(),
Some(1_000.0),
"step 2 still reports its own step-local rate; got {:?}",
step2.metrics.get("iteration_rate"),
);
}
#[test]
fn build_phase_buckets_with_stimulus_emits_rate_components_in_seconds() {
use crate::timeline::StimulusEvent;
let samples = SampleSeries::from_drained_typed(vec![], None);
let stimulus = vec![
StimulusEvent {
elapsed_ms: 1_000,
label: "StepStart[1]".to_string(),
op_kind: None,
detail: None,
total_iterations: Some(0),
step_index: Some(1),
is_terminal: false,
is_step_end: false,
},
StimulusEvent {
elapsed_ms: 3_000, label: "StepEnd[1]".to_string(),
op_kind: None,
detail: None,
total_iterations: Some(1_000), step_index: Some(1),
is_terminal: false,
is_step_end: true,
},
StimulusEvent::terminal(3_100, 1_000),
];
let phases = crate::assert::build_phase_buckets_with_stimulus(&samples, &stimulus);
let step1 = phases
.iter()
.find(|p| p.step_index == 1)
.expect("step 1 bucket synthesized from stimulus");
assert_eq!(
step1.metrics.get("total_phase_duration_sec").copied(),
Some(2.0),
"duration component is SECONDS (2000ms / 1000), not ms; got {:?}",
step1.metrics.get("total_phase_duration_sec"),
);
assert_eq!(
step1.metrics.get("total_phase_iterations").copied(),
Some(1_000.0),
"iteration component is the 1000-iteration delta",
);
assert_eq!(
step1.metrics.get("iteration_rate").copied(),
Some(500.0),
"derived iteration_rate = 1000 iters / 2.0 s = 500/s (NOT 0.5 from a \
ms denominator); got {:?}",
step1.metrics.get("iteration_rate"),
);
}
#[test]
fn build_phase_buckets_with_stimulus_terminal_gives_last_step_rate_no_phantom_bucket() {
use crate::scenario::snapshot::{DrainedSnapshotEntry, MissingStatsReason};
use crate::timeline::StimulusEvent;
let mk = |tag: &str, offset_ms: u64| DrainedSnapshotEntry {
tag: tag.to_string(),
report: fixture_report(),
stats: Err(MissingStatsReason::NoSchedulerBinary),
elapsed_ms: Some(9_000),
boundary_offset_ms: Some(offset_ms),
step_index: Some(9),
};
let drained = vec![mk("periodic_000", 1_500), mk("periodic_001", 2_500)];
let samples = SampleSeries::from_drained_typed(drained, None);
let stim = |elapsed_ms: u64, k: u16, iters: u64| StimulusEvent {
elapsed_ms,
label: format!("StepStart[{k}]"),
op_kind: None,
detail: None,
total_iterations: Some(iters),
step_index: Some(k),
is_terminal: false,
is_step_end: false,
};
let stimulus = vec![
stim(1000, 1, 0),
stim(2000, 2, 1000),
StimulusEvent::terminal(3000, 3000),
];
let phases = crate::assert::build_phase_buckets_with_stimulus(&samples, &stimulus);
let idxs: Vec<u16> = phases.iter().map(|p| p.step_index).collect();
assert_eq!(
idxs,
vec![1, 2],
"terminal must not add a phantom bucket; got {idxs:?}",
);
let step2 = phases
.iter()
.find(|p| p.step_index == 2)
.expect("step 2 bucket present");
assert_eq!(
step2.metrics.get("iteration_rate").copied(),
Some(2000.0),
"last step's iteration_rate must come from the terminal boundary",
);
}
#[test]
fn build_phase_buckets_with_stimulus_first_step_zero_baseline_from_wire() {
use crate::scenario::snapshot::{DrainedSnapshotEntry, MissingStatsReason};
use crate::timeline::StimulusEvent;
let mk = |tag: &str, offset_ms: u64| DrainedSnapshotEntry {
tag: tag.to_string(),
report: fixture_report(),
stats: Err(MissingStatsReason::NoSchedulerBinary),
elapsed_ms: Some(9_000),
boundary_offset_ms: Some(offset_ms),
step_index: Some(9),
};
let drained = vec![mk("periodic_000", 1_500), mk("periodic_001", 2_500)];
let samples = SampleSeries::from_drained_typed(drained, None);
let wire = |elapsed_ms: u32, step_index: u16, iters: u64| crate::vmm::wire::StimulusEvent {
elapsed_ms,
step_index,
op_count: 0,
op_kinds: 0,
cgroup_count: 0,
worker_count: 1,
total_iterations: iters,
};
let stimulus: Vec<StimulusEvent> = [wire(1000, 1, 0), wire(2000, 2, 2000)]
.iter()
.map(StimulusEvent::from_wire)
.collect();
let phases = crate::assert::build_phase_buckets_with_stimulus(&samples, &stimulus);
let step1 = phases
.iter()
.find(|p| p.step_index == 1)
.expect("step 1 bucket present");
assert_eq!(
step1.metrics.get("iteration_rate").copied(),
Some(2000.0),
"first step's iteration_rate must compute from the 0 baseline",
);
}
#[test]
fn build_phase_buckets_with_stimulus_loop_step_rate_no_prior_graft() {
use crate::scenario::snapshot::{DrainedSnapshotEntry, MissingStatsReason};
use crate::timeline::StimulusEvent;
let mk = |tag: &str, offset_ms: u64| DrainedSnapshotEntry {
tag: tag.to_string(),
report: fixture_report(),
stats: Err(MissingStatsReason::NoSchedulerBinary),
elapsed_ms: Some(9_000),
boundary_offset_ms: Some(offset_ms),
step_index: Some(9),
};
let drained = vec![mk("periodic_000", 1_500), mk("periodic_001", 2_500)];
let samples = SampleSeries::from_drained_typed(drained, None);
let stim = |elapsed_ms: u64, k: u16, iters: u64| StimulusEvent {
elapsed_ms,
label: format!("StepStart[{k}]"),
op_kind: None,
detail: None,
total_iterations: Some(iters),
step_index: Some(k),
is_terminal: false,
is_step_end: false,
};
let stimulus = vec![
stim(1000, 1, 0),
stim(2000, 2, 1000),
StimulusEvent::terminal(3000, 3000),
];
let phases = crate::assert::build_phase_buckets_with_stimulus(&samples, &stimulus);
let step1 = phases
.iter()
.find(|p| p.step_index == 1)
.expect("step 1 bucket");
let loop_step = phases
.iter()
.find(|p| p.step_index == 2)
.expect("loop step bucket");
assert_eq!(
step1.metrics.get("iteration_rate").copied(),
Some(1000.0),
"prior step must not absorb the loop step's window",
);
assert_eq!(
loop_step.metrics.get("iteration_rate").copied(),
Some(2000.0),
"loop step must get its own throughput from its start frame + terminal",
);
}
#[test]
fn populate_run_ext_metrics_populated_series_inserts_expected_keys() {
use crate::monitor::dump::FailureDumpReport;
use crate::monitor::scx_walker::DsqState;
use crate::scenario::snapshot::{DrainedSnapshotEntry, MissingStatsReason};
let mk_entry = |tag: &str, ms: u64| DrainedSnapshotEntry {
tag: tag.to_string(),
report: FailureDumpReport {
schema: SCHEMA_SINGLE.to_string(),
dsq_states: vec![DsqState {
origin: "local cpu 0".to_string(),
nr: 5,
..Default::default()
}],
..Default::default()
},
stats: Err(MissingStatsReason::NoSchedulerBinary),
elapsed_ms: Some(ms),
boundary_offset_ms: None,
step_index: Some(0),
};
let drained = vec![mk_entry("periodic_000", 100), mk_entry("periodic_001", 200)];
let samples = SampleSeries::from_drained_typed(drained, None);
let mut target = std::collections::BTreeMap::new();
crate::assert::populate_run_ext_metrics(&samples, &mut target);
let avg = target
.get("avg_dsq_depth")
.copied()
.expect("avg_dsq_depth populated for populated series");
assert!(
(avg - 5.0).abs() < f64::EPSILON,
"expected avg_dsq_depth=5.0, got {avg}",
);
assert!(
!target.contains_key("max_dsq_depth"),
"max_dsq_depth has a typed GauntletRow field; must not leak into ext_metrics",
);
}
#[test]
fn populate_run_ext_metrics_from_phases_folds_per_phase_keys() {
use crate::assert::PhaseBucket;
use std::collections::BTreeMap;
let mut m0 = BTreeMap::new();
m0.insert("avg_imbalance_ratio".to_string(), 2.0);
let mut m1 = BTreeMap::new();
m1.insert("avg_imbalance_ratio".to_string(), 4.0);
let phases = vec![
PhaseBucket {
per_cgroup: Default::default(),
step_index: 1,
label: "Step[0]".to_string(),
start_ms: 0,
end_ms: 100,
sample_count: 5,
metrics: m0,
},
PhaseBucket {
per_cgroup: Default::default(),
step_index: 2,
label: "Step[1]".to_string(),
start_ms: 100,
end_ms: 200,
sample_count: 15,
metrics: m1,
},
];
let mut target = BTreeMap::new();
crate::assert::populate_run_ext_metrics_from_phases(&phases, &mut target);
let avg = target
.get("avg_imbalance_ratio")
.copied()
.expect("avg_imbalance_ratio folded from per-phase");
assert!(
(avg - 3.5).abs() < f64::EPSILON,
"expected weighted mean 3.5, got {avg}",
);
}
#[test]
fn populate_run_ext_metrics_from_phases_skips_typed_backed_keys() {
use crate::assert::PhaseBucket;
use std::collections::BTreeMap;
let mut m = BTreeMap::new();
m.insert("avg_imbalance_ratio".to_string(), 2.0); m.insert("max_imbalance_ratio".to_string(), 3.0); m.insert("stuck_count".to_string(), 2.0); let phases = vec![PhaseBucket {
per_cgroup: Default::default(),
step_index: 1,
label: "Step[0]".to_string(),
start_ms: 0,
end_ms: 100,
sample_count: 5,
metrics: m,
}];
let mut target = BTreeMap::new();
crate::assert::populate_run_ext_metrics_from_phases(&phases, &mut target);
assert!(
target.contains_key("avg_imbalance_ratio"),
"avg_imbalance_ratio is ext-only and must be folded into ext_metrics",
);
assert!(
!target.contains_key("max_imbalance_ratio"),
"max_imbalance_ratio is typed-backed; must NOT leak into ext_metrics from the phase fold",
);
assert!(
!target.contains_key("stuck_count"),
"stuck_count is typed-backed; must NOT leak into ext_metrics (the ext per-phase fold sum is <= the typed whole-run count, never a guaranteed duplicate)",
);
}
#[test]
fn populate_run_ext_metrics_repools_synthesized_zero_capture_phase() {
use crate::assert::PhaseBucket;
use std::collections::BTreeMap;
let cap = BTreeMap::from([
("total_phase_iterations".to_string(), 1200.0),
("total_phase_duration_sec".to_string(), 3.0),
]);
let synth = BTreeMap::from([
("total_phase_iterations".to_string(), 600.0),
("total_phase_duration_sec".to_string(), 1.0),
]);
let phases = vec![
PhaseBucket {
per_cgroup: Default::default(),
step_index: 1,
label: "Step[0]".to_string(),
start_ms: 0,
end_ms: 100,
sample_count: 5,
metrics: cap,
},
PhaseBucket {
per_cgroup: Default::default(),
step_index: 2,
label: "Step[1]".to_string(),
start_ms: 100,
end_ms: 200,
sample_count: 0, metrics: synth,
},
];
let mut target = BTreeMap::new();
crate::assert::populate_run_ext_metrics_from_phases(&phases, &mut target);
let r = target
.get("iteration_rate")
.copied()
.expect("synthesized zero-capture phase's components must re-pool into iteration_rate");
assert!(
(r - 450.0).abs() < f64::EPSILON,
"expected re-pooled 450/s (synthesized 600 iters summed in), NOT \
400 (dropped) or 500 (mean of ratios); got {r}",
);
assert_eq!(
target.get("total_phase_iterations").copied(),
Some(1800.0),
"components sum across phases (weights ignored for Counters)",
);
}