SidecarResult

Struct SidecarResult 

Source
pub struct SidecarResult {
Show 32 fields pub test_name: String, pub topology: String, pub scheduler: String, pub scheduler_commit: Option<String>, pub resolve_source: Option<String>, pub project_commit: Option<String>, pub payload: Option<String>, pub metrics: Vec<PayloadMetrics>, pub passed: bool, pub skipped: bool, pub inconclusive: bool, pub expected_failure: bool, pub stats: ScenarioStats, pub monitor: Option<MonitorSummary>, pub periodic_fired: u32, pub periodic_target: u32, pub vcpus: u32, pub cpu_budget: u32, pub stimulus_events: Vec<StimulusEvent>, pub work_type: String, pub verifier_stats: Vec<ProgVerifierStats>, pub kvm_stats: Option<KvmStatsTotals>, pub sysctls: Vec<String>, pub kargs: Vec<String>, pub kernel_version: Option<String>, pub kernel_commit: Option<String>, pub timestamp: String, pub run_id: String, pub host: Option<HostContext>, pub cleanup_duration_ms: Option<u64>, pub run_source: Option<String>, pub perf_delta_assertions: Vec<PerfDeltaAssertionRecord>,
}
Expand description

Test result sidecar written to KTSTR_SIDECAR_DIR for post-run analysis.

Fields§

§test_name: String

Fully qualified test name (matches KtstrTestEntry::name, the bare function name without the ktstr/ nextest prefix).

§topology: String

Rendered topology label (e.g. 1n2l4c1t) for the variant this sidecar describes.

§scheduler: String

Scheduler name (matches Scheduler::name); "eevdf" for tests run without an scx scheduler.

§scheduler_commit: Option<String>

Best-effort git commit of the scheduler binary used for this run. Currently ALWAYS None for every SchedulerSpec variant — no variant today has a reliable commit source. The field is reserved on the schema so stats tooling can enrich it once a reliable source exists (e.g. a --version probe or ELF-note read on the resolved scheduler binary). See crate::test_support::SchedulerSpec::scheduler_commit for the full per-variant rationale.

Writer always emits ("scheduler_commit": null on absence). Reader-side: serde’s native Option<T> deserialize tolerates absence (a missing key parses as None); see the module-level doc for the full asymmetric contract that governs every nullable on this struct.

§resolve_source: Option<String>

How the userspace scheduler binary was resolved for this run — the snake_case crate::test_support::ResolveSource::as_str tag ("path", "env_var", "path_lookup", "sibling_dir", "target_debug", "target_release", "auto_built", "not_found"). Provenance, not identity: distinct from SidecarResult::scheduler_commit (the binary’s git commit) — this records the discovery PATH, so the stats CLI can answer “was this run’s scheduler auto-built from the workspace HEAD, or resolved from a possibly-stale target/ or $PATH binary?”. "auto_built" is the only tag whose source commit is known to match the workspace tree; every other tag carries the stale-binary hazard documented on the crate::test_support::ResolveSource variant.

Writer always emits ("resolve_source": null on absence — the skip-sidecar path resolves no binary). Reader-side: serde’s native Option<T> deserialize tolerates absence (a missing key parses as None); see the module-level doc for the full asymmetric contract. Excluded from sidecar_variant_hash for the same cross-host grouping reason as scheduler_commit / run_source: two runs of the same semantic variant resolved via different discovery paths must still bucket together.

§project_commit: Option<String>

Best-effort git HEAD of the ktstr project tree at sidecar- write time. Captured by detect_project_commit via gix::discover from the test process’s current working directory; walks up to find the enclosing repo and reads HEAD short-hex, suffixing -dirty when index-vs-HEAD or worktree-vs-index changes are observed (submodules ignored, matching the crate::fetch::local_source dirty-detection pattern). None when cwd is not inside any git repo, or when the gix probe fails for any reason — this is metadata, not a gate, so probe failure must not abort the run.

Distinct from SidecarResult::scheduler_commit: that field tracks the userspace scheduler binary’s commit (currently always None per its own doc); this field tracks the ktstr framework / test-runner commit, so the stats CLI can answer “which version of the harness produced this sidecar?” without inspecting the scheduler.

