SeriesField

Struct SeriesField 

Source
pub struct SeriesField<T> { /* private fields */ }
Expand description

Per-sample column extracted from a SampleSeries. Each slot is a SnapshotResult<T> so a missing or type-mismatched field does NOT abort the whole projection — it surfaces at the temporal-assertion site as a per-sample error the caller decides how to handle.

The label, tags, and per-sample timestamps are carried so failure-path messages name the offending sample without the caller re-threading the series. Tags and elapsed-ms vectors are always the same length as values.

Implementations§

Source§

impl<T> SeriesField<T>

Source

pub fn from_parts( label: impl Into<String>, tags: Vec<String>, elapsed_ms: Vec<u64>, values: Vec<SnapshotResult<T>>, ) -> Self

Build a new field. Internal — projection helpers in crate::scenario::sample call this on the series side. 4-arg backward-compat constructor: defaults phases to all None. New consumers that have phase context per sample should call Self::from_parts_with_phases instead.

Source

pub fn from_parts_with_phases( label: impl Into<String>, tags: Vec<String>, elapsed_ms: Vec<u64>, values: Vec<SnapshotResult<T>>, phases: Vec<Option<Phase>>, ) -> Self

Build a new field with explicit per-sample phase tags. phases.len() MUST equal values.len() — the four parallel vecs (tags / elapsed_ms / values / phases) are addressed by the same index throughout. Like Self::from_parts, this is intended for projection helpers in crate::scenario::sample that already know each sample’s step_index from the drained bridge tuple. 5-arg convenience taking MEASURED Vec<u64> timestamps; wraps each in Some and delegates to Self::from_parts_with_phases_opt. Test fixtures (which always model measured samples) and any all-measured caller use this. The production projection funnel — which threads Option<u64> from the bridge so “not measured” stays distinct from “measured 0” — calls Self::from_parts_with_phases_opt directly.

Source

pub fn from_parts_with_phases_opt( label: impl Into<String>, tags: Vec<String>, elapsed_ms: Vec<Option<u64>>, values: Vec<SnapshotResult<T>>, phases: Vec<Option<Phase>>, ) -> Self

None-aware constructor: elapsed_ms[i] == None means the bridge recorded no timestamp for that sample (not measured), kept distinct from a measured Some(0). Temporal patterns that do timestamp math (EachClaim-free rate_within dt, steady_within warmup gate, converges_to deadline gate) SKIP a None-anchored sample rather than fabricating a 0 (the silent-wrong-answer this distinction prevents).

Source

pub fn phases_iter(&self) -> impl Iterator<Item = Option<Phase>> + '_

Per-sample phase tag, parallel to values. None for fixture / unstamped samples; Some(phase) for production captures whose source row carried a step_index.

Source

pub fn aggregate_by_phase(&self, metric: &MetricDef) -> BTreeMap<Phase, f64>
where T: Copy + Into<f64>,

Per-phase folded reduction: extract the f64 value from each Ok-sample (skipping per-sample errors) and route the per-phase slice through crate::stats::aggregate_samples_for_phase so Counter kinds get the cumulative-delta semantic while other kinds inherit the flat-run aggregator. Returns one entry per phase that has at least one Ok-sample with a finite value; phases with all-err / all-NaN samples are absent from the map (consistent with aggregate_samples_for_phase returning None on that input). Skips None-phase samples — fixture / unstamped data does not have a phase key to bucket against.

The API entry point a test author uses to ask “what was the per-phase reduction of metric X?” without having to thread by_phase + the kind-aware reducer manually.

Source

pub fn sum_by_phase(&self) -> BTreeMap<Phase, f64>
where T: Copy + Into<f64>,

Per-phase SUM of the Ok-sample values — the per-phase total for a metric whose samples are already per-read DELTAS, e.g. a scheduler-defined scx_stats metric the scheduler deltas server-side per reader request (one ktstr snapshot = one request = one delta), read via series.stats(name). Returns one entry per phase with at least one finite Ok-sample; all-err / all-NaN phases and None-phase (fixture / unstamped) samples are excluded, matching Self::aggregate_by_phase.

