1use super::Ctx;
4use super::backdrop::Backdrop;
5use super::ops::{CgroupDef, CpusetSpec, HoldSpec, Op, Step, execute_scenario, execute_steps};
6use crate::assert::AssertResult;
7use crate::workload::*;
8use anyhow::Result;
9use std::time::Duration;
10
11fn cgroup_cpuset_apply_midrun_backdrop() -> Backdrop {
12 Backdrop::new()
13 .push_cgroup(CgroupDef::named("cg_0"))
14 .push_cgroup(CgroupDef::named("cg_1"))
15}
16
17fn cgroup_cpuset_apply_midrun_steps(ctx: &Ctx) -> Vec<Step> {
18 vec![
19 Step::new(vec![], ctx.settled_hold(0.5)),
20 Step::new(
21 vec![
22 Op::set_cpuset("cg_0", CpusetSpec::disjoint(0, 2)),
23 Op::set_cpuset("cg_1", CpusetSpec::disjoint(1, 2)),
24 ],
25 HoldSpec::frac(0.5),
26 ),
27 ]
28}
29
30pub fn custom_cgroup_cpuset_apply_midrun(ctx: &Ctx) -> Result<AssertResult> {
32 execute_scenario(
33 ctx,
34 cgroup_cpuset_apply_midrun_backdrop(),
35 cgroup_cpuset_apply_midrun_steps(ctx),
36 )
37}
38
39pub fn custom_cgroup_cpuset_clear_midrun(ctx: &Ctx) -> Result<AssertResult> {
41 let backdrop = Backdrop::new()
42 .push_cgroup(CgroupDef::named("cg_0").cpuset(CpusetSpec::disjoint(0, 2)))
43 .push_cgroup(CgroupDef::named("cg_1").cpuset(CpusetSpec::disjoint(1, 2)));
44
45 let steps = vec![
46 Step::new(vec![], ctx.settled_hold(0.5)),
47 Step::new(
48 vec![Op::clear_cpuset("cg_0"), Op::clear_cpuset("cg_1")],
49 HoldSpec::frac(0.5),
50 ),
51 ];
52
53 execute_scenario(ctx, backdrop, steps)
54}
55
56fn cgroup_cpuset_resize_backdrop() -> Backdrop {
57 Backdrop::new()
58 .push_cgroup(CgroupDef::named("cg_0").cpuset(CpusetSpec::range(0.0, 0.5)))
59 .push_cgroup(CgroupDef::named("cg_1").cpuset(CpusetSpec::range(0.5, 1.0)))
60}
61
62fn cgroup_cpuset_resize_steps(ctx: &Ctx) -> Vec<Step> {
63 vec![
64 Step::new(vec![], ctx.settled_hold(1.0 / 3.0)),
65 Step::new(
66 vec![
67 Op::set_cpuset("cg_0", CpusetSpec::range(0.0, 0.25)),
68 Op::set_cpuset("cg_1", CpusetSpec::range(0.25, 1.0)),
69 ],
70 HoldSpec::frac(1.0 / 3.0),
71 ),
72 Step::new(
73 vec![
74 Op::set_cpuset("cg_0", CpusetSpec::range(0.0, 0.75)),
75 Op::set_cpuset("cg_1", CpusetSpec::range(0.75, 1.0)),
76 ],
77 HoldSpec::frac(1.0 / 3.0),
78 ),
79 ]
80}
81
82pub fn custom_cgroup_cpuset_resize(ctx: &Ctx) -> Result<AssertResult> {
84 if ctx.topo.all_cpus().len() < 4 {
85 return Ok(AssertResult::skip("need >=4 CPUs"));
86 }
87 execute_scenario(
88 ctx,
89 cgroup_cpuset_resize_backdrop(),
90 cgroup_cpuset_resize_steps(ctx),
91 )
92}
93
94pub fn custom_cgroup_cpuset_swap_disjoint(ctx: &Ctx) -> Result<AssertResult> {
96 if ctx.topo.all_cpus().len() < 8 {
97 return Ok(AssertResult::skip("need >=8 CPUs"));
98 }
99
100 let backdrop = Backdrop::new()
101 .push_cgroup(CgroupDef::named("cg_0").cpuset(CpusetSpec::range(0.0, 0.5)))
102 .push_cgroup(CgroupDef::named("cg_1").cpuset(CpusetSpec::range(0.5, 1.0)));
103
104 let steps = vec![
105 Step::new(vec![], ctx.settled_hold(1.0 / 3.0)),
106 Step::new(
107 vec![
108 Op::set_cpuset("cg_0", CpusetSpec::range(0.5, 1.0)),
109 Op::set_cpuset("cg_1", CpusetSpec::range(0.0, 0.5)),
110 ],
111 HoldSpec::frac(1.0 / 3.0),
112 ),
113 Step::new(
114 vec![
115 Op::set_cpuset("cg_0", CpusetSpec::range(0.0, 0.5)),
116 Op::set_cpuset("cg_1", CpusetSpec::range(0.5, 1.0)),
117 ],
118 HoldSpec::frac(1.0 / 3.0),
119 ),
120 ];
121
122 execute_scenario(ctx, backdrop, steps)
123}
124
125pub fn custom_cgroup_cpuset_workload_imbalance(ctx: &Ctx) -> Result<AssertResult> {
127 let mid = ctx.topo.usable_cpus().len() / 2;
128
129 let steps = vec![Step::with_defs(
130 vec![
131 CgroupDef::named("cg_0")
132 .cpuset(CpusetSpec::disjoint(0, 2))
133 .workers(mid * 2),
134 CgroupDef::named("cg_1")
135 .cpuset(CpusetSpec::disjoint(1, 2))
136 .work_type(WorkType::bursty(
137 Duration::from_millis(50),
138 Duration::from_millis(100),
139 )),
140 ],
141 ctx.settled_hold(1.0),
142 )];
143
144 execute_steps(ctx, steps)
145}
146
147pub fn custom_cgroup_cpuset_change_imbalance(ctx: &Ctx) -> Result<AssertResult> {
149 if ctx.topo.all_cpus().len() < 4 {
150 return Ok(AssertResult::skip("need >=4 CPUs"));
151 }
152
153 let all = ctx.topo.all_cpus();
154 let last = all.len() - 1;
155 let mid = last / 2;
156
157 let narrow = CpusetSpec::exact([all[mid]]);
158
159 let backdrop = Backdrop::new()
160 .push_cgroup(
161 CgroupDef::named("cg_0")
162 .cpuset(CpusetSpec::range(0.0, 0.5))
163 .workers(mid * 2),
164 )
165 .push_cgroup(
166 CgroupDef::named("cg_1")
167 .cpuset(CpusetSpec::range(0.5, 1.0))
168 .workers(2)
169 .work_type(WorkType::bursty(
170 Duration::from_millis(30),
171 Duration::from_millis(100),
172 )),
173 );
174
175 let steps = vec![
176 Step::new(vec![], ctx.settled_hold(1.0 / 3.0)),
177 Step::new(
178 vec![Op::set_cpuset("cg_1", narrow)],
179 HoldSpec::frac(1.0 / 3.0),
180 ),
181 Step::new(
182 vec![Op::set_cpuset("cg_1", CpusetSpec::range(0.5, 1.0))],
183 HoldSpec::frac(1.0 / 3.0),
184 ),
185 ];
186
187 execute_scenario(ctx, backdrop, steps)
188}
189
190pub fn custom_cgroup_cpuset_numa_swap(ctx: &Ctx) -> Result<AssertResult> {
196 if ctx.topo.num_numa_nodes() < 2 {
197 return Ok(AssertResult::skip("need >=2 NUMA nodes"));
198 }
199
200 let backdrop = Backdrop::new()
201 .push_cgroup(CgroupDef::named("cg_0").cpuset(CpusetSpec::numa(0)))
202 .push_cgroup(CgroupDef::named("cg_1").cpuset(CpusetSpec::numa(1)));
203
204 let steps = vec![
205 Step::new(vec![], ctx.settled_hold(0.5)),
206 Step::new(
207 vec![
208 Op::set_cpuset("cg_0", CpusetSpec::numa(1)),
209 Op::set_cpuset("cg_1", CpusetSpec::numa(0)),
210 ],
211 HoldSpec::frac(0.5),
212 ),
213 ];
214
215 execute_scenario(ctx, backdrop, steps)
216}
217
218pub fn custom_cgroup_cpuset_load_shift(ctx: &Ctx) -> Result<AssertResult> {
220 let backdrop = Backdrop::new()
221 .push_cgroup(
222 CgroupDef::named("cg_0")
223 .cpuset(CpusetSpec::disjoint(0, 2))
224 .workers(16),
225 )
226 .push_cgroup(
227 CgroupDef::named("cg_1")
228 .cpuset(CpusetSpec::disjoint(1, 2))
229 .workers(1)
230 .work_type(WorkType::YieldHeavy),
231 );
232
233 let steps = vec![
234 Step::new(vec![], ctx.settled_hold(0.5)),
235 Step::new(
239 vec![Op::spawn_workers("cg_1", WorkSpec::default().workers(16))],
240 HoldSpec::frac(0.5),
241 ),
242 ];
243
244 execute_scenario(ctx, backdrop, steps)
245}
246
247#[cfg(test)]
248mod tests {
249 use super::super::ops::Setup;
250 use super::*;
251 use crate::cgroup::CgroupManager;
252 use crate::topology::TestTopology;
253 use std::time::Duration;
254
255 fn ctx_for_test<'a>(cgroups: &'a CgroupManager, topo: &'a TestTopology) -> Ctx<'a> {
256 Ctx {
257 cgroups,
258 topo,
259 duration: Duration::from_secs(6),
260 workers_per_cgroup: 2,
261 sched_pid: Some(1),
262 settle: Duration::from_millis(100),
263 work_type_override: None,
264 assert: crate::assert::Assert::default_checks(),
265 wait_for_map_write: false,
266 current_step: std::sync::Arc::new(std::sync::atomic::AtomicU16::new(0)),
267 entry_name: None,
268 variant_hash: 0,
269 }
270 }
271
272 #[test]
273 fn apply_midrun_backdrop_declares_two_cgroups() {
274 let backdrop = cgroup_cpuset_apply_midrun_backdrop();
275 assert_eq!(
276 backdrop.cgroups.len(),
277 2,
278 "Backdrop declares cg_0 and cg_1 as persistent"
279 );
280 assert_eq!(backdrop.cgroups[0].name.as_ref(), "cg_0");
281 assert_eq!(backdrop.cgroups[1].name.as_ref(), "cg_1");
282 }
283
284 #[test]
285 fn apply_midrun_builds_two_phase_steps() {
286 let cgroups = CgroupManager::new("/nonexistent");
287 let topo = TestTopology::from_vm_topology(&crate::vmm::topology::Topology::new(1, 1, 4, 1));
288 let ctx = ctx_for_test(&cgroups, &topo);
289
290 let steps = cgroup_cpuset_apply_midrun_steps(&ctx);
291 assert_eq!(steps.len(), 2, "settle + apply phases");
292
293 assert!(
294 matches!(&steps[0].setup, Setup::Defs(defs) if defs.is_empty()),
295 "phase 1 has no step-local CgroupDefs (cgroups live in the Backdrop)",
296 );
297 assert!(steps[0].ops.is_empty(), "phase 1 is a pure settle — no ops");
298
299 assert!(matches!(steps[0].hold, HoldSpec::Fixed(_)));
300 let phase2_ops = &steps[1].ops;
301 assert_eq!(phase2_ops.len(), 2, "set_cpuset once per cgroup");
302 for op in phase2_ops {
303 assert!(matches!(op, Op::SetCpuset { .. }));
304 }
305 assert!(matches!(steps[1].hold, HoldSpec::Frac(f) if (f - 0.5).abs() < f64::EPSILON));
306 }
307
308 #[test]
309 fn resize_backdrop_declares_two_cgroups_with_cpusets() {
310 let backdrop = cgroup_cpuset_resize_backdrop();
311 assert_eq!(backdrop.cgroups.len(), 2);
312 assert!(backdrop.cgroups[0].cpuset.is_some());
313 assert!(backdrop.cgroups[1].cpuset.is_some());
314 }
315
316 #[test]
317 fn resize_builds_three_phase_range_progression() {
318 let cgroups = CgroupManager::new("/nonexistent");
319 let topo = TestTopology::from_vm_topology(&crate::vmm::topology::Topology::new(1, 1, 4, 1));
320 let ctx = ctx_for_test(&cgroups, &topo);
321
322 let steps = cgroup_cpuset_resize_steps(&ctx);
323 assert_eq!(steps.len(), 3);
324 assert!(
325 matches!(&steps[0].setup, Setup::Defs(defs) if defs.is_empty()),
326 "phase 1 is a settle step — cgroups live in the Backdrop",
327 );
328 assert!(steps[0].ops.is_empty(), "phase 1 has no ops");
329 for step in &steps[1..] {
331 assert_eq!(step.ops.len(), 2);
332 for op in &step.ops {
333 assert!(matches!(op, Op::SetCpuset { .. }));
334 }
335 }
336 }
337}