Writer always emits ("project_commit": null on absence). Reader-side: serde’s native Option<T> deserialize tolerates absence (a missing key parses as None) — see the module- level doc for the full asymmetric contract. Excluded from sidecar_variant_hash for the same cross-host grouping reason scheduler_commit is excluded: two runs of the same semantic variant on different ktstr commits must still bucket together so perf-delta can diff them; the commit-drift detection inspects this field directly via --project-commit / --a-project-commit / --b-project-commit.

§payload: Option<String>

Binary payload name (matches Payload::name when entry.payload is set). None when the test declared no binary payload. Writer always emits ("payload": null on absence); reader-side, serde’s native Option<T> deserialize tolerates absence — see the module-level doc for the full asymmetric contract.

§metrics: Vec<PayloadMetrics>

Per-payload extracted metrics collected from ctx.payload(X).run() / .spawn().wait() call sites during the test body.

One PayloadMetrics per invocation, in the order the calls ran. Empty when no payload calls were made (scheduler-only tests, or a binary-only test where the body bailed before running the payload). Writer always emits ("metrics": [] in that case); reader-side, this Vec field is hard-required — non-Option fields fail deserialize on absence. See the module-level doc for the full contract.

§passed: bool

True when the run is a real pass — every assertion that ran produced a positive verdict. Mirrors crate::assert::AssertResult::is_pass. Mutually exclusive with Self::skipped and Self::inconclusive: the three bits (passed, skipped, inconclusive) form a strict 4-state encoding where at most one is set per record. The fourth state — Fail — is the all-false case (no dedicated bit; Self::is_fail derives it). A real pass requires !skipped && !inconclusive AND at least one observed assertion (the empty / all-skip case routes through Self::skipped instead).

§skipped: bool

True when the run was skipped (e.g. topology mismatch, missing resource, in-VM AssertResult::skip return). Mutually exclusive with Self::passed (Pass requires a real assertion; an all-skip stream is Skip, not Pass) and with Self::inconclusive. Stats tooling subtracts skipped runs from “pass count” so non-executions are not reported as passes.

§inconclusive: bool

True when at least one assertion was Outcome::Inconclusive — the run ran but a zero-denominator ratio gate could not be evaluated (e.g. zero iterations across all workers under a max_migration_ratio check). Mutually exclusive with Self::passed and Self::skipped; in the Fail > Inconclusive > Pass > Skip lattice, Inconclusive dominates Pass/Skip but loses to Fail, so a run with both Inconclusive and Fail outcomes records inconclusive = false, passed = false (Fail wins) — inconclusive = true requires !is_fail() && !is_pass() && !is_skip().

Distinct from passed = false (Fail) and skipped = true (precondition unmet) so CI gates and stats tooling can triage zero-denominator runs as “workload didn’t produce the signal the assertion needed” rather than misclassifying them as silent passes (prior to the Outcome::Inconclusive variant the zero-denominator case fell out as Pass) or as hard failures.

§expected_failure: bool

True when the persisted verdict (passed/skipped/ inconclusive) is the POST-inversion FINAL outcome of a run whose underlying scenario actually failed — i.e. an expect_err / expect_auto_repro test whose induced failure was inverted to a pass. Set by the sidecar finalize (finalize_sidecar_verdict) after dispatch resolves the verdict; false for an ordinary pass/skip/fail.

The verdict bits carry the FINAL outcome so the footer, stats analysis, and replay match nextest’s exit code. This flag preserves the one fact that overwrite loses: that the run’s telemetry is failure-mode-dominated (a deliberately short / stalled run). perf-delta ORs it into its exclusion guard so an inverted-to-pass row is still kept OUT of the regression math (its induced-crash telemetry is not real scheduler behavior).

§stats: ScenarioStats

Aggregate per-cgroup statistics merged across every worker.

§monitor: Option<MonitorSummary>

Monitor summary. None means the monitor loop did not run (host-only tests, early VM failure) or sample collection produced no valid data. Writer always emits ("monitor": null on absence); reader-side, serde’s native Option<T> deserialize tolerates absence — see the module-level doc.

§periodic_fired: u32

