diff --git a/crates/libcgroups/src/systemd/cpu.rs b/crates/libcgroups/src/systemd/cpu.rs index b9a8d17f..fa4dccc3 100644 --- a/crates/libcgroups/src/systemd/cpu.rs +++ b/crates/libcgroups/src/systemd/cpu.rs @@ -7,9 +7,9 @@ use oci_spec::runtime::LinuxCpu; use super::controller::Controller; use crate::common::ControllerOpt; -const CPU_WEIGHT: &str = "CPUWeight"; -const CPU_QUOTA: &str = "CPUQuotaPerSecUSec"; -const CPU_PERIOD: &str = "CPUQuotaPeriodUSec"; +pub const CPU_WEIGHT: &str = "CPUWeight"; +pub const CPU_QUOTA: &str = "CPUQuotaPerSecUSec"; +pub const CPU_PERIOD: &str = "CPUQuotaPeriodUSec"; pub(crate) struct Cpu {} @@ -36,7 +36,7 @@ impl Cpu { } if let Some(mut shares) = cpu.shares() { - shares = Self::convert_shares_to_cgroup2(shares); + shares = convert_shares_to_cgroup2(shares); if shares != 0 { properties.insert(CPU_WEIGHT, Box::new(shares)); } @@ -65,14 +65,14 @@ impl Cpu { fn is_realtime_requested(cpu: &LinuxCpu) -> bool { cpu.realtime_period().is_some() || cpu.realtime_runtime().is_some() } +} - fn convert_shares_to_cgroup2(shares: u64) -> u64 { - if shares == 0 { - return 0; - } - - 1 + ((shares - 2) * 9999) / 262142 +pub fn convert_shares_to_cgroup2(shares: u64) -> u64 { + if shares == 0 { + return 0; } + + 1 + ((shares - 2) * 9999) / 262142 } #[cfg(test)] diff --git a/crates/libcgroups/src/systemd/cpuset.rs b/crates/libcgroups/src/systemd/cpuset.rs index 3e1e8f09..e85fb7d6 100644 --- a/crates/libcgroups/src/systemd/cpuset.rs +++ b/crates/libcgroups/src/systemd/cpuset.rs @@ -9,8 +9,8 @@ use crate::common::ControllerOpt; use super::controller::Controller; -const ALLOWED_CPUS: &str = "AllowedCPUs"; -const ALLOWED_NODES: &str = "AllowedMemoryNodes"; +pub const ALLOWED_CPUS: &str = "AllowedCPUs"; +pub const ALLOWED_NODES: &str = "AllowedMemoryNodes"; pub struct CpuSet {} @@ -36,67 +36,64 @@ impl CpuSet { systemd_version: u32, properties: &mut HashMap<&str, Box>, ) -> Result<()> { - if systemd_version < 244 { - bail!( - "systemd version ({}) is too old to support cpuset restrictions", - systemd_version - ); + if systemd_version <= 243 { + bail!("setting cpuset restrictions requires systemd version greather than 243"); } if let Some(cpus) = cpu.cpus() { - let cpu_mask = Self::to_bitmask(cpus).context("could not create bitmask for cpus")?; + let cpu_mask = to_bitmask(cpus).context("could not create bitmask for cpus")?; properties.insert(ALLOWED_CPUS, Box::new(cpu_mask)); } if let Some(mems) = cpu.mems() { let mems_mask = - Self::to_bitmask(mems).context("could not create bitmask for memory nodes")?; + to_bitmask(mems).context("could not create bitmask for memory nodes")?; properties.insert(ALLOWED_NODES, Box::new(mems_mask)); } Ok(()) } +} - fn to_bitmask(range: &str) -> Result> { - let mut bitset = FixedBitSet::with_capacity(8); +pub fn to_bitmask(range: &str) -> Result> { + let mut bitset = FixedBitSet::with_capacity(8); - for cpu_set in range.split_terminator(',') { - let cpu_set = cpu_set.trim(); - if cpu_set.is_empty() { - continue; - } - - let cpus: Vec<&str> = cpu_set.split('-').map(|s| s.trim()).collect(); - if cpus.len() == 1 { - let cpu_index: usize = cpus[0].parse()?; - if cpu_index >= bitset.len() { - bitset.grow(bitset.len() + 8); - } - bitset.set(cpu_index, true); - } else { - let start_index = cpus[0].parse()?; - let end_index = cpus[1].parse()?; - if start_index > end_index { - bail!("invalid cpu range {}", cpu_set); - } - - if end_index >= bitset.len() { - bitset.grow(end_index + 1); - } - - bitset.set_range(start_index..end_index + 1, true); - } + for cpu_set in range.split_terminator(',') { + let cpu_set = cpu_set.trim(); + if cpu_set.is_empty() { + continue; } - // systemd expects a sequence of bytes with no leading zeros, otherwise the values will not be set - // with no error message - Ok(bitset - .as_slice() - .iter() - .flat_map(|b| b.to_be_bytes()) - .skip_while(|b| *b == 0u8) - .collect()) + let cpus: Vec<&str> = cpu_set.split('-').map(|s| s.trim()).collect(); + if cpus.len() == 1 { + let cpu_index: usize = cpus[0].parse()?; + if cpu_index >= bitset.len() { + bitset.grow(bitset.len() + 8); + } + bitset.set(cpu_index, true); + } else { + let start_index = cpus[0].parse()?; + let end_index = cpus[1].parse()?; + if start_index > end_index { + bail!("invalid cpu range {}", cpu_set); + } + + if end_index >= bitset.len() { + bitset.grow(end_index + 1); + } + + bitset.set_range(start_index..end_index + 1, true); + } } + + // systemd expects a sequence of bytes with no leading zeros, otherwise the values will not be set + // with no error message + Ok(bitset + .as_slice() + .iter() + .flat_map(|b| b.to_be_bytes()) + .skip_while(|b| *b == 0u8) + .collect()) } #[cfg(test)] @@ -110,7 +107,7 @@ mod tests { fn to_bitmask_single_value() -> Result<()> { let cpus = "0"; // 0000 0001 - let bitmask = CpuSet::to_bitmask(cpus).context("to bitmask")?; + let bitmask = to_bitmask(cpus).context("to bitmask")?; assert_eq!(bitmask.len(), 1); assert_eq!(bitmask[0], 1); @@ -121,7 +118,7 @@ mod tests { fn to_bitmask_multiple_single_values() -> Result<()> { let cpus = "0,1,2"; // 0000 0111 - let bitmask = CpuSet::to_bitmask(cpus).context("to bitmask")?; + let bitmask = to_bitmask(cpus).context("to bitmask")?; assert_eq!(bitmask.len(), 1); assert_eq!(bitmask[0], 7); @@ -132,7 +129,7 @@ mod tests { fn to_bitmask_range_value() -> Result<()> { let cpus = "0-2"; // 0000 0111 - let bitmask = CpuSet::to_bitmask(cpus).context("to bitmask")?; + let bitmask = to_bitmask(cpus).context("to bitmask")?; assert_eq!(bitmask.len(), 1); assert_eq!(bitmask[0], 7); @@ -143,7 +140,7 @@ mod tests { fn to_bitmask_interchanged_range() -> Result<()> { let cpus = "2-0"; - let result = CpuSet::to_bitmask(cpus).context("to bitmask"); + let result = to_bitmask(cpus).context("to bitmask"); assert!(result.is_err()); Ok(()) } @@ -153,7 +150,7 @@ mod tests { let cpus = vec!["2-", "-2"]; for c in cpus { - let result = CpuSet::to_bitmask(c).context("to bitmask"); + let result = to_bitmask(c).context("to bitmask"); assert!(result.is_err()); } @@ -164,7 +161,7 @@ mod tests { fn to_bitmask_mixed() -> Result<()> { let cpus = "0,2-4,7,9-10"; // 0000 0110 1001 1101 - let bitmask = CpuSet::to_bitmask(cpus).context("to bitmask")?; + let bitmask = to_bitmask(cpus).context("to bitmask")?; assert_eq!(bitmask.len(), 2); assert_eq!(bitmask[0], 6); @@ -176,7 +173,7 @@ mod tests { fn to_bitmask_extra_characters() -> Result<()> { let cpus = "0, 2- 4,,7 ,,9-10"; // 0000 0110 1001 1101 - let bitmask = CpuSet::to_bitmask(cpus).context("to bitmask")?; + let bitmask = to_bitmask(cpus).context("to bitmask")?; assert_eq!(bitmask.len(), 2); assert_eq!(bitmask[0], 6); assert_eq!(bitmask[1], 157); diff --git a/crates/libcgroups/src/systemd/manager.rs b/crates/libcgroups/src/systemd/manager.rs index 2c12771f..dbe7d247 100644 --- a/crates/libcgroups/src/systemd/manager.rs +++ b/crates/libcgroups/src/systemd/manager.rs @@ -22,8 +22,11 @@ use super::{ memory::Memory, pids::Pids, }; -use crate::common::{self, CgroupManager, ControllerOpt, FreezerState, PathBufExt}; use crate::stats::Stats; +use crate::{ + common::{self, CgroupManager, ControllerOpt, FreezerState, PathBufExt}, + systemd::unified::Unified, +}; const CGROUP_PROCS: &str = "cgroup.procs"; const CGROUP_CONTROLLERS: &str = "cgroup.controllers"; @@ -290,6 +293,7 @@ impl CgroupManager for Manager { }; } + Unified::apply(controller_opt, systemd_version, &mut properties)?; log::debug!("{:?}", properties); self.client diff --git a/crates/libcgroups/src/systemd/memory.rs b/crates/libcgroups/src/systemd/memory.rs index ca0ccfe5..c2573f1e 100644 --- a/crates/libcgroups/src/systemd/memory.rs +++ b/crates/libcgroups/src/systemd/memory.rs @@ -8,9 +8,11 @@ use crate::common::ControllerOpt; use super::controller::Controller; -const MEMORY_LOW: &str = "MemoryLow"; -const MEMORY_MAX: &str = "MemoryMax"; -const MEMORY_SWAP: &str = "MemorySwapMax"; +pub const MEMORY_MIN: &str = "MemoryMin"; +pub const MEMORY_LOW: &str = "MemoryLow"; +pub const MEMORY_HIGH: &str = "MemoryHigh"; +pub const MEMORY_MAX: &str = "MemoryMax"; +pub const MEMORY_SWAP: &str = "MemorySwapMax"; pub struct Memory {} diff --git a/crates/libcgroups/src/systemd/mod.rs b/crates/libcgroups/src/systemd/mod.rs index 2bcc9f2e..d5bc9cb4 100644 --- a/crates/libcgroups/src/systemd/mod.rs +++ b/crates/libcgroups/src/systemd/mod.rs @@ -8,6 +8,7 @@ mod dbus; pub mod manager; mod memory; mod pids; +mod unified; /// Checks if the system was booted with systemd pub fn booted() -> bool { diff --git a/crates/libcgroups/src/systemd/pids.rs b/crates/libcgroups/src/systemd/pids.rs index 3107311c..e29cf2a2 100644 --- a/crates/libcgroups/src/systemd/pids.rs +++ b/crates/libcgroups/src/systemd/pids.rs @@ -8,7 +8,7 @@ use crate::common::ControllerOpt; use super::controller::Controller; -const TASKS_MAX: &str = "TasksMax"; +pub const TASKS_MAX: &str = "TasksMax"; pub struct Pids {} diff --git a/crates/libcgroups/src/systemd/unified.rs b/crates/libcgroups/src/systemd/unified.rs new file mode 100644 index 00000000..d0fe129f --- /dev/null +++ b/crates/libcgroups/src/systemd/unified.rs @@ -0,0 +1,106 @@ +use anyhow::{bail, Context, Result}; +use dbus::arg::RefArg; +use std::collections::HashMap; + +use super::{ + controller::Controller, + cpu::{self, convert_shares_to_cgroup2}, + cpuset::{self, to_bitmask}, + memory, pids, +}; +use crate::common::ControllerOpt; + +pub struct Unified {} + +impl Controller for Unified { + fn apply( + options: &ControllerOpt, + systemd_version: u32, + properties: &mut HashMap<&str, Box>, + ) -> Result<()> { + if let Some(unified) = options.resources.unified() { + log::debug!("Applying unified resource restrictions"); + Self::apply(unified, systemd_version, properties) + .context("failed to apply unified resource restrictions")?; + } + + Ok(()) + } +} + +impl Unified { + fn apply( + unified: &HashMap, + systemd_version: u32, + properties: &mut HashMap<&str, Box>, + ) -> Result<()> { + for (key, value) in unified { + match key.as_str() { + "cpu.weight" => { + let shares = value + .parse::() + .with_context(|| format!("failed to parse cpu weight: {}", value))?; + properties.insert(cpu::CPU_WEIGHT, Box::new(convert_shares_to_cgroup2(shares))); + } + "cpu.max" => { + let parts: Vec<&str> = value.split_whitespace().collect(); + if parts.is_empty() || parts.len() > 2 { + bail!("invalid format for cpu.max: {}", value); + } + + let quota = parts[0] + .parse::() + .with_context(|| format!("failed to parse cpu quota: {}", parts[0]))?; + properties.insert(cpu::CPU_QUOTA, Box::new(quota)); + + if parts.len() == 2 { + let period = parts[1].parse::().with_context(|| { + format!("failed to to parse cpu period: {}", parts[1]) + })?; + properties.insert(cpu::CPU_PERIOD, Box::new(period)); + } + } + cpuset @ ("cpuset.cpus" | "cpuset.mems") => { + if systemd_version <= 243 { + bail!( + "setting {} requires systemd version greater than 243", + cpuset + ); + } + + let bitmask = to_bitmask(value) + .with_context(|| format!("invalid value for cpuset.cpus: {}", value))?; + + let systemd_cpuset = match cpuset { + "cpuset.cpus" => cpuset::ALLOWED_CPUS, + "cpuset.mems" => cpuset::ALLOWED_NODES, + file_name => unreachable!("{} was not matched", file_name), + }; + + properties.insert(systemd_cpuset, Box::new(bitmask)); + } + memory @ ("memory.min" | "memory.low" | "memory.high" | "memory.max") => { + let value = value + .parse::() + .with_context(|| format!("failed to parse {}: {}", memory, value))?; + let systemd_memory = match memory { + "memory.min" => memory::MEMORY_MIN, + "memory.low" => memory::MEMORY_LOW, + "memory.high" => memory::MEMORY_HIGH, + "memory.max" => memory::MEMORY_MAX, + file_name => unreachable!("{} was not matched", file_name), + }; + properties.insert(systemd_memory, Box::new(value)); + } + "pids.max" => { + let pids = value.trim().parse::()?; + properties.insert(pids::TASKS_MAX, Box::new(pids)); + } + + unknown => log::warn!("could not apply {}. Unknown property.", unknown), + } + } + + Ok(()) + } +}