ktstr/scenario/
scenarios.rs

1//! Curated canned scenarios for common scheduler test patterns.
2//!
3//! Each function takes a [`Ctx`] and returns `Result<AssertResult>`.
4//! These are thin wrappers over existing scenario implementations,
5//! providing better names in a single discoverable namespace.
6//!
7//! # Categories
8//!
9//! - **Basic**: steady-state cgroups with no dynamic ops.
10//! - **Cpuset**: cpuset assignment and mid-run mutation.
11//! - **Dynamic**: cgroup add/remove during a running workload.
12//! - **Affinity**: per-worker CPU affinity patterns.
13//! - **Stress**: host/cgroup contention and mixed workload types.
14//! - **Nested**: workers in nested sub-cgroups.
15//!
16//! # Example
17//!
18//! ```rust,no_run
19//! use ktstr::prelude::*;
20//!
21//! #[ktstr_test(llcs = 2, cores = 4, threads = 1)]
22//! fn test_steady(ctx: &Ctx) -> Result<AssertResult> {
23//!     scenarios::steady(ctx)
24//! }
25//! ```
26
27use anyhow::Result;
28
29use crate::assert::AssertResult;
30use crate::workload::WorkType;
31
32use super::Ctx;
33use super::ops::{CgroupDef, CpusetSpec, execute_defs};
34
35// ---------------------------------------------------------------------------
36// Basic
37// ---------------------------------------------------------------------------
38
39/// Two cgroups, no cpusets, equal CPU-spin load.
40///
41/// Simplest possible scenario: tests that the scheduler can handle
42/// two cgroups running simultaneously without starvation.
43pub fn steady(ctx: &Ctx) -> Result<AssertResult> {
44    execute_defs(
45        ctx,
46        vec![CgroupDef::named("cg_0"), CgroupDef::named("cg_1")],
47    )
48}
49
50/// Two cgroups with LLC-aligned cpusets.
51///
52/// Each cgroup gets CPUs from a different LLC. Tests scheduler
53/// behavior when cgroups are partitioned along cache boundaries.
54/// Skips on single-LLC topologies.
55pub fn steady_llc(ctx: &Ctx) -> Result<AssertResult> {
56    if ctx.topo.num_llcs() < 2 {
57        return Ok(AssertResult::skip("need >=2 LLCs"));
58    }
59    execute_defs(
60        ctx,
61        vec![
62            CgroupDef::named("cg_0").cpuset(CpusetSpec::llc(0)),
63            CgroupDef::named("cg_1").cpuset(CpusetSpec::llc(1)),
64        ],
65    )
66}
67
68/// Two cgroups with 32 mixed workers each (oversubscribed).
69///
70/// Worker count far exceeds CPU count, testing dispatch under
71/// heavy oversubscription with mixed workload types.
72pub fn oversubscribed(ctx: &Ctx) -> Result<AssertResult> {
73    execute_defs(
74        ctx,
75        vec![
76            CgroupDef::named("cg_0")
77                .workers(32)
78                .work_type(WorkType::Mixed),
79            CgroupDef::named("cg_1")
80                .workers(32)
81                .work_type(WorkType::Mixed),
82        ],
83    )
84}
85
86// ---------------------------------------------------------------------------
87// Cpuset — delegates to super::cpuset
88// ---------------------------------------------------------------------------
89
90/// Two cgroups start without cpusets, then get disjoint cpusets mid-run.
91///
92/// Tests the scheduler's response to cpuset assignment on running
93/// cgroups. Workers must migrate to their assigned CPUs.
94pub fn cpuset_apply(ctx: &Ctx) -> Result<AssertResult> {
95    super::cpuset::custom_cgroup_cpuset_apply_midrun(ctx)
96}
97
98/// Two cgroups start with disjoint cpusets, then cpusets are cleared mid-run.
99///
100/// Tests the scheduler's response to cpuset removal. Workers that
101/// were confined to a subset of CPUs become free to run anywhere.
102pub fn cpuset_clear(ctx: &Ctx) -> Result<AssertResult> {
103    super::cpuset::custom_cgroup_cpuset_clear_midrun(ctx)
104}
105
106/// Two cgroups with cpusets that shrink then grow.
107///
108/// Three-phase scenario: even split, then shrink cg_0 / grow cg_1,
109/// then reverse. Tests scheduler adaptation to cpuset resizing.
110pub fn cpuset_resize(ctx: &Ctx) -> Result<AssertResult> {
111    super::cpuset::custom_cgroup_cpuset_resize(ctx)
112}
113
114// ---------------------------------------------------------------------------
115// Dynamic — delegates to super::dynamic
116// ---------------------------------------------------------------------------
117
118/// Two cgroups initially, then one or two more added mid-run.
119///
120/// Tests the scheduler's response to new cgroups appearing while
121/// workers are already running.
122pub fn cgroup_add(ctx: &Ctx) -> Result<AssertResult> {
123    super::dynamic::custom_cgroup_add_midrun(ctx)
124}
125
126/// Four cgroups initially, then the second half removed mid-run.
127///
128/// Tests the scheduler's response to cgroup removal while workers
129/// in surviving cgroups continue running.
130pub fn cgroup_remove(ctx: &Ctx) -> Result<AssertResult> {
131    super::dynamic::custom_cgroup_remove_midrun(ctx)
132}
133
134// ---------------------------------------------------------------------------
135// Affinity — delegates to super::affinity
136// ---------------------------------------------------------------------------
137
138/// Two cgroups with worker affinities randomized mid-run.
139///
140/// Workers start with no affinity, then get random CPU subsets
141/// applied four times during the run.
142pub fn affinity_change(ctx: &Ctx) -> Result<AssertResult> {
143    super::affinity::custom_cgroup_affinity_change(ctx)
144}
145
146/// Two cgroups with workers pinned to a 2-CPU subset.
147///
148/// All workers in both cgroups share the same narrow affinity mask.
149/// Tests scheduler behavior under heavy contention on few CPUs.
150pub fn affinity_pinned(ctx: &Ctx) -> Result<AssertResult> {
151    super::affinity::custom_cgroup_multicpu_pin(ctx)
152}
153
154// ---------------------------------------------------------------------------
155// Stress — delegates to super::basic / super::interaction
156// ---------------------------------------------------------------------------
157
158/// Host workers competing with cgroup workers for CPU time.
159///
160/// Two cgroups plus unconstrained host workers (one per CPU).
161/// Tests scheduler fairness between cgroup-managed and
162/// non-cgroup-managed tasks.
163pub fn host_contention(ctx: &Ctx) -> Result<AssertResult> {
164    super::basic::custom_host_cgroup_contention(ctx)
165}
166
167/// Heavy + bursty + IO cgroups.
168///
169/// Three cgroups with different workload types: CPU-heavy, bursty
170/// wake/sleep, and synchronous IO. Tests fairness across mixed
171/// workload patterns.
172pub fn mixed_workloads(ctx: &Ctx) -> Result<AssertResult> {
173    super::interaction::custom_cgroup_imbalance_mixed_workload(ctx)
174}
175
176// ---------------------------------------------------------------------------
177// Nested — delegates to super::nested
178// ---------------------------------------------------------------------------
179
180/// Workers in nested sub-cgroups.
181///
182/// Creates a multi-level cgroup hierarchy (cg_0/sub_a, cg_0/sub_b,
183/// cg_1/sub_b, cg_1/sub_a/deep) with workers at the leaf level.
184/// Tests scheduler handling of nested cgroup hierarchies.
185pub fn nested_steady(ctx: &Ctx) -> Result<AssertResult> {
186    super::nested::custom_nested_cgroup_steady(ctx)
187}
188
189/// Move tasks between nested cgroups.
190///
191/// Creates nested cgroups, spawns workers in one, then moves them
192/// through the hierarchy (sub -> parent -> cross-hierarchy sub ->
193/// parent). Tests task migration across nesting levels.
194pub fn nested_task_move(ctx: &Ctx) -> Result<AssertResult> {
195    super::nested::custom_nested_cgroup_task_move(ctx)
196}
197
198#[cfg(test)]
199mod tests {
200    use super::*;
201
202    /// Verify all canned scenario functions have the expected signature:
203    /// `fn(&Ctx) -> Result<AssertResult>`.
204    #[test]
205    fn all_scenario_fns_have_correct_signature() {
206        let fns: Vec<fn(&Ctx) -> Result<AssertResult>> = vec![
207            steady,
208            steady_llc,
209            cpuset_apply,
210            cpuset_clear,
211            cpuset_resize,
212            cgroup_add,
213            cgroup_remove,
214            affinity_change,
215            affinity_pinned,
216            oversubscribed,
217            host_contention,
218            mixed_workloads,
219            nested_steady,
220            nested_task_move,
221        ];
222        // 14 canned scenarios.
223        assert_eq!(fns.len(), 14);
224    }
225}