use std::path::PathBuf;
use crate::{
parse::{parse, parse_next},
v1::{self, cgroup::CgroupHelper, Cgroup, CgroupPath},
Result,
};
#[derive(Debug)]
pub struct Subsystem {
path: CgroupPath,
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct Resources {
pub shares: Option<u64>,
pub cfs_quota_us: Option<i64>,
pub cfs_period_us: Option<u64>,
pub rt_runtime_us: Option<i64>,
pub rt_period_us: Option<u64>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Stat {
pub nr_periods: u64,
pub nr_throttled: u64,
pub throttled_time: u64,
}
impl_cgroup! {
Subsystem, Cpu,
fn apply(&mut self, resources: &v1::Resources) -> Result<()> {
let res: &self::Resources = &resources.cpu;
macro_rules! a {
($field: ident, $setter: ident) => {
if let Some(r) = res.$field {
self.$setter(r)?;
}
};
}
a!(shares, set_shares);
a!(cfs_quota_us, set_cfs_quota_us);
a!(cfs_period_us, set_cfs_period_us);
a!(rt_runtime_us, set_rt_runtime_us);
a!(rt_period_us, set_rt_period_us);
Ok(())
}
}
impl Subsystem {
gen_getter!(
cpu,
"the throttling statistics of this cgroup",
stat,
Stat,
parse_stat
);
gen_getter!(cpu, "the CPU time shares", shares: link, u64, parse);
gen_setter!(cpu, "CPU time shares", shares: link, set_shares, u64, 2048);
gen_getter!(
cpu,
"the total available CPU time within a period (in microseconds)",
cfs_quota_us: link,
i64,
parse
);
gen_setter!(
cpu,
"total available CPU time within a period (in microseconds)"
: "Setting -1 removes the current limit.",
cfs_quota_us : link,
set_cfs_quota_us,
quota: i64,
500 * 1000
);
gen_getter!(
cpu,
"the length of period (in microseconds)",
cfs_period_us: link,
u64,
parse
);
gen_setter!(
cpu,
"length of period (in microseconds)",
cfs_period_us: link,
set_cfs_period_us,
period: u64,
1000 * 1000
);
gen_getter!(
cpu,
"the total available CPU time for realtime tasks within a period (in microseconds)",
rt_runtime_us: link,
i64,
parse
);
gen_setter!(
cpu,
"total available CPU time for realtime tasks within a period (in microseconds)"
: "Setting -1 removes the current limit.",
rt_runtime_us : link,
set_rt_runtime_us,
runtime: i64,
500 * 1000
);
gen_getter!(
cpu,
"the length of period for realtime tasks (in microseconds)",
rt_period_us: link,
u64,
parse
);
gen_setter!(
cpu,
"the length of period for realtime tasks (in microseconds)",
rt_period_us: link,
set_rt_period_us,
period: u64,
1000 * 1000
);
}
fn parse_stat(reader: impl std::io::Read) -> Result<Stat> {
use std::io::{BufRead, BufReader};
let (mut nr_periods, mut nr_throttled, mut throttled_time) = (None, None, None);
for line in BufReader::new(reader).lines() {
let line = line?;
let mut entry = line.split_whitespace();
match entry.next() {
Some("nr_periods") => {
if nr_periods.is_some() {
bail_parse!();
}
nr_periods = Some(parse_next(&mut entry)?);
}
Some("nr_throttled") => {
if nr_throttled.is_some() {
bail_parse!();
}
nr_throttled = Some(parse_next(&mut entry)?);
}
Some("throttled_time") => {
if throttled_time.is_some() {
bail_parse!();
}
throttled_time = Some(parse_next(&mut entry)?);
}
_ => bail_parse!(),
};
if entry.next().is_some() {
bail_parse!();
}
}
match (nr_periods, nr_throttled, throttled_time) {
(Some(nr_periods), Some(nr_throttled), Some(throttled_time)) => Ok(Stat {
nr_periods,
nr_throttled,
throttled_time,
}),
_ => {
bail_parse!();
}
}
}
impl Into<v1::Resources> for Resources {
fn into(self) -> v1::Resources {
v1::Resources {
cpu: self,
..v1::Resources::default()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ErrorKind;
#[test]
fn test_subsystem_create_file_exists() -> Result<()> {
gen_subsystem_test!(Cpu, ["stat", "shares", "cfs_quota_us", "cfs_period_us"])
}
#[test]
fn test_subsystem_apply() -> Result<()> {
gen_subsystem_test!(
Cpu,
Resources {
shares: Some(1024),
cfs_quota_us: Some(100_000),
cfs_period_us: Some(1_000_000),
rt_runtime_us: None,
rt_period_us: None,
},
(shares, 1024),
(cfs_quota_us, 100_000),
(cfs_period_us, 1_000_000)
)
}
#[test]
fn test_subsystem_stat() -> Result<()> {
gen_subsystem_test!(
Cpu,
stat,
Stat {
nr_periods: 0,
nr_throttled: 0,
throttled_time: 0
}
)
}
#[test]
#[ignore] fn test_subsystem_stat_throttled() -> Result<()> {
let mut cgroup =
Subsystem::new(CgroupPath::new(v1::SubsystemKind::Cpu, gen_cgroup_name!()));
cgroup.create()?;
let pid = crate::Pid::from(std::process::id());
cgroup.add_proc(pid)?;
cgroup.set_cfs_quota_us(1000)?;
crate::consume_cpu_until(|| cgroup.stat().unwrap().nr_throttled > 0, 30);
let stat = cgroup.stat()?;
assert!(stat.nr_periods > 0);
assert!(stat.throttled_time > 0);
cgroup.remove_proc(pid)?;
cgroup.delete()
}
#[test]
fn test_subsystem_shares() -> Result<()> {
gen_subsystem_test!(Cpu, shares, 1024, set_shares, 2048)
}
#[test]
fn test_subsystem_cfs_quota_us() -> Result<()> {
gen_subsystem_test!(Cpu, cfs_quota_us, -1, set_cfs_quota_us, 100 * 1000)
}
#[test]
fn test_subsystem_cfs_period_us() -> Result<()> {
gen_subsystem_test!(
Cpu,
cfs_period_us,
100 * 1000,
set_cfs_period_us,
1000 * 1000
)
}
#[test]
fn test_parse_stat() -> Result<()> {
const CONTENT_OK: &str = "\
nr_periods 256
nr_throttled 8
throttled_time 32
";
assert_eq!(
parse_stat(CONTENT_OK.as_bytes())?,
Stat {
nr_periods: 256,
nr_throttled: 8,
throttled_time: 32
}
);
assert_eq!(
parse_stat("".as_bytes()).unwrap_err().kind(),
ErrorKind::Parse
);
const CONTENT_NG_NOT_INT: &str = "\
nr_periods invalid
nr_throttled 8
throttled_time 32
";
const CONTENT_NG_MISSING_DATA: &str = "\
nr_periods 256
throttled_time 32
";
const CONTENT_NG_EXTRA_DATA: &str = "\
nr_periods 256
nr_throttled 8 256
throttled_time 32
";
const CONTENT_NG_EXTRA_ROW: &str = "\
nr_periods 256
nr_throttled 8
throttled_time 32
invalid 256
";
for case in &[
CONTENT_NG_NOT_INT,
CONTENT_NG_MISSING_DATA,
CONTENT_NG_EXTRA_DATA,
CONTENT_NG_EXTRA_ROW,
] {
assert_eq!(
parse_stat(case.as_bytes()).unwrap_err().kind(),
ErrorKind::Parse
);
}
Ok(())
}
}