Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Scenarios

A scenario is the scheduling condition a test creates — which cgroups exist, which CPUs they may use, what their workers do, and what changes mid-run. The canned scenarios in scenarios::* exist so those conditions have names: scenarios::steady(ctx) produces the same reproducible condition against every scheduler you point it at, which is what makes results comparable across schedulers and commits.

use ktstr::prelude::*;

#[ktstr_test(llcs = 1, cores = 2, threads = 1)]
fn my_test(ctx: &Ctx) -> Result<AssertResult> {
    scenarios::steady(ctx)
}

Canned scenarios (scenarios::*)

FunctionCondition testedSetup
steadyBaseline fairness2 cgroups, no cpusets, equal CPU-spin load
steady_llcLLC-boundary scheduling2 cgroups on different LLCs (skips on 1-LLC topologies)
oversubscribedDispatch under oversubscription2 cgroups, 32 mixed workers each
cpuset_applyCpuset assignment on running tasksDisjoint cpusets applied mid-run
cpuset_clearCpuset removal on confined tasksCpusets cleared mid-run
cpuset_resizeCpuset resizing adaptationCpusets shrink then grow
cgroup_addScheduler reaction to a new cgroupCgroups created while others run
cgroup_removeScheduler reaction to cgroup removalCgroups torn down while others run
affinity_changeAffinity mask changesWorker affinities randomized mid-run
affinity_pinnedNarrow-affinity contentionWorkers pinned to a 2-CPU subset
host_contentionCgroup vs host-task fairnessRoot-cgroup workers beside managed cgroups
mixed_workloadsMixed workload fairnessHeavy + bursty + IO cgroups
nested_steadyNested cgroup hierarchyWorkers in nested sub-cgroups
nested_task_moveCross-level task migrationTasks moved between nested cgroups

More specialized custom_* functions live in the ktstr::scenario::{affinity, basic, cpuset, dynamic, interaction, nested, performance, stress} modules — see the API docs.

Start here

Against a new scheduler, run steady first — it is the smallest condition that can fail (two cgroups, spin load, nothing dynamic). Then steady_llc on a 2-LLC topology to see cache-boundary placement, then mixed_workloads and oversubscribed for load diversity. The dynamic scenarios (cpuset_*, cgroup_*, affinity_*) each isolate one reconfiguration path; reach for the one matching the code you changed.

Run parameters

A scenario body does not pick its own duration or topology — the #[ktstr_test] attribute does. The workload runs for the test’s duration_s (see the macro reference), on the topology the attribute declares, and a gauntlet run re-executes the same body across a whole topology matrix. Worker counts and cpusets come from the scenario’s own CgroupDefs.

Every scenario ends the same way: worker reports are collected and the opted-in checks run against them. A run’s stats roll-up looks like:

--- stats ---
2 workers, 4 cpus, 2 migrations, worst_spread=0.0%, worst_gap=21ms
  cg0: workers=1 cpus=2 spread=0.0% gap=10ms migrations=1 iter=209600
  cg1: workers=1 cpus=2 spread=0.0% gap=21ms migrations=1 iter=189252

Reading Failure Output walks the full anatomy.

From canned to custom

Scenarios graduate in three stages; move down only when the stage above can’t express the condition:

  1. Canned — call a scenarios::* function. Zero setup, named, comparable.
  2. Your own cgroup layoutexecute_defs(ctx, vec![...]) with CgroupDefs you declare: your worker counts, work types, cpusets, still one static phase.
  3. Stepsexecute_steps / execute_scenario with Steps and Ops for anything that changes mid-run (cpuset swaps, scheduler replacement, snapshots, kernel-memory reads), plus a Backdrop for state that must outlive the steps.

Ops, Steps, and Backdrop documents stage 2 and 3 — the CgroupDef builder, every Op, and all the execute_* entry points. A custom scenario is just the #[ktstr_test] function body itself; Custom Scenarios covers writing bodies that go beyond Steps entirely.