This is the crate::stats::MetricKind::DeltaSum per-phase reduction WITHOUT requiring a crate::stats::MetricDef — the ergonomic accessor for the common case “I read a delta-reported scx_stats metric; give me each phase’s total.” For a registered metric, aggregate_by_phase(&def) with def.kind == DeltaSum gives the identical result.

Boundary: the first in-phase delta straddles the phase boundary (it spans from the last pre-phase read to the first in-phase read, so it carries a little pre-phase activity); it is attributed to the phase its read lands in — a slight left-edge over-attribution, the deliberate semantic since a per-read delta cannot be split.

Source

pub fn for_each_phase(&self, f: impl FnMut(Phase, &[SampleTriple<'_, T>]))

Apply f once per phase, with the per-phase slice of SampleTriple<T>s. None-phase samples (fixture / unstamped) are skipped — callers wanting them call Self::by_phase directly and handle the second-tuple none_bucket themselves. Phases iterate in Phase order (BASELINE first, then Step[0], Step[1], …) per the BTreeMap key ordering, which lets temporal-pattern consumers apply a per-phase reduction without restating the by_phase unpacking at every call site.

field.for_each_phase(|phase, samples| {
    // apply pattern X to samples, scoped to `phase`
});
Source

pub fn by_phase(&self) -> ByPhasePartition<'_, T>

Partition samples by phase. None-phase samples bucket into the returned none_bucket outside the BTreeMap; phase values bucket by their Phase key. Each bucket retains the per-sample SampleTriple<T> the standard Self::iter_full yields.

Source

pub fn label(&self) -> &str

Label for failure-message rendering.

Source

pub fn len(&self) -> usize

Number of samples in the field.

Source

pub fn is_empty(&self) -> bool

True when no samples are present.

Source

pub fn values_iter(&self) -> impl Iterator<Item = &SnapshotResult<T>>

Iterate over per-sample values (each a SnapshotResult<T>).

Source

pub fn iter_full( &self, ) -> impl Iterator<Item = (&str, Option<u64>, &SnapshotResult<T>)>

Iterate over the full per-sample triple — (tag, elapsed_ms, &SnapshotResult<T>). Lets a caller consume the projected column alongside its sample identity without re-threading the source SampleSeries. Yields entries in the same order as the underlying Vec<SnapshotResult<T>> storage; tags and elapsed-ms vectors are guaranteed equal-length to values by Self::from_parts_with_phases_opt’s assert_eq! checks (which run in both debug and release builds).

Source

pub fn each<'v>(&self, verdict: &'v mut Verdict) -> EachClaim<'_, 'v, T>

Open a per-sample claim builder for scalar comparators (at_least, at_most, between). Each successful sample runs the comparator independently; the first failure records a detail; subsequent failures pile on so the timeline shows every offending sample, not just the first. Borrows the verdict mutably for the duration of the comparator chain.

Source

pub fn phase(&self, phase: Phase) -> Vec<SampleTriple<'_, T>>

Iterate the SampleTriples for one specific phase. Sugar for Self::by_phase().0.get(&phase).map(...) that drops the tuple-destructure noise the user otherwise repeats at every per-phase site. Returns an empty iterator when the phase had no samples; callers that need to distinguish “empty bucket” from “phase never observed” can use Self::by_phase directly.

Source

pub fn value_at_phase(&self, phase: Phase) -> Option<T>
where T: Copy,

Single-value reduction for a cumulative counter or any metric where “the last sample of the phase” is the load-bearing value: returns the last Ok-sample’s value for phase, or None when the phase had zero Ok-samples. Per-sample errors are skipped so a one-off projection hiccup doesn’t drop the whole phase. The “value at end of phase” semantic is what cross-phase counter comparisons (e.g. “dispatch count of Step[1] ≤ 0.85 × dispatch count of Step[0]”) almost always want, so this primitive removes the closure-over-by_phase-triples boilerplate the user otherwise writes at every site.

Source

pub fn last_per_phase(&self) -> BTreeMap<Phase, T>
where T: Copy,

Reduce to a per-phase last-Ok-value map. The companion to Self::value_at_phase for callers that want every phase at once: each present phase maps to its last successful sample’s value. Phases with all-Err samples are absent from the map. Matches the “cumulative-counter-at-end-of-phase” semantic Self::aggregate_by_phase applies for Counter metrics, but skips the kind-aware fold so callers reaching for the raw last value don’t pay the projection cost.

Source

pub fn first_per_phase(&self) -> BTreeMap<Phase, T>
where T: Copy,

Reduce to a per-phase first-Ok-value map. The symmetric companion to Self::last_per_phase: each present phase maps to its first successful sample’s value. Per-sample errors are skipped so a one-off projection hiccup at the start of a phase doesn’t drop the whole phase. Phases with all-Err samples are absent from the map.

The load-bearing pairing is last_per_phase - first_per_phase — the work done WITHIN a phase, with no contribution from prior phases — surfaced directly via Self::counter_delta_per_phase.

Source

pub fn counter_delta_per_phase(&self) -> BTreeMap<Phase, T>
where T: Copy + PartialOrd + Sub<Output = T> + Default + Debug,

Per-phase cumulative-counter delta: last_per_phase(p) - first_per_phase(p) for every phase with at least one Ok sample. The reducer A/B-compare tests reach for when a metric is a cumulative counter still accruing from prior phases — value_at_phase’s last-sample reading carries the whole-run accumulation, which lets prior-phase activity muddy the cross-phase ratio. The delta isolates the work performed WITHIN each phase so ratio(phase_delta(later) / phase_delta(earlier)) answers the question the operator actually asked: “did later’s activity drop relative to earlier’s?”

Single-Ok-sample phases yield a delta of zero (first == last). Phases with all-Err samples are absent. Phases that appear in first_per_phase but not last_per_phase (the out-of-order edge case) are also absent — the delta is well-defined only when both endpoints are present.

The reducer is intentionally generic: the test author owns (same_delta, cross_delta)-style compositions (e.g. fold two counter-delta maps into a per-phase fraction) without the framework needing a registered MetricDef. For Counter-typed registered metrics, the equivalent MetricDef-aware path is Self::aggregate_by_phase.

Counter regression: when a phase’s last sample reads LOWER than its first, the reducer emits a tracing::warn! naming the field label + phase + (first, last) and stores T::default() for that phase (e.g. 0 for u64). The underlying counter is assumed non-decreasing within a phase (the common case for BPF per-event counters). A regression can mean either an upstream-signal bug (the counter source itself rolled back) OR a framework picker drift mid-phase (e.g. crate::scenario::sample::SampleSeries::bpf_live_u64 resolved to different bss copies across same-phase snapshots in a post-Op::ReplaceScheduler swap window). Either way the regression is reported as zero progress rather than panicking, so a single bad sample does not crash the assertion engine.

Source

pub fn ratio_across_phases<'v>( &self, verdict: &'v mut Verdict, earlier: Phase, later: Phase, ) -> CrossPhaseRatio<'v, T>
where T: Copy + Into<f64> + Display,

Cross-phase comparator: pin value_at_phase(later) / value_at_phase(earlier) against a ceiling AND record the computed ratio + both phase values in the verdict so --nocapture runs surface the actual numbers without a per-test println! boilerplate. Records a failure when either phase had no Ok-samples, when the earlier value is zero (no baseline), or when the ratio exceeds ceiling. On success records an informational note carrying the observed ratio + both values so the operator can see the margin against the threshold.

Returns &mut Verdict for chaining.

Source§

impl<T> SeriesField<T>
where T: PartialOrd + Display + Copy,

Source

pub fn nondecreasing<'v>(&self, verdict: &'v mut Verdict) -> &'v mut Verdict

