1use super::Ctx;
4use super::ops::{CgroupDef, Op, Step, execute_steps};
5use crate::assert::AssertResult;
6use crate::workload::*;
7use anyhow::Result;
8use std::time::Duration;
9
10fn host_cgroup_contention_steps(ctx: &Ctx) -> Vec<Step> {
11 vec![
12 Step::with_defs(
13 vec![CgroupDef::named("cg_0"), CgroupDef::named("cg_1")],
14 ctx.settled_hold(1.0),
15 )
16 .set_ops(vec![Op::spawn_host(
17 WorkSpec::default().workers(ctx.topo.total_cpus()),
18 )]),
19 ]
20}
21
22pub fn custom_host_cgroup_contention(ctx: &Ctx) -> Result<AssertResult> {
27 execute_steps(ctx, host_cgroup_contention_steps(ctx))
28}
29
30fn sched_mixed_steps(ctx: &Ctx) -> Vec<Step> {
31 let configs = [
32 (SchedPolicy::Normal, WorkType::SpinWait),
33 (SchedPolicy::Batch, WorkType::SpinWait),
34 (SchedPolicy::Idle, WorkType::SpinWait),
35 (
36 SchedPolicy::Fifo(1),
37 WorkType::bursty(Duration::from_millis(500), Duration::from_millis(250)),
38 ),
39 ];
40
41 let mut ops = vec![Op::add_cgroup("cg_0"), Op::add_cgroup("cg_1")];
42 for name in ["cg_0", "cg_1"] {
43 for &(policy, ref wtype) in &configs {
44 ops.push(Op::spawn_workers(
45 name,
46 WorkSpec::default()
47 .workers(2)
48 .sched_policy(policy)
49 .work_type(wtype.clone()),
50 ));
51 }
52 }
53
54 vec![Step::new(ops, ctx.settled_hold(1.0))]
55}
56
57pub fn custom_sched_mixed(ctx: &Ctx) -> Result<AssertResult> {
61 execute_steps(ctx, sched_mixed_steps(ctx))
62}
63
64pub fn custom_crash_light(ctx: &Ctx) -> Result<AssertResult> {
88 let ops = vec![
89 Op::add_cgroup("cg_0"),
90 Op::spawn_workers(
91 "cg_0",
92 WorkSpec::default()
93 .workers(4)
94 .work_type(WorkType::YieldHeavy),
95 ),
96 ];
97 execute_steps(ctx, vec![Step::new(ops, ctx.settled_hold(1.0))])
98}
99
100fn cgroup_pipe_io_steps(ctx: &Ctx) -> Vec<Step> {
101 let mut ops = vec![Op::add_cgroup("cg_0"), Op::add_cgroup("cg_1")];
102 for name in ["cg_0", "cg_1"] {
103 ops.push(Op::spawn_workers(
104 name,
105 WorkSpec::default()
106 .workers(2)
107 .work_type(WorkType::pipe_io(1024)),
108 ));
109 ops.push(Op::spawn_workers(
110 name,
111 WorkSpec::default().workers(ctx.workers_per_cgroup),
112 ));
113 }
114
115 vec![Step::new(ops, ctx.settled_hold(1.0))]
116}
117
118pub fn custom_cgroup_pipe_io(ctx: &Ctx) -> Result<AssertResult> {
121 execute_steps(ctx, cgroup_pipe_io_steps(ctx))
122}
123
124#[cfg(test)]
125mod tests {
126 use super::super::ops::{Setup, SpawnPlacement};
127 use super::*;
128 use crate::cgroup::CgroupManager;
129 use crate::topology::TestTopology;
130 use std::time::Duration;
131
132 fn ctx_for_test<'a>(cgroups: &'a CgroupManager, topo: &'a TestTopology) -> Ctx<'a> {
133 Ctx {
134 cgroups,
135 topo,
136 duration: Duration::from_secs(1),
137 workers_per_cgroup: 3,
138 sched_pid: Some(1),
139 settle: Duration::from_millis(100),
140 work_type_override: None,
141 assert: crate::assert::Assert::default_checks(),
142 wait_for_map_write: false,
143 current_step: std::sync::Arc::new(std::sync::atomic::AtomicU16::new(0)),
144 entry_name: None,
145 variant_hash: 0,
146 }
147 }
148
149 fn def_names(step: &Step) -> Vec<String> {
150 match &step.setup {
151 Setup::Defs(defs) => defs.iter().map(|d| d.name.to_string()).collect(),
152 Setup::Factory(_) => Vec::new(),
153 }
154 }
155
156 #[test]
157 fn host_cgroup_contention_builds_two_defs_and_host_spawn() {
158 let cgroups = CgroupManager::new("/nonexistent");
159 let topo = TestTopology::from_vm_topology(&crate::vmm::topology::Topology::new(1, 1, 4, 1));
160 let ctx = ctx_for_test(&cgroups, &topo);
161
162 let steps = host_cgroup_contention_steps(&ctx);
163 assert_eq!(steps.len(), 1);
164 assert_eq!(def_names(&steps[0]), ["cg_0", "cg_1"]);
165 assert_eq!(steps[0].ops.len(), 1);
166 match &steps[0].ops[0] {
167 Op::Spawn {
168 placement: SpawnPlacement::RunnerCgroup,
169 work,
170 } => {
171 assert_eq!(work.num_workers, Some(topo.total_cpus()));
172 }
173 other => panic!("expected Op::Spawn(RunnerCgroup), got {other:?}"),
174 }
175 }
176
177 #[test]
178 fn sched_mixed_builds_two_add_cgroups_and_eight_spawns() {
179 let cgroups = CgroupManager::new("/nonexistent");
180 let topo = TestTopology::from_vm_topology(&crate::vmm::topology::Topology::new(1, 1, 4, 1));
181 let ctx = ctx_for_test(&cgroups, &topo);
182
183 let steps = sched_mixed_steps(&ctx);
184 assert_eq!(steps.len(), 1);
185 let ops = &steps[0].ops;
186 let adds = ops
187 .iter()
188 .filter(|o| matches!(o, Op::AddCgroup { .. }))
189 .count();
190 let spawns = ops
191 .iter()
192 .filter(|o| {
193 matches!(
194 o,
195 Op::Spawn {
196 placement: SpawnPlacement::Cgroup(_),
197 ..
198 }
199 )
200 })
201 .count();
202 assert_eq!(adds, 2, "two cgroups added");
203 assert_eq!(spawns, 8, "4 policies × 2 cgroups = 8 spawns");
204 for op in ops {
205 if let Op::Spawn {
206 placement: SpawnPlacement::Cgroup(_),
207 work,
208 } = op
209 {
210 assert_eq!(work.num_workers, Some(2));
211 }
212 }
213 }
214
215 #[test]
216 fn cgroup_pipe_io_spawn_counts_follow_workers_per_cgroup() {
217 let cgroups = CgroupManager::new("/nonexistent");
218 let topo = TestTopology::from_vm_topology(&crate::vmm::topology::Topology::new(1, 1, 4, 1));
219 let ctx = ctx_for_test(&cgroups, &topo);
220
221 let steps = cgroup_pipe_io_steps(&ctx);
222 assert_eq!(steps.len(), 1);
223 let ops = &steps[0].ops;
224 let spawns: Vec<_> = ops
225 .iter()
226 .filter_map(|o| match o {
227 Op::Spawn {
228 placement: SpawnPlacement::Cgroup(cgroup),
229 work,
230 } => Some((cgroup.to_string(), work.num_workers)),
231 _ => None,
232 })
233 .collect();
234 assert_eq!(spawns.len(), 4, "pipe_io spawn + spinwait spawn per cgroup");
235 let spinwait_workers: Vec<_> = spawns
236 .iter()
237 .filter(|(_, n)| *n == Some(ctx.workers_per_cgroup))
238 .collect();
239 assert_eq!(spinwait_workers.len(), 2);
240 let pipe_workers: Vec<_> = spawns.iter().filter(|(_, n)| *n == Some(2)).collect();
241 assert_eq!(pipe_workers.len(), 2);
242 }
243}