mirror of
https://github.com/kata-containers/cgroups-rs.git
synced 2026-08-05 02:13:23 +00:00
manager: Introduce FsManager
`Manager` is a trait to unify the interface of cgroups. It is designed for OCI containers. Its `set()` takes Linux resources of the OCI spec to set cgroups. The `FsManager`, the concrete implementation of `Manager`, manipulates cgroups through cgroupfs, and supports both cgroups v1 and v2. Signed-off-by: Xuewei Niu <niuxuewei.nxw@antgroup.com>
This commit is contained in:
@@ -17,9 +17,11 @@ nix = { version = "0.25.0", default-features = false, features = ["event", "fs",
|
||||
libc = "0.2"
|
||||
serde = { version = "1.0", features = ["derive"], optional = true }
|
||||
thiserror = "1"
|
||||
oci-spec = { version = "0.8.1", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
libc = "0.2.76"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
oci = ["oci-spec"]
|
||||
|
||||
@@ -13,8 +13,8 @@ use std::path::PathBuf;
|
||||
|
||||
use crate::fs::error::ErrorKind::*;
|
||||
use crate::fs::error::*;
|
||||
|
||||
use crate::fs::{ControllIdentifier, ControllerInternal, Controllers, Resources, Subsystem};
|
||||
use crate::FreezerState;
|
||||
|
||||
/// A controller that allows controlling the `freezer` subsystem of a Cgroup.
|
||||
///
|
||||
@@ -31,17 +31,6 @@ pub struct FreezerController {
|
||||
v2: bool,
|
||||
}
|
||||
|
||||
/// The current state of the control group
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum FreezerState {
|
||||
/// The processes in the control group are _not_ frozen.
|
||||
Thawed,
|
||||
/// The processes in the control group are in the processes of being frozen.
|
||||
Freezing,
|
||||
/// The processes in the control group are frozen.
|
||||
Frozen,
|
||||
}
|
||||
|
||||
impl ControllerInternal for FreezerController {
|
||||
fn control_type(&self) -> Controllers {
|
||||
Controllers::Freezer
|
||||
|
||||
@@ -245,6 +245,7 @@ mod sealed {
|
||||
}
|
||||
|
||||
pub(crate) use crate::fs::sealed::{ControllerInternal, CustomizedAttribute};
|
||||
use crate::CgroupPid;
|
||||
|
||||
/// A Controller is a subsystem attached to the control group.
|
||||
///
|
||||
@@ -771,26 +772,6 @@ pub struct Resources {
|
||||
pub blkio: BlkIoResources,
|
||||
}
|
||||
|
||||
/// A structure representing a `pid`. Currently implementations exist for `u64` and
|
||||
/// `std::process::Child`.
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct CgroupPid {
|
||||
/// The process identifier
|
||||
pub pid: u64,
|
||||
}
|
||||
|
||||
impl From<u64> for CgroupPid {
|
||||
fn from(u: u64) -> CgroupPid {
|
||||
CgroupPid { pid: u }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&std::process::Child> for CgroupPid {
|
||||
fn from(u: &std::process::Child) -> CgroupPid {
|
||||
CgroupPid { pid: u.id() as u64 }
|
||||
}
|
||||
}
|
||||
|
||||
impl Subsystem {
|
||||
fn enter(self, path: &Path) -> Self {
|
||||
match self {
|
||||
|
||||
42
src/lib.rs
42
src/lib.rs
@@ -5,3 +5,45 @@
|
||||
//
|
||||
|
||||
pub mod fs;
|
||||
#[cfg(feature = "oci")]
|
||||
pub mod manager;
|
||||
#[cfg(feature = "oci")]
|
||||
pub use manager::{FsManager, Manager};
|
||||
pub mod stats;
|
||||
pub use stats::CgroupStats;
|
||||
|
||||
/// The maximum value for CPU shares in cgroups v1
|
||||
pub const CPU_SHARES_V1_MAX: u64 = 262144;
|
||||
/// The maximum value for CPU weight in cgroups v2
|
||||
pub const CPU_WEIGHT_V2_MAX: u64 = 10000;
|
||||
|
||||
/// The current state of the control group
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum FreezerState {
|
||||
/// The processes in the control group are _not_ frozen.
|
||||
Thawed,
|
||||
/// The processes in the control group are in the processes of being frozen.
|
||||
Freezing,
|
||||
/// The processes in the control group are frozen.
|
||||
Frozen,
|
||||
}
|
||||
|
||||
/// A structure representing a `pid`. Currently implementations exist for `u64` and
|
||||
/// `std::process::Child`.
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
|
||||
pub struct CgroupPid {
|
||||
/// The process identifier
|
||||
pub pid: u64,
|
||||
}
|
||||
|
||||
impl From<u64> for CgroupPid {
|
||||
fn from(u: u64) -> CgroupPid {
|
||||
CgroupPid { pid: u }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&std::process::Child> for CgroupPid {
|
||||
fn from(u: &std::process::Child) -> CgroupPid {
|
||||
CgroupPid { pid: u.id() as u64 }
|
||||
}
|
||||
}
|
||||
|
||||
69
src/manager/conv.rs
Normal file
69
src/manager/conv.rs
Normal file
@@ -0,0 +1,69 @@
|
||||
// Copyright (c) 2025 Ant Group
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 or MIT
|
||||
//
|
||||
|
||||
use crate::manager::error::{Error, Result};
|
||||
use crate::{CPU_SHARES_V1_MAX, CPU_WEIGHT_V2_MAX};
|
||||
|
||||
// Converts CPU shares, used by cgroup v1, to CPU weight, used by cgroup
|
||||
// v2.
|
||||
//
|
||||
// Cgroup v1 CPU shares has a range of [2^1...2^18], i.e. [2...262144],
|
||||
// and the default value is 1024.
|
||||
//
|
||||
// Cgroup v2 CPU weight has a range of [10^0...10^4], i.e. [1...10000],
|
||||
// and the default value is 100.
|
||||
pub(crate) fn cpu_shares_to_cgroup_v2(shares: u64) -> u64 {
|
||||
if shares == 0 {
|
||||
return 0;
|
||||
}
|
||||
if shares <= 2 {
|
||||
return 1;
|
||||
}
|
||||
if shares >= CPU_SHARES_V1_MAX {
|
||||
return CPU_WEIGHT_V2_MAX;
|
||||
}
|
||||
|
||||
(((shares - 2) * 9999) / 262142) + 1
|
||||
}
|
||||
|
||||
// ConvertMemorySwapToCgroupV2Value converts MemorySwap value from OCI spec
|
||||
// for use by cgroup v2 drivers. A conversion is needed since
|
||||
// Resources.MemorySwap is defined as memory+swap combined, while in cgroup
|
||||
// v2 swap is a separate value.
|
||||
pub(crate) fn memory_swap_to_cgroup_v2(memswap_limit: i64, mem_limit: i64) -> Result<i64> {
|
||||
// For compatibility with cgroup1 controller, set swap to unlimited in
|
||||
// case the memory is set to unlimited, and swap is not explicitly set,
|
||||
// treating the request as "set both memory and swap to unlimited".
|
||||
if mem_limit == -1 && memswap_limit == 0 {
|
||||
return Ok(-1);
|
||||
}
|
||||
|
||||
// -1 is "max", 0 is "unset", so treat as is
|
||||
if memswap_limit == -1 || memswap_limit == 0 {
|
||||
return Ok(memswap_limit);
|
||||
}
|
||||
|
||||
// Unlimited memory, so treat swap as is.
|
||||
if mem_limit == -1 {
|
||||
return Ok(memswap_limit);
|
||||
}
|
||||
|
||||
// Unset or unknown memory, can't calculate swap.
|
||||
if mem_limit == 0 {
|
||||
return Err(Error::InvalidLinuxResource);
|
||||
}
|
||||
|
||||
// Does not make sense to subtract a negative value.
|
||||
if mem_limit < 0 {
|
||||
return Err(Error::InvalidLinuxResource);
|
||||
}
|
||||
|
||||
// Sanity check.
|
||||
if memswap_limit < mem_limit {
|
||||
return Err(Error::InvalidLinuxResource);
|
||||
}
|
||||
|
||||
Ok(memswap_limit - mem_limit)
|
||||
}
|
||||
20
src/manager/error.rs
Normal file
20
src/manager/error.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright (c) 2025 Ant Group
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 or MIT
|
||||
//
|
||||
|
||||
use crate::fs::error::Error as CgroupfsError;
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error("invalid argument")]
|
||||
InvalidArgument,
|
||||
|
||||
#[error("invalid linux resource")]
|
||||
InvalidLinuxResource,
|
||||
|
||||
#[error("cgroupfs error: {0}")]
|
||||
Cgroupfs(#[from] CgroupfsError),
|
||||
}
|
||||
1088
src/manager/fs.rs
Normal file
1088
src/manager/fs.rs
Normal file
File diff suppressed because it is too large
Load Diff
77
src/manager/mod.rs
Normal file
77
src/manager/mod.rs
Normal file
@@ -0,0 +1,77 @@
|
||||
// Copyright (c) 2025 Ant Group
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 or MIT
|
||||
//
|
||||
|
||||
mod error;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub use error::{Error, Result};
|
||||
mod fs;
|
||||
pub use fs::FsManager;
|
||||
mod conv;
|
||||
|
||||
use oci_spec::runtime::LinuxResources;
|
||||
|
||||
use crate::{CgroupPid, CgroupStats, FreezerState};
|
||||
|
||||
/// Manage cgroups designed for OCI containers.
|
||||
pub trait Manager: Send + Sync {
|
||||
/// Add a process specified by its tgid.
|
||||
fn add_proc(&mut self, tgid: CgroupPid) -> Result<()>;
|
||||
|
||||
/// Add a thread specified by its pid.
|
||||
fn add_thread(&mut self, pid: CgroupPid) -> Result<()>;
|
||||
|
||||
/// Get the list of pids joint to the cgroups.
|
||||
fn pids(&self) -> Result<Vec<CgroupPid>>;
|
||||
|
||||
/// Set the freezer cgroup to the specified state.
|
||||
fn freeze(&self, state: FreezerState) -> Result<()>;
|
||||
|
||||
/// Remove the cgroups.
|
||||
fn destroy(&mut self) -> Result<()>;
|
||||
|
||||
/// Set the resources to the cgroups.
|
||||
fn set(&mut self, resources: &LinuxResources) -> Result<()>;
|
||||
|
||||
/// Get the cgroup path.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// - `subsystem`: cgroup subsystem, for cgroup v1 the value should not
|
||||
/// be empty, while for cgroup v2 the only valid value is `None`.
|
||||
fn cgroup_path(&self, subsystem: Option<&str>) -> Result<String>;
|
||||
|
||||
/// Enable CPUs, topdown from root in cgroup hierarchy, this would be
|
||||
/// useful for CPU hotplug in the guest.
|
||||
///
|
||||
/// The caller should update cgroup resources manually, in particular
|
||||
/// cpuset, after this, in order to use the new CPUs (or avoid using
|
||||
/// offline CPUs).
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// - `cpus`: online CPUs in the same format with `cat
|
||||
/// /sys/devices/system/cpu/online`, e.g. "0-3,6-7".
|
||||
fn enable_cpus_topdown(&self, cpus: &str) -> Result<()>;
|
||||
|
||||
/// Get cgroup stats.
|
||||
fn stats(&self) -> CgroupStats;
|
||||
|
||||
/// Get the mappings of subsystems to their relative path. The full
|
||||
/// path would be something like "{mountpoint}/{relative_path}". The
|
||||
/// mappings of mountpoints see "mounts()".
|
||||
fn paths(&self) -> &HashMap<String, String>;
|
||||
|
||||
/// Get the mappings of subsystems to their mountpoints. The full
|
||||
/// path would be something like "{mountpoint}/{relative_path}". The
|
||||
/// mappings of relative paths see "paths()".
|
||||
fn mounts(&self) -> &HashMap<String, String>;
|
||||
|
||||
/// Indicate whether the cgroup manager is using systemd.
|
||||
fn systemd(&self) -> bool;
|
||||
|
||||
/// Indicate whether the cgroup manager is using cgroup v2.
|
||||
fn v2(&self) -> bool;
|
||||
}
|
||||
156
src/stats.rs
Normal file
156
src/stats.rs
Normal file
@@ -0,0 +1,156 @@
|
||||
// Copyright (c) 2018 Levente Kurusa
|
||||
// Copyright (c) 2025 Ant Group
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 or MIT
|
||||
//
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct CgroupStats {
|
||||
pub cpu: CpuCgroupStats,
|
||||
pub memory: MemoryCgroupStats,
|
||||
pub pids: PidsCgroupStats,
|
||||
pub blkio: BlkioCgroupStats,
|
||||
pub hugetlb: HugeTlbCgroupStats,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct CpuCgroupStats {
|
||||
pub cpu_acct: Option<CpuAcctStats>,
|
||||
pub cpu_throttling: Option<CpuThrottlingStats>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct CpuAcctStats {
|
||||
/// Usage in userspace, read from `cpuacct.stat` from the line starting
|
||||
/// with `user`. Set 0 if no data.
|
||||
pub user_usage: u64,
|
||||
/// Usage in kernelspace, read from `cpuacct.stat` from the line
|
||||
/// starting with `system`. Set 0 if no data.
|
||||
pub system_usage: u64,
|
||||
/// Total usage, read from `cpuacct.usage`. Set 0 if no data.
|
||||
pub total_usage: u64,
|
||||
/// Per-CPU usage, read from `cpuacct.usage_percpu`.
|
||||
pub usage_percpu: Vec<u64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct CpuThrottlingStats {
|
||||
/// Periods, read from `cpu.stat` from the line starting with
|
||||
/// `nr_periods`. Set 0 if no data.
|
||||
pub periods: u64,
|
||||
/// Throttled periods, read from `cpu.stat` from the line starting with
|
||||
/// `nr_throttled`. Set 0 if no data.
|
||||
pub throttled_periods: u64,
|
||||
/// Throttled time, read from `cpu.stat` from the line starting with
|
||||
/// `throttled_time`. Set 0 if no data.
|
||||
pub throttled_time: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct MemoryCgroupStats {
|
||||
pub memory: Option<MemoryStats>,
|
||||
pub memory_swap: Option<MemoryStats>,
|
||||
pub kernel_memory: Option<MemoryStats>,
|
||||
|
||||
/// Use hierarchy, read from `memory.use_hierarchy` in cgroups v1. Only
|
||||
/// available in cgroups v1.
|
||||
pub use_hierarchy: bool,
|
||||
|
||||
// The following data is read from `memory.stat`, see also
|
||||
// `crate::fs::memory::MemoryStat::stat`.
|
||||
pub cache: u64,
|
||||
pub rss: u64,
|
||||
pub rss_huge: u64,
|
||||
pub shmem: u64,
|
||||
pub mapped_file: u64,
|
||||
pub dirty: u64,
|
||||
pub writeback: u64,
|
||||
pub swap: u64,
|
||||
pub pgpgin: u64,
|
||||
pub pgpgout: u64,
|
||||
pub pgfault: u64,
|
||||
pub pgmajfault: u64,
|
||||
pub inactive_anon: u64,
|
||||
pub active_anon: u64,
|
||||
pub inactive_file: u64,
|
||||
pub active_file: u64,
|
||||
pub unevictable: u64,
|
||||
pub hierarchical_memory_limit: i64,
|
||||
pub hierarchical_memsw_limit: i64,
|
||||
pub total_cache: u64,
|
||||
pub total_rss: u64,
|
||||
pub total_rss_huge: u64,
|
||||
pub total_shmem: u64,
|
||||
pub total_mapped_file: u64,
|
||||
pub total_dirty: u64,
|
||||
pub total_writeback: u64,
|
||||
pub total_swap: u64,
|
||||
pub total_pgpgin: u64,
|
||||
pub total_pgpgout: u64,
|
||||
pub total_pgfault: u64,
|
||||
pub total_pgmajfault: u64,
|
||||
pub total_inactive_anon: u64,
|
||||
pub total_active_anon: u64,
|
||||
pub total_inactive_file: u64,
|
||||
pub total_active_file: u64,
|
||||
pub total_unevictable: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct MemoryStats {
|
||||
/// Memory [swap] usage, read from `memory[.memsw].usage_in_bytes` in
|
||||
/// cgroups v1 and `memory[.swap].current` in cgroups v2.
|
||||
pub usage: u64,
|
||||
/// Maximum memory [swap] usage observed by cgroups, read from
|
||||
/// `memory[.memsw].max_usage_in_bytes` in cgroups v1 and
|
||||
/// `memory[.swap].peak` in cgroups v2.
|
||||
pub max_usage: u64,
|
||||
/// Memory [swap] limit, read from `memory[.memsw].limit_in_bytes` in
|
||||
/// cgroups v1 and `memory[.swap].max` in cgroups v2.
|
||||
pub limit: i64,
|
||||
/// Failure count, read from `memory[.memsw].failcnt`. Only available in
|
||||
/// cgroups v1.
|
||||
pub fail_cnt: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct PidsCgroupStats {
|
||||
/// Current number of processes in the cgroup, read from `pids.current`.
|
||||
pub current: u64,
|
||||
/// Maximum number of processes in the cgroup, read from `pids.limit`.
|
||||
pub limit: i64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct BlkioCgroupStats {
|
||||
pub io_service_bytes_recursive: Vec<BlkioStat>,
|
||||
pub io_serviced_recursive: Vec<BlkioStat>,
|
||||
pub io_queued_recursive: Vec<BlkioStat>,
|
||||
pub io_service_time_recursive: Vec<BlkioStat>,
|
||||
pub io_wait_time_recursive: Vec<BlkioStat>,
|
||||
pub io_merged_recursive: Vec<BlkioStat>,
|
||||
pub io_time_recursive: Vec<BlkioStat>,
|
||||
pub sectors_recursive: Vec<BlkioStat>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct BlkioStat {
|
||||
pub major: u64,
|
||||
pub minor: u64,
|
||||
pub op: String,
|
||||
pub value: u64,
|
||||
}
|
||||
|
||||
/// A structure representing the statistics of the `hugetlb` subsystem of a
|
||||
/// Cgroup. The key is the huge page size, and the value is the statistics
|
||||
/// for that size.
|
||||
pub type HugeTlbCgroupStats = HashMap<String, HugeTlbStat>;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct HugeTlbStat {
|
||||
pub usage: u64,
|
||||
pub max_usage: u64,
|
||||
pub fail_cnt: u64,
|
||||
}
|
||||
Reference in New Issue
Block a user