#![allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
reason = "test code"
)]
use std::num::NonZeroU64;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering};
use super::{Cgroups, RUN_CPU_WEIGHT};
use crate::backlog::ceiling::{Mark, Unapplied, verdict};
use crate::{Ceiling, Shares};
static ROOTS: AtomicU64 = AtomicU64::new(0);
struct FakeRoot(PathBuf);
impl FakeRoot {
fn delegated() -> FakeRoot {
let root = FakeRoot::fresh();
for name in ["cgroup.procs", "cgroup.subtree_control"] {
std::fs::write(root.path().join(name), "").unwrap();
}
root
}
fn plain() -> FakeRoot {
FakeRoot::fresh()
}
fn fresh() -> FakeRoot {
let path = std::env::temp_dir().join(format!(
"sprawling-cgroup-{}-{}",
std::process::id(),
ROOTS.fetch_add(1, Ordering::Relaxed)
));
std::fs::create_dir_all(&path).unwrap();
FakeRoot(path)
}
fn path(&self) -> &Path {
&self.0
}
fn read(&self, relative: &str) -> String {
std::fs::read_to_string(self.0.join(relative)).unwrap()
}
fn has(&self, relative: &str) -> bool {
self.0.join(relative).exists()
}
}
impl Drop for FakeRoot {
fn drop(&mut self) {
if let Err(kept) = std::fs::remove_dir_all(&self.0) {
eprintln!(
"the fake cgroup root stayed at {}: {kept}",
self.0.display()
);
}
}
}
#[test]
fn a_run_reads_unshared_where_the_root_cannot_be_written() {
let root = FakeRoot::plain();
let run = kernel::RunId::from_bytes([7; 16]);
let limit = NonZeroU64::new(8 << 30).unwrap();
let asked = Shares::CpuAndMemory { limit };
let mut cgroups = Cgroups::adopt(root.path(), 4242);
let why = if cfg!(target_os = "macos") {
Unapplied::Platform
} else {
Unapplied::NotDelegated
};
assert_eq!(
cgroups.enter(run, 111, asked),
Mark::Unapplied { limit, why }
);
assert_eq!(cgroups.enter(run, 112, Shares::Cpu), Mark::NotAsked);
assert_eq!(cgroups.held(run), Shares::Unset);
assert!(!root.has("core"), "the harness must not be moved");
assert!(
!root.has(&format!("run-{run}")),
"no run cgroup may be made"
);
}
#[test]
fn two_runs_hold_the_same_weight_and_the_limit_the_arm_asked_for() {
let root = FakeRoot::delegated();
let limit = NonZeroU64::new(8 << 30).unwrap();
let asked = Shares::CpuAndMemory { limit };
let first = kernel::RunId::from_bytes([7; 16]);
let second = kernel::RunId::from_bytes([8; 16]);
let mut cgroups = Cgroups::adopt(root.path(), 4242);
assert_eq!(cgroups.enter(first, 111, asked), Mark::Unread { limit });
assert_eq!(cgroups.enter(second, 222, Shares::Cpu), Mark::NotAsked);
assert_eq!(cgroups.held(first), asked);
assert_eq!(cgroups.held(second), Shares::Cpu);
assert_eq!(root.read("core/cgroup.procs"), "4242");
assert_eq!(root.read("cgroup.subtree_control"), "+cpu +memory");
assert_eq!(
root.read(&format!("run-{first}/cpu.weight")),
RUN_CPU_WEIGHT.to_string()
);
assert_eq!(
root.read(&format!("run-{second}/cpu.weight")),
RUN_CPU_WEIGHT.to_string()
);
assert_eq!(
root.read(&format!("run-{first}/memory.max")),
limit.get().to_string()
);
assert_eq!(root.read(&format!("run-{second}/memory.max")), "max");
assert_eq!(root.read(&format!("run-{first}/cgroup.procs")), "111");
assert_eq!(root.read(&format!("run-{second}/cgroup.procs")), "222");
cgroups.forget(first);
assert_eq!(cgroups.held(first), Shares::Unset);
}
#[test]
fn cpu_only_clears_a_previous_ceiling_when_a_run_directory_is_reused() {
let root = FakeRoot::delegated();
let run = kernel::RunId::from_bytes([9; 16]);
let mut first = Cgroups::adopt(root.path(), 4242);
let capped = Shares::CpuAndMemory {
limit: NonZeroU64::new(64 << 20).unwrap(),
};
assert!(matches!(first.enter(run, 111, capped), Mark::Unread { .. }));
drop(first);
let mut restarted = Cgroups::adopt(root.path(), 4242);
let held = restarted.enter(run, 222, Shares::Cpu);
assert_eq!(
(
held,
restarted.held(run),
root.read(&format!("run-{run}/memory.max"))
),
(Mark::NotAsked, Shares::Cpu, "max".to_owned())
);
}
#[test]
fn the_oom_line_alone_tells_a_hit_from_a_reclaim() {
let root = FakeRoot::delegated();
let run = kernel::RunId::from_bytes([10; 16]);
let limit = NonZeroU64::new(64 << 20).unwrap();
let events = root.path().join(format!("run-{run}/memory.events"));
std::fs::create_dir_all(events.parent().unwrap()).unwrap();
std::fs::write(&events, "low 0\nhigh 0\nmax 0\noom 0\noom_kill 0\n").unwrap();
let mut cgroups = Cgroups::adopt(root.path(), 4242);
let mark = cgroups.enter(run, 111, Shares::CpuAndMemory { limit });
assert_eq!(mark, Mark::Watching { limit, before: 0 });
std::fs::write(&events, "low 0\nhigh 0\nmax 4\noom 0\noom_kill 0\n").unwrap();
assert_eq!(verdict(mark, cgroups.refusals(run)), None);
std::fs::write(&events, "low 0\nhigh 0\nmax 5\noom 1\noom_kill 1\n").unwrap();
assert_eq!(
verdict(mark, cgroups.refusals(run)),
Some(Ceiling::Hit { limit })
);
std::fs::remove_file(&events).unwrap();
assert_eq!(
verdict(mark, cgroups.refusals(run)),
Some(Ceiling::Unread { limit })
);
}
#[test]
fn a_command_outside_its_run_cgroup_says_why() {
let root = FakeRoot::delegated();
let limit = NonZeroU64::new(64 << 20).unwrap();
let asked = Shares::CpuAndMemory { limit };
let mut cgroups = Cgroups::adopt(root.path(), 4242);
let blocked = kernel::RunId::from_bytes([11; 16]);
std::fs::write(root.path().join(format!("run-{blocked}")), "a file").unwrap();
assert_eq!(
cgroups.enter(blocked, 111, asked),
Mark::Unapplied {
limit,
why: Unapplied::Refused
}
);
let unjoined = kernel::RunId::from_bytes([12; 16]);
std::fs::create_dir_all(root.path().join(format!("run-{unjoined}/cgroup.procs"))).unwrap();
assert_eq!(
cgroups.enter(unjoined, 222, asked),
Mark::Unapplied {
limit,
why: Unapplied::Unjoined
}
);
}