ScenarioStats

Struct ScenarioStats 

Source
pub struct ScenarioStats {
    pub cgroups: Vec<CgroupStats>,
    pub total_workers: usize,
    pub total_cpus: usize,
    pub total_migrations: u64,
    pub worst_spread: f64,
    pub worst_gap_ms: u64,
    pub worst_gap_cpu: usize,
    pub worst_migration_ratio: f64,
    pub total_iterations: u64,
    pub ext_metrics: BTreeMap<String, f64>,
    pub phases: Vec<PhaseBucket>,
}
Expand description

Aggregated statistics across all cgroups in a scenario.

Fields§

§cgroups: Vec<CgroupStats>

Per-cgroup stats, one entry per cgroup.

§total_workers: usize

Sum of workers across all cgroups.

§total_cpus: usize

Sum of per-cgroup distinct CPU counts (not deduplicated across cgroups).

§total_migrations: u64

Sum of migration counts across all cgroups.

§worst_spread: f64

Worst spread across any cgroup (highest).

§worst_gap_ms: u64

Worst gap across any cgroup (highest, ms). Paired with worst_gap_cpu — both come from the same cgroup.

§worst_gap_cpu: usize

CPU where the worst gap occurred across all cgroups. Paired with worst_gap_ms — both come from the same cgroup.

§worst_migration_ratio: f64

Worst migration ratio across any cgroup (highest).

§total_iterations: u64

Sum of iteration counts across all cgroups.

§ext_metrics: BTreeMap<String, f64>

Extensible metrics for the generic comparison pipeline. Populated from per-cgroup ext_metrics (worst value across cgroups).

§phases: Vec<PhaseBucket>

Per-phase metric buckets in step-index order. A scenario with N Steps populates N + 1 entries: phase 0 is the BASELINE settle window before Step 0 fires, phases 1..=N align with Step 0..Step N-1 in scenario order (1-indexed Steps so the BASELINE encoding doesn’t collide with first-Step’s index).

Empty when the scenario produced no periodic captures (Default::default() yields vec![]). The existing flat-bucket scalars on this struct are independent of the per-phase view — they remain the “all phases merged” reading, unchanged in semantics by the introduction of phases.

Auto-populated by the framework: scenarios that fire periodic captures (via crate::test_support::KtstrTestEntry::num_snapshots or crate::scenario::ops::Op::CaptureSnapshot) have this field populated automatically inside crate::test_support::eval’s evaluate_vm_result — test code never needs to call crate::assert::build_phase_buckets manually. The auto- populate path drains the snapshot bridge from the crate::vmm::VmResult returned by the framework and folds the per-sample readings through crate::stats::aggregate_samples_for_phase per metric. Single-phase scenarios that fire no captures leave this vec![]; the flat-bucket scalars on this struct cover the single-phase case.

See PhaseBucket for the per-phase shape.

Implementations§

Source§

impl ScenarioStats

Source

pub fn phase(&self, phase: Phase) -> Option<&PhaseBucket>

Look up the phase bucket for a PhasePhase::BASELINE for the pre-first-Step settle window, Phase::step(k) for the test author’s 0-indexed Step k. The typed Phase keeps the 1-indexed encoding (Step 0 lives at the underlying step_index = 1) invisible at the call site.

Returns None when no bucket with that phase exists (single-phase scenario, the scenario didn’t reach that Step, or a phase past the last).

Source

pub fn phase_metric( &self, phase: Phase, metric: impl Into<MetricId>, ) -> Option<f64>

Shortcut: look up a single metric value in a specific phase by phase-index. Returns None when: (a) the phase is absent (no bucket with step_index in Self::phases), (b) the phase exists but had no finite samples for that metric, OR (c) metric is a dynamic crate::stats::MetricId key (a scheduler-runtime / payload string) not present in this phase’s stores. A built-in crate::stats::BuiltinMetric cannot be a typo — that is a compile error — so this case needs a dynamic key.

Two stores are checked: PhaseBucket::metrics (via PhaseBucket::get) and, failing that, the cross-cgroup phase SUM of a per-cgroup Counter (PhaseBucket::cgroup_counter_total) for "total_migrations" / "total_iterations" / "total_cpu_time_ns" — registered Counters with no per-sample source, so they live ONLY in the per-cgroup carriers; without this fallback the pooled lookup returned a silent None while the per-cgroup Self::phase_cgroup_metric surfaced the value. Symmetric with crate::vmm::VmResult::phase_metric.

Sentinel-free: Some(0.0) means the reducer produced a real zero from finite samples, NOT “missing data”. See PhaseBucket::metrics for the registry source. When debugging an unexpected None on a dynamic key, check crate::stats::MetricId::def().is_some() to tell an unregistered key from absent data (built-in ids always resolve).

