Skip to main content

libcgroups/v1/
manager.rs

1use std::collections::HashMap;
2use std::fs;
3use std::path::{Path, PathBuf};
4use std::time::Duration;
5
6use nix::unistd::Pid;
7use pathrs::flags::OpenFlags;
8use pathrs::procfs::{ProcfsBase, ProcfsHandle};
9use procfs::{FromRead, ProcError, ProcessCGroups};
10
11use super::blkio::{Blkio, V1BlkioStatsError};
12use super::controller::Controller;
13use super::controller_type::CONTROLLERS;
14use super::cpu::{Cpu, V1CpuStatsError};
15use super::cpuacct::{CpuAcct, V1CpuAcctStatsError};
16use super::cpuset::{CpuSet, V1CpuSetControllerError};
17use super::devices::Devices;
18use super::freezer::{Freezer, V1FreezerControllerError};
19use super::hugetlb::{HugeTlb, V1HugeTlbControllerError, V1HugeTlbStatsError};
20use super::memory::{Memory, V1MemoryControllerError, V1MemoryStatsError};
21use super::network_classifier::NetworkClassifier;
22use super::network_priority::NetworkPriority;
23use super::perf_event::PerfEvent;
24use super::pids::Pids;
25use super::util::V1MountPointError;
26use super::{ControllerType as CtrlType, util};
27use crate::common::{
28    self, AnyCgroupManager, CGROUP_PROCS, CgroupManager, ControllerOpt, FreezerState,
29    JoinSafelyError, PathBufExt, WrapIoResult, WrappedIoError,
30};
31use crate::stats::{PidStatsError, Stats, StatsProvider};
32
33pub struct Manager {
34    subsystems: HashMap<CtrlType, PathBuf>,
35}
36
37#[derive(thiserror::Error, Debug)]
38pub enum V1ManagerError {
39    #[error("io error: {0}")]
40    WrappedIo(#[from] WrappedIoError),
41    #[error("mount point error: {0}")]
42    MountPoint(#[from] V1MountPointError),
43    #[error("proc error: {0}")]
44    Proc(#[from] ProcError),
45    #[error("while joining paths: {0}")]
46    JoinSafely(#[from] JoinSafelyError),
47    #[error("cgroup {0} is required to fulfill the request, but is not supported by this system")]
48    CGroupRequired(CtrlType),
49    #[error("subsystem does not exist")]
50    SubsystemDoesNotExist,
51    #[error(transparent)]
52    Pathrs(#[from] pathrs::error::Error),
53
54    #[error(transparent)]
55    BlkioController(WrappedIoError),
56    #[error(transparent)]
57    CpuController(WrappedIoError),
58    #[error(transparent)]
59    CpuAcctController(WrappedIoError),
60    #[error(transparent)]
61    CpuSetController(#[from] V1CpuSetControllerError),
62    #[error(transparent)]
63    FreezerController(#[from] V1FreezerControllerError),
64    #[error(transparent)]
65    HugeTlbController(#[from] V1HugeTlbControllerError),
66    #[error(transparent)]
67    MemoryController(#[from] V1MemoryControllerError),
68    #[error(transparent)]
69    PidsController(WrappedIoError),
70
71    #[error(transparent)]
72    BlkioStats(#[from] V1BlkioStatsError),
73    #[error(transparent)]
74    CpuStats(#[from] V1CpuStatsError),
75    #[error(transparent)]
76    CpuAcctStats(#[from] V1CpuAcctStatsError),
77    #[error(transparent)]
78    PidsStats(#[from] PidStatsError),
79    #[error(transparent)]
80    HugeTlbStats(#[from] V1HugeTlbStatsError),
81    #[error(transparent)]
82    MemoryStats(#[from] V1MemoryStatsError),
83}
84
85impl Manager {
86    /// Constructs a new cgroup manager with cgroups_path being relative to the root of the subsystem
87    pub fn new(cgroup_path: &Path) -> Result<Self, V1ManagerError> {
88        let mut subsystems = HashMap::new();
89        for subsystem in CONTROLLERS {
90            if let Ok(subsystem_path) = Self::get_subsystem_path(cgroup_path, subsystem) {
91                subsystems.insert(*subsystem, subsystem_path);
92            } else {
93                tracing::warn!("cgroup {} not supported on this system", subsystem);
94            }
95        }
96
97        Ok(Manager { subsystems })
98    }
99
100    fn get_subsystem_path(
101        cgroup_path: &Path,
102        subsystem: &CtrlType,
103    ) -> Result<PathBuf, V1ManagerError> {
104        tracing::debug!("Get path for subsystem: {}", subsystem);
105        let mount_point = util::get_subsystem_mount_point(subsystem)?;
106
107        let cgroup = ProcessCGroups::from_read(ProcfsHandle::new()?.open(
108            ProcfsBase::ProcSelf,
109            "cgroup",
110            OpenFlags::O_RDONLY | OpenFlags::O_CLOEXEC,
111        )?)?
112        .into_iter()
113        .find(|c| c.controllers.contains(&subsystem.to_string()))
114        .ok_or(V1ManagerError::SubsystemDoesNotExist)?;
115
116        let p = if cgroup_path.as_os_str().is_empty() {
117            mount_point.join_safely(Path::new(&cgroup.pathname))?
118        } else {
119            mount_point.join_safely(cgroup_path)?
120        };
121
122        Ok(p)
123    }
124
125    fn get_required_controllers(
126        &self,
127        controller_opt: &ControllerOpt,
128    ) -> Result<HashMap<&CtrlType, &PathBuf>, V1ManagerError> {
129        let mut required_controllers = HashMap::new();
130
131        for controller in CONTROLLERS {
132            let required = match controller {
133                CtrlType::Cpu => Cpu::needs_to_handle(controller_opt).is_some(),
134                CtrlType::CpuAcct => CpuAcct::needs_to_handle(controller_opt).is_some(),
135                CtrlType::CpuSet => CpuSet::needs_to_handle(controller_opt).is_some(),
136                CtrlType::Devices => Devices::needs_to_handle(controller_opt).is_some(),
137                CtrlType::HugeTlb => HugeTlb::needs_to_handle(controller_opt).is_some(),
138                CtrlType::Memory => Memory::needs_to_handle(controller_opt).is_some(),
139                CtrlType::Pids => Pids::needs_to_handle(controller_opt).is_some(),
140                CtrlType::PerfEvent => PerfEvent::needs_to_handle(controller_opt).is_some(),
141                CtrlType::Blkio => Blkio::needs_to_handle(controller_opt).is_some(),
142                CtrlType::NetworkPriority => {
143                    NetworkPriority::needs_to_handle(controller_opt).is_some()
144                }
145                CtrlType::NetworkClassifier => {
146                    NetworkClassifier::needs_to_handle(controller_opt).is_some()
147                }
148                CtrlType::Freezer => Freezer::needs_to_handle(controller_opt).is_some(),
149            };
150
151            if required {
152                if let Some(subsystem_path) = self.subsystems.get(controller) {
153                    required_controllers.insert(controller, subsystem_path);
154                } else {
155                    return Err(V1ManagerError::CGroupRequired(*controller));
156                }
157            }
158        }
159
160        Ok(required_controllers)
161    }
162
163    pub fn any(self) -> AnyCgroupManager {
164        AnyCgroupManager::V1(self)
165    }
166}
167
168impl CgroupManager for Manager {
169    type Error = V1ManagerError;
170
171    fn get_all_pids(&self) -> Result<Vec<Pid>, Self::Error> {
172        let devices = self.subsystems.get(&CtrlType::Devices);
173        if let Some(p) = devices {
174            Ok(common::get_all_pids(p)?)
175        } else {
176            Err(V1ManagerError::SubsystemDoesNotExist)
177        }
178    }
179
180    fn add_task(&self, pid: Pid) -> Result<(), Self::Error> {
181        for (ctrl_type, cgroup_path) in &self.subsystems {
182            match ctrl_type {
183                CtrlType::Cpu => Cpu::add_task(pid, cgroup_path)?,
184                CtrlType::CpuAcct => CpuAcct::add_task(pid, cgroup_path)?,
185                CtrlType::CpuSet => CpuSet::add_task(pid, cgroup_path)?,
186                CtrlType::Devices => Devices::add_task(pid, cgroup_path)?,
187                CtrlType::HugeTlb => HugeTlb::add_task(pid, cgroup_path)?,
188                CtrlType::Memory => Memory::add_task(pid, cgroup_path)?,
189                CtrlType::Pids => Pids::add_task(pid, cgroup_path)?,
190                CtrlType::PerfEvent => PerfEvent::add_task(pid, cgroup_path)?,
191                CtrlType::Blkio => Blkio::add_task(pid, cgroup_path)?,
192                CtrlType::NetworkPriority => NetworkPriority::add_task(pid, cgroup_path)?,
193                CtrlType::NetworkClassifier => NetworkClassifier::add_task(pid, cgroup_path)?,
194                CtrlType::Freezer => Freezer::add_task(pid, cgroup_path)?,
195            }
196        }
197
198        Ok(())
199    }
200
201    fn apply(&self, controller_opt: &ControllerOpt) -> Result<(), Self::Error> {
202        for (ctrl_type, cgroup_path) in self.get_required_controllers(controller_opt)? {
203            match ctrl_type {
204                CtrlType::Cpu => Cpu::apply(controller_opt, cgroup_path)?,
205                CtrlType::CpuAcct => CpuAcct::apply(controller_opt, cgroup_path)?,
206                CtrlType::CpuSet => CpuSet::apply(controller_opt, cgroup_path)?,
207                CtrlType::Devices => Devices::apply(controller_opt, cgroup_path)?,
208                CtrlType::HugeTlb => HugeTlb::apply(controller_opt, cgroup_path)?,
209                CtrlType::Memory => Memory::apply(controller_opt, cgroup_path)?,
210                CtrlType::Pids => Pids::apply(controller_opt, cgroup_path)?,
211                CtrlType::PerfEvent => PerfEvent::apply(controller_opt, cgroup_path)?,
212                CtrlType::Blkio => Blkio::apply(controller_opt, cgroup_path)?,
213                CtrlType::NetworkPriority => NetworkPriority::apply(controller_opt, cgroup_path)?,
214                CtrlType::NetworkClassifier => {
215                    NetworkClassifier::apply(controller_opt, cgroup_path)?
216                }
217                CtrlType::Freezer => Freezer::apply(controller_opt, cgroup_path)?,
218            }
219        }
220
221        Ok(())
222    }
223
224    fn remove(&self) -> Result<(), Self::Error> {
225        for cgroup_path in self.subsystems.values() {
226            if cgroup_path.exists() {
227                tracing::debug!("remove cgroup {:?}", cgroup_path);
228                let procs_path = cgroup_path.join(CGROUP_PROCS);
229                let procs = fs::read_to_string(&procs_path).wrap_read(&procs_path)?;
230
231                for line in procs.lines() {
232                    let pid: i32 = line
233                        .parse()
234                        .map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidData, err))
235                        .wrap_other(&procs_path)?;
236                    let _ = nix::sys::signal::kill(Pid::from_raw(pid), nix::sys::signal::SIGKILL);
237                }
238
239                common::delete_with_retry(cgroup_path, 4, Duration::from_millis(100))?;
240            }
241        }
242
243        Ok(())
244    }
245
246    fn freeze(&self, state: FreezerState) -> Result<(), Self::Error> {
247        let controller_opt = ControllerOpt {
248            resources: &Default::default(),
249            freezer_state: Some(state),
250            oom_score_adj: None,
251            disable_oom_killer: false,
252        };
253        Ok(Freezer::apply(
254            &controller_opt,
255            self.subsystems
256                .get(&CtrlType::Freezer)
257                .ok_or(V1ManagerError::SubsystemDoesNotExist)?,
258        )?)
259    }
260
261    fn stats(&self) -> Result<Stats, Self::Error> {
262        let mut stats = Stats::default();
263
264        for (ctrl_type, cgroup_path) in &self.subsystems {
265            match ctrl_type {
266                CtrlType::Cpu => stats.cpu.throttling = Cpu::stats(cgroup_path)?,
267                CtrlType::CpuAcct => stats.cpu.usage = CpuAcct::stats(cgroup_path)?,
268                CtrlType::Pids => stats.pids = Pids::stats(cgroup_path)?,
269                CtrlType::HugeTlb => stats.hugetlb = HugeTlb::stats(cgroup_path)?,
270                CtrlType::Blkio => stats.blkio = Blkio::stats(cgroup_path)?,
271                CtrlType::Memory => stats.memory = Memory::stats(cgroup_path)?,
272                _ => continue,
273            }
274        }
275
276        Ok(stats)
277    }
278}