Periodic-capture coverage for this run: how many periodic snapshot boundaries actually fired (periodic_fired) out of the configured num_snapshots target (periodic_target). Carried verbatim from crate::prelude::VmResult so cross-run tooling can read the coverage off the persisted sidecar (previously only the in-memory result exposed it). 0/0 for runs with no periodic captures configured. Hard-required u32 fields — old sidecars predating them re-generate on the next run (sidecar data is disposable).

§periodic_target: u32§vcpus: u32

Guest vCPU count and the effective host-CPU budget the vCPU threads ran on, carried verbatim from crate::prelude::VmResult. Drive the cpu-budget comparison Dimension (cross-budget runs are not paired — confining 32 vCPUs to 4 host CPUs measures something else) and the overcommit marker: cpu_budget < vcpus means the host time-sliced the guest’s vCPUs, confounding the timing metrics (wake-latency / off-CPU / run-delay — schedstat run_delay tracks rq->clock, which follows the guest TSC and is not steal-adjusted, so the off-host window inflates it for tasks waiting across it). Hard-required u32 (old sidecars re-generate; sidecar data is disposable). EXCLUDED from sidecar_variant_hash: a budget change is a different measurement, separated downstream by the Dimension, not the identity bucket.

§cpu_budget: u32§stimulus_events: Vec<StimulusEvent>

Ordered stimulus events published by the guest step executor while the scenario ran.

§work_type: String

WorkSpec type label used for post-hoc filtering and A/B comparison (distinct from the WorkType enum — this is the text name).

§verifier_stats: Vec<ProgVerifierStats>

Per-BPF-program verifier statistics captured from the VM’s scheduler (when one was loaded). Empty when no scheduler programs were inspected. Writer always emits as "verifier_stats": [] in that case; reader-side, this Vec field is hard-required (non-Option fields fail deserialize on absence). See the module-level doc.

§kvm_stats: Option<KvmStatsTotals>

Aggregate per-vCPU KVM stats read after VM exit. None when the VM did not run (host-only tests) or KVM stats were unavailable. Writer always emits ("kvm_stats": null on absence); reader-side, serde’s native Option<T> deserialize tolerates absence — see the module-level doc.

§sysctls: Vec<String>

Effective sysctls active during this test run, recorded as raw sysctl.key=value cmdline strings. Writer always emits as "sysctls": [] when none; reader-side, this Vec field is hard-required (non-Option fields fail deserialize on absence). See the module-level doc.

§kargs: Vec<String>

Effective kernel command-line args active during this test run. Writer always emits as "kargs": [] when none; reader-side, this Vec field is hard-required (non-Option fields fail deserialize on absence). See the module-level doc.

§kernel_version: Option<String>

Kernel version of the VM under test (from cache metadata, e.g. "6.14.2"). Populated from the cache entry’s metadata.json version field, with fallback to the kernel source tree’s include/config/kernel.release when KTSTR_KERNEL points at a raw source path rather than a cache key; None for host-only tests or when neither source yields a version string. The host’s running kernel release is carried separately in host.kernel_release. Writer always emits ("kernel_version": null on absence); reader-side, serde’s native Option<T> deserialize tolerates absence — see the module-level doc for the full asymmetric contract.

§kernel_commit: Option<String>

Kernel SOURCE TREE git HEAD short hex (7 chars via oid::to_hex_with_len(7)), with -dirty suffix appended when HEAD-vs-index or index-vs-worktree changes are observed. Probes via gix::open against the kernel directory resolved from KTSTR_KERNEL (not gix::discover — the kernel dir is explicit, not walked-up). Captured by detect_kernel_commit at sidecar-write time.

Distinct from sibling fields:

  • SidecarResult::kernel_version — release string read from cache metadata or include/config/kernel.release, e.g. "6.14.2". Two runs of 6.14.2 from a clean tree and a -dirty worktree at the same HEAD share kernel_version but differ on kernel_commit.
  • SidecarResult::project_commit — ktstr framework HEAD captured from the test process’s cwd. Tracks “what version of the harness produced this sidecar?” independently of the kernel under test.
  • SidecarResult::scheduler_commit — userspace scheduler binary’s commit (currently always None).