Pass when every consecutive pair satisfies values[i] <= values[i+1]. A common shape for kernel counters whose only legal direction is up. Per-sample projection errors are SKIPPED — the affected pair-comparison is dropped, the skip count is logged as a verdict Note so coverage gaps stay visible, and the verdict is NOT flipped on a missing-sample condition (which is structurally missing data, not a regression). Adjacent samples on either side of a gap are still checked against each other.

Source

pub fn strictly_increasing<'v>( &self, verdict: &'v mut Verdict, ) -> &'v mut Verdict

Pass when every consecutive pair satisfies values[i] < values[i+1]. Useful for counters that MUST advance every period (e.g. a heartbeat tick). Same skip-on- projection-error semantics as Self::nondecreasing.

Source§

impl SeriesField<f64>

Source

pub fn rate_within<'v>( &self, verdict: &'v mut Verdict, lo: f64, hi: f64, ) -> &'v mut Verdict

Pass when every consecutive (delta_value / delta_ms) lies in [lo, hi]. The rate is computed with millisecond resolution from the per-sample elapsed-ms timestamps, so a counter that should advance at ~1 unit/ms reads cleanly as rate_within(0.5, 2.0). A zero-time delta between adjacent samples lands as a caller-side or framework failure (samples too close to compute a rate); the detail names the offending pair.