Pass Phase::BASELINE for the settle window or Phase::step(k) for the test author’s 0-indexed Step k — the typed Phase hides the 1-indexed encoding (see Self::phase).

Source

pub fn cgroup_balance_ratio(&self) -> Option<f64>

Cross-cgroup balance: the ratio of the busiest cell’s per-worker throughput to the quietest’s — max / min over each cgroup’s CgroupStats::iterations_per_worker. The bread-and-butter scheduler-fairness assertion (every balance test hand-rolls this max/min over self.cgroups today).

No-worker cgroups (iterations_per_worker() == None) are SKIPPED: a 0-worker cell is a config condition, not a balance signal. Returns None when fewer than two cgroups have workers (a ratio needs two); check the cgroup count separately if every declared cell must have workers. A cell that ran workers but completed zero iterations (measured Some(0.0)) drives the ratio to f64::INFINITY so starvation SURFACES rather than vanishing — matching the None-vs-Some(0.0) discipline of CgroupStats::iterations_per_worker. For an explicit starvation gate, check min > 0 over the same cgroups separately.

Whole-run aggregate: this reads self.cgroups, which sums over all phases. For a single phase’s per-cgroup balance in a multi-phase scenario, read each cgroup’s per-phase throughput (total_iterations / num_workers) from crate::vmm::VmResult::phase_cgroup — the per-phase per-cgroup carriers now landed in PhaseBucket::per_cgroup.

Source

pub fn phase_cgroup_metric( &self, phase: Phase, cgroup: &str, metric: impl Into<MetricId>, ) -> Option<f64>

Per-cgroup analog of Self::phase_metric: look up metric for a named cgroup in a Phase (Phase::BASELINE / Phase::step(k)), via that cgroup’s per-phase carrier (PhaseCgroupStats::get), falling back to PhaseCgroupStats::cgroup_counter for the per-cgroup Counters total_migrations/total_iterations/total_cpu_time_ns. None when the phase has no bucket, no carrier for cgroup, the carrier carried no finite value for the metric, OR the metric is an unregistered dynamic key (a built-in id is typo-proof; an unregistered crate::stats::MetricId has no crate::stats::MetricId::def, same as Self::phase_metric). The N-cgroups-to-N-queryable-sets surface on the AssertResult-holding path (the in-VM post_vm path uses crate::vmm::VmResult::phase_cgroup_metric).

Source

pub fn has_steps(&self) -> bool

True iff the scenario produced at least one Step-phase bucket (any phase with step_index >= 1). False when phases is empty OR contains only BASELINE (the pre-first-Step settle window).

Use this to fail a phase-aware assertion BEFORE calling Self::phase with a Phase::step(k) on a scenario that silently never advanced past BASELINE: a test that declared no Steps, OR a scenario that bailed in setup before any Step ran, would otherwise see Self::phase return None for every Step and the test would either panic on .expect(...) or pass vacuously.

anyhow::ensure!(
    r.stats.has_steps(),
    "scenario produced no Step-phase buckets — \
     declare a Step or read Phase::BASELINE",
);
let throughput = r.stats.phase_metric(Phase::step(0), "throughput");
Source

pub fn run_metric(&self, metric: impl Into<MetricId>) -> Option<f64>

Run-level value for a metric by registry name, for the ext-sourced metric family that carries no typed ScenarioStats field.

Resolves Self::ext_metrics — the run-level map the framework fills post-merge with every metric whose value has no typed struct field: the pooled wake-latency / run-delay distributions and worst-cgroup iteration efficiencies (the MetricKind::Distribution / MetricKind::WorstLowest registry kinds — worst_p99_wake_latency_us, worst_run_delay_us, worst_iterations_per_cpu_sec, …), the derived rates (iteration_rate, and the pooled iterations_per_cpu_sec — distinct from the worst_iterations_per_cpu_sec selector above), the per-thread-group system_time_ns / user_time_ns, and avg_imbalance_ratio / avg_dsq_depth. This is the run-level analogue of Self::phase_metric for that family: code holding the run’s AssertResult reads r.stats.run_metric("worst_run_delay_us") instead of reaching into the raw ext_metrics map by string key (ScenarioStats is the AssertResult::stats field — the value a test body, or a callback that builds an AssertResult via collect_all / execute_scenario, holds). A post_vm callback instead receives a VmResult, which has NO stats field and no run-level Distribution surface — compare those cross-run via cargo ktstr perf-delta.

The ext family is populated only by the #[ktstr_test] eval flow’s post-merge producer (populate_run_distribution_metrics). An AssertResult built by a DIRECT host assertion (assert_not_starved / AssertPlan::assert_cgroup, which never run that producer) carries the per-cgroup values on Self::cgroups but none of these run-level roll-ups, so run_metric returns None for them on that path — read the per-cgroup CgroupStats field directly (e.g. r.stats.cgroups[i].p99_wake_latency_us) there.