None when:

  • KTSTR_KERNEL is unset or empty;
  • the resolved KernelId is Version / CacheKey whose underlying source is Tarball / Git (no source tree on disk to probe);
  • the resolved kernel directory is not a git repository (gix::open fails);
  • HEAD cannot be read (unborn HEAD on a fresh git init with zero commits);
  • any other gix probe failure — metadata, not a gate.

Writer always emits ("kernel_commit": null on absence); reader-side, serde’s native Option<T> deserialize tolerates absence — see the module-level doc for the full asymmetric contract. Excluded from sidecar_variant_hash for the same cross-host grouping reason scheduler_commit and project_commit are excluded: two runs of the same semantic variant on different kernel-source HEADs must still bucket together so perf-delta can diff them; the commit-drift detection inspects this field directly via the --kernel-commit filter.

§timestamp: String

ISO 8601 timestamp of when this test run started.

§run_id: String

Unique identifier for the test run. Composed as {run_id_timestamp}-{counter} — the YYYYMMDDTHHMMSSZ process-start stamp followed by a process-local monotonic counter. Every sidecar produced in one cargo ktstr test invocation shares the same timestamp prefix; the counter distinguishes concurrent gauntlet variants within that invocation. Distinct from the run DIRECTORY name (keyed {kernel}-{project_commit}, see sidecar_dir) — the directory groups runs by what they tested, the run_id groups sidecars by which process emitted them.

§host: Option<HostContext>

Host context — static-ish runtime state (CPU model, memory size, THP policy, kernel release, host cmdline, scheduler tunables). Populated by production sidecar writers.

None causes:

  • test-fixture path: not the production sidecar writer (production writers always populate host).
  • pre-enrichment archive: sidecar predates the host-context landing — re-run the test to regenerate under the current schema (no migration shim exists per the pre-1.0 disposable-data contract).

Deliberately excluded from the variant hash so gauntlet variants on different hosts collapse into the same hash bucket.

No serde attributes: writer always emits ("host": null when None); reader-side, serde’s native Option<T> deserialize tolerates absence (a missing key parses as None). The asymmetric contract is crate-wide — see the module-level doc. Pre-1.0, sidecar data is disposable, so regenerate by re-running the test rather than carrying a compat shim for older JSON; the reader-side tolerance exists so an in-flight schema rename of an Option field does not break parsing of older sidecars during the same producer-version, not as a long-term migration story.

§cleanup_duration_ms: Option<u64>

Wall-clock milliseconds spent in KtstrVm::collect_results — the host-side teardown window from BSP exit through SHM drain (mirrors VmResult::cleanup_duration; Duration is converted to u64 ms here because every other timing field on this struct that lands in a sidecar-comparison CLI uses integer ms or seconds, and JSON has no native Duration). None when the run was killed by the watchdog before collect_results returned, or for the host_only / host-only-stub paths that never boot a VM. Writer always emits ("cleanup_duration_ms": null on absence); reader-side, serde’s native Option<T> deserialize tolerates absence — see the module-level doc for the full asymmetric contract.

§run_source: Option<String>

Provenance tag for this sidecar — distinguishes a developer’s local run from a CI run so cross-environment comparisons in perf-delta can narrow on (or contrast across) the run environment without inferring it from host.

Recorded by detect_run_source at sidecar-write time:

  • Some("ci") when KTSTR_CI_ENV is set non-empty (CI runner scripts export it before invoking the test binary; local runs never set it).
  • Some("local") otherwise — the default for any sidecar produced by a developer-driven invocation.
  • The third documented value ("archive") is NEVER written here: a sidecar cannot know it will later be archived. The stats CLI applies the "archive" tag at LOAD time when its --dir flag points at a non-default pool root, overriding whatever was on disk via apply_archive_source_override.

Option<String> (rather than an enum) keeps the schema extensible without a serde-version bump if a future producer wants a new tag (e.g. "benchmark"); the consumer side treats unknown values the same as known ones — they are strings the operator can pass via --run-source to filter on. Writer always emits ("run_source": null on absence); reader-side, serde’s native Option<T> deserialize tolerates absence — see the module-level doc for the full asymmetric contract. Excluded from sidecar_variant_hash for the same cross-host grouping reason host is excluded — two runs of the same semantic variant from different environments must still bucket together so perf-delta can pair them; --run-source is the explicit knob for source-aware narrowing.

