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}