use std::{collections::HashSet, fmt, iter::FromIterator, path::PathBuf};
use crate::{
parse::{parse, parse_01_bool},
v1::{self, cgroup::CgroupHelper, Cgroup, CgroupPath},
Error, ErrorKind, Result,
};
#[derive(Debug)]
pub struct Subsystem {
path: CgroupPath,
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct Resources {
pub cpus: Option<IdSet>,
pub mems: Option<IdSet>,
pub memory_migrate: Option<bool>,
pub cpu_exclusive: Option<bool>,
pub mem_exclusive: Option<bool>,
pub mem_hardwall: Option<bool>,
pub memory_pressure_enabled: Option<bool>,
pub memory_spread_page: Option<bool>,
pub memory_spread_slab: Option<bool>,
pub sched_load_balance: Option<bool>,
pub sched_relax_domain_level: Option<i32>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IdSet(HashSet<u32>);
impl_cgroup! {
Subsystem, Cpuset,
fn apply(&mut self, resources: &v1::Resources) -> Result<()> {
let res: &self::Resources = &resources.cpuset;
macro_rules! a {
($field: ident, $setter: ident) => {
if let Some(r) = res.$field {
self.$setter(r)?;
}
};
}
if let Some(ref cpus) = res.cpus {
self.set_cpus(cpus)?;
}
if let Some(ref mems) = res.mems {
self.set_mems(mems)?;
}
a!(memory_migrate, set_memory_migrate);
a!(cpu_exclusive, set_cpu_exclusive);
a!(mem_exclusive, set_mem_exclusive);
a!(mem_hardwall, set_mem_hardwall);
a!(memory_pressure_enabled, set_memory_pressure_enabled);
a!(memory_spread_page, set_memory_spread_page);
a!(memory_spread_slab, set_memory_spread_slab);
a!(sched_load_balance, set_sched_load_balance);
a!(sched_relax_domain_level, set_sched_relax_domain_level);
Ok(())
}
}
macro_rules! _gen_getter {
($desc: literal, $field: ident $( : $link : ident )?, $ty: ty, $parser: ident) => {
gen_getter!(cpuset, $desc, $field $( : $link )?, $ty, $parser);
};
}
macro_rules! _gen_setter {
($desc: literal, $field: ident : link, $setter: ident, $ty: ty, $val: expr) => {
gen_setter!(cpuset, $desc, $field: link, $setter, $ty, $val);
};
(
$desc: literal,
$field: ident : link,
$setter: ident,
$arg: ident : $ty: ty as $as: ty,
$val: expr
) => {
gen_setter!(cpuset, $desc, $field: link, $setter, $arg: $ty as $as, $val);
};
}
const MEMORY_PRESSURE_ENABLED: &str = "cpuset.memory_pressure_enabled";
const CLONE_CHILDREN: &str = "cgroup.clone_children";
const DOMAIN_LEVEL_MIN: i32 = -1;
const DOMAIN_LEVEL_MAX: i32 = 5;
impl Subsystem {
_gen_getter!(
"the set of CPUs this cgroup can use",
cpus: link,
IdSet,
parse
);
_gen_setter!(
"a set of CPUs this cgroup can use",
cpus: link,
set_cpus,
&IdSet,
&"0,1".parse::<cpuset::IdSet>()?
);
_gen_getter!(
"the set of memory nodes this cgroup can use",
mems: link,
IdSet,
parse
);
_gen_setter!(
"a set of memory nodes this cgroup can use",
mems: link,
set_mems,
&IdSet,
&"0,1".parse::<cpuset::IdSet>()?
);
_gen_getter!(
"whether the memory used by this cgroup should be migrated
when memory selection is updated,",
memory_migrate: link,
bool,
parse_01_bool
);
_gen_setter!(
"whether the memory used by this cgroup should be migrated
when memory selection is updated,",
memory_migrate: link,
set_memory_migrate,
enable: bool as i32,
true
);
_gen_getter!(
"whether the selected CPUs should be exclusive to this cgroup,",
cpu_exclusive: link,
bool,
parse_01_bool
);
_gen_setter!(
"whether the selected CPUs should be exclusive to this cgroup,",
cpu_exclusive: link,
set_cpu_exclusive,
exclusive: bool as i32,
true
);
_gen_getter!(
"whether the selected memory nodes should be exclusive to this cgroup,",
mem_exclusive: link,
bool,
parse_01_bool
);
_gen_setter!(
"whether the selected memory nodes should be exclusive to this cgroup,",
mem_exclusive: link,
set_mem_exclusive,
exclusive: bool as i32,
true
);
_gen_getter!(
"whether this cgroup is \"hardwalled\"",
mem_hardwall: link,
bool,
parse_01_bool
);
_gen_setter!(
"whether this cgroup is \"hardwalled\"",
mem_hardwall: link,
set_mem_hardwall,
enable: bool as i32,
true
);
_gen_getter!(
"the running average of the memory pressure faced by this cgroup,",
memory_pressure,
u64,
parse
);
with_doc! { concat!(
gen_doc!(
reads;
"cpuset.memory_pressure_enabled",
"whether the kernel computes the memory pressure of this cgroup,"
),
gen_doc!(see; memory_pressure_enabled),
"# Errors
This field is present only in the root cgroup. If you call this method on a non-root cgroup, an
error is returned with kind [`ErrorKind::InvalidOperation`]. On the root cgroup, returns an error if
failed to read and parse `cpuset.memory_pressure_enabled` file.
[`ErrorKind::InvalidOperation`]: ../../enum.ErrorKind.html#variant.InvalidOperation\n\n",
gen_doc!(eg_read; cpuset, memory_pressure_enabled)),
pub fn memory_pressure_enabled(&self) -> Result<bool> {
if self.is_root() {
self.open_file_read(MEMORY_PRESSURE_ENABLED)
.and_then(parse_01_bool)
} else {
Err(Error::new(ErrorKind::InvalidOperation))
}
}
}
with_doc! { concat!(
gen_doc!(
sets;
"cpuset.memory_pressure_enabled",
"whether the kernel computes the memory pressure of this cgroup,"
),
gen_doc!(see; memory_pressure_enabled),
"# Errors
This field is present only in the root cgroup. If you call this method on a non-root cgroup, an
error is returned with kind [`ErrorKind::InvalidOperation`]. On the root cgroup, returns an error if
failed to write to `cpuset.memory_pressure_enabled` file.
[`ErrorKind::InvalidOperation`]: ../../enum.ErrorKind.html#variant.InvalidOperation\n\n",
gen_doc!(eg_write; cpuset, set_memory_pressure_enabled, true)),
pub fn set_memory_pressure_enabled(&mut self, enable: bool) -> Result<()> {
if self.is_root() {
self.write_file(MEMORY_PRESSURE_ENABLED, enable as i32)
} else {
Err(Error::new(ErrorKind::InvalidOperation))
}
}
}
_gen_getter!(
"whether file system buffers are spread across the selected memory nodes,",
memory_spread_page: link,
bool,
parse_01_bool
);
_gen_setter!(
"whether file system buffers are spread across the selected memory nodes,",
memory_spread_page: link,
set_memory_spread_page,
enable: bool as i32,
true
);
_gen_getter!(
"whether the kernel slab caches for file I/O are spread across the selected memory nodes,",
memory_spread_slab: link,
bool,
parse_01_bool
);
_gen_setter!(
"whether the kernel slab caches for file I/O are spread across the selected memory nodes,",
memory_spread_slab: link,
set_memory_spread_slab,
enable: bool as i32,
true
);
_gen_getter!(
"whether the kernel balances the load across the selected CPUs,",
sched_load_balance: link,
bool,
parse_01_bool
);
_gen_setter!(
"whether the kernel balances the load across the selected CPUs,",
sched_load_balance: link,
set_sched_load_balance,
enable: bool as i32,
true
);
_gen_getter!(
"how much work the kernel do to balance the load on this cgroup,",
sched_relax_domain_level: link,
i32,
parse
);
with_doc! { concat!(
gen_doc!(
sets;
"cpuset.sched_relax_domain_level",
"how much work the kernel do to balance the load on this cgroup,"
: "The value must be between -1 and 5 (inclusive)."
),
gen_doc!(see; sched_relax_domain_level),
"# Errors
Returns an error with kind [`ErrorKind::InvalidArgument`] if the level is out-of-range. Returns an
error if failed to write to `cpuset.sched_relax_domain_level` file of this cgroup.
[`ErrorKind::InvalidArgument`]: ../../enum.ErrorKind.html#variant.InvalidArgument\n\n",
gen_doc!(eg_write; cpuset, set_sched_relax_domain_level, 0)),
pub fn set_sched_relax_domain_level(&mut self, level: i32) -> Result<()> {
if level < DOMAIN_LEVEL_MIN || level > DOMAIN_LEVEL_MAX {
return Err(Error::new(ErrorKind::InvalidArgument));
}
self.write_file("cpuset.sched_relax_domain_level", level)
}
}
with_doc! { concat!(
gen_doc!(
reads;
"cgroup.clone_children",
"whether a new cpuset cgroup will copy the configuration from its parent cgroup,"
),
gen_doc!(see),
gen_doc!(err_read; "cgroup.clone_children"),
gen_doc!(eg_read; cpuset, clone_children)),
pub fn clone_children(&self) -> Result<bool> {
self.open_file_read(CLONE_CHILDREN).and_then(parse_01_bool)
}
}
with_doc! { concat!(
gen_doc!(
sets;
"cgroup.clone_children",
"whether a new cpuset cgroup will copy the configuration from its parent cgroup,"
),
gen_doc!(see),
gen_doc!(err_write; "cgroup.clone_children"),
gen_doc!(eg_write; cpuset, set_clone_children, true)),
pub fn set_clone_children(&mut self, clone: bool) -> Result<()> {
self.write_file(CLONE_CHILDREN, clone as i32)
}
}
}
impl Into<v1::Resources> for Resources {
fn into(self) -> v1::Resources {
v1::Resources {
cpuset: self,
..v1::Resources::default()
}
}
}
impl FromIterator<u32> for IdSet {
fn from_iter<I: IntoIterator<Item = u32>>(iter: I) -> Self {
let mut s = IdSet::new();
for id in iter {
s.add(id);
}
s
}
}
impl std::str::FromStr for IdSet {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
let s = s.trim();
if s.is_empty() {
return Ok(IdSet::new());
}
let mut result = Vec::new();
for comma_split in s.split(',') {
let mut dash_split = comma_split.split('-');
match (dash_split.next(), dash_split.next(), dash_split.next()) {
(Some(start), Some(end), None) => {
let start = start.parse()?;
let end = end.parse()?;
if end < start {
bail_parse!();
}
for n in start..=end {
result.push(n);
}
}
(Some(single), None, None) => {
result.push(single.parse()?);
}
_ => {
bail_parse!();
}
}
}
Ok(Self::from_iter(result.into_iter()))
}
}
impl fmt::Display for IdSet {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.0.is_empty() {
return Ok(());
}
let mut ids = self.0.iter().collect::<Vec<_>>();
ids.sort();
let mut ids = ids.into_iter().copied();
#[derive(Debug)]
enum IdSegment {
Single(u32),
Range(u32, u32),
}
let mut current = IdSegment::Single(ids.next().unwrap());
let mut segments = Vec::new();
for id in ids {
use IdSegment::*;
match current {
Single(cur) if id == cur + 1 => {
current = Range(cur, id);
}
Range(start, end) if id == end + 1 => {
current = Range(start, id);
}
_ => {
segments.push(current);
current = Single(id);
}
}
}
segments.push(current);
let mut buf = String::new();
for seg in segments {
use IdSegment::*;
let s = match seg {
Single(id) => format!("{},", id),
Range(s, e) => {
if e == s + 1 {
format!("{},{},", s, e)
} else {
format!("{}-{},", s, e)
}
}
};
buf.push_str(&s);
}
buf.truncate(buf.len() - 1);
f.write_str(&buf)
}
}
impl IdSet {
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
Self(HashSet::new())
}
pub fn to_hash_set(&self) -> HashSet<u32> {
self.0.clone()
}
pub fn add(&mut self, id: u32) {
self.0.insert(id);
}
pub fn remove(&mut self, id: u32) {
self.0.remove(&id);
}
}
#[cfg(test)]
mod tests {
use super::*;
use v1::SubsystemKind;
#[test]
#[rustfmt::skip]
fn test_subsystem_create_file_exists() -> Result<()> {
let root = Subsystem::new(CgroupPath::new(SubsystemKind::Cpuset, PathBuf::new()));
assert!(root.file_exists(MEMORY_PRESSURE_ENABLED));
gen_subsystem_test!(
Cpuset,
[
"cpus", "mems", "memory_migrate", "cpu_exclusive", "mem_exclusive", "mem_hardwall",
"memory_pressure", "memory_spread_page", "memory_spread_slab", "sched_load_balance",
"sched_relax_domain_level",
]
)?;
let mut non_root =
Subsystem::new(CgroupPath::new(SubsystemKind::Cpuset, gen_cgroup_name!()));
non_root.create()?;
assert!(non_root.file_exists(CLONE_CHILDREN));
assert!(!non_root.file_exists(MEMORY_PRESSURE_ENABLED));
non_root.delete()
}
#[test]
#[ignore] fn test_subsystem_apply() -> Result<()> {
let id_set = [0].iter().copied().collect::<IdSet>();
gen_subsystem_test!(
Cpuset,
Resources {
cpus: Some(id_set.clone()),
mems: Some(id_set.clone()),
memory_migrate: Some(true),
cpu_exclusive: Some(true),
mem_exclusive: Some(true),
mem_hardwall: Some(true),
memory_pressure_enabled: None, memory_spread_page: Some(true),
memory_spread_slab: Some(true),
sched_load_balance: Some(false),
sched_relax_domain_level: None, },
(cpus, id_set),
(mems, id_set),
(memory_migrate, true),
(cpu_exclusive, true),
(mem_exclusive, true),
(mem_hardwall, true),
(memory_spread_page, true),
(memory_spread_slab, true),
(sched_load_balance, false),
)
}
#[test]
fn test_subsystem_cpus() -> Result<()> {
let mut cgroup = Subsystem::new(CgroupPath::new(SubsystemKind::Cpuset, gen_cgroup_name!()));
cgroup.create()?;
let id_set = [0].iter().copied().collect();
cgroup.set_cpus(&id_set)?;
assert_eq!(cgroup.cpus()?, id_set);
cgroup.delete()
}
#[test]
fn test_subsystem_mems() -> Result<()> {
let mut cgroup = Subsystem::new(CgroupPath::new(SubsystemKind::Cpuset, gen_cgroup_name!()));
cgroup.create()?;
let id_set = [0].iter().copied().collect();
cgroup.set_mems(&id_set)?;
assert_eq!(cgroup.mems()?, id_set);
cgroup.delete()
}
#[test]
fn test_subsystem_memory_migrate() -> Result<()> {
gen_subsystem_test!(Cpuset, memory_migrate, false, set_memory_migrate, true)
}
#[test]
#[ignore] fn test_subsystem_cpu_exclusive() -> Result<()> {
gen_subsystem_test!(Cpuset, cpu_exclusive, false, set_cpu_exclusive, true)
}
#[test]
#[ignore] fn test_subsystem_mem_exclusive() -> Result<()> {
gen_subsystem_test!(Cpuset, mem_exclusive, false, set_mem_exclusive, true)
}
#[test]
fn test_subsystem_mem_hardwall() -> Result<()> {
gen_subsystem_test!(Cpuset, mem_hardwall, false, set_mem_hardwall, true)
}
#[test]
fn test_subsystem_memory_pressure() -> Result<()> {
gen_subsystem_test!(Cpuset, memory_pressure, 0)
}
#[test]
#[ignore] fn test_subsystem_memory_pressure_enabled() -> Result<()> {
let mut root = Subsystem::new(CgroupPath::new(SubsystemKind::Cpuset, PathBuf::new()));
let enabled = root.memory_pressure_enabled()?;
root.set_memory_pressure_enabled(!enabled)?;
assert_eq!(root.memory_pressure_enabled()?, !enabled);
root.set_memory_pressure_enabled(enabled)?;
assert_eq!(root.memory_pressure_enabled()?, enabled);
Ok(())
}
#[test]
fn err_subsystem_memory_pressure_enabled() -> Result<()> {
gen_subsystem_test!(
Memory,
set_memory_pressure_enabled,
(InvalidOperation, true)
)
}
#[test]
fn test_subsystem_memory_spread_page() -> Result<()> {
gen_subsystem_test!(
Cpuset,
memory_spread_page,
false,
set_memory_spread_page,
true
)
}
#[test]
fn test_subsystem_memory_spread_slab() -> Result<()> {
gen_subsystem_test!(
Cpuset,
memory_spread_slab,
false,
set_memory_spread_slab,
true
)
}
#[test]
fn test_subsystem_sched_load_balance() -> Result<()> {
gen_subsystem_test!(
Cpuset,
sched_load_balance,
true,
set_sched_load_balance,
false
)
}
#[test]
fn test_subsystem_sched_relax_domain_level() -> Result<()> {
gen_subsystem_test!(Cpuset, sched_relax_domain_level, DOMAIN_LEVEL_MIN)
}
#[test]
fn err_subsystem_sched_relax_domain_level() -> Result<()> {
gen_subsystem_test!(
Memory,
set_sched_relax_domain_level,
(InvalidArgument, DOMAIN_LEVEL_MIN - 1),
(InvalidArgument, DOMAIN_LEVEL_MAX + 1)
)
}
#[test]
fn test_subsystem_clone_children() -> Result<()> {
gen_subsystem_test!(Cpuset, clone_children, false, set_clone_children, true)
}
#[test]
fn test_id_set_from_str() {
macro_rules! hashset {
( $( $x: expr ),* $(, )? ) => {{
#![allow(unused_mut, clippy::let_and_return)]
let mut s = HashSet::new();
$( s.insert($x); )*
s
}};
}
let test_cases = vec![
("", hashset! {}),
("0", hashset! {0}),
("1,2", hashset! {1, 2}),
("0,2,4,6", hashset! {0, 2, 4, 6}),
("2-6", hashset! {2, 3, 4, 5, 6}),
("0-2,5-7", hashset! {0, 1, 2, 5, 6, 7}),
("2-3,4-5,6-7", hashset! {2, 3, 4, 5, 6, 7}),
("1,3,5-7,9,10", hashset! {1, 3, 5, 6, 7, 9, 10}),
(" 1,3,5-7,9,10 ", hashset! {1, 3, 5, 6, 7, 9, 10}),
("0-65535", (0..65536).collect()),
]
.into_iter();
for (case, expected) in test_cases {
assert_eq!(case.parse::<IdSet>().unwrap().to_hash_set(), expected);
}
}
#[test]
fn err_id_set_from_str() {
for case in &[
",",
",0",
"0,",
"0, 1",
"-",
"-0",
"0-",
"0-,1",
"0,-1",
"1-0",
"-1",
"0.1",
"invalid",
"0,invalid",
] {
assert_eq!(case.parse::<IdSet>().unwrap_err().kind(), ErrorKind::Parse);
}
}
#[test]
fn test_id_set_fmt() {
let test_cases = vec![
(vec![], ""),
(vec![0], "0"),
(vec![1, 2], "1,2"),
(vec![0, 2, 4, 6], "0,2,4,6"),
(vec![2, 3, 4, 5, 6], "2-6"),
(vec![0, 1, 2, 5, 6, 7], "0-2,5-7"),
(vec![1, 3, 4, 5, 7, 9, 10, 11], "1,3-5,7,9-11"),
(vec![1, 3, 5, 6, 7, 9, 10], "1,3,5-7,9,10"),
((0..65536).collect(), "0-65535"),
]
.into_iter();
for (case, expected) in test_cases {
let id_set = case.iter().copied().collect::<IdSet>();
assert_eq!(id_set.to_string(), expected.to_string());
}
}
}