Field name run_source (renamed from source) disambiguates from crate::cache::KernelSource / KernelMetadata.source — those describe the kernel build’s input (tarball / git / local), this describes the run-environment provenance.

On-disk JSON key changed from "source" to "run_source" in the field rename. No #[serde(alias = "source")] is in place: archived sidecars written before the rename carry the "source" key, which the current schema treats as an unknown field. Because SidecarResult’s derive does NOT set deny_unknown_fields, the deserialize does not fail outright — instead serde silently DROPS the stale "source" payload and lands run_source = None (since Option<T>’s “tolerate absence” rule kicks in for the missing "run_source" field). The data is lost, not preserved. This is deliberate per the project’s pre-1.0 disposable-data contract: re-running tests regenerates sidecars under the new key rather than carrying compat shims forward. Consumers who need the run-source classification on archived JSON must either rename the key in-place before deserialize, or re-run the test to regenerate the sidecar with the new schema. Tooling that runs against the renamed schema and observes a None run_source cannot distinguish “sidecar pre-dates the field” from “sidecar pre-dates the rename and lost its tag” — both lower-bound at None for filter purposes.

§perf_delta_assertions: Vec<PerfDeltaAssertionRecord>

Per-test crate::test_support::PerfDeltaAssertions declared on the entry, serialized so cargo ktstr perf-delta --noise-adjust’s host-side compare can enforce them across commits (the entry registry in the parent process describes only HEAD’s tests, not a baseline/cached sidecar’s commit, so the declaration must travel WITH the run). Empty when the test declared none. Inert here — a normal cargo ktstr test writes them but never gates on them; only the --noise-adjust compare consults them (the scalar compare warns that declared gates were skipped).

Writer always emits ("perf_delta_assertions": [] on absence); reader- side this Vec field is hard-required (non-Option fails deserialize on absence) — see the module-level doc for the full contract.

Implementations§

Source§

impl SidecarResult

Source

pub fn is_pass(&self) -> bool

Convenience accessor mirroring crate::assert::AssertResult::is_pass. SidecarResult is the wire-format mirror of an AssertResult; this method exposes the same is_pass / is_fail / is_skip / is_inconclusive vocabulary so consumers can swap between the two without re-learning field names.

Returns true only when the run reached a real Pass — neither skipped, inconclusive, nor failed. The triple-conjunct guard matches AssertResult’s Fail > Inconclusive > Pass > Skip dominance under the strict 4-state mutex this struct encodes. CI gates that want “ship-on-pass” semantics call this method and only this method.

Part of the is_pass / is_fail / is_inconclusive / is_skip vocabulary uniform across the verdict surfaces: crate::assert::AssertResult::is_pass / Self::is_pass / crate::assert::Outcome::is_pass / MonitorVerdict::is_pass (in the monitor module, which is pub(crate)) / Verdict::is_pass (re-exported at crate::assert::Verdict) / GauntletRow::is_pass (in the stats module, which is pub(crate)).

Source

pub fn is_fail(&self) -> bool

Convenience accessor mirroring crate::assert::AssertResult::is_fail. The four-state encoding uses three stored bits (passed, skipped, inconclusive) in strict mutual exclusion (at most one set); Fail is the all-false derived state, no dedicated bit. is_fail reads “none of the three bits are set”, which under Fail > Inconclusive > Pass > Skip dominance correctly resolves a mixed Fail+Inconclusive stream as Fail.

Source

pub fn is_skip(&self) -> bool

Convenience accessor mirroring crate::assert::AssertResult::is_skip.

Source

pub fn is_inconclusive(&self) -> bool

Convenience accessor mirroring crate::assert::AssertResult::is_inconclusive. True when the run could not be evaluated (zero-denominator ratio gate); false on real Pass, real Fail, or Skip. CI gates that gate on “did we get a real verdict?” should test r.is_pass() || r.is_fail() and treat both is_skip() and is_inconclusive() as “couldn’t measure”.

Trait Implementations§

Source§

impl Debug for SidecarResult

Source§

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

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

impl<'de> Deserialize<'de> for SidecarResult

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 Serialize for SidecarResult

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

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> 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, 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,