use std::{collections::HashMap, path::PathBuf};
use crate::{
v1::{self, Cgroup, CgroupPath, SubsystemKind},
Pid, Result,
};
macro_rules! gen_unified_repr {
( $( ($subsystem: ident, $subsystem_mut: ident, $kind: ident, $name: literal) ),* $(, )? ) => {
use v1::{$( $subsystem ),*};
#[derive(Debug)]
pub struct UnifiedRepr {
$( $subsystem: Option<Subsys<$subsystem::Subsystem>> ),*
}
#[derive(Debug)]
struct Subsys<T> {
subsystem: T,
create: bool,
}
impl UnifiedRepr {
pub fn new(name: PathBuf) -> Self {
Self::with_subsystems(name, &[$(SubsystemKind::$kind),*])
}
pub fn with_subsystems(name: PathBuf, subsystems: &[SubsystemKind]) -> Self {
Self::with_custom_name_subsystems(
subsystems.iter().map(|k| (*k, CgroupPath::new(*k, name.clone())))
)
}
pub fn with_custom_name_subsystems(
subsystems: impl IntoIterator<Item = (SubsystemKind, CgroupPath)>,
) -> Self {
$( let mut $subsystem = None; )*
for (kind, path) in subsystems {
match kind {
$(
SubsystemKind::$kind => {
$subsystem = Some(Subsys {
subsystem: $subsystem::Subsystem::new(path),
create: true,
});
}
)*
}
}
Self { $( $subsystem ),* }
}
pub fn supports(&self, subsystem_kind: SubsystemKind) -> bool {
match subsystem_kind {
$(SubsystemKind::$kind => self.$subsystem.is_some()),*
}
}
pub fn skip_create(&mut self, skip_subsystems: &[SubsystemKind]) {
for kind in skip_subsystems {
match kind {
$(
SubsystemKind::$kind => {
if let Some(ref mut s) = self.$subsystem {
s.create = false;
}
}
)*
}
}
}
pub fn create(&mut self) -> Result<()> {
$(
if let Some(ref mut s) = self.$subsystem {
if s.create {
s.subsystem.create()?;
}
}
)*
Ok(())
}
pub fn apply(&mut self, resources: &v1::Resources) -> Result<()> {
$(
if let Some(ref mut s) = self.$subsystem {
s.subsystem.apply(&resources)?;
}
)*
Ok(())
}
pub fn delete(&mut self) -> Result<()> {
$(
if let Some(ref mut s) = self.$subsystem {
if s.create {
s.subsystem.delete()?;
}
}
)*
Ok(())
}
pub fn tasks(&self) -> Result<HashMap<SubsystemKind, Vec<Pid>>> {
let mut tasks = HashMap::new();
$(
if let Some(ref s) = self.$subsystem {
tasks.insert(SubsystemKind::$kind, s.subsystem.tasks()?);
}
)*
Ok(tasks)
}
pub fn add_task(&mut self, pid: Pid) -> Result<()> {
$(
if let Some(ref mut s) = self.$subsystem {
s.subsystem.add_task(pid)?;
}
)*
Ok(())
}
pub fn remove_task(&mut self, pid: Pid) -> Result<()> {
$(
if let Some(ref mut s) = self.$subsystem {
s.subsystem.remove_task(pid)?;
}
)*
Ok(())
}
pub fn procs(&self) -> Result<HashMap<SubsystemKind, Vec<Pid>>> {
let mut procs = HashMap::new();
$(
if let Some(ref s) = self.$subsystem {
procs.insert(SubsystemKind::$kind, s.subsystem.procs()?);
}
)*
Ok(procs)
}
pub fn add_proc(&mut self, pid: Pid) -> Result<()> {
$(
if let Some(ref mut s) = self.$subsystem {
s.subsystem.add_proc(pid)?;
}
)*
Ok(())
}
pub fn remove_proc(&mut self, pid: Pid) -> Result<()> {
$(
if let Some(ref mut s) = self.$subsystem {
s.subsystem.remove_proc(pid)?;
}
)*
Ok(())
}
$(
with_doc!(
concat!("Returns a reference to the ", $name, " subsystem."),
pub fn $subsystem(&self) -> Option<&$subsystem::Subsystem> {
self.$subsystem.as_ref().map(|s| &s.subsystem)
}
);
with_doc!(
concat!("Returns a mutable reference to the ", $name, " subsystem."),
pub fn $subsystem_mut(&mut self) -> Option<&mut $subsystem::Subsystem> {
self.$subsystem.as_mut().map(|s| &mut s.subsystem)
}
);
)*
}
};
}
gen_unified_repr! {
(cpu, cpu_mut, Cpu, "CPU"),
(cpuset, cpuset_mut, Cpuset, "cpuset"),
(cpuacct, cpuacct_mut, Cpuacct, "cpuacct"),
(memory, memory_mut, Memory, "memory"),
(hugetlb, hugetlb_mut, HugeTlb, "hugetlb"),
(devices, devices_mut, Devices, "devices"),
(blkio, blkio_mut, BlkIo, "blkio"),
(rdma, rdma_mut, Rdma, "RDMA"),
(net_prio, net_prio_mut, NetPrio, "net_prio"),
(net_cls, net_cls_mut, NetCls, "net_cls"),
(pids, pids_mut, Pids, "pids"),
(freezer, freezer_mut, Freezer, "freezer"),
(perf_event, perf_event_mut, PerfEvent, "perf_event"),
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_unified_repr_subsystems() {
let cgroups = UnifiedRepr::new(gen_cgroup_name!());
assert!(cgroups.supports(SubsystemKind::Cpu));
assert!(cgroups.cpu().is_some());
assert!(cgroups.supports(SubsystemKind::Cpuset));
assert!(cgroups.cpuset().is_some());
let cgroups = UnifiedRepr::with_subsystems(gen_cgroup_name!(), &[]);
assert!(!cgroups.supports(SubsystemKind::Cpu));
assert!(cgroups.cpu().is_none());
assert!(!cgroups.supports(SubsystemKind::Cpuset));
assert!(cgroups.cpuset().is_none());
let cgroups = UnifiedRepr::with_subsystems(gen_cgroup_name!(), &[SubsystemKind::Cpu]);
assert!(cgroups.supports(SubsystemKind::Cpu));
assert!(cgroups.cpu().is_some());
assert!(!cgroups.supports(SubsystemKind::Cpuset));
assert!(cgroups.cpuset().is_none());
}
#[test]
fn test_unified_repr_create_delete() -> Result<()> {
{
let mut cgroups = UnifiedRepr::with_subsystems(
gen_cgroup_name!(),
&[SubsystemKind::Cpu, SubsystemKind::Cpuset],
);
cgroups.create()?;
assert!(cgroups.cpu().unwrap().path().exists());
assert!(cgroups.cpuset().unwrap().path().exists());
cgroups.delete()?;
assert!(!cgroups.cpu().unwrap().path().exists());
assert!(!cgroups.cpuset().unwrap().path().exists());
}
{
let name = gen_cgroup_name!();
let mut cgroups = UnifiedRepr::with_subsystems(name.clone(), &[]);
cgroups.create()?;
let cpu = cpu::Subsystem::new(CgroupPath::new(SubsystemKind::Cpu, name.clone()));
assert!(!cpu.path().exists());
let cpuset = cpuset::Subsystem::new(CgroupPath::new(SubsystemKind::Cpuset, name));
assert!(!cpuset.path().exists());
cgroups.delete()?;
}
{
let name = gen_cgroup_name!();
let mut cgroups = UnifiedRepr::with_subsystems(name.clone(), &[SubsystemKind::Cpu]);
cgroups.create()?;
let cpu = cpu::Subsystem::new(CgroupPath::new(SubsystemKind::Cpu, name.clone()));
assert!(cpu.path().exists());
let cpuset = cpuset::Subsystem::new(CgroupPath::new(SubsystemKind::Cpuset, name));
assert!(!cpuset.path().exists());
cgroups.delete()?;
assert!(!cpu.path().exists());
assert!(!cpuset.path().exists());
}
Ok(())
}
#[test]
fn test_unified_repr_skip_create() -> Result<()> {
let mut cgroups = UnifiedRepr::with_subsystems(
gen_cgroup_name!(),
&[SubsystemKind::Cpu, SubsystemKind::Cpuset],
);
cgroups.skip_create(&[SubsystemKind::Cpuset]);
cgroups.create()?;
assert!(cgroups.cpu().unwrap().path().exists());
assert!(!cgroups.cpuset().unwrap().path().exists());
cgroups.delete()?;
assert!(!cgroups.cpu().unwrap().path().exists());
assert!(!cgroups.cpuset().unwrap().path().exists());
Ok(())
}
#[test]
#[ignore] fn test_unified_repr_add_get_remove_tasks() -> Result<()> {
let mut cgroups = UnifiedRepr::with_subsystems(gen_cgroup_name!(), &[SubsystemKind::Cpu]);
cgroups.create()?;
let pid = Pid::from(std::process::id());
cgroups.add_task(pid)?;
assert_eq!(cgroups.cpu().unwrap().tasks()?, vec![pid]);
assert_eq!(
cgroups.tasks()?,
hashmap! { (SubsystemKind::Cpu, vec![pid]) }
);
cgroups.remove_task(pid)?;
assert!(cgroups.cpu().unwrap().tasks()?.is_empty());
assert!(cgroups.tasks()?[&SubsystemKind::Cpu].is_empty());
cgroups.delete()
}
#[test]
#[ignore] fn test_unified_repr_add_get_remove_procs() -> Result<()> {
use std::process::{self, Command};
let mut cgroups = UnifiedRepr::with_subsystems(gen_cgroup_name!(), &[SubsystemKind::Cpu]);
cgroups.create()?;
let pid = Pid::from(process::id());
cgroups.add_proc(pid)?;
assert_eq!(cgroups.cpu().unwrap().procs()?, vec![pid]);
assert_eq!(
cgroups.procs()?,
hashmap! { (SubsystemKind::Cpu, vec![pid]) }
);
let mut child = Command::new("sleep").arg("1").spawn().unwrap();
let child_pid = Pid::from(&child);
assert!(
cgroups.cpu().unwrap().procs()? == vec![pid, child_pid]
|| cgroups.cpu().unwrap().procs()? == vec![child_pid, pid]
);
assert!(
cgroups.procs()? == hashmap! { (SubsystemKind::Cpu, vec![pid, child_pid]) }
|| cgroups.procs()? == hashmap! { (SubsystemKind::Cpu, vec![child_pid, pid]) }
);
child.wait()?;
assert!(cgroups.cpu().unwrap().procs()? == vec![pid]);
assert!(cgroups.procs()? == hashmap! { (SubsystemKind::Cpu, vec![pid]) });
cgroups.remove_proc(pid)?;
assert!(cgroups.cpu().unwrap().procs()?.is_empty());
assert!(cgroups.procs()?[&SubsystemKind::Cpu].is_empty());
cgroups.delete()
}
#[test]
fn test_unified_repr_apply() -> Result<()> {
#![allow(clippy::identity_op)]
const GB: u64 = 1 << 30;
let mut cgroups = UnifiedRepr::new(gen_cgroup_name!());
cgroups.skip_create(&[SubsystemKind::Cpuacct, SubsystemKind::NetCls]);
cgroups.create()?;
let id_set = [0].iter().copied().collect::<cpuset::IdSet>();
let class_id = [0x10, 0x1].into();
let pids_max = crate::Max::Limit(42);
let mut resources = v1::Resources::default();
resources.cpu.shares = Some(1000);
resources.cpuset.cpus = Some(id_set.clone());
resources.memory.limit_in_bytes = Some(1 * GB as i64);
resources.hugetlb.limits = [(hugetlb::HugepageSize::Mb2, hugetlb::Limit::Pages(1))]
.iter()
.copied()
.collect();
resources.devices.deny = vec!["a".parse::<devices::Access>().unwrap()];
resources.blkio.weight = Some(1000);
resources.net_prio.ifpriomap = hashmap! { ("lo".to_string(), 1)};
resources.net_cls.classid = Some(class_id);
resources.pids.max = Some(pids_max);
resources.freezer.state = Some(freezer::State::Frozen);
cgroups.apply(&resources)?;
assert_eq!(cgroups.cpu().unwrap().shares()?, 1000);
assert_eq!(cgroups.cpuset().unwrap().cpus()?, id_set);
assert_eq!(cgroups.memory().unwrap().limit_in_bytes()?, 1 * GB);
assert_eq!(
cgroups
.hugetlb()
.unwrap()
.limit_in_pages(hugetlb::HugepageSize::Mb2)?,
1
);
assert!(cgroups.devices().unwrap().list()?.is_empty());
assert_eq!(cgroups.blkio().unwrap().weight()?, 1000);
assert_eq!(cgroups.net_prio().unwrap().ifpriomap()?["lo"], 1);
assert_eq!(cgroups.net_cls().unwrap().classid()?, class_id);
assert_eq!(cgroups.pids().unwrap().max()?, pids_max);
assert_eq!(cgroups.freezer().unwrap().state()?, freezer::State::Frozen);
cgroups.delete()
}
}