Sentinel-free, matching Self::phase_metric: None means the metric is absent from this run (no contributing cgroup or carrier, or a name not present in the map); Some(0.0) is a real measured zero. Check crate::stats::MetricId::def on a dynamic key to tell an unregistered key from genuinely-absent data (built-in ids always resolve). (The map also carries any user-defined extensible-metric keys, plus the framework-internal Rate-component Counters — total_phase_iterations / total_phase_duration_sec / total_iterations_pooled / total_cpu_time_sec, the numerator/denominator plumbing behind iteration_rate / iterations_per_cpu_sec — all of which resolve here too; prefer the derived rate over its raw components.)

RESOLVED here None-aware, ahead of the ext lookup, via a typed dispatch (typed_sentinel_metric): the cross-cgroup metrics worst_spread, worst_migration_ratio, worst_gap_ms, total_migrations, total_iterations, worst_page_locality, worst_cross_node_migration_ratio. The dispatch RE-DERIVES each from the per-cgroup (self.cgroups) / per-phase (self.phases[].per_cgroup, the NUMA metrics) carriers — None when no carrier measured it, Some(0.0) for a measured zero — preserving the sentinel-free contract. The 5 non-NUMA metrics are 0.0-sentinel typed struct fields (a not-measured carrier coerces to 0.0, indistinguishable from a measured zero, so the re-derivation recovers the distinction); the two NUMA roll-ups (worst_page_locality, worst_cross_node_migration_ratio) have no struct field and re-pool purely from the per-phase NUMA carriers. (worst_wake_latency_tail_ratio resolves via the ext lookup as the WakeLatencyTailRatio key.)

NOT resolved here (not in ext_metrics, no typed dispatch):

  • the monitor-sourced run-level metrics (max_imbalance_ratio, max_dsq_depth, stuck_count, total_fallback, total_keep_last), which ScenarioStats does not hold run-level — read those per-phase via Self::phase_metric.

So this resolves the ext-sourced family AND the typed cross-cgroup fields (via the dispatch); only the monitor-sourced run-level metrics remain unresolved here (ScenarioStats holds them only per-phase).

Trait Implementations§

Source§

impl Clone for ScenarioStats

Source§

fn clone(&self) -> ScenarioStats

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ScenarioStats

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for ScenarioStats

Source§

fn default() -> ScenarioStats

Returns the “default value” for a type. Read more
Source§

impl<'de> Deserialize<'de> for ScenarioStats

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl PartialEq for ScenarioStats

Source§

fn eq(&self, other: &ScenarioStats) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
Source§

impl ScenarioStatsClaim for ScenarioStats

Source§

fn claim_cgroups<'a>( &'a self, verdict: &'a mut Verdict, ) -> SeqClaim<'a, CgroupStats>

Source§

fn claim_total_workers<'a>( &'a self, verdict: &'a mut Verdict, ) -> ClaimBuilder<'a, usize>

Source§

fn claim_total_cpus<'a>( &'a self, verdict: &'a mut Verdict, ) -> ClaimBuilder<'a, usize>

Source§

fn claim_total_migrations<'a>( &'a self, verdict: &'a mut Verdict, ) -> ClaimBuilder<'a, u64>

Source§

fn claim_worst_spread<'a>( &'a self, verdict: &'a mut Verdict, ) -> ClaimBuilder<'a, f64>

Source§

fn claim_worst_gap_ms<'a>( &'a self, verdict: &'a mut Verdict, ) -> ClaimBuilder<'a, u64>

Source§

fn claim_worst_gap_cpu<'a>( &'a self, verdict: &'a mut Verdict, ) -> ClaimBuilder<'a, usize>

Source§

fn claim_worst_migration_ratio<'a>( &'a self, verdict: &'a mut Verdict, ) -> ClaimBuilder<'a, f64>

Source§

fn claim_total_iterations<'a>( &'a self, verdict: &'a mut Verdict, ) -> ClaimBuilder<'a, u64>

Source§

fn claim_phases<'a>( &'a self, verdict: &'a mut Verdict, ) -> SeqClaim<'a, PhaseBucket>

Source§

impl Serialize for ScenarioStats

Source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>
where __S: Serializer,

Serialize this value into the given Serde serializer. Read more
Source§

impl StructuralPartialEq for ScenarioStats

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
§

impl<T> Pointable for T

§

const ALIGN: usize

The alignment of pointer.
§

type Init = T

The type for initializers.
§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
§

impl<T> PolicyExt for T
where T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a [WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,

§

impl<T> MaybeSend for T
where T: Send,

§

impl<T> MaybeSend for T
where T: Send,