Source

pub fn steady_within<'v>( &self, verdict: &'v mut Verdict, warmup_ms: u64, tolerance: f64, ) -> &'v mut Verdict

Pass when every post-warmup sample (elapsed_ms >= warmup_ms) lies inside mean·(1-tolerance), mean·(1+tolerance). tolerance is a fraction (0.10 = ±10%). The mean is computed over the post-warmup samples only — the warmup region is excluded so ramp-up does not bias the steady- state baseline. Per-sample projection errors are SKIPPED (with a verdict Note logging the count and tags); they are treated as gaps in coverage, not band violations, so a missing post-warmup sample does not flip the verdict.

Source

pub fn converges_to<'v>( &self, verdict: &'v mut Verdict, target: f64, tolerance: f64, deadline_ms: u64, ) -> &'v mut Verdict

Pass when three consecutive samples land inside [target-tolerance, target+tolerance] AT OR BEFORE deadline_ms. The intent is “the system stabilizes near target by the deadline” — three consecutive in-band samples are the convergence-witness shape. Failures fire when the deadline passes without a witness.

Source

pub fn ratio_within<'v>( &self, verdict: &'v mut Verdict, other: &SeriesField<f64>, lo: f64, hi: f64, ) -> &'v mut Verdict

Pass when every consecutive (self_value / other_value) lies in [lo, hi]. Cross-field correlation: e.g. ensure a per-cgroup utilization always tracks a per-cgroup runtime within a fixed band. The two series MUST have matching length and tags; mismatches fire a single caller-error detail. Per-sample projection errors on EITHER lhs or rhs are SKIPPED — the affected pair is dropped, the skip count is logged as a verdict Note, and the verdict is NOT flipped on missing-data conditions.

Source§

impl SeriesField<bool>

Source

pub fn always_true<'v>(&self, verdict: &'v mut Verdict) -> &'v mut Verdict

Pass when every sample’s value is true. Per-sample projection errors fail the assertion. Use for boolean invariants — e.g. “scheduler is alive at every periodic boundary” projected as snap.var("scheduler_alive").as_bool().

Trait Implementations§

Source§

impl<T: Clone> Clone for SeriesField<T>

Source§

fn clone(&self) -> SeriesField<T>

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<T: Debug> Debug for SeriesField<T>

Source§

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

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl<T> Freeze for SeriesField<T>

§

impl<T> RefUnwindSafe for SeriesField<T>
where T: RefUnwindSafe,

§

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

§

impl<T> Sync for SeriesField<T>
where T: Sync,

§

impl<T> Unpin for SeriesField<T>
where T: Unpin,

§

impl<T> UnwindSafe for SeriesField<T>
where T: UnwindSafe,

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
§

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

§

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