SampleSeries

Struct SampleSeries 

Source
pub struct SampleSeries { /* private fields */ }
Expand description

Ordered collection of Samples drained from a SnapshotBridge after a VM run completes. Owns the underlying tuples so projection closures can borrow into the reports / stats without copying.

Test authors construct a SampleSeries from super::snapshot::SnapshotBridge::drain_ordered_with_stats via Self::from_drained; non-periodic tags (e.g. Op::CaptureSnapshot captures) coexist in the drain output and are tolerated by the projection helpers — the typical pattern is to pre-filter to periodic tags via Self::periodic_only before asserting.

Implementations§

Source§

impl SampleSeries

Source

pub fn bpf<T, F>(&self, label: impl Into<String>, project: F) -> SeriesField<T>
where F: Fn(&Snapshot<'_>) -> SnapshotResult<T>,

Project the series along the BPF axis. The closure receives each sample’s Snapshot and returns a SnapshotResult<T> — typically a typed value extracted via snap.var(...).as_u64() or snap.map(...).at(...).get(...).as_u64(). Errors flow through into the resulting SeriesField as per-sample Err slots so a temporal-assertion pattern can decide whether to fail or skip on a missing field.

label is owned (impl Into<String>) and lands in crate::assert::temporal::SeriesField::label for failure- message rendering. Callers may pass a &'static str literal or a runtime-built String (for auto-discovered struct or JSON key names).

Source

pub fn bpf_live_u64(&self, name: &str) -> SeriesField<u64>

Project the live scheduler’s <obj>.<section> global variable named name as u64. Per-row equivalent of snap.live_var(name).as_u64(), but with the placeholder short-circuit baked in.

Why this exists. Single-binary tests can use series.bpf("label", |s| s.var(name).as_u64()) directly — var() auto-disambiguates via the active-scheduler walker when multiple maps share a global symbol (e.g. post- Op::ReplaceScheduler with two scheduler instances). The bpf_live_u64 helper saves the closure boilerplate AND makes the live-resolution semantics visible in the call site’s NAME, not buried in a projector body. The label on the resulting SeriesField is the name argument verbatim.

Source

pub fn bpf_live_i64(&self, name: &str) -> SeriesField<i64>

Sibling of Self::bpf_live_u64 projecting as i64.

Source

pub fn bpf_live_f64(&self, name: &str) -> SeriesField<f64>

Sibling of Self::bpf_live_u64 projecting as f64.

Source

pub fn live_bpf_vars_via<const N: usize, P>( &self, names: [&str; N], picker: P, ) -> [SeriesField<u64>; N]
where P: for<'a> Fn(&[(&'a str, Vec<SnapshotField<'a>>)]) -> Option<usize> + Copy,

Per-snapshot co-picked BPF projection of N counters from the SAME global-section map. Lifts Snapshot::live_vars_via to the series level: for each sample, calls the picker ONCE per snapshot and projects the resulting N SnapshotFields as u64 into N parallel SeriesFields.

Why this exists. The single-name Self::bpf closure shape forces tests that need two co-picked counters (e.g. nr_cross_dispatch + nr_same_dispatch from the same scheduler bss copy after Op::ReplaceScheduler) to call the picker TWICE per snapshot — once for each derived SeriesField — paying picker cost 2N instead of N. The per-snapshot dedup happens here: one live_vars_via call per row, eagerly split into N u64 vectors before any SeriesField materializes.

Lifetime / coverage gaps surface per field. If a snapshot is a placeholder, every field’s slot for that row carries the same crate::scenario::snapshot::SnapshotError::PlaceholderSample. If live_vars_via fails (no candidate map has all N names, or the picker returns None), every field’s slot carries the same underlying crate::scenario::snapshot::SnapshotError — the failure is shared, not split. Per-field .as_u64() casts that fail (the picked field doesn’t render as a u64) surface as per-field crate::scenario::snapshot::SnapshotError::TypeMismatch without contaminating sibling fields.

The label routed onto each resulting SeriesField is the caller-supplied name from names at the matching position.

Source

pub fn bpf_map<'a>(&'a self, map_name: &'a str) -> BpfMapProjector<'a>

Auto-project a top-level BPF map’s struct members. The returned BpfMapProjector auto-discovers struct member names at sample 0 and exposes them via .field_u64(name) / .field_i64(name) / .field_f64(name) — a caller that wants every scalar field of a BSS struct without enumerating each one by hand calls series.bpf_map("scx_obj.bss").at(0) and then .field_u64("nr_dispatched") for the field of interest.

Top-level scalar fields only. The auto-projector reads directly-named struct members (e.g. "nr_dispatched", "stall"). Nested struct members (e.g. "ctx.weight") and deeper paths are NOT auto-discoverable through the typed field_* helpers — for those, use the manual closure projection SampleSeries::bpf with |snap| snap.var("ctx").get("weight").as_u64() (or the equivalent map-walking shape). Per-CPU maps are also out of scope: they require an explicit .cpu(N) narrow on the Snapshot accessor surface, so callers route through the manual closure path for those as well.

Source§

impl SampleSeries

Source

pub fn host(&self) -> Option<HostView<'_>>

Borrowed view over the per-sample host-side per-CPU snapshot data captured into each FailureDumpReport::per_cpu_time. Returns None when the series is empty; otherwise yields a HostView that exposes the per-CPU timeline (rows sorted by elapsed-ms) and a closure-based projector compatible with the temporal-assertion patterns in crate::assert::temporal.

Orthogonal to Self::monitor: host() is the per-sample per-CPU TIMELINE; monitor() is the per-VM-run cross-CPU AGGREGATE. Tests that want both perspectives chain them independently from the same series.

The returned HostView<'_> borrows from this series, so the series must outlive any projection chained off the view.

Source§

impl SampleSeries

Source

pub fn monitor(&self) -> Option<MonitorView<'_>>

Borrowed view over the per-VM-run host monitor report associated with this series. None when the monitor did not run (host-only tests, early VM failure, or Self::from_drained was called with None monitor).

Monitor is per-series — aggregates inside the returned MonitorView refer to THAT series’ monitoring window only; no cross-series merge is supported. A test that constructs two SampleSeries from two VM runs gets two independent monitors.

The returned MonitorView<'_> borrows from this series, so the series must outlive any projection chained off the view (e.g. series.monitor().map(|m| m.scx_events()?.total_pairs()) — the whole chain is bound by series’s lifetime).

Source§

impl SampleSeries

Source

pub fn stats<T, F>( &self, label: impl Into<String>, project: F, ) -> SeriesField<T>
where F: Fn(StatsValue<'_>) -> SnapshotResult<T>,

Project the series along the stats axis. The closure receives each sample’s stats JSON (when present) and returns a SnapshotResult<T>. Samples whose stats is Err(reason) get a Err(MissingStats { tag, reason }) slot — temporal assertions surface that as a per-sample missing-stats failure rather than vacuously skipping it, so a coverage gap is never silent and the operator sees the why (no scheduler binary configured, relay timed out, scheduler returned errno, etc.).

label is owned (impl Into<String>) and matches the shape of Self::bpf — pass a literal or a runtime-built String for auto-discovered keys.

Source

pub fn stats_live_u64(&self, path: &str) -> SeriesField<u64>

Project the live scheduler’s stats JSON field at path as u64. Per-row equivalent of series.stats(label, |s| s.get(path).as_u64()) with the boilerplate elided. Mirrors Self::bpf_live_u64 for naming parity across axes.

Why “live” applies — per-request freshness, not a buffer. Each periodic snapshot issues a FRESH scx_stats request just before the freeze rendezvous fires; the response in row.stats came from whichever scheduler was alive at request-issue time. There is no relay buffer of “the last stats we saw” — a stale-pre-swap response cannot land in a post-swap sample. After Op::ReplaceScheduler the host reconnects to the new scheduler’s scx_stats endpoint before the next periodic boundary issues its request, so post-swap samples carry the new scheduler’s data. The _live suffix matches the BPF axis naming for cross-axis vocabulary consistency AND describes the actual freshness guarantee — same semantic across both axes.

Source

pub fn stats_live_i64(&self, path: &str) -> SeriesField<i64>

Sibling of Self::stats_live_u64 projecting as i64.

Source

pub fn stats_live_f64(&self, path: &str) -> SeriesField<f64>

Sibling of Self::stats_live_u64 projecting as f64.

Source

pub fn stats_path<'a>(&'a self, path: &str) -> StatsPathProjector<'a>

Auto-project a stats-JSON sub-tree. The returned StatsPathProjector resolves the tree at sample 0 and exposes object keys via .key(name) (for nested layer / cgroup objects) or .field(name) (for scalar leaves). path may be empty — series.stats_path("") projects from the root and is the canonical entry for system-level stats fields like busy, antistall, system_cpu_util_ewma, etc.

Source§

impl SampleSeries

Source

pub fn from_drained( drained: Vec<(String, FailureDumpReport, Option<Value>, Option<u64>)>, monitor: Option<MonitorReport>, ) -> Self

Build a series from the bridge’s drained tuple. Every entry is preserved in the order the bridge surfaced, including non-periodic tags — callers that want the periodic-only view chain .periodic_only().

monitor is the per-VM-run MonitorReport (typically result.monitor.clone() from a VmResult). Pass None when the monitor did not run (host-only tests, early VM failure). Surfaced via Self::monitor for typed projection of the summary + scx_events + per-sample timelines.

Source

pub fn from_drained_typed( drained: Vec<DrainedSnapshotEntry>, monitor: Option<MonitorReport>, ) -> Self

Production-path constructor: takes the typed Result<serde_json::Value, MissingStatsReason> shape returned by SnapshotBridge::drain_ordered_with_stats, preserving the specific failure mode (relay error, scheduler errno, watchdog cancellation, etc.). Use this when the caller has access to the bridge drain output; tests prefer Self::from_drained which accepts the simpler Option shape and collapses absent → NoSchedulerBinary.

Source

pub fn empty() -> Self

Empty series. Useful for tests and for the no-periodic- capture case where every assertion vacuously passes.

Source

pub fn is_empty(&self) -> bool

True when no samples are present.

Source

pub fn len(&self) -> usize

Number of samples in the series.

Source

pub fn periodic_only(self) -> Self

Filter the series to entries whose tag begins with "periodic_". Periodic captures are the only entries the temporal-assertion patterns are designed for; on-demand Op::CaptureSnapshot and watchpoint-fire captures share the bridge’s tag namespace and would otherwise mix into the timeline as off-cadence outliers. Consumes self because the filter rebuilds the owning row vec — when a borrowed view is needed instead, see Self::periodic_ref which iterates the same rows without taking ownership.

Source

pub fn periodic_ref(&self) -> impl Iterator<Item = Sample<'_>>

Borrowed equivalent of Self::periodic_only: yields a borrowed-view iterator over Samples whose tag starts with "periodic_", without consuming the series. Use when a single test asserts on both periodic-only and all-captures views from the same series.

Source

pub fn iter_samples(&self) -> impl Iterator<Item = Sample<'_>>

Iterate over Sample views borrowing into this series. Each yielded Sample<'_> carries the tag, elapsed-ms, borrowed Snapshot, borrowed Result<&Value, &MissingStatsReason> stats, the per-sample phase step index, and the workload-relative boundary offset.

Source

pub fn by_stamped_phase(&self) -> BTreeMap<u16, Vec<Sample<'_>>>

Group samples by the RAW bridge-stamped scenario phase. The returned map is keyed by step_index (1-indexed phase encoding — 0 is BASELINE, 1..=N align with scenario Step ordinals); each entry is the ordered run of samples that fell in that phase, preserving the iteration order produced by Self::iter_samples.

Samples that lack a stamped step index (the unstamped fixture path via super::snapshot::SnapshotBridge::capture / super::snapshot::SnapshotBridge::store / super::snapshot::SnapshotBridge::store_with_stats) fall under key 0 per the “no stamped index” fallback — the same bucket BASELINE samples land in. The fixture / BASELINE collision is acceptable because both flavours represent pre-first-Step (or unstamped) state from the bucketer’s perspective; production callers that need to distinguish can inspect Sample::step_index directly.

CAVEAT — prefer Self::by_stimulus_phase when a stimulus timeline is available: the bridge stamp is the step active at (deferred) FIRE time, so under the dump-prerequisite gate a burst of captures can all stamp the same late CURRENT_STEP and collapse every sample into one phase. by_stimulus_phase re-derives the phase from each sample’s timing-independent boundary_offset_ms, which is immune to the burst.

The phase-aware aggregator consumes this map to compute per-phase metric reductions (Counter last - first delta, Gauge / Peak / Timestamp via crate::stats::aggregate_samples).

Source

pub fn by_stimulus_phase( &self, stimulus_events: &[StimulusEvent], ) -> BTreeMap<u16, Vec<Sample<'_>>>

Group samples by the guest step whose stimulus window contains each sample’s workload-relative boundary_offset_ms, rather than the raw bridge-stamped step_index (Self::by_stamped_phase). The returned map uses the same 1-indexed phase key (0 = BASELINE, 1..=N = Step ordinals) and preserves Self::iter_samples order within each bucket.

This is the correct grouping whenever a stimulus timeline is available: boundary_offset_ms is derived from the scheduled boundary, NOT the (deferred) fire time, so it survives the dump-prerequisite-gate burst that makes every periodic capture stamp the same late CURRENT_STEP (the phases.len() == 1 collapse by_stamped_phase is subject to). Samples with no boundary_offset_ms (on-demand / fixture captures) fall back to their stamped step_index.

Unlike the folded scalar crate::assert::PhaseBuckets that crate::assert::build_phase_buckets_with_stimulus returns, this keeps the per-sample Sample views (full Snapshot / dsq access) per phase. build_phase_buckets_with_stimulus itself is built on this method.

Trait Implementations§

Source§

impl Clone for SampleSeries

Source§

fn clone(&self) -> SampleSeries

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 SampleSeries

Source§

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

Formats the value using the given formatter. Read more

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
§

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

§

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