pub struct CgroupStats {Show 19 fields
pub num_workers: usize,
pub num_cpus: usize,
pub avg_off_cpu_pct: Option<f64>,
pub min_off_cpu_pct: Option<f64>,
pub max_off_cpu_pct: Option<f64>,
pub spread: Option<f64>,
pub max_gap_ms: u64,
pub max_gap_cpu: usize,
pub total_migrations: u64,
pub migration_ratio: f64,
pub p99_wake_latency_us: f64,
pub median_wake_latency_us: f64,
pub wake_latency_cv: f64,
pub total_iterations: u64,
pub mean_run_delay_us: f64,
pub worst_run_delay_us: f64,
pub page_locality: f64,
pub cross_node_migration_ratio: f64,
pub ext_metrics: BTreeMap<String, f64>,
}Expand description
Per-cgroup statistics from worker telemetry.
§Percentile convention
p99_wake_latency_us and median_wake_latency_us are computed
by percentile using the NEAREST-RANK (Type 1) definition:
the value at ceil(n * p) - 1 in sorted order. No interpolation
between samples. This matches the percentile convention used
throughout schbench and the BPF latency histograms the project
cross-references, so a ktstr p99 reading aligns with a
schbench lat99 without adjustment. For small n (wake
reservoirs cap at MAX_WAKE_SAMPLES = 100_000 per worker —
see workload.rs) nearest-rank is also numerically stable —
interpolation between the two nearest ranks would be
implementation-defined at sample-set boundaries.
§CV pooling scope
wake_latency_cv is POOLED across every sample from every
worker in the cgroup, not a per-worker CV averaged back. That
collapses per-worker dispersion into the cgroup-wide signal:
two workers with uniformly low jitter but different means
produce a high pooled CV (mean-shift between workers inflates
stddev), while per-worker CV would show neither worker as
bad. This is intentional for the fairness threshold
(max_wake_latency_cv): a scheduler that gives worker A
10µs wakes and worker B 1ms wakes is failing fairness even if
each worker on its own is tight. Tests comparing single-worker
behavior should scope their assertions to per-worker data
rather than this aggregate.
§Derived ratios
Two metrics are DERIVED rather than measured and live as
&self methods, NOT as serde-serialized fields:
Self::wake_latency_tail_ratio (= p99/median) and
Self::iterations_per_worker (= total_iterations/num_workers).
Pre-1.0 cleanup eliminated the prior stored-field shadow and
derive_ratios stamper. Consumers always recompute on read,
so a hand-constructed fixture or a deserialized sidecar from an
older build cannot silently carry a stale ratio. The roll-up
fields on ScenarioStats::worst_wake_latency_tail_ratio /
ScenarioStats::worst_iterations_per_worker aggregate these
methods over per-cgroup Self entries during
AssertResult::merge.
Fields§
§num_workers: usizeNumber of workers in this cgroup.
num_cpus: usizeDistinct CPUs used across all workers in this cgroup.
avg_off_cpu_pct: Option<f64>Mean off-CPU percentage across workers (off_cpu_ns /
wall_time_ns * 100). None when no worker reported a
positive wall_time_ns (off-CPU% is undefined without wall
time) — distinct from Some(0.0), a measured “never off
CPU”. The Option keeps a not-measured cgroup from reading
as a perfectly-on-CPU one in the telemetry consumers
(ScenarioStats.cgroups).
min_off_cpu_pct: Option<f64>Minimum off-CPU percentage across workers. None under the
same no-measurable-wall-time condition as avg_off_cpu_pct.
max_off_cpu_pct: Option<f64>Maximum off-CPU percentage across workers. None under the
same no-measurable-wall-time condition as avg_off_cpu_pct.
spread: Option<f64>max_off_cpu_pct - min_off_cpu_pct. Measures scheduling
fairness within the cgroup. None when off-CPU% was not
measured (no worker with positive wall time) — a not-measured
cgroup is inconclusive for fairness, NOT “spread 0 = perfectly
fair”. Some(0.0) means a real measured zero spread.
max_gap_ms: u64Longest scheduling gap across all workers (ms).
max_gap_cpu: usizeCPU where the longest scheduling gap occurred.
total_migrations: u64Sum of CPU migration counts across all workers.
migration_ratio: f64Migrations per iteration (total_migrations / total_iterations).
p99_wake_latency_us: f6499th percentile wake latency across all workers (microseconds).
median_wake_latency_us: f64Median wake latency across all workers (microseconds).
wake_latency_cv: f64Coefficient of variation (stddev / mean) of wake latencies.
Computed over the POOLED latency samples from every worker in
the cgroup, not as a mean of per-worker CVs. Per-worker
dispersion is therefore masked: a cgroup with one tight
worker and one wildly variable worker can report a moderate
pooled CV that looks healthier than either constituent. Use
WorkerReport::wake_latencies_ns directly if per-worker
CV is needed.
total_iterations: u64Sum of iteration counts across all workers.
mean_run_delay_us: f64Mean schedstat run delay across workers (microseconds).
worst_run_delay_us: f64Worst schedstat run delay across workers (microseconds).
page_locality: f64Fraction of pages on the expected NUMA node(s) (0.0-1.0).
Derived from /proc/self/numa_maps and the worker’s
MemPolicy.
cross_node_migration_ratio: f64Cross-node page migration ratio from /proc/vmstat
numa_pages_migrated delta divided by total allocated pages.
ext_metrics: BTreeMap<String, f64>Extensible metrics for the generic comparison pipeline.
Implementations§
Source§impl CgroupStats
impl CgroupStats
Sourcepub fn wake_latency_tail_ratio(&self) -> f64
pub fn wake_latency_tail_ratio(&self) -> f64
Wake-latency tail amplification:
p99_wake_latency_us / median_wake_latency_us. Returns 0.0
when median_wake_latency_us <= 0.0 so the result never
propagates NaN / Infinity into downstream
finite_or_zero filters. Method-only access (no stored
shadow) — recomputed every call from the raw fields.
Unitless; ≥1.0 by definition of order statistics (p99 cannot
undershoot the median on the same sample set). Values far
above 1.0 signal a long tail — the scheduler wakes most
workers promptly but occasionally stalls some, a regression
axis that neither median_* nor p99_* exposes in
isolation.
Sourcepub fn iterations_per_worker(&self) -> Option<f64>
pub fn iterations_per_worker(&self) -> Option<f64>
Throughput per parallel degree:
total_iterations / num_workers. None when
num_workers == 0 (no worker reported, so per-worker
throughput is undefined — distinct from a measured zero);
Some(0.0) when workers ran but completed zero iterations
(a real throughput collapse). The None / Some(0.0) split
is load-bearing: the cross-cgroup roll-up in
AssertResult::merge must treat a measured zero as the
worst reading (it wins the “lowest” bucket) while skipping a
no-data cgroup — collapsing both to 0.0 would hide a
starved cgroup behind the no-data sentinel. Method-only
access (no stored shadow) — recomputed every call from the
raw fields.
Only meaningful across runs of the SAME variant (equal
scenario duration): cross-variant comparison is misleading
because this metric is NOT rate-normalized — a longer-
running scenario racks up more iterations per worker even if
the scheduler is identical. stats compare-style
comparisons hold scenario, topology, and work_type constant
before reading this method.
Trait Implementations§
Source§impl CgroupStatsClaim for CgroupStats
impl CgroupStatsClaim for CgroupStats
fn claim_num_workers<'a>( &'a self, verdict: &'a mut Verdict, ) -> ClaimBuilder<'a, usize>
fn claim_num_cpus<'a>( &'a self, verdict: &'a mut Verdict, ) -> ClaimBuilder<'a, usize>
fn claim_avg_off_cpu_pct<'a>( &'a self, verdict: &'a mut Verdict, ) -> ClaimBuilder<'a, Option<f64>>
fn claim_min_off_cpu_pct<'a>( &'a self, verdict: &'a mut Verdict, ) -> ClaimBuilder<'a, Option<f64>>
fn claim_max_off_cpu_pct<'a>( &'a self, verdict: &'a mut Verdict, ) -> ClaimBuilder<'a, Option<f64>>
fn claim_spread<'a>( &'a self, verdict: &'a mut Verdict, ) -> ClaimBuilder<'a, Option<f64>>
fn claim_max_gap_ms<'a>( &'a self, verdict: &'a mut Verdict, ) -> ClaimBuilder<'a, u64>
fn claim_max_gap_cpu<'a>( &'a self, verdict: &'a mut Verdict, ) -> ClaimBuilder<'a, usize>
fn claim_total_migrations<'a>( &'a self, verdict: &'a mut Verdict, ) -> ClaimBuilder<'a, u64>
fn claim_migration_ratio<'a>( &'a self, verdict: &'a mut Verdict, ) -> ClaimBuilder<'a, f64>
fn claim_p99_wake_latency_us<'a>( &'a self, verdict: &'a mut Verdict, ) -> ClaimBuilder<'a, f64>
fn claim_median_wake_latency_us<'a>( &'a self, verdict: &'a mut Verdict, ) -> ClaimBuilder<'a, f64>
fn claim_wake_latency_cv<'a>( &'a self, verdict: &'a mut Verdict, ) -> ClaimBuilder<'a, f64>
fn claim_total_iterations<'a>( &'a self, verdict: &'a mut Verdict, ) -> ClaimBuilder<'a, u64>
fn claim_mean_run_delay_us<'a>( &'a self, verdict: &'a mut Verdict, ) -> ClaimBuilder<'a, f64>
fn claim_worst_run_delay_us<'a>( &'a self, verdict: &'a mut Verdict, ) -> ClaimBuilder<'a, f64>
fn claim_page_locality<'a>( &'a self, verdict: &'a mut Verdict, ) -> ClaimBuilder<'a, f64>
fn claim_cross_node_migration_ratio<'a>( &'a self, verdict: &'a mut Verdict, ) -> ClaimBuilder<'a, f64>
Source§impl Clone for CgroupStats
impl Clone for CgroupStats
Source§fn clone(&self) -> CgroupStats
fn clone(&self) -> CgroupStats
1.0.0 · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreSource§impl Debug for CgroupStats
impl Debug for CgroupStats
Source§impl Default for CgroupStats
impl Default for CgroupStats
Source§fn default() -> CgroupStats
fn default() -> CgroupStats
Source§impl<'de> Deserialize<'de> for CgroupStats
impl<'de> Deserialize<'de> for CgroupStats
Source§fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
Source§impl PartialEq for CgroupStats
impl PartialEq for CgroupStats
Source§impl Serialize for CgroupStats
impl Serialize for CgroupStats
impl StructuralPartialEq for CgroupStats
Auto Trait Implementations§
impl Freeze for CgroupStats
impl RefUnwindSafe for CgroupStats
impl Send for CgroupStats
impl Sync for CgroupStats
impl Unpin for CgroupStats
impl UnwindSafe for CgroupStats
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
§impl<T> Instrument for T
impl<T> Instrument for T
§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
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 moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
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