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: StringFully qualified test name (matches KtstrTestEntry::name,
the bare function name without the ktstr/ nextest prefix).
topology: StringRendered topology label (e.g. 1n2l4c1t) for the variant this
sidecar describes.
scheduler: StringScheduler 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: boolTrue 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: boolTrue 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: boolTrue 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: boolTrue 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: ScenarioStatsAggregate 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: u32Periodic-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: u32See Self::periodic_fired.
vcpus: u32Guest 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: u32See Self::vcpus.
stimulus_events: Vec<StimulusEvent>Ordered stimulus events published by the guest step executor while the scenario ran.
work_type: StringWorkSpec 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 orinclude/config/kernel.release, e.g."6.14.2". Two runs of6.14.2from a clean tree and a-dirtyworktree at the same HEAD sharekernel_versionbut differ onkernel_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 alwaysNone).
None when:
KTSTR_KERNELis unset or empty;- the resolved
KernelIdisVersion/CacheKeywhose underlying source isTarball/Git(no source tree on disk to probe); - the resolved kernel directory is not a git repository
(
gix::openfails); - HEAD cannot be read (unborn HEAD on a fresh
git initwith 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: StringISO 8601 timestamp of when this test run started.
run_id: StringUnique 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")whenKTSTR_CI_ENVis 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--dirflag points at a non-default pool root, overriding whatever was on disk viaapply_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
impl SidecarResult
Sourcepub fn is_pass(&self) -> bool
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)).
Sourcepub fn is_fail(&self) -> bool
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.
Sourcepub fn is_skip(&self) -> bool
pub fn is_skip(&self) -> bool
Convenience accessor mirroring
crate::assert::AssertResult::is_skip.
Sourcepub fn is_inconclusive(&self) -> bool
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
impl Debug for SidecarResult
Source§impl<'de> Deserialize<'de> for SidecarResult
impl<'de> Deserialize<'de> for SidecarResult
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>,
Auto Trait Implementations§
impl Freeze for SidecarResult
impl RefUnwindSafe for SidecarResult
impl Send for SidecarResult
impl Sync for SidecarResult
impl Unpin for SidecarResult
impl UnwindSafe for SidecarResult
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
§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