1
0
Fork 0
mirror of https://github.com/containers/youki synced 2024-05-19 14:06:20 +02:00
youki/cgroups/src/v1/cpu.rs

246 lines
7.4 KiB
Rust
Raw Normal View History

use std::path::Path;
2021-06-03 22:21:51 +02:00
2021-07-25 19:54:42 +02:00
use anyhow::{bail, Context, Result};
use oci_spec::runtime::LinuxCpu;
2021-06-03 22:21:51 +02:00
2021-08-01 22:49:31 +02:00
use crate::{
common::{self, ControllerOpt},
2021-07-25 19:54:42 +02:00
stats::{CpuThrottling, StatsProvider},
};
2021-06-03 22:21:51 +02:00
use super::Controller;
const CGROUP_CPU_SHARES: &str = "cpu.shares";
const CGROUP_CPU_QUOTA: &str = "cpu.cfs_quota_us";
const CGROUP_CPU_PERIOD: &str = "cpu.cfs_period_us";
const CGROUP_CPU_RT_RUNTIME: &str = "cpu.rt_runtime_us";
const CGROUP_CPU_RT_PERIOD: &str = "cpu.rt_period_us";
2021-07-25 19:54:42 +02:00
const CGROUP_CPU_STAT: &str = "cpu.stat";
2021-06-03 22:21:51 +02:00
pub struct Cpu {}
impl Controller for Cpu {
type Resource = LinuxCpu;
fn apply(controller_opt: &ControllerOpt, cgroup_root: &Path) -> Result<()> {
2021-06-03 22:21:51 +02:00
log::debug!("Apply Cpu cgroup config");
if let Some(cpu) = Self::needs_to_handle(controller_opt) {
Self::apply(cgroup_root, cpu).context("failed to apply cpu resource restrictions")?;
2021-06-03 22:21:51 +02:00
}
Ok(())
}
fn needs_to_handle<'a>(controller_opt: &'a ControllerOpt) -> Option<&'a Self::Resource> {
if let Some(cpu) = &controller_opt.resources.cpu() {
if cpu.shares().is_some()
|| cpu.period().is_some()
|| cpu.quota().is_some()
|| cpu.realtime_period().is_some()
|| cpu.realtime_runtime().is_some()
{
return Some(cpu);
}
}
None
}
2021-06-03 22:21:51 +02:00
}
2021-07-25 19:54:42 +02:00
impl StatsProvider for Cpu {
type Stats = CpuThrottling;
fn stats(cgroup_path: &Path) -> Result<Self::Stats> {
let mut stats = CpuThrottling::default();
let stat_path = cgroup_path.join(CGROUP_CPU_STAT);
let stat_content = common::read_cgroup_file(&stat_path)?;
let parts: Vec<&str> = stat_content.split_ascii_whitespace().collect();
if parts.len() < 6 {
bail!(
"{} contains less than the expected number of entries",
stat_path.display()
);
}
if parts[0] != "nr_periods" {
bail!(
"{} does not contain the number of elapsed periods",
stat_path.display()
);
}
if parts[2] != "nr_throttled" {
bail!(
"{} does not contain the number of throttled periods",
stat_path.display()
);
}
if parts[4] != "throttled_time" {
bail!(
"{} does not contain the total time tasks have spent throttled",
stat_path.display()
);
}
stats.periods = parts[1].parse().context("failed to parse nr_periods")?;
stats.throttled_periods = parts[3].parse().context("failed to parse nr_throttled")?;
stats.throttled_time = parts[5].parse().context("failed to parse throttled time")?;
Ok(stats)
}
}
2021-06-03 22:21:51 +02:00
impl Cpu {
fn apply(root_path: &Path, cpu: &LinuxCpu) -> Result<()> {
if let Some(cpu_shares) = cpu.shares() {
2021-06-03 22:21:51 +02:00
if cpu_shares != 0 {
2021-06-04 15:34:07 +02:00
common::write_cgroup_file(root_path.join(CGROUP_CPU_SHARES), cpu_shares)?;
2021-06-03 22:21:51 +02:00
}
}
if let Some(cpu_period) = cpu.period() {
2021-06-03 22:21:51 +02:00
if cpu_period != 0 {
2021-06-04 15:34:07 +02:00
common::write_cgroup_file(root_path.join(CGROUP_CPU_PERIOD), cpu_period)?;
2021-06-03 22:21:51 +02:00
}
}
if let Some(cpu_quota) = cpu.quota() {
2021-06-03 22:21:51 +02:00
if cpu_quota != 0 {
2021-06-04 15:34:07 +02:00
common::write_cgroup_file(root_path.join(CGROUP_CPU_QUOTA), cpu_quota)?;
2021-06-03 22:21:51 +02:00
}
}
if let Some(rt_runtime) = cpu.realtime_runtime() {
2021-06-03 22:21:51 +02:00
if rt_runtime != 0 {
2021-06-04 15:34:07 +02:00
common::write_cgroup_file(root_path.join(CGROUP_CPU_RT_RUNTIME), rt_runtime)?;
2021-06-03 22:21:51 +02:00
}
}
if let Some(rt_period) = cpu.realtime_period() {
2021-06-03 22:21:51 +02:00
if rt_period != 0 {
2021-06-04 15:34:07 +02:00
common::write_cgroup_file(root_path.join(CGROUP_CPU_RT_PERIOD), rt_period)?;
2021-06-03 22:21:51 +02:00
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test::{create_temp_dir, set_fixture, setup};
use oci_spec::runtime::LinuxCpuBuilder;
use std::fs;
#[test]
fn test_set_shares() {
// arrange
let (tmp, shares) = setup("test_set_shares", CGROUP_CPU_SHARES);
let _ = set_fixture(&tmp, CGROUP_CPU_SHARES, "")
.unwrap_or_else(|_| panic!("set test fixture for {}", CGROUP_CPU_SHARES));
let cpu = LinuxCpuBuilder::default().shares(2048u64).build().unwrap();
// act
Cpu::apply(&tmp, &cpu).expect("apply cpu");
// assert
let content = fs::read_to_string(shares)
.unwrap_or_else(|_| panic!("read {} file content", CGROUP_CPU_SHARES));
assert_eq!(content, 2048.to_string());
}
#[test]
fn test_set_quota() {
// arrange
const QUOTA: i64 = 200000;
let (tmp, max) = setup("test_set_quota", CGROUP_CPU_QUOTA);
let cpu = LinuxCpuBuilder::default().quota(QUOTA).build().unwrap();
// act
Cpu::apply(&tmp, &cpu).expect("apply cpu");
// assert
let content = fs::read_to_string(max)
.unwrap_or_else(|_| panic!("read {} file content", CGROUP_CPU_QUOTA));
assert_eq!(content, QUOTA.to_string());
}
#[test]
fn test_set_period() {
// arrange
const PERIOD: u64 = 100000;
let (tmp, max) = setup("test_set_period", CGROUP_CPU_PERIOD);
let cpu = LinuxCpuBuilder::default().period(PERIOD).build().unwrap();
// act
Cpu::apply(&tmp, &cpu).expect("apply cpu");
// assert
let content = fs::read_to_string(max)
.unwrap_or_else(|_| panic!("read {} file content", CGROUP_CPU_PERIOD));
assert_eq!(content, PERIOD.to_string());
}
#[test]
fn test_set_rt_runtime() {
2021-06-04 15:34:07 +02:00
// arrange
const RUNTIME: i64 = 100000;
let (tmp, max) = setup("test_set_rt_runtime", CGROUP_CPU_RT_RUNTIME);
let cpu = LinuxCpuBuilder::default()
.realtime_runtime(RUNTIME)
.build()
.unwrap();
2021-06-04 15:34:07 +02:00
// act
Cpu::apply(&tmp, &cpu).expect("apply cpu");
// assert
let content = fs::read_to_string(max)
.unwrap_or_else(|_| panic!("read {} file content", CGROUP_CPU_RT_RUNTIME));
assert_eq!(content, RUNTIME.to_string());
}
#[test]
fn test_set_rt_period() {
2021-06-04 15:34:07 +02:00
// arrange
const PERIOD: u64 = 100000;
let (tmp, max) = setup("test_set_rt_period", CGROUP_CPU_RT_PERIOD);
let cpu = LinuxCpuBuilder::default()
.realtime_period(PERIOD)
.build()
.unwrap();
2021-06-04 15:34:07 +02:00
// act
Cpu::apply(&tmp, &cpu).expect("apply cpu");
2021-06-04 15:34:07 +02:00
// assert
let content = fs::read_to_string(max)
.unwrap_or_else(|_| panic!("read {} file content", CGROUP_CPU_RT_PERIOD));
assert_eq!(content, PERIOD.to_string());
}
2021-07-26 00:26:46 +02:00
#[test]
fn test_stat_cpu_throttling() {
let tmp = create_temp_dir("test_stat_cpu_throttling").expect("create test directory");
let stat_content = &[
"nr_periods 165000",
"nr_throttled 27",
"throttled_time 1080",
]
.join("\n");
set_fixture(&tmp, CGROUP_CPU_STAT, stat_content).expect("create stat file");
2021-07-26 00:26:46 +02:00
let actual = Cpu::stats(&tmp).expect("get cgroup stats");
let expected = CpuThrottling {
periods: 165000,
throttled_periods: 27,
throttled_time: 1080,
};
assert_eq!(actual, expected);
}
}