├── .cargo └── config ├── .gitignore ├── LICENSE-Apache-2.0 ├── Cargo.toml ├── LICENSE-MIT ├── .vscode └── settings.json ├── .travis.yml ├── src ├── v1 │ ├── perf_event.rs │ ├── command_ext.rs │ ├── mod.rs │ ├── pids.rs │ ├── net_prio.rs │ ├── net_cls.rs │ ├── freezer.rs │ ├── macros.rs │ ├── rdma.rs │ ├── cpu.rs │ ├── devices.rs │ ├── cpuacct.rs │ ├── hugetlb.rs │ ├── unified_repr.rs │ └── builder.rs ├── macros.rs ├── parse.rs ├── error.rs └── lib.rs ├── LICENSE └── README.md /.cargo/config: -------------------------------------------------------------------------------- 1 | [target.x86_64-unknown-linux-gnu] 2 | runner = 'sudo -E' 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | /target 13 | **/*.rs.bk 14 | Cargo.lock 15 | -------------------------------------------------------------------------------- /LICENSE-Apache-2.0: -------------------------------------------------------------------------------- 1 | Copyright 2019 Hidehito Yabuuchi 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | https://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "controlgroup" 3 | version = "0.3.0" # NOTE: Update `html_root_url` attribute in lib.rs 4 | edition = "2018" 5 | authors = ["Hidehito Yabuuchi "] 6 | license = "MIT OR Apache-2.0" 7 | 8 | description = "Native Rust crate for cgroup operations" 9 | readme = "README.md" 10 | repository = "https://github.com/ordovicia/controlgroup-rs" 11 | documentation = "https://docs.rs/controlgroup" 12 | 13 | categories = ["os", "api-bindings", "os::unix-apis"] 14 | keywords = ["cgroup", "controlgroup", "linux", "container"] 15 | 16 | exclude = ["/.gitignore", "/.travis.yml", "/.vscode"] 17 | 18 | [badges] 19 | travis-ci = { repository = "ordovicia/controlgroup-rs" } 20 | 21 | [dependencies] 22 | # none 23 | 24 | [dev-dependencies] 25 | num_cpus = "1.11.1" 26 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Hidehito Yabuuchi 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "TLBs", 4 | "UnifiedRepr", 5 | "blkio", 6 | "cgroup", 7 | "cgroupfs", 8 | "cgroups", 9 | "classid", 10 | "clippy", 11 | "concat", 12 | "controlgroup", 13 | "cpuacct", 14 | "cpus", 15 | "cpuset", 16 | "cpusets", 17 | "err", 18 | "failcnt", 19 | "hardwall", 20 | "hardwalled", 21 | "hashset", 22 | "hugepage", 23 | "hugetlb", 24 | "ifpriomap", 25 | "inval", 26 | "iops", 27 | "kmem", 28 | "lsblk", 29 | "mems", 30 | "memsw", 31 | "mknod", 32 | "net_cls", 33 | "net_prio", 34 | "numa", 35 | "percpu", 36 | "perf_event", 37 | "pgfault", 38 | "pgmajfault", 39 | "pgpgin", 40 | "pgpgout", 41 | "pids", 42 | "prio", 43 | "prioidx", 44 | "procs", 45 | "rdma", 46 | "realtime", 47 | "repr", 48 | "rustfmt", 49 | "sched", 50 | "shmem", 51 | "structfield", 52 | "subsys", 53 | "swappiness", 54 | "tmpfs", 55 | "tymethod", 56 | "unevictable", 57 | "writeback" 58 | ] 59 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | 3 | matrix: 4 | include: 5 | - name: Xenial-stable 6 | os: linux 7 | dist: xenial # 16.04 8 | rust: stable 9 | - name: Xenial-beta 10 | os: linux 11 | dist: xenial # 16.04 12 | rust: beta 13 | - name: Xenial-nightly 14 | os: linux 15 | dist: xenial # 16.04 16 | rust: nightly 17 | - name: Xenial-msrv 18 | os: linux 19 | dist: xenial # 16.04 20 | rust: 1.37.0 21 | - name: Bionic-stable 22 | os: linux 23 | dist: bionic # 18.04 24 | rust: stable 25 | - name: Clippy 26 | rust: nightly 27 | script: 28 | - rustup component add clippy || travis_terminate 0 29 | - cargo clippy --tests -- -D clippy::all 30 | 31 | script: 32 | - cargo build --verbose 33 | - cargo test --verbose 34 | # must not be executed in parallel 35 | - cargo test --verbose -- --ignored --test-threads=1 add_get_remove_tasks # Cgroup, UnifiedRepr 36 | - cargo test --verbose -- --ignored --test-threads=1 add_get_remove_procs # Cgroup, UnifiedRepr 37 | - cargo test --verbose -- --ignored --test-threads=1 subsystem_stat_throttled # cpu, memory 38 | - cargo test --verbose -- --ignored --test-threads=1 exclusive # cpuset 39 | - cargo test --verbose -- --ignored cpuset::tests::test_subsystem_apply 40 | - cargo test --verbose -- --ignored cpuacct::tests::test_subsystem_stat_updated 41 | - cargo test --verbose -- --ignored pids::tests::test_subsystem_current 42 | # (temporarily) overrides the root cgroup 43 | - cargo test --verbose -- --ignored release_agent # Cgroup 44 | - cargo test --verbose -- --ignored memory_pressure_enabled # cpuset 45 | -------------------------------------------------------------------------------- /src/v1/perf_event.rs: -------------------------------------------------------------------------------- 1 | //! Definition of a perf_event subsystem. 2 | //! 3 | //! [`Subsystem`] implements [`Cgroup`] trait and subsystem-specific operations. 4 | //! 5 | //! By using perf_event subsystem, you can monitor processes using `perf` tool in cgroup unit. This 6 | //! subsystem does not have any configurable parameters. 7 | //! 8 | //! # Examples 9 | //! 10 | //! ```no_run 11 | //! # fn main() -> controlgroup::Result<()> { 12 | //! use std::{path::PathBuf, process::Command}; 13 | //! use controlgroup::{Pid, v1::{perf_event, Cgroup, CgroupPath, SubsystemKind}}; 14 | //! 15 | //! let mut perf_event_cgroup = perf_event::Subsystem::new( 16 | //! CgroupPath::new(SubsystemKind::PerfEvent, PathBuf::from("students/charlie"))); 17 | //! perf_event_cgroup.create()?; 18 | //! 19 | //! // Add tasks to this cgroup. 20 | //! let child = Command::new("sleep") 21 | //! .arg("10") 22 | //! .spawn() 23 | //! .expect("command failed"); 24 | //! let child_pid = Pid::from(&child); 25 | //! perf_event_cgroup.add_task(child_pid)?; 26 | //! 27 | //! // You can monitor the child process with `perf` in the cgroup unit. 28 | //! 29 | //! // Do something ... 30 | //! 31 | //! perf_event_cgroup.remove_task(child_pid)?; 32 | //! perf_event_cgroup.delete()?; 33 | //! # Ok(()) 34 | //! # } 35 | //! ``` 36 | //! 37 | //! [`Subsystem`]: struct.Subsystem.html 38 | //! [`Cgroup`]: ../trait.Cgroup.html 39 | 40 | use std::path::PathBuf; 41 | 42 | use crate::{ 43 | v1::{self, CgroupPath}, 44 | Result, 45 | }; 46 | 47 | /// Handler of a perf_event subsystem. 48 | #[derive(Debug)] 49 | pub struct Subsystem { 50 | path: CgroupPath, 51 | } 52 | 53 | impl_cgroup! { 54 | Subsystem, PerfEvent, 55 | 56 | /// Does nothing as a perf_event cgroup has no parameters. 57 | fn apply(&mut self, _resources: &v1::Resources) -> Result<()> { 58 | Ok(()) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/macros.rs: -------------------------------------------------------------------------------- 1 | macro_rules! with_doc { 2 | ($doc: expr, $( $tt: tt )*) => { 3 | #[doc = $doc] 4 | $( $tt )* 5 | }; 6 | } 7 | 8 | macro_rules! subsys_file { 9 | ($subsystem: ident, $field: ident) => { 10 | concat!(stringify!($subsystem), ".", stringify!($field)) 11 | }; 12 | ($subsystem: literal, $field: ident) => { 13 | concat!($subsystem, ".", stringify!($field)) 14 | }; 15 | } 16 | 17 | macro_rules! bail_parse { 18 | () => { 19 | return Err(crate::Error::new(crate::ErrorKind::Parse)); 20 | }; 21 | } 22 | 23 | #[cfg(test)] 24 | macro_rules! gen_cgroup_name { 25 | () => { 26 | std::path::PathBuf::from(format!( 27 | "controlgroup_rs-{}-{}", 28 | std::path::Path::new(file!()) 29 | .file_stem() 30 | .and_then(std::ffi::OsStr::to_str) 31 | .unwrap(), 32 | line!() 33 | )) 34 | }; 35 | } 36 | 37 | #[cfg(test)] 38 | macro_rules! hashmap { 39 | ( $( ( $k: expr, $v: expr $(, )? ) ),* $(, )? ) => { { 40 | #[allow(unused_mut, clippy::let_and_return)] 41 | 42 | let mut hashmap = std::collections::HashMap::new(); 43 | $( hashmap.insert($k, $v); )* 44 | hashmap 45 | } }; 46 | } 47 | 48 | #[cfg(test)] 49 | mod tests { 50 | #[test] 51 | fn test_subsys_file() { 52 | assert_eq!(subsys_file!(cgroup, procs), "cgroup.procs"); 53 | } 54 | 55 | #[test] 56 | fn test_gen_cgroup_name() { 57 | assert_eq!( 58 | gen_cgroup_name!(), 59 | std::path::PathBuf::from("controlgroup_rs-macros-58") 60 | ); 61 | } 62 | 63 | #[test] 64 | fn test_hashmap() { 65 | assert_eq!( 66 | hashmap! { (0, "zero"), (1, "one") }, 67 | [(0, "zero"), (1, "one")] 68 | .iter() 69 | .copied() 70 | .collect::>() 71 | ); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Hidehito Yabuuchi 2 | 3 | Licensed under the MIT license , or the Apache 4 | License, Version 2.0 at your option. 5 | All files in the project carrying such notice may not be copied, modified, or distributed except 6 | according to those terms. 7 | 8 | 9 | This project was started as a fork of levex/cgroups-rs (https://github.com/levex/cgroups-rs), and 10 | developed by redesigning and reimplementing the whole project. 11 | levex/cgroups-rs was licensed under MIT OR Apache-2.0. 12 | The original `LICENSE`, `LICENSE-MIT`, and `LICENSE-Apache-2.0` files of levex/cgroups-rs were as 13 | follows: 14 | 15 | 16 | ===== Beginning of the original `LICENSE` file ===== 17 | 18 | This crate is licensed under either of 19 | 20 | - "Apache License, Version 2.0, (See LICENSE-Apache-2.0 file); or 21 | - "MIT license" (See LICENSE-MIT file), 22 | 23 | at your option. 24 | 25 | ===== End of the original `LICENSE` file ===== 26 | 27 | 28 | ===== Beginning of the original `LICENSE-MIT` file ===== 29 | 30 | MIT License 31 | 32 | Copyright (c) 2018 Levente Kurusa 33 | 34 | Permission is hereby granted, free of charge, to any person obtaining a copy 35 | of this software and associated documentation files (the "Software"), to deal 36 | in the Software without restriction, including without limitation the rights 37 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 38 | copies of the Software, and to permit persons to whom the Software is 39 | furnished to do so, subject to the following conditions: 40 | 41 | The above copyright notice and this permission notice shall be included in all 42 | copies or substantial portions of the Software. 43 | 44 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 45 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 46 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 47 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 48 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 49 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 50 | SOFTWARE. 51 | 52 | ===== End of the original `LICENSE-MIT` file ===== 53 | 54 | 55 | ===== Beginning of the original `LICENSE-Apache-2.0` file ===== 56 | 57 | Copyright 2018 Levente Kurusa 58 | 59 | Licensed under the Apache License, Version 2.0 (the "License"); 60 | you may not use this file except in compliance with the License. 61 | You may obtain a copy of the License at 62 | 63 | http://www.apache.org/licenses/LICENSE-2.0 64 | 65 | Unless required by applicable law or agreed to in writing, software 66 | distributed under the License is distributed on an "AS IS" BASIS, 67 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 68 | See the License for the specific language governing permissions and 69 | limitations under the License. 70 | 71 | ===== End of the original `LICENSE-Apache-2.0` file ===== 72 | -------------------------------------------------------------------------------- /src/parse.rs: -------------------------------------------------------------------------------- 1 | use std::{error::Error as StdErr, io, str::FromStr}; 2 | 3 | use crate::{Error, Result}; 4 | 5 | pub fn parse(mut reader: R) -> Result 6 | where 7 | T: FromStr, 8 | ::Err: StdErr + Sync + Send + 'static, 9 | R: io::Read, 10 | { 11 | let mut buf = String::new(); 12 | reader.read_to_string(&mut buf)?; 13 | buf.trim().parse::().map_err(Error::parse) 14 | } 15 | 16 | pub fn parse_next(mut iter: I) -> Result 17 | where 18 | T: FromStr, 19 | ::Err: StdErr + Sync + Send + 'static, 20 | I: Iterator, 21 | S: AsRef, 22 | { 23 | match iter.next() { 24 | Some(s) => s.as_ref().parse::().map_err(Error::parse), 25 | None => { 26 | bail_parse!(); 27 | } 28 | } 29 | } 30 | 31 | pub fn parse_vec(mut reader: R) -> Result> 32 | where 33 | T: FromStr, 34 | ::Err: StdErr + Sync + Send + 'static, 35 | R: io::Read, 36 | { 37 | let mut buf = String::new(); 38 | reader.read_to_string(&mut buf)?; 39 | 40 | buf.split_whitespace() 41 | .map(|e| e.parse::().map_err(Error::parse)) 42 | .collect() 43 | } 44 | 45 | pub fn parse_01_bool(reader: R) -> Result { 46 | parse::(reader).and_then(|n| match n { 47 | 0 => Ok(false), 48 | 1 => Ok(true), 49 | _ => { 50 | bail_parse!(); 51 | } 52 | }) 53 | } 54 | 55 | #[cfg(test)] 56 | mod tests { 57 | use super::*; 58 | use crate::ErrorKind; 59 | 60 | #[test] 61 | fn test_parse() { 62 | assert_eq!(parse::("42".as_bytes()).unwrap(), 42); 63 | assert_eq!(parse::("true".as_bytes()).unwrap(), true); 64 | 65 | assert_eq!( 66 | parse::("invalid".as_bytes()).unwrap_err().kind(), 67 | ErrorKind::Parse 68 | ); 69 | } 70 | 71 | #[test] 72 | fn test_parse_next() { 73 | use std::iter; 74 | 75 | assert_eq!(parse_next::(Some("42").iter()).unwrap(), 42); 76 | assert_eq!(parse_next::(iter::once("true")).unwrap(), true); 77 | 78 | assert_eq!( 79 | parse_next::(Some("invalid").iter()) 80 | .unwrap_err() 81 | .kind(), 82 | ErrorKind::Parse 83 | ); 84 | 85 | assert_eq!( 86 | parse_next::(iter::empty()) 87 | .unwrap_err() 88 | .kind(), 89 | ErrorKind::Parse 90 | ); 91 | } 92 | 93 | #[test] 94 | fn test_parse_vec() { 95 | assert_eq!(parse_vec::("".as_bytes()).unwrap(), vec![]); 96 | assert_eq!(parse_vec::("0".as_bytes()).unwrap(), vec![0]); 97 | assert_eq!( 98 | parse_vec::("0 1 2 3".as_bytes()).unwrap(), 99 | vec![0, 1, 2, 3] 100 | ); 101 | assert_eq!( 102 | parse_vec::("true false true".as_bytes()).unwrap(), 103 | vec![true, false, true] 104 | ); 105 | 106 | assert_eq!( 107 | parse_vec::("0 invalid".as_bytes()) 108 | .unwrap_err() 109 | .kind(), 110 | ErrorKind::Parse 111 | ); 112 | } 113 | 114 | #[test] 115 | fn test_parse_01_bool() { 116 | assert_eq!(parse_01_bool("0".as_bytes()).unwrap(), false); 117 | assert_eq!(parse_01_bool("1".as_bytes()).unwrap(), true); 118 | 119 | assert_eq!( 120 | parse_01_bool("2".as_bytes()).unwrap_err().kind(), 121 | ErrorKind::Parse 122 | ); 123 | 124 | assert_eq!( 125 | parse_01_bool("invalid".as_bytes()).unwrap_err().kind(), 126 | ErrorKind::Parse 127 | ); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use std::{error::Error as StdError, fmt}; 2 | 3 | /// Result type returned from this crate. 4 | pub type Result = std::result::Result; 5 | 6 | /// Error type that can be returned from this crate, in the [`Result::Err`] variant. The kind and 7 | /// lower-level source of this error can be obtained via [`kind`] and [`source`] methods, 8 | /// respectively. 9 | /// 10 | /// [`Result::Err`]: https://doc.rust-lang.org/std/result/enum.Result.html#variant.Err 11 | /// [`kind`]: #method.kind 12 | /// [`source`]: https://doc.rust-lang.org/nightly/std/error/trait.Error.html#method.source 13 | #[derive(Debug)] 14 | pub struct Error { 15 | kind: ErrorKind, 16 | source: Option>, 17 | } 18 | 19 | /// Kinds of errors that can occur while operating on cgroups. 20 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 21 | pub enum ErrorKind { 22 | /// Failed to do an I/O operation on a cgroup file system. 23 | Io, 24 | 25 | /// Failed to parse a content of a cgroup file into a value. 26 | /// 27 | /// In a future version, there will be some information attached to this variant. 28 | Parse, 29 | 30 | /// You passed an invalid argument. 31 | /// 32 | /// In a future version, this variant may have some information attached, or be replaced with 33 | /// more fine-grained variants. 34 | /// 35 | /// Note that this crate catches not all errors caused by an invalid argument. In some cases, 36 | /// the system (kernel) raises an lower-level error, and this crate returns an [`Error`] with 37 | /// other `ErrorKind`, typically `Io`. The lower-level source can be obtained via 38 | /// [`Error::source`] method. 39 | /// 40 | /// [`Error`]: struct.Error.html 41 | /// [`Error::source`]: https://doc.rust-lang.org/nightly/std/error/trait.Error.html#method.source 42 | InvalidArgument, 43 | 44 | /// You tried to do something invalid. 45 | /// 46 | /// In a future version, this variant may have some information attached, or be replaced with 47 | /// more fine-grained variants. 48 | /// 49 | /// Note that this crate catches not all errors caused by an invalid operation. In some cases, 50 | /// the system (kernel) raises an lower-level error, and this crate returns an [`Error`] with 51 | /// other `ErrorKind`, typically `Io`. The lower-level source can be obtained via 52 | /// [`Error::source`] method. 53 | /// 54 | /// [`Error`]: struct.Error.html 55 | /// [`Error::source`]: https://doc.rust-lang.org/nightly/std/error/trait.Error.html#method.source 56 | InvalidOperation, 57 | } 58 | 59 | impl StdError for Error { 60 | fn source(&self) -> Option<&(dyn StdError + 'static)> { 61 | match self.source { 62 | Some(ref x) => Some(&**x), 63 | None => None, 64 | } 65 | } 66 | } 67 | 68 | impl fmt::Display for Error { 69 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 70 | f.write_str(match self.kind { 71 | ErrorKind::Io => "Unable to do an I/O operation on a cgroup file system", 72 | ErrorKind::Parse => "Unable to parse a content of a cgroup file", 73 | ErrorKind::InvalidArgument => "Invalid argument", 74 | ErrorKind::InvalidOperation => "Invalid operation", 75 | })?; 76 | 77 | if let Some(ref source) = self.source { 78 | write!(f, ": {}", source)?; 79 | } 80 | 81 | Ok(()) 82 | } 83 | } 84 | 85 | impl Error { 86 | pub(crate) fn new(kind: ErrorKind) -> Self { 87 | Self { kind, source: None } 88 | } 89 | 90 | pub(crate) fn with_source(kind: ErrorKind, source: E) -> Self 91 | where 92 | E: StdError + Sync + Send + 'static, 93 | { 94 | Self { 95 | kind, 96 | source: Some(Box::new(source)), 97 | } 98 | } 99 | 100 | pub(crate) fn parse(source: E) -> Self 101 | where 102 | E: StdError + Sync + Send + 'static, 103 | { 104 | Self::with_source(ErrorKind::Parse, source) 105 | } 106 | 107 | /// Returns the kind of this error. 108 | pub fn kind(&self) -> ErrorKind { 109 | self.kind 110 | } 111 | } 112 | 113 | impl From for Error { 114 | fn from(e: std::io::Error) -> Self { 115 | Self::with_source(ErrorKind::Io, e) 116 | } 117 | } 118 | 119 | impl From for Error { 120 | fn from(e: std::num::ParseIntError) -> Self { 121 | Self::with_source(ErrorKind::Parse, e) 122 | } 123 | } 124 | 125 | #[cfg(test)] 126 | #[allow(unreachable_code, dead_code)] 127 | fn test_error_impl_sync_send() { 128 | let _e: Error = unimplemented!(); 129 | let _: &dyn Sync = &_e; 130 | let _: &dyn Send = &_e; 131 | } 132 | -------------------------------------------------------------------------------- /src/v1/command_ext.rs: -------------------------------------------------------------------------------- 1 | use std::{fs, os::unix::process::CommandExt as _}; 2 | 3 | use crate::v1::{Cgroup, UnifiedRepr}; 4 | 5 | /// Extension to the [`std::process::Command`] builder for attaching a command process to one or 6 | /// more cgroups on start. 7 | /// 8 | /// [`std::process::Command`]: https://doc.rust-lang.org/std/process/struct.Command.html 9 | pub trait CommandExt { 10 | /// Attaches a command process to a cgroup on start. 11 | fn cgroup(&mut self, cgroup: &mut C) -> &mut Self; 12 | 13 | /// Attaches a command process to each subsystem supported by a [`UnifiedRepr`] on start. 14 | /// 15 | /// [`UnifiedRepr`]: struct.UnifiedRepr.html 16 | fn cgroups_unified_repr(&mut self, cgroups: &mut UnifiedRepr) -> &mut Self; 17 | } 18 | 19 | impl CommandExt for std::process::Command { 20 | // NOTE: Keep the example below in sync with `README.md` and `lib.rs` 21 | 22 | /// Attaches this command process to a cgroup on start. 23 | /// 24 | /// The process will run within the cgroup from the beginning of its execution. 25 | /// 26 | /// Multiple cgroups can be registered for the process attachment. The process will be attached 27 | /// to the cgroups in order of their registration. 28 | /// 29 | /// # Examples 30 | /// 31 | /// ```no_run 32 | /// # fn main() -> controlgroup::Result<()> { 33 | /// use std::path::PathBuf; 34 | /// use controlgroup::v1::{cpu, Cgroup, CgroupPath, SubsystemKind}; 35 | /// // Import extension trait 36 | /// use controlgroup::v1::CommandExt as _; 37 | /// 38 | /// let mut cgroup = cpu::Subsystem::new( 39 | /// CgroupPath::new(SubsystemKind::Cpu, PathBuf::from("students/charlie"))); 40 | /// cgroup.create()?; 41 | /// 42 | /// let mut child = std::process::Command::new("sleep") 43 | /// .arg("1") 44 | /// // Attach this command process to a cgroup on start 45 | /// .cgroup(&mut cgroup) 46 | /// // This process will run within the cgroup 47 | /// .spawn() 48 | /// .unwrap(); 49 | /// 50 | /// println!("{:?}", cgroup.stat()?); 51 | /// 52 | /// child.wait().unwrap(); 53 | /// cgroup.delete()?; 54 | /// 55 | /// # Ok(()) 56 | /// # } 57 | /// ``` 58 | fn cgroup(&mut self, cgroup: &mut C) -> &mut Self { 59 | let path = cgroup.path().join("cgroup.procs"); 60 | unsafe { self.pre_exec(move || fs::write(&path, std::process::id().to_string())) } 61 | // FIXME: is it safe to write to the same file in parallel? 62 | } 63 | 64 | /// Attaches this command process to each subsystem supported by a [`UnifiedRepr`] on start. 65 | /// 66 | /// See [`cgroup`] for more information. 67 | /// 68 | /// [`UnifiedRepr`]: struct.UnifiedRepr.html 69 | /// [`cgroup`]: #method.cgroup 70 | fn cgroups_unified_repr(&mut self, cgroups: &mut UnifiedRepr) -> &mut Self { 71 | macro_rules! a { 72 | ( $($subsystem: ident),* $(, )? ) => { $( 73 | if let Some(subsys) = cgroups.$subsystem() { 74 | self.cgroup(subsys); 75 | } 76 | )* }; 77 | } 78 | 79 | a! { 80 | cpu_mut, 81 | cpuset_mut, 82 | cpuacct_mut, 83 | memory_mut, 84 | hugetlb_mut, 85 | devices_mut, 86 | blkio_mut, 87 | rdma_mut, 88 | net_prio_mut, 89 | net_cls_mut, 90 | pids_mut, 91 | freezer_mut, 92 | perf_event_mut, 93 | } 94 | 95 | self 96 | } 97 | } 98 | 99 | #[cfg(test)] 100 | mod tests { 101 | use super::*; 102 | use crate::{ 103 | v1::{cpu, CgroupPath, SubsystemKind}, 104 | Pid, Result, 105 | }; 106 | 107 | #[test] 108 | fn test_command_ext_cgroup() -> Result<()> { 109 | let mut cgroup = 110 | cpu::Subsystem::new(CgroupPath::new(SubsystemKind::Cpu, gen_cgroup_name!())); 111 | cgroup.create()?; 112 | 113 | let mut child = std::process::Command::new("sleep") 114 | .arg("1") 115 | .cgroup(&mut cgroup) 116 | .spawn() 117 | .unwrap(); 118 | 119 | let pid = child.id(); 120 | assert_eq!(cgroup.procs().unwrap(), vec![Pid::from(pid)]); 121 | 122 | child.wait()?; 123 | cgroup.delete() 124 | } 125 | 126 | #[test] 127 | fn test_command_ext_unified() -> Result<()> { 128 | use crate::v1::cpuset; 129 | use SubsystemKind::*; 130 | 131 | let mut cgroups = UnifiedRepr::new(gen_cgroup_name!()); 132 | cgroups.skip_create(&[Cpuacct, NetCls]); 133 | cgroups.create()?; 134 | 135 | cgroups.cpuset_mut().unwrap().apply({ 136 | let id_set = [0].iter().copied().collect::(); 137 | &cpuset::Resources { 138 | cpus: Some(id_set.clone()), 139 | mems: Some(id_set), 140 | ..cpuset::Resources::default() 141 | } 142 | .into() 143 | })?; 144 | 145 | let mut child = std::process::Command::new("sleep") 146 | .arg("1") 147 | .cgroups_unified_repr(&mut cgroups) 148 | .spawn() 149 | .unwrap(); 150 | 151 | let pid = Pid::from(child.id()); 152 | assert_eq!( 153 | cgroups.procs().unwrap(), 154 | hashmap! { 155 | (BlkIo, vec![pid]), 156 | (Cpu, vec![pid]), 157 | (Cpuacct, vec![pid]), 158 | (Cpuset, vec![pid]), 159 | (Devices, vec![pid]), 160 | (Freezer, vec![pid]), 161 | (HugeTlb, vec![pid]), 162 | (Memory, vec![pid]), 163 | (NetCls, vec![pid]), 164 | (NetPrio, vec![pid]), 165 | (PerfEvent, vec![pid]), 166 | (Pids, vec![pid]), 167 | (Rdma, vec![pid]), 168 | } 169 | ); 170 | 171 | child.wait()?; 172 | cgroups.delete() 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/v1/mod.rs: -------------------------------------------------------------------------------- 1 | //! Operations on cgroups in a v1 hierarchy. 2 | //! 3 | //! Operations for each subsystem are implemented in each module. See [`cpu::Subsystem`] for 4 | //! example. Currently this crate supports [CPU], [cpuset], [cpuacct], [memory], [hugetlb], 5 | //! [devices], [blkio], [RDMA], [net_prio], [net_cls], [pids], [freezer], and [perf_event] 6 | //! subsystems. 7 | //! 8 | //! [`Cgroup`] trait defines the common operations on a cgroup. All subsystem handlers implement 9 | //! this trait and subsystem-specific operations. 10 | //! 11 | //! [`UnifiedRepr`] provides an access to a set of cgroups in the v1 hierarchies as if it is in a v2 12 | //! hierarchy. 13 | //! 14 | //! [`Builder`] provides a way to configure a set of cgroups in the builder pattern. 15 | //! 16 | //! For more information about cgroup v1, see the kernel's documentation 17 | //! [Documentation/cgroup-v1/cgroups.txt]. 18 | //! 19 | //! [`cpu::Subsystem`]: cpu/struct.Subsystem.html 20 | //! [CPU]: cpu/index.html 21 | //! [cpuset]: cpuset/index.html 22 | //! [cpuacct]: cpuacct/index.html 23 | //! [memory]: memory/index.html 24 | //! [hugetlb]: hugetlb/index.html 25 | //! [devices]: devices/index.html 26 | //! [blkio]: blkio/index.html 27 | //! [RDMA]: rdma/index.html 28 | //! [net_prio]: net_prio/index.html 29 | //! [net_cls]: net_cls/index.html 30 | //! [pids]: pids/index.html 31 | //! [freezer]: freezer/index.html 32 | //! [perf_event]: perf_event/index.html 33 | //! 34 | //! [`Cgroup`]: trait.Cgroup.html 35 | //! [`UnifiedRepr`]: struct.UnifiedRepr.html 36 | //! [`Builder`]: builder/struct.Builder.html 37 | //! 38 | //! [Documentation/cgroup-v1/cgroups.txt]: https://www.kernel.org/doc/Documentation/cgroup-v1/cgroups.txt 39 | 40 | use std::{fmt, path::Path}; 41 | 42 | #[macro_use] 43 | mod macros; 44 | 45 | #[macro_use] 46 | mod cgroup; 47 | pub mod blkio; 48 | pub mod builder; 49 | mod command_ext; 50 | pub mod cpu; 51 | pub mod cpuacct; 52 | pub mod cpuset; 53 | pub mod devices; 54 | pub mod freezer; 55 | pub mod hugetlb; 56 | pub mod memory; 57 | pub mod net_cls; 58 | pub mod net_prio; 59 | pub mod perf_event; 60 | pub mod pids; 61 | pub mod rdma; 62 | mod unified_repr; 63 | 64 | pub use builder::Builder; 65 | pub use cgroup::{Cgroup, CgroupPath}; 66 | pub use command_ext::CommandExt; 67 | pub use unified_repr::UnifiedRepr; 68 | 69 | const CGROUPFS_MOUNT_POINT: &str = "/sys/fs/cgroup"; 70 | 71 | /// Kinds of subsystems that are now available in this crate. 72 | /// 73 | /// `SubsystemKind` implements [`AsRef`]`<`[`Path`]`>` and [`Display`]. The resulting path or string 74 | /// is a standard directory name for the subsystem (e.g. `SubsystemKind::Cpu` => `cpu`). 75 | /// 76 | /// ``` 77 | /// use std::path::Path; 78 | /// use controlgroup::v1::SubsystemKind; 79 | /// 80 | /// assert_eq!(SubsystemKind::Cpu.as_ref(), Path::new("cpu")); 81 | /// assert_eq!(SubsystemKind::Memory.as_ref(), Path::new("memory")); 82 | /// 83 | /// assert_eq!(SubsystemKind::Devices.to_string(), "devices"); 84 | /// assert_eq!(SubsystemKind::PerfEvent.to_string(), "perf_event"); 85 | /// ``` 86 | /// 87 | /// [`AsRef`]: https://doc.rust-lang.org/std/convert/trait.AsRef.html 88 | /// [`Path`]: https://doc.rust-lang.org/std/path/struct.Path.html 89 | /// [`Display`]: https://doc.rust-lang.org/std/fmt/trait.Display.html 90 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 91 | pub enum SubsystemKind { 92 | /// CPU subsystem. 93 | Cpu, 94 | /// Cpuset subsystem. 95 | Cpuset, 96 | /// Cpuacct (CPU accounting) subsystem. 97 | Cpuacct, 98 | /// Memory subsystem. 99 | Memory, 100 | /// HugeTLB subsystem. 101 | HugeTlb, 102 | /// Devices subsystem. 103 | Devices, 104 | /// BlkIo subsystem. 105 | BlkIo, 106 | /// RDMA subsystem. 107 | Rdma, 108 | /// net_prio subsystem. 109 | NetPrio, 110 | /// net_cls subsystem. 111 | NetCls, 112 | /// Pids subsystem. 113 | Pids, 114 | /// Freezer subsystem. 115 | Freezer, 116 | /// perf_event subsystem. 117 | PerfEvent, 118 | } 119 | 120 | /// Compound of resource limits and constraints for all subsystems. 121 | #[derive(Debug, Default, Clone, PartialEq, Eq)] 122 | pub struct Resources { 123 | /// Resource limit on how much CPU time this cgroup can use. 124 | pub cpu: cpu::Resources, 125 | /// Resource limit on which CPUs and memory nodes this cgroup can use, and how they are 126 | /// controlled by the system. 127 | pub cpuset: cpuset::Resources, 128 | /// Resource limit on what amount and how this cgroup can use memory. 129 | pub memory: memory::Resources, 130 | /// Resource limit no how many hugepage TLBs this cgroup can use. 131 | pub hugetlb: hugetlb::Resources, 132 | /// Allow or deny this cgroup to perform specific accesses to devices. 133 | pub devices: devices::Resources, 134 | /// Throttle bandwidth of block I/O by this cgroup. 135 | pub blkio: blkio::Resources, 136 | /// Resource limit on how much this cgroup can use RDMA/IB devices. 137 | pub rdma: rdma::Resources, 138 | /// Priority map of traffic originating from this cgroup. 139 | pub net_prio: net_prio::Resources, 140 | /// Tag network packets from this cgroup with a class ID. 141 | pub net_cls: net_cls::Resources, 142 | /// Resource limit on how many processes this cgroup can have. 143 | pub pids: pids::Resources, 144 | /// Freeze tasks in this cgroup. 145 | pub freezer: freezer::Resources, 146 | } 147 | 148 | impl AsRef for SubsystemKind { 149 | fn as_ref(&self) -> &Path { 150 | Path::new(self.as_str()) 151 | } 152 | } 153 | 154 | impl fmt::Display for SubsystemKind { 155 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 156 | f.write_str(self.as_str()) 157 | } 158 | } 159 | 160 | impl SubsystemKind { 161 | fn as_str(self) -> &'static str { 162 | match self { 163 | Self::Cpu => "cpu", 164 | Self::Cpuset => "cpuset", 165 | Self::Cpuacct => "cpuacct", 166 | Self::Memory => "memory", 167 | Self::HugeTlb => "hugetlb", 168 | Self::Devices => "devices", 169 | Self::BlkIo => "blkio", 170 | Self::Rdma => "rdma", 171 | Self::NetPrio => "net_prio", 172 | Self::NetCls => "net_cls", 173 | Self::Pids => "pids", 174 | Self::Freezer => "freezer", 175 | Self::PerfEvent => "perf_event", 176 | } 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /src/v1/pids.rs: -------------------------------------------------------------------------------- 1 | //! Operations on a Pids subsystem. 2 | //! 3 | //! [`Subsystem`] implements [`Cgroup`] trait and subsystem-specific operations. 4 | //! 5 | //! For more information about this subsystem, see the kernel's documentation 6 | //! [Documentation/cgroup-v1/pids.txt]. 7 | //! 8 | //! # Examples 9 | //! 10 | //! ```no_run 11 | //! # fn main() -> controlgroup::Result<()> { 12 | //! use std::path::PathBuf; 13 | //! use controlgroup::{Pid, Max, v1::{pids, Cgroup, CgroupPath, SubsystemKind}}; 14 | //! 15 | //! let mut pids_cgroup = pids::Subsystem::new( 16 | //! CgroupPath::new(SubsystemKind::Pids, PathBuf::from("students/charlie"))); 17 | //! pids_cgroup.create()?; 18 | //! 19 | //! // Limit the maximum number of processes this cgroup can have. 20 | //! pids_cgroup.set_max(Max::Limit(42))?; 21 | //! 22 | //! // Add a task to this cgroup. 23 | //! let pid = Pid::from(std::process::id()); 24 | //! pids_cgroup.add_task(pid)?; 25 | //! 26 | //! // Do something ... 27 | //! 28 | //! println!("cgroup now has {} processes", pids_cgroup.current()?); 29 | //! println!("cgroup has hit the limit {} times", pids_cgroup.events()?.1); 30 | //! 31 | //! pids_cgroup.remove_task(pid)?; 32 | //! pids_cgroup.delete()?; 33 | //! # Ok(()) 34 | //! # } 35 | //! ``` 36 | //! 37 | //! [`Subsystem`]: struct.Subsystem.html 38 | //! [`Cgroup`]: ../trait.Cgroup.html 39 | //! 40 | //! [Documentation/cgroup-v1/pids.txt]: https://www.kernel.org/doc/Documentation/cgroup-v1/pids.txt 41 | 42 | use std::path::PathBuf; 43 | 44 | use crate::{ 45 | parse::{parse, parse_next}, 46 | v1::{self, cgroup::CgroupHelper, Cgroup, CgroupPath}, 47 | Max, Result, 48 | }; 49 | 50 | /// Handler of a Pids subsystem. 51 | #[derive(Debug)] 52 | pub struct Subsystem { 53 | path: CgroupPath, 54 | } 55 | 56 | /// Resource limit on how many processes a cgroup can have. 57 | /// 58 | /// See the kernel's documentation for more information about the fields. 59 | #[derive(Debug, Default, Clone, PartialEq, Eq)] 60 | pub struct Resources { 61 | /// If [`Max::Max`], the system does not limit the number of processes this cgroup can have. If 62 | /// [`Max::Limit(n)`], this cgroup can have `n` processes at most. 63 | /// 64 | /// [`Max::Max`]: ../../enum.Max.html#variant.Max 65 | /// [`Max::Limit(n)`]: ../../enum.Max.html#variant.Limit 66 | pub max: Option, 67 | } 68 | 69 | impl_cgroup! { 70 | Subsystem, Pids, 71 | 72 | /// Applies `resources.pids.max` if it is `Some`. 73 | fn apply(&mut self, resources: &v1::Resources) -> Result<()> { 74 | if let Some(max) = resources.pids.max { 75 | self.set_max(max)?; 76 | } 77 | 78 | Ok(()) 79 | } 80 | } 81 | 82 | impl Subsystem { 83 | gen_getter!( 84 | pids, 85 | "the maximum number of processes this cgroup can have", 86 | max: link, 87 | Max, 88 | parse 89 | ); 90 | 91 | gen_setter!( 92 | pids, 93 | "a maximum number of processes this cgroup can have,", 94 | max: link, 95 | set_max, 96 | Max, 97 | controlgroup::Max::Limit(2) 98 | ); 99 | 100 | gen_getter!( 101 | pids, 102 | "the number of processes this cgroup currently has", 103 | current, 104 | u32, 105 | parse 106 | ); 107 | 108 | gen_getter!( 109 | pids, 110 | "the event counter, i.e. a pair of the maximum number of processes, 111 | and the number of times fork failed due to the limit", 112 | events, 113 | (Max, u64), 114 | parse_events 115 | ); 116 | } 117 | 118 | impl Into for Resources { 119 | fn into(self) -> v1::Resources { 120 | v1::Resources { 121 | pids: self, 122 | ..v1::Resources::default() 123 | } 124 | } 125 | } 126 | 127 | fn parse_events(mut reader: impl std::io::Read) -> Result<(Max, u64)> { 128 | let mut buf = String::new(); 129 | reader.read_to_string(&mut buf)?; 130 | 131 | let mut entry = buf.split_whitespace(); 132 | let max = parse_next(&mut entry)?; 133 | let cnt = parse_next(&mut entry)?; 134 | 135 | if entry.next().is_some() { 136 | bail_parse!(); 137 | } 138 | 139 | Ok((max, cnt)) 140 | } 141 | 142 | #[cfg(test)] 143 | mod tests { 144 | use super::*; 145 | 146 | #[test] 147 | fn test_subsystem_create_file_exists() -> Result<()> { 148 | gen_subsystem_test!(Pids, ["max", "current", "events"]) 149 | } 150 | 151 | #[test] 152 | fn test_subsystem_apply() -> Result<()> { 153 | gen_subsystem_test!( 154 | Pids, 155 | Resources { 156 | max: Some(Max::Limit(42)), 157 | }, 158 | (max, Max::Limit(42)), 159 | ) 160 | } 161 | 162 | #[test] 163 | fn test_subsystem_max() -> Result<()> { 164 | gen_subsystem_test!(Pids, max, Max::Max, set_max, Max::Limit(42)) 165 | } 166 | 167 | #[test] 168 | #[ignore] // must not be executed in parallel 169 | fn test_subsystem_current() -> Result<()> { 170 | let mut cgroup = 171 | Subsystem::new(CgroupPath::new(v1::SubsystemKind::Pids, gen_cgroup_name!())); 172 | cgroup.create()?; 173 | assert_eq!(cgroup.current()?, 0); 174 | 175 | let pid = crate::Pid::from(std::process::id()); 176 | cgroup.add_proc(pid)?; 177 | assert!(cgroup.current()? > 0); 178 | 179 | cgroup.remove_proc(pid)?; 180 | assert_eq!(cgroup.current()?, 0); 181 | 182 | cgroup.delete() 183 | } 184 | 185 | #[test] 186 | fn test_subsystem_events() -> Result<()> { 187 | gen_subsystem_test!(Pids, events, (Max::Max, 0)) 188 | } 189 | 190 | #[test] 191 | fn test_parse_events() -> Result<()> { 192 | const CONTENT_OK_MAX: &str = "max 0\n"; 193 | assert_eq!(parse_events(CONTENT_OK_MAX.as_bytes())?, (Max::Max, 0)); 194 | 195 | const CONTENT_OK_LIM: &str = "42 7\n"; 196 | assert_eq!( 197 | parse_events(CONTENT_OK_LIM.as_bytes())?, 198 | (Max::Limit(42), 7) 199 | ); 200 | 201 | const CONTENT_NG_MAX: &str = "invalid 0\n"; 202 | const CONTENT_NG_LIM: &str = "max invalid\n"; 203 | const CONTENT_NG_MISSING_DATA: &str = "42\n"; 204 | const CONTENT_NG_EXTRA_DATA: &str = "max 0 invalid\n"; 205 | 206 | for case in &[ 207 | CONTENT_NG_MAX, 208 | CONTENT_NG_LIM, 209 | CONTENT_NG_MISSING_DATA, 210 | CONTENT_NG_EXTRA_DATA, 211 | "", 212 | ] { 213 | assert_eq!( 214 | parse_events(case.as_bytes()).unwrap_err().kind(), 215 | crate::ErrorKind::Parse 216 | ); 217 | } 218 | 219 | Ok(()) 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /src/v1/net_prio.rs: -------------------------------------------------------------------------------- 1 | //! Operations on a net_prio subsystem. 2 | //! 3 | //! [`Subsystem`] implements [`Cgroup`] trait and subsystem-specific operations. 4 | //! 5 | //! For more information about this subsystem, see the kernel's documentation 6 | //! [Documentation/cgroup-v1/net_prio.txt]. 7 | //! 8 | //! # Examples 9 | //! 10 | //! ```no_run 11 | //! # fn main() -> controlgroup::Result<()> { 12 | //! use std::{collections::HashMap, path::PathBuf}; 13 | //! use controlgroup::{Pid, v1::{self, net_prio, Cgroup, CgroupPath, SubsystemKind}}; 14 | //! 15 | //! let mut net_prio_cgroup = net_prio::Subsystem::new( 16 | //! CgroupPath::new(SubsystemKind::NetPrio, PathBuf::from("students/charlie"))); 17 | //! net_prio_cgroup.create()?; 18 | //! 19 | //! // Set a map of priorities assigned to traffic originating from this cgroup. 20 | //! net_prio_cgroup.set_ifpriomap([("lo", 0), ("wlp1s0", 1)].iter())?; 21 | //! 22 | //! // Add a task to this cgroup. 23 | //! let pid = Pid::from(std::process::id()); 24 | //! net_prio_cgroup.add_task(pid)?; 25 | //! 26 | //! // Do something ... 27 | //! 28 | //! net_prio_cgroup.remove_task(pid)?; 29 | //! net_prio_cgroup.delete()?; 30 | //! # Ok(()) 31 | //! # } 32 | //! ``` 33 | //! 34 | //! [`Subsystem`]: struct.Subsystem.html 35 | //! [`Cgroup`]: ../trait.Cgroup.html 36 | //! 37 | //! [Documentation/cgroup-v1/net_prio.txt]: https://www.kernel.org/doc/Documentation/cgroup-v1/net_prio.txt 38 | 39 | use std::{collections::HashMap, path::PathBuf}; 40 | 41 | use crate::{ 42 | parse::{parse, parse_next}, 43 | v1::{self, Cgroup, CgroupPath}, 44 | Error, ErrorKind, Result, 45 | }; 46 | 47 | /// Handler of a net_prio subsystem. 48 | #[derive(Debug)] 49 | pub struct Subsystem { 50 | path: CgroupPath, 51 | } 52 | 53 | /// Priority map of traffic originating from a cgroup. 54 | /// 55 | /// See the kernel's documentation for more information about the field. 56 | #[derive(Debug, Default, Clone, PartialEq, Eq)] 57 | pub struct Resources { 58 | /// Map of priorities assigned to traffic originating from this cgroup. 59 | /// 60 | /// No priority will be set if this map is empty. 61 | pub ifpriomap: HashMap, 62 | } 63 | 64 | impl_cgroup! { 65 | Subsystem, NetPrio, 66 | 67 | /// Applies `resources.net_prio.ifpriomap` if it is not empty. 68 | fn apply(&mut self, resources: &v1::Resources) -> Result<()> { 69 | let prio_map = &resources.net_prio.ifpriomap; 70 | 71 | if prio_map.is_empty() { 72 | Ok(()) 73 | } else { 74 | self.set_ifpriomap(prio_map.iter()) 75 | } 76 | } 77 | } 78 | 79 | impl Subsystem { 80 | gen_getter!( 81 | net_prio, 82 | "the system-internal representation of this cgroup", 83 | prioidx, 84 | u64, 85 | parse 86 | ); 87 | 88 | gen_getter!( 89 | net_prio, "the map of priorities assigned to traffic originating from this cgroup,", 90 | ifpriomap : link, HashMap, parse_ifpriomap 91 | ); 92 | 93 | with_doc! { concat!( 94 | gen_doc!( 95 | sets; 96 | "net_prio.ifpriomap", 97 | "a map of priorities assigned to traffic originating from this cgroup," 98 | : "The first element of the iterator item is traffic name, 99 | and the second is its priority." 100 | ), 101 | gen_doc!(see; ifpriomap), 102 | gen_doc!(err_write; "net_prio.ifpriomap"), 103 | gen_doc!(eg_write; net_prio, set_ifpriomap, [("lo", 0), ("wlp1s", 1)].iter())), 104 | pub fn set_ifpriomap(&mut self, prio_map: I) -> Result<()> 105 | where 106 | I: Iterator, 107 | T: crate::RefKv, 108 | K: std::fmt::Display, 109 | { 110 | use std::io::Write; 111 | 112 | let mut file = self.open_file_write("net_prio.ifpriomap")?; 113 | for if_prio in prio_map { 114 | let (interface, prio) = if_prio.ref_kv(); 115 | 116 | // write!(file, "{} {}", interface, prio)?; // not work 117 | file.write_all(format!("{} {}", interface, prio).as_bytes())?; 118 | } 119 | 120 | Ok(()) 121 | } 122 | } 123 | } 124 | 125 | impl Into for Resources { 126 | fn into(self) -> v1::Resources { 127 | v1::Resources { 128 | net_prio: self, 129 | ..v1::Resources::default() 130 | } 131 | } 132 | } 133 | 134 | fn parse_ifpriomap(reader: impl std::io::Read) -> Result> { 135 | use std::io::{BufRead, BufReader}; 136 | 137 | let mut prio_map = HashMap::new(); 138 | let buf = BufReader::new(reader); 139 | 140 | for line in buf.lines() { 141 | let line = line?; 142 | let mut entry = line.split_whitespace(); 143 | 144 | let interface = entry.next().ok_or_else(|| Error::new(ErrorKind::Parse))?; 145 | let prio = parse_next(&mut entry)?; 146 | 147 | if entry.next().is_some() { 148 | bail_parse!(); 149 | } 150 | 151 | prio_map.insert(interface.to_string(), prio); 152 | } 153 | 154 | Ok(prio_map) 155 | } 156 | 157 | #[cfg(test)] 158 | mod tests { 159 | use super::*; 160 | use v1::SubsystemKind; 161 | 162 | #[test] 163 | fn test_subsystem_create_file_exists() -> Result<()> { 164 | gen_subsystem_test!(NetPrio, ["prioidx", "ifpriomap"]) 165 | } 166 | 167 | #[test] 168 | fn test_subsystem_apply() -> Result<()> { 169 | let mut cgroup = Subsystem::new(CgroupPath::new( 170 | v1::SubsystemKind::NetPrio, 171 | gen_cgroup_name!(), 172 | )); 173 | cgroup.create()?; 174 | 175 | cgroup.apply( 176 | &Resources { 177 | ifpriomap: hashmap! {("lo".to_string(), 1)}, 178 | } 179 | .into(), 180 | )?; 181 | 182 | assert_eq!(cgroup.ifpriomap()?["lo"], 1); 183 | 184 | cgroup.delete() 185 | } 186 | 187 | #[test] 188 | fn test_subsystem_prioidx() -> Result<()> { 189 | let mut cgroup = 190 | Subsystem::new(CgroupPath::new(SubsystemKind::NetPrio, gen_cgroup_name!())); 191 | cgroup.create()?; 192 | 193 | let _ = cgroup.prioidx()?; 194 | 195 | cgroup.delete() 196 | } 197 | 198 | #[test] 199 | fn test_subsystem_ifpriomap() -> Result<()> { 200 | let mut cgroup = 201 | Subsystem::new(CgroupPath::new(SubsystemKind::NetPrio, gen_cgroup_name!())); 202 | cgroup.create()?; 203 | 204 | let mut priorities = cgroup.ifpriomap()?; 205 | for (_, prio) in priorities.iter_mut() { 206 | *prio += 1; 207 | } 208 | 209 | cgroup.set_ifpriomap(priorities.iter())?; 210 | assert_eq!(cgroup.ifpriomap()?, priorities); 211 | 212 | cgroup.delete() 213 | } 214 | 215 | #[test] 216 | fn test_parse_ifpriomap() -> Result<()> { 217 | const CONTENT_OK: &str = "\ 218 | lo 0 219 | wlp1s0 1 220 | "; 221 | 222 | assert_eq!( 223 | parse_ifpriomap(CONTENT_OK.as_bytes())?, 224 | hashmap! { ("lo".to_string(), 0), ("wlp1s0".to_string(), 1) } 225 | ); 226 | 227 | assert_eq!(parse_ifpriomap("".as_bytes())?, HashMap::new(),); 228 | 229 | const CONTENT_NG_NOT_INT: &str = "\ 230 | lo 0 231 | wlp1s0 invalid 232 | "; 233 | 234 | const CONTENT_NG_MISSING_DATA: &str = "\ 235 | lo 236 | wlp1s0 1 237 | "; 238 | 239 | const CONTENT_NG_EXTRA_DATA: &str = "\ 240 | lo 241 | wlp1s0 1 invalid 242 | "; 243 | 244 | for case in &[ 245 | CONTENT_NG_NOT_INT, 246 | CONTENT_NG_MISSING_DATA, 247 | CONTENT_NG_EXTRA_DATA, 248 | ] { 249 | assert_eq!( 250 | parse_ifpriomap(case.as_bytes()).unwrap_err().kind(), 251 | ErrorKind::Parse 252 | ); 253 | } 254 | 255 | Ok(()) 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # controlgroup-rs 2 | 3 | [![Build Status](https://travis-ci.com/ordovicia/controlgroup-rs.svg?branch=master)](https://travis-ci.com/ordovicia/controlgroup-rs) 4 | [![Docs](https://docs.rs/controlgroup/badge.svg)](https://docs.rs/controlgroup) 5 | 6 | Native Rust crate for cgroup operations. 7 | 8 | Currently this crate supports only cgroup v1 hierarchy, implemented in `v1` module. 9 | 10 | ## Support 11 | 12 | ### Not implemented features 13 | 14 | The following features are not yet implemented in a convenient way, but you can read and write 15 | cgroup files directly via low-level APIs. 16 | 17 | * Memory subsystem: Reading `memory.kmem.slabinfo` file 18 | 19 | ### Tested distributions 20 | 21 | This crate is tested on 22 | 23 | * Ubuntu 16.04 (Xenial) 24 | * Ubuntu 18.04 (Bionic) 25 | 26 | on Travis-CI. 27 | 28 | ### Not tested features 29 | 30 | * CPU subsystem 31 | * Reading and writing parameters for scheduling realtime tasks 32 | * Cpuset subsystem 33 | * Getting memory pressure faced by a cgroup 34 | * Memory subsystem 35 | * Getting per-NUMA-node statistics on NUMA systems 36 | * HugeTLB subsystem 37 | * Monitoring hugepage TLB usage by a cgroup 38 | * BlkIO subsystem: 39 | * Setting weights for devices 40 | * Monitoring blkio throughput consumed by a cgroup 41 | * RDMA subsystem 42 | 43 | ## Examples for v1 hierarchy 44 | 45 | ### Create a cgroup controlled by the CPU subsystem 46 | 47 | ```rust 48 | use std::path::PathBuf; 49 | use controlgroup::{Pid, v1::{cpu, Cgroup, CgroupPath, SubsystemKind, Resources}}; 50 | 51 | // Define and create a new cgroup controlled by the CPU subsystem. 52 | let mut cgroup = cpu::Subsystem::new( 53 | CgroupPath::new(SubsystemKind::Cpu, PathBuf::from("students/charlie"))); 54 | cgroup.create()?; 55 | 56 | // Attach the self process to the cgroup. 57 | let pid = Pid::from(std::process::id()); 58 | cgroup.add_task(pid)?; 59 | 60 | // Define resource limits and constraints for this cgroup. 61 | // Here we just use the default for an example. 62 | let resources = Resources::default(); 63 | 64 | // Apply the resource limits. 65 | cgroup.apply(&resources)?; 66 | 67 | // Low-level file operations are also supported. 68 | let stat_file = cgroup.open_file_read("cpu.stat")?; 69 | 70 | // Do something ... 71 | 72 | // Now, remove self process from the cgroup. 73 | cgroup.remove_task(pid)?; 74 | 75 | // ... and delete the cgroup. 76 | cgroup.delete()?; 77 | 78 | // Note that subsystem handlers does not implement `Drop` and therefore when the 79 | // handler is dropped, the cgroup will stay around. 80 | ``` 81 | 82 | ### Create a set of cgroups controlled by multiple subsystems 83 | 84 | `v1::Builder` provides a way to configure cgroups in the builder pattern. 85 | 86 | ```rust 87 | use std::path::PathBuf; 88 | use controlgroup::{ 89 | Max, 90 | v1::{devices, hugetlb::{self, HugepageSize}, net_cls, rdma, Builder, SubsystemKind}, 91 | }; 92 | 93 | let mut cgroups = 94 | // Start building a (set of) cgroup(s). 95 | Builder::new(PathBuf::from("students/charlie")) 96 | // Start configuring the CPU resource limits. 97 | .cpu() 98 | .shares(1000) 99 | .cfs_quota_us(500 * 1000) 100 | .cfs_period_us(1000 * 1000) 101 | // Finish configuring the CPU resource limits. 102 | .done() 103 | // Start configuring the cpuset resource limits. 104 | .cpuset() 105 | .cpus([0].iter().copied().collect()) 106 | .mems([0].iter().copied().collect()) 107 | .memory_migrate(true) 108 | .done() 109 | .memory() 110 | .limit_in_bytes(4 * (1 << 30)) 111 | .soft_limit_in_bytes(3 * (1 << 30)) 112 | .use_hierarchy(true) 113 | .done() 114 | .hugetlb() 115 | .limits( 116 | [ 117 | (HugepageSize::Mb2, hugetlb::Limit::Pages(4)), 118 | (HugepageSize::Gb1, hugetlb::Limit::Pages(2)), 119 | ].iter().copied() 120 | ) 121 | .done() 122 | .devices() 123 | .deny(vec!["a *:* rwm".parse::().unwrap()]) 124 | .allow(vec!["c 1:3 mr".parse::().unwrap()]) 125 | .done() 126 | .blkio() 127 | .weight(1000) 128 | .weight_device([([8, 0].into(), 100)].iter().copied()) 129 | .read_bps_device([([8, 0].into(), 10 * (1 << 20))].iter().copied()) 130 | .write_iops_device([([8, 0].into(), 100)].iter().copied()) 131 | .done() 132 | .rdma() 133 | .max( 134 | [( 135 | "mlx4_0".to_string(), 136 | rdma::Limit { 137 | hca_handle: 2.into(), 138 | hca_object: Max::Max, 139 | }, 140 | )].iter().cloned(), 141 | ) 142 | .done() 143 | .net_prio() 144 | .ifpriomap( 145 | [("lo".to_string(), 0), ("wlp1s0".to_string(), 1)].iter().cloned(), 146 | ) 147 | .done() 148 | .net_cls() 149 | .classid([0x10, 0x1].into()) 150 | .done() 151 | .pids() 152 | .max(42.into()) 153 | .done() 154 | .freezer() 155 | // Tasks in this cgroup will be frozen. 156 | .freeze() 157 | .done() 158 | // Enable CPU accounting for this cgroup. 159 | // Cpuacct subsystem has no parameter, so this method does not return a subsystem builder, 160 | // just enables the accounting. 161 | .cpuacct() 162 | // Enable monitoring this cgroup via `perf` tool. 163 | // Like `cpuacct()` method, this method does not return a subsystem builder. 164 | .perf_event() 165 | // Skip creating directories for Cpuacct subsystem and net_cls subsystem. 166 | // This is useful when some subsystems share hierarchy with others. 167 | .skip_create(vec![SubsystemKind::Cpuacct, SubsystemKind::NetCls]) 168 | // Actually build cgroups with the configuration. 169 | .build()?; 170 | 171 | let pid = std::process::id().into(); 172 | cgroups.add_task(pid)?; 173 | 174 | // Do something ... 175 | 176 | cgroups.remove_task(pid)?; 177 | cgroups.delete()?; 178 | ``` 179 | 180 | ### Spawn a process within one or more cgroups 181 | 182 | `v1::CommandExt` extends the `std::process::Command` builder to attach a command 183 | process to one or more cgroups on start. 184 | 185 | ```rust 186 | use std::path::PathBuf; 187 | use controlgroup::v1::{cpu, Cgroup, CgroupPath, SubsystemKind}; 188 | // Import extension trait 189 | use controlgroup::v1::CommandExt as _; 190 | 191 | let mut cgroup = cpu::Subsystem::new( 192 | CgroupPath::new(SubsystemKind::Cpu, PathBuf::from("students/charlie"))); 193 | cgroup.create()?; 194 | 195 | let mut child = std::process::Command::new("sleep") 196 | .arg("1") 197 | // Attach this command process to a cgroup on start 198 | .cgroup(&mut cgroup) 199 | // This process will run within the cgroup 200 | .spawn() 201 | .unwrap(); 202 | 203 | println!("{:?}", cgroup.stat()?); 204 | 205 | child.wait().unwrap(); 206 | cgroup.delete()?; 207 | ``` 208 | 209 | ## MSRV (Minimum Supported Rust Version) 210 | 211 | ``` 212 | rustc 1.37.0 (eae3437df 2019-08-13) 213 | ``` 214 | 215 | If you want to use this crate with older Rust, please leave a comment on [issue #1]. 216 | 217 | [issue #1]: https://github.com/ordovicia/controlgroup-rs/issues/1 218 | 219 | ## Disclaimer 220 | 221 | This project was started as a fork of [levex/cgroups-rs], and developed by redesigning and 222 | reimplementing the whole project. 223 | [levex/cgroups-rs] was licensed under MIT OR Apache-2.0. 224 | 225 | See [LICENSE](LICENSE) for detail. 226 | 227 | [levex/cgroups-rs]: https://github.com/levex/cgroups-rs 228 | 229 | ## License 230 | 231 | Copyright 2019 Hidehito Yabuuchi \ 232 | 233 | Licensed under the MIT license , or the Apache 234 | License, Version 2.0 at your option. 235 | All files in the project carrying such notice may not be copied, modified, or distributed except 236 | according to those terms. 237 | 238 | 239 | Unless you explicitly state otherwise, any contribution intentionally submitted 240 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 241 | dual licensed as above, without any additional terms or conditions. 242 | -------------------------------------------------------------------------------- /src/v1/net_cls.rs: -------------------------------------------------------------------------------- 1 | //! Operations on a net_cls subsystem. 2 | //! 3 | //! [`Subsystem`] implements [`Cgroup`] trait and subsystem-specific operations. 4 | //! 5 | //! For more information about this subsystem, see the kernel's documentation 6 | //! [Documentation/cgroup-v1/net_cls.txt]. 7 | //! 8 | //! # Examples 9 | //! 10 | //! ```no_run 11 | //! # fn main() -> controlgroup::Result<()> { 12 | //! use std::path::PathBuf; 13 | //! use controlgroup::{Pid, v1::{self, net_cls, Cgroup, CgroupPath, SubsystemKind}}; 14 | //! 15 | //! let mut net_cls_cgroup = net_cls::Subsystem::new( 16 | //! CgroupPath::new(SubsystemKind::NetCls, PathBuf::from("students/charlie"))); 17 | //! net_cls_cgroup.create()?; 18 | //! 19 | //! // Tag network packets from this cgroup with a class ID. 20 | //! net_cls_cgroup.set_classid([0x10, 0x1].into())?; 21 | //! 22 | //! // Add a task to this cgroup. 23 | //! let pid = Pid::from(std::process::id()); 24 | //! net_cls_cgroup.add_task(pid)?; 25 | //! 26 | //! // Do something ... 27 | //! 28 | //! net_cls_cgroup.remove_task(pid)?; 29 | //! net_cls_cgroup.delete()?; 30 | //! # Ok(()) 31 | //! # } 32 | //! ``` 33 | //! 34 | //! [`Subsystem`]: struct.Subsystem.html 35 | //! [`Cgroup`]: ../trait.Cgroup.html 36 | //! 37 | //! [Documentation/cgroup-v1/net_cls.txt]: https://www.kernel.org/doc/Documentation/cgroup-v1/net_cls.txt 38 | 39 | use std::{fmt, path::PathBuf, str::FromStr}; 40 | 41 | use crate::{ 42 | parse::parse, 43 | v1::{self, Cgroup, CgroupPath}, 44 | Error, Result, 45 | }; 46 | 47 | /// Handler of a net_cls subsystem. 48 | #[derive(Debug)] 49 | pub struct Subsystem { 50 | path: CgroupPath, 51 | } 52 | 53 | /// Tag network packets from a cgroup with a class ID. 54 | /// 55 | /// See the kernel's documentation for more information about the field. 56 | #[derive(Debug, Default, Clone, PartialEq, Eq)] 57 | pub struct Resources { 58 | /// Class ID to be attached to network packets originating from this cgroup. 59 | pub classid: Option, 60 | } 61 | 62 | /// Class ID. 63 | /// 64 | /// Besides writing a struct literal, `ClassId` can be instantiated by [`parse`]-ing a class ID 65 | /// string. If failed, `parse` returns an error with kind [`ErrorKind::Parse`]. 66 | /// 67 | /// ``` 68 | /// use controlgroup::v1::net_cls::ClassId; 69 | /// 70 | /// assert_eq!("0x100001".parse::().unwrap(), ClassId { major: 0x10, minor: 0x1}); 71 | /// assert_eq!("0X0123ABCD".parse::().unwrap(), ClassId { major: 0x0123, minor: 0xABCD}); 72 | /// ``` 73 | /// 74 | /// `ClassId` implements [`Display`]. The result is a hexadecimal string, same as one written to 75 | /// `net_cls.classid` file. 76 | /// 77 | /// ``` 78 | /// use std::string::ToString; 79 | /// use controlgroup::v1::net_cls::ClassId; 80 | /// 81 | /// assert_eq!(ClassId { major: 0x10, minor: 0x1}.to_string(), "0x100001"); 82 | /// assert_eq!(ClassId { major: 0x0123, minor: 0xABCD}.to_string(), "0x123ABCD"); 83 | /// ``` 84 | /// 85 | /// `ClassId` also supports conversion from/into [`u32`] and from/into `[`[`u16`]`; 2]`. 86 | /// 87 | /// ``` 88 | /// use controlgroup::v1::net_cls::ClassId; 89 | /// 90 | /// assert_eq!(ClassId::from(0x10_0001), ClassId { major: 0x10, minor: 0x1}); 91 | /// assert_eq!(ClassId::from(0x0123_ABCD), ClassId { major: 0x0123, minor: 0xABCD}); 92 | /// 93 | /// let id: u32 = ClassId { major: 0x10, minor: 0x1}.into(); 94 | /// assert_eq!(id, 0x10_0001); 95 | /// 96 | /// let id: u32 = ClassId { major: 0x0123, minor: 0xABCD}.into(); 97 | /// assert_eq!(id, 0x0123_ABCD); 98 | /// ``` 99 | /// 100 | /// ``` 101 | /// use controlgroup::v1::net_cls::ClassId; 102 | /// 103 | /// assert_eq!(ClassId::from([0x10, 0x1]), ClassId { major: 0x10, minor: 0x1}); 104 | /// assert_eq!(ClassId::from([0x0123, 0xABCD]), ClassId { major: 0x0123, minor: 0xABCD}); 105 | /// 106 | /// let id: [u16; 2] = ClassId { major: 0x10, minor: 0x1}.into(); 107 | /// assert_eq!(id, [0x10, 0x1]); 108 | /// 109 | /// let id: [u16; 2] = ClassId { major: 0x0123, minor: 0xABCD}.into(); 110 | /// assert_eq!(id, [0x0123, 0xABCD]); 111 | /// ``` 112 | /// 113 | /// [`parse`]: https://doc.rust-lang.org/std/primitive.str.html#method.parse 114 | /// [`ErrorKind::Parse`]: ../../enum.ErrorKind.html#variant.Parse 115 | /// 116 | /// [`Display`]: https://doc.rust-lang.org/std/fmt/trait.Display.html 117 | /// 118 | /// [`u32`]: https://doc.rust-lang.org/std/primitive.u32.html 119 | /// [`u16`]: https://doc.rust-lang.org/std/primitive.u16.html 120 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 121 | pub struct ClassId { 122 | /// Major number. 123 | pub major: u16, 124 | /// Minor number. 125 | pub minor: u16, 126 | } 127 | 128 | impl_cgroup! { 129 | Subsystem, NetCls, 130 | 131 | /// Applies `resources.net_cls.classid` if it is `Some`. 132 | fn apply(&mut self, resources: &v1::Resources) -> Result<()> { 133 | if let Some(id) = resources.net_cls.classid { 134 | self.set_classid(id)?; 135 | } 136 | 137 | Ok(()) 138 | } 139 | } 140 | 141 | const CLASSID: &str = "net_cls.classid"; 142 | 143 | impl Subsystem { 144 | with_doc! { concat!( 145 | gen_doc!( 146 | reads; 147 | "net_cls.classid", 148 | "the class ID of network packets from this cgroup," 149 | ), 150 | gen_doc!(see; classid), 151 | gen_doc!(err_read; "net_cls.classid"), 152 | gen_doc!(eg_read; net_cls, classid)), 153 | pub fn classid(&self) -> Result { 154 | let raw: u32 = self.open_file_read(CLASSID).and_then(parse)?; 155 | Ok(raw.into()) 156 | } 157 | } 158 | 159 | with_doc! { concat!( 160 | gen_doc!( 161 | sets; 162 | "net_cls.classid", 163 | "a class ID to network packets from this cgroup," 164 | ), 165 | gen_doc!(see; classid), 166 | gen_doc!(err_write; "net_cls.classid"), 167 | gen_doc!(eg_write; net_cls, set_classid, [0x10, 0x1].into())), 168 | pub fn set_classid(&mut self, id: ClassId) -> Result<()> { 169 | let raw: u32 = id.into(); 170 | std::fs::write(self.path().join(CLASSID), format!("{:#08X}", raw)).map_err(Into::into) 171 | } 172 | } 173 | } 174 | 175 | impl Into for Resources { 176 | fn into(self) -> v1::Resources { 177 | v1::Resources { 178 | net_cls: self, 179 | ..v1::Resources::default() 180 | } 181 | } 182 | } 183 | 184 | impl FromStr for ClassId { 185 | type Err = Error; 186 | 187 | fn from_str(s: &str) -> Result { 188 | let len = s.len(); 189 | if len < 7 || len > 10 || (&s[0..2] != "0x" && &s[0..2] != "0X") { 190 | bail_parse!(); 191 | } 192 | 193 | let major = u16::from_str_radix(&s[2..(len - 4)], 16)?; 194 | let minor = u16::from_str_radix(&s[(len - 4)..len], 16)?; 195 | 196 | Ok(ClassId { major, minor }) 197 | } 198 | } 199 | 200 | impl fmt::Display for ClassId { 201 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 202 | write!(f, "{:#X}{:04X}", self.major, self.minor) 203 | } 204 | } 205 | 206 | impl From for ClassId { 207 | fn from(id: u32) -> Self { 208 | Self { 209 | major: ((id & 0xffff_0000) >> 16) as u16, 210 | minor: (id & 0xffff) as u16, 211 | } 212 | } 213 | } 214 | 215 | impl From<[u16; 2]> for ClassId { 216 | fn from(id: [u16; 2]) -> Self { 217 | Self { 218 | major: id[0], 219 | minor: id[1], 220 | } 221 | } 222 | } 223 | 224 | impl Into for ClassId { 225 | fn into(self) -> u32 { 226 | (u32::from(self.major) << 16) | u32::from(self.minor) 227 | } 228 | } 229 | 230 | impl Into<[u16; 2]> for ClassId { 231 | fn into(self) -> [u16; 2] { 232 | [self.major, self.minor] 233 | } 234 | } 235 | 236 | #[cfg(test)] 237 | mod tests { 238 | use super::*; 239 | 240 | #[test] 241 | fn test_subsystem_create_file_exists() -> Result<()> { 242 | gen_subsystem_test!(NetCls, ["classid"]) 243 | } 244 | 245 | #[test] 246 | fn test_subsystem_apply() -> Result<()> { 247 | gen_subsystem_test!( 248 | NetCls, 249 | Resources { 250 | classid: Some([0x10, 0x1].into()), 251 | }, 252 | (classid, [0x10, 0x1].into()), 253 | ) 254 | } 255 | 256 | #[test] 257 | fn test_subsystem_classid() -> Result<()> { 258 | gen_subsystem_test!( 259 | NetCls, 260 | classid, 261 | ClassId { major: 0, minor: 0 }, 262 | set_classid, 263 | ClassId { 264 | major: 0x10, 265 | minor: 0x1 266 | } 267 | ) 268 | } 269 | 270 | #[test] 271 | fn err_class_id_from_str() { 272 | for case in &[ 273 | "", 274 | "invalid", 275 | "0xinvalid", 276 | "0x0123invalid", 277 | "01234567", 278 | "0xffff", 279 | "0x012345678", 280 | ] { 281 | assert_eq!( 282 | case.parse::().unwrap_err().kind(), 283 | crate::ErrorKind::Parse 284 | ); 285 | } 286 | } 287 | } 288 | -------------------------------------------------------------------------------- /src/v1/freezer.rs: -------------------------------------------------------------------------------- 1 | //! Operations on a Freezer subsystem. 2 | //! 3 | //! [`Subsystem`] implements [`Cgroup`] trait and subsystem-specific operations. 4 | //! 5 | //! For more information about this subsystem, see the kernel's documentation 6 | //! [Documentation/cgroup-v1/freezer-subsystem.txt]. 7 | //! 8 | //! # Examples 9 | //! 10 | //! ```no_run 11 | //! # fn main() -> controlgroup::Result<()> { 12 | //! use std::{path::PathBuf, process::Command}; 13 | //! use controlgroup::{Pid, v1::{freezer, Cgroup, CgroupPath, SubsystemKind}}; 14 | //! 15 | //! let mut freezer_cgroup = freezer::Subsystem::new( 16 | //! CgroupPath::new(SubsystemKind::Freezer, PathBuf::from("students/charlie"))); 17 | //! freezer_cgroup.create()?; 18 | //! 19 | //! // Add a task to this cgroup. 20 | //! let mut child = Command::new("sleep") 21 | //! .arg("10") 22 | //! .spawn() 23 | //! .expect("command failed"); 24 | //! let child_pid = Pid::from(&child); 25 | //! freezer_cgroup.add_task(child_pid)?; 26 | //! 27 | //! freezer_cgroup.freeze()?; 28 | //! // Child process is now frozen. 29 | //! 30 | //! freezer_cgroup.thaw()?; 31 | //! // Child process has been thawed. 32 | //! 33 | //! println!("cgroup is now {}", freezer_cgroup.state()?); 34 | //! 35 | //! freezer_cgroup.remove_task(child_pid)?; 36 | //! freezer_cgroup.delete()?; 37 | //! # Ok(()) 38 | //! # } 39 | //! ``` 40 | //! 41 | //! [`Subsystem`]: struct.Subsystem.html 42 | //! [`Cgroup`]: ../trait.Cgroup.html 43 | //! 44 | //! [Documentation/cgroup-v1/freezer-subsystem.txt]: https://www.kernel.org/doc/Documentation/cgroup-v1/freezer-subsystem.txt 45 | 46 | use std::{fmt, path::PathBuf}; 47 | 48 | use crate::{ 49 | parse::{parse, parse_01_bool}, 50 | v1::{self, cgroup::CgroupHelper, Cgroup, CgroupPath}, 51 | Error, ErrorKind, Result, 52 | }; 53 | 54 | /// Handler of a Freezer subsystem. 55 | #[derive(Debug)] 56 | pub struct Subsystem { 57 | path: CgroupPath, 58 | } 59 | 60 | /// Freeze tasks in a cgroup. 61 | /// 62 | /// See the kernel's documentation for more information about the field. 63 | #[derive(Debug, Default, Clone, PartialEq, Eq)] 64 | pub struct Resources { 65 | /// If `State::Frozen`, tasks in this cgroup will be frozen. If `State::Thawed`, they will be 66 | /// thawed. Note that applying `State::Freezing` is invalid, and `apply` will raise an error. 67 | pub state: Option, 68 | } 69 | 70 | /// Freezer state of a cgroup. 71 | /// 72 | /// `State` implements [`FromStr`], so you can [`parse`] a string into a `State`. If failed, 73 | /// `parse` returns an error with kind [`ErrorKind::Parse`]. 74 | /// 75 | /// ``` 76 | /// use controlgroup::v1::freezer; 77 | /// 78 | /// let thawed = "THAWED".parse::().unwrap(); 79 | /// assert_eq!(thawed, freezer::State::Thawed); 80 | /// 81 | /// let freezing = "FREEZING".parse::().unwrap(); 82 | /// assert_eq!(freezing, freezer::State::Freezing); 83 | /// 84 | /// let frozen = "FROZEN".parse::().unwrap(); 85 | /// assert_eq!(frozen, freezer::State::Frozen); 86 | /// ``` 87 | /// 88 | /// `State` also implements [`Display`]. The resulting string is in upper case, as in 89 | /// `freezer.state` file. 90 | /// 91 | /// ``` 92 | /// use std::string::ToString; 93 | /// use controlgroup::v1::freezer; 94 | /// 95 | /// assert_eq!(freezer::State::Thawed.to_string(), "THAWED"); 96 | /// assert_eq!(freezer::State::Freezing.to_string(), "FREEZING"); 97 | /// assert_eq!(freezer::State::Frozen.to_string(), "FROZEN"); 98 | /// ``` 99 | /// 100 | /// [`FromStr`]: https://doc.rust-lang.org/std/str/trait.FromStr.html 101 | /// [`parse`]: https://doc.rust-lang.org/std/primitive.str.html#method.parse 102 | /// [`ErrorKind::Parse`]: ../../enum.ErrorKind.html#variant.Parse 103 | /// 104 | /// [`Display`]: https://doc.rust-lang.org/std/fmt/trait.Display.html 105 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 106 | pub enum State { 107 | /// Tasks in this cgroup are thawed, i.e. not frozen. 108 | Thawed, 109 | /// Tasks in this cgroup are in the processes of being frozen. 110 | Freezing, 111 | /// Tasks in this cgroup are frozen. 112 | Frozen, 113 | } 114 | 115 | impl_cgroup! { 116 | Subsystem, Freezer, 117 | 118 | /// Freezes or thaws tasks in this cgroup according to `resources.freezer.state`. 119 | /// 120 | /// Note that only `State::Frozen` and `State::Thawed` are valid. Applying `State::Freezing` 121 | /// will return an error with kind `ErrorKind::InvalidArgument`. 122 | fn apply(&mut self, resources: &v1::Resources) -> Result<()> { 123 | match resources.freezer.state { 124 | Some(State::Frozen) => self.freeze(), 125 | Some(State::Thawed) => self.thaw(), 126 | Some(State::Freezing) => Err(Error::new(ErrorKind::InvalidArgument)), 127 | None => Ok(()) 128 | } 129 | } 130 | } 131 | 132 | macro_rules! _gen_setter { 133 | ($desc: literal, $setter: ident, $val: expr) => { 134 | with_doc! { concat!( 135 | $desc, " tasks in this cgroup by writing to `freezer.state` file.\n\n", 136 | gen_doc!(see), 137 | gen_doc!(err_write; "freezer.state"), 138 | gen_doc!(eg_write; freezer, $setter)), 139 | pub fn $setter(&mut self) -> Result<()> { 140 | self.write_file("freezer.state", $val) 141 | } 142 | } 143 | }; 144 | } 145 | 146 | impl Subsystem { 147 | gen_getter!( 148 | freezer, 149 | "the current state of this cgroup", 150 | state: link, 151 | State, 152 | parse 153 | ); 154 | 155 | gen_getter!( 156 | freezer, 157 | "whether this cgroup itself is frozen or in processes of being frozen,", 158 | self_freezing, 159 | bool, 160 | parse_01_bool 161 | ); 162 | 163 | gen_getter!( 164 | freezer, 165 | "whether any parent cgroups of this cgroup is frozen or in processes of being frozen,", 166 | parent_freezing, 167 | bool, 168 | parse_01_bool 169 | ); 170 | 171 | _gen_setter!("Freezes", freeze, State::Frozen); 172 | _gen_setter!("Thaws, i.e. un-freezes", thaw, State::Thawed); 173 | } 174 | 175 | impl Into for Resources { 176 | fn into(self) -> v1::Resources { 177 | v1::Resources { 178 | freezer: self, 179 | ..v1::Resources::default() 180 | } 181 | } 182 | } 183 | 184 | impl std::str::FromStr for State { 185 | type Err = Error; 186 | 187 | fn from_str(s: &str) -> Result { 188 | match s { 189 | "THAWED" => Ok(Self::Thawed), 190 | "FREEZING" => Ok(Self::Freezing), 191 | "FROZEN" => Ok(Self::Frozen), 192 | _ => { 193 | bail_parse!(); 194 | } 195 | } 196 | } 197 | } 198 | 199 | impl fmt::Display for State { 200 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 201 | match self { 202 | Self::Thawed => write!(f, "THAWED"), 203 | Self::Freezing => write!(f, "FREEZING"), 204 | Self::Frozen => write!(f, "FROZEN"), 205 | } 206 | } 207 | } 208 | 209 | #[cfg(test)] 210 | mod tests { 211 | use super::*; 212 | use v1::SubsystemKind; 213 | 214 | #[test] 215 | fn test_subsystem_create_file_exists() -> Result<()> { 216 | gen_subsystem_test!(Freezer, ["state", "self_freezing", "parent_freezing"]) 217 | } 218 | 219 | #[test] 220 | fn test_subsystem_apply() -> Result<()> { 221 | gen_subsystem_test!( 222 | Freezer, 223 | Resources { 224 | state: Some(State::Frozen), 225 | }, 226 | (state, State::Frozen), 227 | ) 228 | } 229 | 230 | #[test] 231 | fn test_subsystem_state_freeze_thaw() -> Result<()> { 232 | let mut cgroup = 233 | Subsystem::new(CgroupPath::new(SubsystemKind::Freezer, gen_cgroup_name!())); 234 | cgroup.create()?; 235 | assert_eq!(cgroup.state()?, State::Thawed); 236 | 237 | cgroup.freeze()?; 238 | assert_eq!(cgroup.state()?, State::Frozen); 239 | 240 | cgroup.thaw()?; 241 | assert_eq!(cgroup.state()?, State::Thawed); 242 | 243 | cgroup.delete() 244 | } 245 | 246 | #[test] 247 | fn test_subsystem_self_freezing_freeze_thaw() -> Result<()> { 248 | let mut cgroup = 249 | Subsystem::new(CgroupPath::new(SubsystemKind::Freezer, gen_cgroup_name!())); 250 | cgroup.create()?; 251 | assert!(!cgroup.self_freezing()?); 252 | 253 | cgroup.freeze()?; 254 | assert!(cgroup.self_freezing()?); 255 | 256 | cgroup.thaw()?; 257 | assert!(!cgroup.self_freezing()?); 258 | 259 | cgroup.delete() 260 | } 261 | 262 | #[test] 263 | fn test_subsystem_parent_freezing_freeze_thaw() -> Result<()> { 264 | let name = gen_cgroup_name!(); 265 | 266 | let mut child = Subsystem::new(CgroupPath::new(SubsystemKind::Freezer, name.join("child"))); 267 | let mut parent = Subsystem::new(CgroupPath::new(SubsystemKind::Freezer, name)); 268 | 269 | parent.create()?; 270 | child.create()?; 271 | 272 | assert!(!parent.parent_freezing()?); 273 | assert!(!child.parent_freezing()?); 274 | 275 | parent.freeze()?; 276 | assert!(child.parent_freezing()?); 277 | 278 | parent.thaw()?; 279 | assert!(!child.parent_freezing()?); 280 | 281 | child.delete()?; 282 | parent.delete() 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /src/v1/macros.rs: -------------------------------------------------------------------------------- 1 | macro_rules! gen_doc { 2 | (reads; $file: expr, $desc: literal $( : $detail: literal )?) => { concat!( 3 | "Reads ", $desc, " from `", $file, "` file.", 4 | $( " ", $detail, )? "\n\n", 5 | ) }; 6 | (reads_see; $file: expr, $method: ident) => { concat!( 7 | "Reads `", $file, "` file.", 8 | gen_doc!(_see_method; $method) 9 | ) }; 10 | 11 | (sets; $file: expr, $desc: literal $( : $detail: literal )?) => { concat!( 12 | "Sets ", $desc, " by writing to `", $file, "` file.", 13 | $( " ", $detail, )? "\n\n", 14 | ) }; 15 | (sets_see; $file_prefix: literal, $field: ident, $method: ident) => { concat!( 16 | "Writes to `", subsys_file!($file_prefix, $field), "` file.", 17 | gen_doc!(_see_method; $method) 18 | ) }; 19 | 20 | (see $(; $field: ident )?) => { concat!( 21 | "See" 22 | $(, " [`Resources.", stringify!($field), "`]", 23 | "(struct.Resources.html#structfield.", stringify!($field), ") and" )?, 24 | " the kernel's documentation for more information about this field.\n\n" 25 | ) }; 26 | 27 | (err_read; $file: expr) => { concat!( 28 | "# Errors\n\n", 29 | "Returns an error if failed to read and parse `", $file, "` file of this cgroup.\n\n" 30 | ) }; 31 | (err_write; $file: expr) => { concat!( 32 | "# Errors\n\n", 33 | "Returns an error if failed to write to `", $file, "` file of this cgroup.\n\n" 34 | ) }; 35 | 36 | (eg_read; $subsys: ident, $field: ident $(, $val: expr )*) => { concat!( 37 | "# Examples 38 | 39 | ```no_run 40 | # fn main() -> controlgroup::Result<()> { 41 | use std::path::PathBuf; 42 | use controlgroup::v1::{", stringify!($subsys), ", Cgroup, CgroupPath, SubsystemKind}; 43 | 44 | let cgroup = ", stringify!($subsys), "::Subsystem::new( 45 | CgroupPath::new(SubsystemKind::", _kind!($subsys), ", PathBuf::from(\"students/charlie\"))); 46 | 47 | let ", stringify!($field), " = cgroup.", stringify!($field), "(", stringify!($( $val ),* ), ")?; 48 | # Ok(()) 49 | # } 50 | ```") }; 51 | 52 | (eg_write; $subsys: ident, $setter: ident $(, $val: expr )*) => { concat!( 53 | "# Examples 54 | 55 | ```no_run 56 | # fn main() -> controlgroup::Result<()> { 57 | use std::path::PathBuf; 58 | use controlgroup::v1::{", stringify!($subsys), ", Cgroup, CgroupPath, SubsystemKind}; 59 | 60 | let mut cgroup = ", stringify!($subsys), "::Subsystem::new( 61 | CgroupPath::new(SubsystemKind::", _kind!($subsys), ", PathBuf::from(\"students/charlie\"))); 62 | 63 | cgroup.", stringify!($setter), "(", stringify!($( $val ),* ), ")?; 64 | # Ok(()) 65 | # } 66 | ```") }; 67 | 68 | (_see_method; $method: ident) => { concat!( 69 | " See [`", stringify!($method), "`](#method.", stringify!($method), ")", 70 | " method for more information." 71 | ) }; 72 | } 73 | 74 | macro_rules! gen_getter { 75 | ( 76 | $subsys: ident, 77 | $desc: literal $( : $detail: literal )?, 78 | $field: ident $( : $link : ident )?, 79 | $ty: ty, 80 | $parser: ident 81 | ) => { with_doc! { concat!( 82 | gen_doc!(reads; subsys_file!($subsys, $field), $desc $( : $detail )?), 83 | _link!($field $( : $link )?), 84 | gen_doc!(err_read; subsys_file!($subsys, $field)), 85 | gen_doc!(eg_read; $subsys, $field)), 86 | pub fn $field(&self) -> Result<$ty> { 87 | self.open_file_read(subsys_file!($subsys, $field)).and_then($parser) 88 | } 89 | } }; 90 | 91 | ( 92 | cgroup; 93 | $file: expr, 94 | $desc: literal $( : $detail: literal )?, 95 | $getter: ident, 96 | $ty: ty, 97 | $parser: ident 98 | ) => { with_doc! { concat!( 99 | gen_doc!(reads; $file, $desc $( : $detail )?), 100 | gen_doc!(see), 101 | gen_doc!(err_read; $file), 102 | gen_doc!(eg_read; cpu, $getter)), 103 | fn $getter(&self) -> Result<$ty> { 104 | self.open_file_read($file).and_then($parser) 105 | } 106 | } }; 107 | } 108 | 109 | macro_rules! gen_setter { 110 | ( 111 | $subsys: ident, 112 | $desc: literal $( : $detail: literal )?, 113 | $field: ident $( : $link: ident )?, 114 | $setter: ident, 115 | $ty: ty, 116 | $( $val: expr ),* 117 | ) => { with_doc! { 118 | gen_setter!( 119 | _doc; $subsys, $desc $( : $detail )?, $field $( : $link )?, $setter, $( $val ),* 120 | ), 121 | pub fn $setter(&mut self, $field: $ty) -> Result<()> { 122 | self.write_file(subsys_file!($subsys, $field), $field) 123 | } 124 | } }; 125 | 126 | ( 127 | $subsys: ident, 128 | $desc: literal $( : $detail: literal )?, 129 | $field: ident $( : $link : ident )?, 130 | $setter: ident, 131 | $arg: ident : $ty: ty $( as $as: ty )?, 132 | $( $val: expr ),* 133 | ) => { with_doc! { 134 | gen_setter!( 135 | _doc; $subsys, $desc $( : $detail )?, $field $( : $link )?, $setter, $( $val ),* 136 | ), 137 | pub fn $setter(&mut self, $arg: $ty) -> Result<()> { 138 | self.write_file(subsys_file!($subsys, $field), $arg $( as $as )?) 139 | } 140 | } }; 141 | 142 | ( 143 | _doc; 144 | $subsys: ident, 145 | $desc: literal $( : $detail: literal )?, 146 | $field: ident $( : $link : ident )?, 147 | $setter: ident, 148 | $( $val: expr ),* 149 | ) => { concat!( 150 | gen_doc!(sets; subsys_file!($subsys, $field), $desc $( : $detail )?), 151 | _link!($field $( : $link )?), 152 | gen_doc!(err_write; subsys_file!($subsys, $field)), 153 | gen_doc!(eg_write; $subsys, $setter, $( $val ),*) 154 | ) }; 155 | } 156 | 157 | #[cfg(test)] 158 | macro_rules! gen_subsystem_test { 159 | // Test `create`, `file_exists`, and `delete` 160 | ($kind: ident, [ $( $file: literal ),* $(, )?]) => { { 161 | use crate::v1::{CgroupPath, SubsystemKind}; 162 | 163 | let files = vec![$( 164 | format!("{}.{}", SubsystemKind::$kind.as_str(), $file) 165 | ),*]; 166 | 167 | let mut cgroup = Subsystem::new( 168 | CgroupPath::new(SubsystemKind::$kind, gen_cgroup_name!())); 169 | cgroup.create()?; 170 | 171 | assert!(files.iter().all(|f| cgroup.file_exists(f))); 172 | assert!(!cgroup.file_exists("does_not_exist")); 173 | 174 | cgroup.delete()?; 175 | assert!(files.iter().all(|f| !cgroup.file_exists(f))); 176 | 177 | let ok: Result<()> = Ok(()); 178 | ok 179 | } }; 180 | 181 | // Test errors 182 | ($kind: ident, $field: ident, $( ($err_kind: ident, $($arg: expr),*) ),* $(, )?) => { { 183 | use crate::{ErrorKind, v1::{CgroupPath, SubsystemKind}}; 184 | 185 | let mut cgroup = Subsystem::new( 186 | CgroupPath::new(SubsystemKind::$kind, gen_cgroup_name!())); 187 | cgroup.create()?; 188 | 189 | $( 190 | assert_eq!(cgroup.$field($( $arg ),*).unwrap_err().kind(), ErrorKind::$err_kind); 191 | )* 192 | 193 | cgroup.delete() 194 | } }; 195 | 196 | // Test `apply` 197 | ($kind: ident, $resources: expr, $( ($field: ident, $val: expr) ),* $(, )?) => { { 198 | let mut cgroup = 199 | Subsystem::new(CgroupPath::new(v1::SubsystemKind::$kind, gen_cgroup_name!())); 200 | cgroup.create()?; 201 | 202 | cgroup.apply(&$resources.into())?; 203 | 204 | $( 205 | assert_eq!(cgroup.$field()?, $val); 206 | )* 207 | 208 | cgroup.delete() 209 | } }; 210 | 211 | // Test a read-only field 212 | ($kind: ident, $field: ident, $default: expr) => { { 213 | use crate::v1::{CgroupPath, SubsystemKind}; 214 | 215 | let mut cgroup = Subsystem::new( 216 | CgroupPath::new(SubsystemKind::$kind, gen_cgroup_name!())); 217 | cgroup.create()?; 218 | assert_eq!(cgroup.$field()?, $default); 219 | 220 | cgroup.delete() 221 | } }; 222 | 223 | // Test a read-write field 224 | ($kind: ident, $field: ident, $default: expr, $setter: ident, $( $val: expr ),* $(, )?) => { { 225 | use crate::v1::{CgroupPath, SubsystemKind}; 226 | 227 | let mut cgroup = Subsystem::new( 228 | CgroupPath::new(SubsystemKind::$kind, gen_cgroup_name!())); 229 | cgroup.create()?; 230 | assert_eq!(cgroup.$field()?, $default); 231 | 232 | $( 233 | cgroup.$setter($val)?; 234 | assert_eq!(cgroup.$field()?, $val); 235 | )* 236 | 237 | cgroup.delete() 238 | } }; 239 | } 240 | 241 | macro_rules! _kind { 242 | (cpu) => { 243 | "Cpu" 244 | }; 245 | (cpuset) => { 246 | "Cpuset" 247 | }; 248 | (cpuacct) => { 249 | "Cpuacct" 250 | }; 251 | (memory) => { 252 | "Memory" 253 | }; 254 | (hugetlb) => { 255 | "HugeTlb" 256 | }; 257 | (devices) => { 258 | "Devices" 259 | }; 260 | (blkio) => { 261 | "BlkIo" 262 | }; 263 | (rdma) => { 264 | "Rdma" 265 | }; 266 | (net_prio) => { 267 | "NetPrio" 268 | }; 269 | (net_cls) => { 270 | "NetCls" 271 | }; 272 | (pids) => { 273 | "Pids" 274 | }; 275 | (freezer) => { 276 | "Freezer" 277 | }; // (perf_event) => { "PerfEvent" }; 278 | } 279 | 280 | macro_rules! _link { 281 | ($field: ident : link) => { 282 | gen_doc!(see; $field); 283 | }; 284 | ($field: ident) => { 285 | gen_doc!(see); 286 | } 287 | } 288 | -------------------------------------------------------------------------------- /src/v1/rdma.rs: -------------------------------------------------------------------------------- 1 | //! Operations on an RDMA subsystem. 2 | //! 3 | //! [`Subsystem`] implements [`Cgroup`] trait and subsystem-specific operations. 4 | //! 5 | //! For more information about this subsystem, see the kernel's documentation 6 | //! [Documentation/cgroup-v1/rdma.txt]. 7 | //! 8 | //! # Examples 9 | //! 10 | //! ```no_run 11 | //! # fn main() -> controlgroup::Result<()> { 12 | //! use std::{collections::HashMap, path::PathBuf}; 13 | //! use controlgroup::{Pid, Max, v1::{self, rdma, Cgroup, CgroupPath, SubsystemKind}}; 14 | //! 15 | //! let mut rdma_cgroup = rdma::Subsystem::new( 16 | //! CgroupPath::new(SubsystemKind::Rdma, PathBuf::from("students/charlie"))); 17 | //! rdma_cgroup.create()?; 18 | //! 19 | //! // Limit the usage of RDMA/IB devices. 20 | //! let rdma_limits = [ 21 | //! ( 22 | //! "mlx4_0", 23 | //! rdma::Limit { 24 | //! hca_handle: 2.into(), 25 | //! hca_object: 2000.into(), 26 | //! }, 27 | //! ), 28 | //! ( 29 | //! "ocrdma1", 30 | //! rdma::Limit { 31 | //! hca_handle: 3.into(), 32 | //! hca_object: Max::Max, 33 | //! }, 34 | //! ), 35 | //! ]; 36 | //! 37 | //! rdma_cgroup.set_max(rdma_limits.iter())?; 38 | //! 39 | //! // Add tasks to this cgroup. 40 | //! let pid = Pid::from(std::process::id()); 41 | //! rdma_cgroup.add_task(pid)?; 42 | //! 43 | //! // Do something ... 44 | //! 45 | //! // Print the current usage of RDMA/IB devices. 46 | //! for (device, usage) in rdma_cgroup.current()? { 47 | //! println!("{}: {}", device, usage); 48 | //! } 49 | //! 50 | //! rdma_cgroup.remove_task(pid)?; 51 | //! rdma_cgroup.delete()?; 52 | //! # Ok(()) 53 | //! # } 54 | //! ``` 55 | //! 56 | //! [`Subsystem`]: struct.Subsystem.html 57 | //! [`Cgroup`]: ../trait.Cgroup.html 58 | //! 59 | //! [Documentation/cgroup-v1/rdma.txt]: https://www.kernel.org/doc/Documentation/cgroup-v1/rdma.txt 60 | 61 | use std::{collections::HashMap, fmt, path::PathBuf}; 62 | 63 | use crate::{ 64 | parse::parse_next, 65 | v1::{self, Cgroup, CgroupPath}, 66 | Error, ErrorKind, Max, Result, 67 | }; 68 | 69 | /// Handler of an RDMA subsystem. 70 | #[derive(Debug)] 71 | pub struct Subsystem { 72 | path: CgroupPath, 73 | } 74 | 75 | /// Resource limit on how much a cgroup can use RDMA/IB devices. 76 | /// 77 | /// See the kernel's documentation for more information about the fields. 78 | #[derive(Debug, Default, Clone, PartialEq, Eq)] 79 | pub struct Resources { 80 | /// How much this cgroup can use each RDMA/IB device. The key is the device name, and the value 81 | /// is limit for the device. 82 | /// 83 | /// No limits will be applied if this map is empty. 84 | pub max: HashMap, 85 | } 86 | 87 | /// Limit or usage of an RDMA/IB device. 88 | #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] 89 | pub struct Limit { 90 | /// Max number or usage of HCA handles. 91 | pub hca_handle: Max, 92 | /// Max number or usage of HCA objects. 93 | pub hca_object: Max, 94 | } 95 | 96 | impl_cgroup! { 97 | Subsystem, Rdma, 98 | 99 | /// Applies `resources.rdma.max` if it is not empty. 100 | fn apply(&mut self, resources: &v1::Resources) -> Result<()> { 101 | let max = &resources.rdma.max; 102 | 103 | if max.is_empty() { 104 | Ok(()) 105 | } else { 106 | self.set_max(max.iter()) 107 | } 108 | } 109 | } 110 | 111 | impl Subsystem { 112 | gen_getter!( 113 | rdma, "the current usage of RDMA/IB devices", 114 | current, HashMap, parse_limits 115 | ); 116 | 117 | gen_getter!( 118 | rdma, "the usage limits on RDMA/IB devices", 119 | max : link, HashMap, parse_limits 120 | ); 121 | 122 | with_doc! { concat!( 123 | gen_doc!( 124 | sets; "rdma.max", 125 | "usage limits on RDMA/IB devices" 126 | : "The first element of the iterator item is device name, 127 | and the second is limit for the device." 128 | ), 129 | gen_doc!(see; max), 130 | gen_doc!(err_write; "rdma.max"), 131 | gen_doc!( 132 | eg_write; 133 | rdma, 134 | set_max, 135 | [( 136 | "mlx4_0", 137 | rdma::Limit { hca_handle: 3.into(), hca_object: controlgroup::Max::Max } 138 | )].iter() 139 | )), 140 | pub fn set_max(&mut self, limits: I) -> Result<()> 141 | where 142 | I: Iterator, 143 | T: crate::RefKv, 144 | K: fmt::Display, 145 | { 146 | use std::io::Write; 147 | 148 | let mut file = self.open_file_write("rdma.max")?; 149 | for lim in limits { 150 | let (device, limit) = lim.ref_kv(); 151 | 152 | // write!(file, "{} {}", interface, prio)?; // not work 153 | file.write_all(format!("{} {}", device, limit).as_bytes())?; 154 | } 155 | 156 | Ok(()) 157 | } 158 | } 159 | } 160 | 161 | fn parse_limits(reader: impl std::io::Read) -> Result> { 162 | use std::io::{BufRead, BufReader}; 163 | 164 | let mut result = HashMap::new(); 165 | let buf = BufReader::new(reader); 166 | 167 | for line in buf.lines() { 168 | let line = line?; 169 | let mut entry = line.split_whitespace(); 170 | 171 | let device = entry.next().ok_or_else(|| Error::new(ErrorKind::Parse))?; 172 | 173 | let (mut hca_handle, mut hca_object) = (None, None); 174 | for e in entry.by_ref().take(2) { 175 | let mut kv = e.split('='); 176 | 177 | match kv.next() { 178 | // FIXME: is column order guaranteed? 179 | Some("hca_handle") => { 180 | if hca_handle.is_some() { 181 | bail_parse!(); 182 | } 183 | hca_handle = Some(parse_next(kv)?); 184 | } 185 | Some("hca_object") => { 186 | if hca_object.is_some() { 187 | bail_parse!(); 188 | } 189 | hca_object = Some(parse_next(kv)?); 190 | } 191 | _ => { 192 | bail_parse!(); 193 | } 194 | } 195 | } 196 | 197 | match (hca_handle, hca_object, entry.next()) { 198 | (Some(hca_handle), Some(hca_object), None) => { 199 | result.insert( 200 | device.to_string(), 201 | Limit { 202 | hca_handle, 203 | hca_object, 204 | }, 205 | ); 206 | } 207 | _ => { 208 | bail_parse!(); 209 | } 210 | } 211 | } 212 | 213 | Ok(result) 214 | } 215 | 216 | impl Into for Resources { 217 | fn into(self) -> v1::Resources { 218 | v1::Resources { 219 | rdma: self, 220 | ..v1::Resources::default() 221 | } 222 | } 223 | } 224 | 225 | impl fmt::Display for Limit { 226 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 227 | write!( 228 | f, 229 | "hca_handle={} hca_object={}", 230 | self.hca_handle, self.hca_object 231 | ) 232 | } 233 | } 234 | 235 | #[cfg(test)] 236 | mod tests { 237 | use super::*; 238 | use v1::SubsystemKind; 239 | 240 | #[test] 241 | #[ignore] // some systems have no RDMA/IB devices 242 | fn test_subsystem_create_file_exists() -> Result<()> { 243 | gen_subsystem_test!(Rdma, ["current", "max"]) 244 | } 245 | 246 | #[test] 247 | fn test_subsystem_apply() -> Result<()> { 248 | // 249 | 250 | Ok(()) 251 | } 252 | 253 | #[test] 254 | #[ignore] // some systems have no RDMA/IB devices 255 | fn test_subsystem_current() -> Result<()> { 256 | let mut cgroup = Subsystem::new(CgroupPath::new(SubsystemKind::Rdma, gen_cgroup_name!())); 257 | cgroup.create()?; 258 | 259 | let _ = cgroup.current()?; 260 | 261 | cgroup.delete() 262 | } 263 | 264 | #[test] 265 | #[ignore] // some systems have no RDMA/IB devices 266 | fn test_subsystem_max() -> Result<()> { 267 | let mut cgroup = Subsystem::new(CgroupPath::new(SubsystemKind::Rdma, gen_cgroup_name!())); 268 | cgroup.create()?; 269 | 270 | let mut limits = cgroup.max()?; 271 | for (_, limit) in limits.iter_mut() { 272 | limit.hca_handle = match limit.hca_handle { 273 | Max::Max => Max::Limit(3), 274 | Max::Limit(_) => Max::Max, 275 | }; 276 | 277 | limit.hca_object = match limit.hca_object { 278 | Max::Max => Max::Limit(3000), 279 | Max::Limit(_) => Max::Max, 280 | }; 281 | } 282 | 283 | cgroup.set_max(limits.iter())?; 284 | assert_eq!(cgroup.max()?, limits); 285 | 286 | cgroup.delete() 287 | } 288 | 289 | #[test] 290 | fn test_parse_limits() -> Result<()> { 291 | const CONTENT_OK_0: &str = "\ 292 | mlx4_0 hca_handle=2 hca_object=2000 293 | ocrdma1 hca_handle=3 hca_object=max 294 | "; 295 | 296 | const CONTENT_OK_1: &str = "\ 297 | mlx4_0 hca_object=2000 hca_handle=2 298 | ocrdma1 hca_object=max hca_handle=3 299 | "; 300 | 301 | let expected = hashmap! { 302 | ( 303 | "mlx4_0".to_string(), 304 | Limit { 305 | hca_handle: Max::Limit(2), 306 | hca_object: Max::Limit(2000), 307 | }, 308 | ), 309 | ( 310 | "ocrdma1".to_string(), 311 | Limit { 312 | hca_handle: Max::Limit(3), 313 | hca_object: Max::Max, 314 | }, 315 | ), 316 | }; 317 | 318 | assert_eq!(parse_limits(CONTENT_OK_0.as_bytes())?, expected); 319 | assert_eq!(parse_limits(CONTENT_OK_1.as_bytes())?, expected); 320 | 321 | assert!(parse_limits("".as_bytes())?.is_empty()); 322 | 323 | const CONTENT_NG_NOT_INT: &str = "\ 324 | mlx4_0 hca_object=invalid hca_handle=2000 325 | "; 326 | 327 | const CONTENT_NG_INVALID_KEY: &str = "\ 328 | mlx4_0 invalid=2 329 | "; 330 | 331 | const CONTENT_NG_MISSING_DATA: &str = "\ 332 | mlx4_0 hca_object=2 333 | "; 334 | 335 | const CONTENT_NG_EXTRA_DATA: &str = "\ 336 | mlx4_0 hca_object=2 hca_handle=2000 invalid 337 | "; 338 | 339 | for case in &[ 340 | CONTENT_NG_NOT_INT, 341 | CONTENT_NG_INVALID_KEY, 342 | CONTENT_NG_MISSING_DATA, 343 | CONTENT_NG_EXTRA_DATA, 344 | ] { 345 | assert_eq!( 346 | parse_limits(case.as_bytes()).unwrap_err().kind(), 347 | ErrorKind::Parse 348 | ); 349 | } 350 | 351 | Ok(()) 352 | } 353 | } 354 | -------------------------------------------------------------------------------- /src/v1/cpu.rs: -------------------------------------------------------------------------------- 1 | //! Operations on a CPU subsystem. 2 | //! 3 | //! [`Subsystem`] implements [`Cgroup`] trait and subsystem-specific operations. 4 | //! 5 | //! For more information about this subsystem, see the kernel's documentation 6 | //! [Documentation/scheduler/sched-design-CFS.txt] 7 | //! paragraph 7 ("GROUP SCHEDULER EXTENSIONS TO CFS"), and [Documentation/scheduler/sched-bwc.txt]. 8 | //! 9 | //! # Examples 10 | //! 11 | //! ```no_run 12 | //! # fn main() -> controlgroup::Result<()> { 13 | //! use std::path::PathBuf; 14 | //! use controlgroup::{Pid, v1::{self, cpu, Cgroup, CgroupPath, SubsystemKind}}; 15 | //! 16 | //! let mut cpu_cgroup = cpu::Subsystem::new( 17 | //! CgroupPath::new(SubsystemKind::Cpu, PathBuf::from("students/charlie"))); 18 | //! cpu_cgroup.create()?; 19 | //! 20 | //! // Define a resource limit about how a cgroup can use CPU time. 21 | //! let resources = cpu::Resources { 22 | //! shares: Some(1024), 23 | //! cfs_quota_us: Some(500_000), 24 | //! cfs_period_us: Some(1_000_000), 25 | //! ..cpu::Resources::default() 26 | //! }; 27 | //! 28 | //! // Apply the resource limit to this cgroup. 29 | //! cpu_cgroup.apply(&resources.into())?; 30 | //! 31 | //! // Add tasks to this cgroup. 32 | //! let pid = Pid::from(std::process::id()); 33 | //! cpu_cgroup.add_task(pid)?; 34 | //! 35 | //! // Do something ... 36 | //! 37 | //! // Get the throttling statistics of this cgroup. 38 | //! println!("{:?}", cpu_cgroup.stat()?); 39 | //! 40 | //! cpu_cgroup.remove_task(pid)?; 41 | //! cpu_cgroup.delete()?; 42 | //! # Ok(()) 43 | //! # } 44 | //! ``` 45 | //! 46 | //! [`Subsystem`]: struct.Subsystem.html 47 | //! [`Cgroup`]: ../trait.Cgroup.html 48 | //! 49 | //! [Documentation/scheduler/sched-design-CFS.txt]: https://www.kernel.org/doc/Documentation/scheduler/sched-design-CFS.txt 50 | //! [Documentation/scheduler/sched-bwc.txt]: https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt 51 | 52 | use std::path::PathBuf; 53 | 54 | use crate::{ 55 | parse::{parse, parse_next}, 56 | v1::{self, cgroup::CgroupHelper, Cgroup, CgroupPath}, 57 | Result, 58 | }; 59 | 60 | /// Handler of a CPU subsystem. 61 | #[derive(Debug)] 62 | pub struct Subsystem { 63 | path: CgroupPath, 64 | } 65 | 66 | /// Resource limit on how much CPU time a cgroup can use. 67 | /// 68 | /// See the kernel's documentation for more information about the fields. 69 | #[derive(Debug, Default, Clone, PartialEq, Eq)] 70 | pub struct Resources { 71 | /// Weight of how much of the total CPU time should be provided to this cgroup. 72 | pub shares: Option, 73 | /// Total available CPU time for this cgroup within a period (in microseconds). 74 | /// 75 | /// Setting -1 removes the current limit. 76 | pub cfs_quota_us: Option, 77 | /// Length of a period (in microseconds). 78 | pub cfs_period_us: Option, 79 | 80 | /// Total available CPU time for realtime tasks in this cgroup within a period (in microseconds). 81 | /// 82 | /// Setting -1 removes the current limit. 83 | pub rt_runtime_us: Option, 84 | /// Length of a period for realtime tasks (in microseconds). 85 | pub rt_period_us: Option, 86 | } 87 | 88 | /// Throttling statistics of a cgroup. 89 | #[derive(Debug, Clone, PartialEq, Eq)] 90 | pub struct Stat { 91 | /// Number of periods (as specified in [`Resources.cfs_period_us`]) that have elapsed. 92 | /// 93 | /// [`Resources.cfs_period_us`]: struct.Resources.html#structfield.cfs_period_us 94 | pub nr_periods: u64, 95 | /// Number of times this cgroup has been throttled. 96 | pub nr_throttled: u64, 97 | /// Total time duration for which this cgroup has been throttled (in nanoseconds). 98 | pub throttled_time: u64, 99 | } 100 | 101 | impl_cgroup! { 102 | Subsystem, Cpu, 103 | 104 | /// Applies the `Some` fields in `resources.cpu`. 105 | fn apply(&mut self, resources: &v1::Resources) -> Result<()> { 106 | let res: &self::Resources = &resources.cpu; 107 | 108 | macro_rules! a { 109 | ($field: ident, $setter: ident) => { 110 | if let Some(r) = res.$field { 111 | self.$setter(r)?; 112 | } 113 | }; 114 | } 115 | 116 | a!(shares, set_shares); 117 | a!(cfs_quota_us, set_cfs_quota_us); 118 | a!(cfs_period_us, set_cfs_period_us); 119 | a!(rt_runtime_us, set_rt_runtime_us); 120 | a!(rt_period_us, set_rt_period_us); 121 | 122 | Ok(()) 123 | } 124 | } 125 | 126 | impl Subsystem { 127 | gen_getter!( 128 | cpu, 129 | "the throttling statistics of this cgroup", 130 | stat, 131 | Stat, 132 | parse_stat 133 | ); 134 | 135 | gen_getter!(cpu, "the CPU time shares", shares: link, u64, parse); 136 | gen_setter!(cpu, "CPU time shares", shares: link, set_shares, u64, 2048); 137 | 138 | gen_getter!( 139 | cpu, 140 | "the total available CPU time within a period (in microseconds)", 141 | cfs_quota_us: link, 142 | i64, 143 | parse 144 | ); 145 | gen_setter!( 146 | cpu, 147 | "total available CPU time within a period (in microseconds)" 148 | : "Setting -1 removes the current limit.", 149 | cfs_quota_us : link, 150 | set_cfs_quota_us, 151 | quota: i64, 152 | 500 * 1000 153 | ); 154 | 155 | gen_getter!( 156 | cpu, 157 | "the length of period (in microseconds)", 158 | cfs_period_us: link, 159 | u64, 160 | parse 161 | ); 162 | gen_setter!( 163 | cpu, 164 | "length of period (in microseconds)", 165 | cfs_period_us: link, 166 | set_cfs_period_us, 167 | period: u64, 168 | 1000 * 1000 169 | ); 170 | 171 | gen_getter!( 172 | cpu, 173 | "the total available CPU time for realtime tasks within a period (in microseconds)", 174 | rt_runtime_us: link, 175 | i64, 176 | parse 177 | ); 178 | gen_setter!( 179 | cpu, 180 | "total available CPU time for realtime tasks within a period (in microseconds)" 181 | : "Setting -1 removes the current limit.", 182 | rt_runtime_us : link, 183 | set_rt_runtime_us, 184 | runtime: i64, 185 | 500 * 1000 186 | ); 187 | 188 | gen_getter!( 189 | cpu, 190 | "the length of period for realtime tasks (in microseconds)", 191 | rt_period_us: link, 192 | u64, 193 | parse 194 | ); 195 | gen_setter!( 196 | cpu, 197 | "the length of period for realtime tasks (in microseconds)", 198 | rt_period_us: link, 199 | set_rt_period_us, 200 | period: u64, 201 | 1000 * 1000 202 | ); 203 | } 204 | 205 | fn parse_stat(reader: impl std::io::Read) -> Result { 206 | use std::io::{BufRead, BufReader}; 207 | 208 | let (mut nr_periods, mut nr_throttled, mut throttled_time) = (None, None, None); 209 | 210 | for line in BufReader::new(reader).lines() { 211 | let line = line?; 212 | let mut entry = line.split_whitespace(); 213 | 214 | match entry.next() { 215 | Some("nr_periods") => { 216 | if nr_periods.is_some() { 217 | bail_parse!(); 218 | } 219 | nr_periods = Some(parse_next(&mut entry)?); 220 | } 221 | Some("nr_throttled") => { 222 | if nr_throttled.is_some() { 223 | bail_parse!(); 224 | } 225 | nr_throttled = Some(parse_next(&mut entry)?); 226 | } 227 | Some("throttled_time") => { 228 | if throttled_time.is_some() { 229 | bail_parse!(); 230 | } 231 | throttled_time = Some(parse_next(&mut entry)?); 232 | } 233 | _ => bail_parse!(), 234 | }; 235 | 236 | if entry.next().is_some() { 237 | bail_parse!(); 238 | } 239 | } 240 | 241 | match (nr_periods, nr_throttled, throttled_time) { 242 | (Some(nr_periods), Some(nr_throttled), Some(throttled_time)) => Ok(Stat { 243 | nr_periods, 244 | nr_throttled, 245 | throttled_time, 246 | }), 247 | _ => { 248 | bail_parse!(); 249 | } 250 | } 251 | } 252 | 253 | impl Into for Resources { 254 | fn into(self) -> v1::Resources { 255 | v1::Resources { 256 | cpu: self, 257 | ..v1::Resources::default() 258 | } 259 | } 260 | } 261 | 262 | #[cfg(test)] 263 | mod tests { 264 | use super::*; 265 | use crate::ErrorKind; 266 | 267 | #[test] 268 | fn test_subsystem_create_file_exists() -> Result<()> { 269 | gen_subsystem_test!(Cpu, ["stat", "shares", "cfs_quota_us", "cfs_period_us"]) 270 | } 271 | 272 | #[test] 273 | fn test_subsystem_apply() -> Result<()> { 274 | gen_subsystem_test!( 275 | Cpu, 276 | Resources { 277 | shares: Some(1024), 278 | cfs_quota_us: Some(100_000), 279 | cfs_period_us: Some(1_000_000), 280 | rt_runtime_us: None, 281 | rt_period_us: None, 282 | }, 283 | (shares, 1024), 284 | (cfs_quota_us, 100_000), 285 | (cfs_period_us, 1_000_000) 286 | ) 287 | } 288 | 289 | #[test] 290 | fn test_subsystem_stat() -> Result<()> { 291 | gen_subsystem_test!( 292 | Cpu, 293 | stat, 294 | Stat { 295 | nr_periods: 0, 296 | nr_throttled: 0, 297 | throttled_time: 0 298 | } 299 | ) 300 | } 301 | 302 | #[test] 303 | #[ignore] // must not executed in parallel 304 | fn test_subsystem_stat_throttled() -> Result<()> { 305 | let mut cgroup = 306 | Subsystem::new(CgroupPath::new(v1::SubsystemKind::Cpu, gen_cgroup_name!())); 307 | cgroup.create()?; 308 | 309 | let pid = crate::Pid::from(std::process::id()); 310 | cgroup.add_proc(pid)?; 311 | 312 | cgroup.set_cfs_quota_us(1000)?; // 1% 313 | 314 | crate::consume_cpu_until(|| cgroup.stat().unwrap().nr_throttled > 0, 30); 315 | // dbg!(cgroup.stat()?); 316 | 317 | let stat = cgroup.stat()?; 318 | assert!(stat.nr_periods > 0); 319 | assert!(stat.throttled_time > 0); 320 | 321 | cgroup.remove_proc(pid)?; 322 | cgroup.delete() 323 | } 324 | 325 | #[test] 326 | fn test_subsystem_shares() -> Result<()> { 327 | gen_subsystem_test!(Cpu, shares, 1024, set_shares, 2048) 328 | } 329 | 330 | #[test] 331 | fn test_subsystem_cfs_quota_us() -> Result<()> { 332 | gen_subsystem_test!(Cpu, cfs_quota_us, -1, set_cfs_quota_us, 100 * 1000) 333 | } 334 | 335 | #[test] 336 | fn test_subsystem_cfs_period_us() -> Result<()> { 337 | gen_subsystem_test!( 338 | Cpu, 339 | cfs_period_us, 340 | 100 * 1000, 341 | set_cfs_period_us, 342 | 1000 * 1000 343 | ) 344 | } 345 | 346 | #[test] 347 | fn test_parse_stat() -> Result<()> { 348 | const CONTENT_OK: &str = "\ 349 | nr_periods 256 350 | nr_throttled 8 351 | throttled_time 32 352 | "; 353 | 354 | assert_eq!( 355 | parse_stat(CONTENT_OK.as_bytes())?, 356 | Stat { 357 | nr_periods: 256, 358 | nr_throttled: 8, 359 | throttled_time: 32 360 | } 361 | ); 362 | 363 | assert_eq!( 364 | parse_stat("".as_bytes()).unwrap_err().kind(), 365 | ErrorKind::Parse 366 | ); 367 | 368 | const CONTENT_NG_NOT_INT: &str = "\ 369 | nr_periods invalid 370 | nr_throttled 8 371 | throttled_time 32 372 | "; 373 | 374 | const CONTENT_NG_MISSING_DATA: &str = "\ 375 | nr_periods 256 376 | throttled_time 32 377 | "; 378 | 379 | const CONTENT_NG_EXTRA_DATA: &str = "\ 380 | nr_periods 256 381 | nr_throttled 8 256 382 | throttled_time 32 383 | "; 384 | 385 | const CONTENT_NG_EXTRA_ROW: &str = "\ 386 | nr_periods 256 387 | nr_throttled 8 388 | throttled_time 32 389 | invalid 256 390 | "; 391 | 392 | for case in &[ 393 | CONTENT_NG_NOT_INT, 394 | CONTENT_NG_MISSING_DATA, 395 | CONTENT_NG_EXTRA_DATA, 396 | CONTENT_NG_EXTRA_ROW, 397 | ] { 398 | assert_eq!( 399 | parse_stat(case.as_bytes()).unwrap_err().kind(), 400 | ErrorKind::Parse 401 | ); 402 | } 403 | 404 | Ok(()) 405 | } 406 | } 407 | -------------------------------------------------------------------------------- /src/v1/devices.rs: -------------------------------------------------------------------------------- 1 | //! Operations on a Devices subsystem. 2 | //! 3 | //! [`Subsystem`] implements [`Cgroup`] trait and subsystem-specific operations. 4 | //! 5 | //! For more information about this subsystem, see the kernel's documentation 6 | //! [Documentation/cgroup-v1/devices.txt]. 7 | //! 8 | //! # Examples 9 | //! 10 | //! ```no_run 11 | //! # fn main() -> controlgroup::Result<()> { 12 | //! use std::{collections::HashMap, path::PathBuf}; 13 | //! use controlgroup::{Pid, v1::{self, devices::{self, Access}, Cgroup, CgroupPath, SubsystemKind}}; 14 | //! 15 | //! let mut devices_cgroup = devices::Subsystem::new( 16 | //! CgroupPath::new(SubsystemKind::Devices, PathBuf::from("students/charlie"))); 17 | //! devices_cgroup.create()?; 18 | //! 19 | //! // Deny and allow accesses to devices by this cgroup. 20 | //! let denied = "a".parse::().unwrap(); 21 | //! devices_cgroup.deny(&denied)?; 22 | //! 23 | //! let allowed = "c 1:3 mr".parse::().unwrap(); 24 | //! devices_cgroup.allow(&allowed)?; 25 | //! 26 | //! // Add tasks to this cgroup. 27 | //! let pid = Pid::from(std::process::id()); 28 | //! devices_cgroup.add_task(pid)?; 29 | //! 30 | //! // Print allowed accesses. 31 | //! for access in devices_cgroup.list()? { 32 | //! println!("{}", access); 33 | //! } 34 | //! 35 | //! // Do something ... 36 | //! 37 | //! devices_cgroup.remove_task(pid)?; 38 | //! devices_cgroup.delete()?; 39 | //! # Ok(()) 40 | //! # } 41 | //! ``` 42 | //! 43 | //! [`Subsystem`]: struct.Subsystem.html 44 | //! [`Cgroup`]: ../trait.Cgroup.html 45 | //! 46 | //! [Documentation/cgroup-v1/devices.txt]: https://www.kernel.org/doc/Documentation/cgroup-v1/devices.txt 47 | 48 | use std::{fmt, path::PathBuf, str::FromStr}; 49 | 50 | use crate::{ 51 | parse::parse_next, 52 | v1::{self, cgroup::CgroupHelper, Cgroup, CgroupPath}, 53 | Error, Result, 54 | }; 55 | 56 | /// Handler of a Devices subsystem. 57 | #[derive(Debug)] 58 | pub struct Subsystem { 59 | path: CgroupPath, 60 | } 61 | 62 | /// Allow or deny a cgroup to perform specific accesses to devices. 63 | /// 64 | /// See the kernel's documentation for more information about the fields. 65 | #[derive(Debug, Default, Clone, PartialEq, Eq)] 66 | pub struct Resources { 67 | /// Deny this cgroup to perform these accesses. 68 | pub deny: Vec, 69 | /// Allow this cgroup to perform these accesses. 70 | pub allow: Vec, 71 | } 72 | 73 | /// Access to devices of specific type and number. 74 | /// 75 | /// `Access` implements [`FromStr`] and [`Display`]. You can convert a `Access` into a string and 76 | /// vice versa. `parse` returns an error with kind [`ErrorKind::Parse`] if failed. 77 | /// 78 | /// ``` 79 | /// use controlgroup::{Device, DeviceNumber, v1::devices::{Access, AccessType, DeviceType}}; 80 | /// 81 | /// let access = "c 1:3 mr".parse::().unwrap(); 82 | /// assert_eq!( 83 | /// access, 84 | /// Access { 85 | /// device_type: DeviceType::Char, 86 | /// device_number: [1, 3].into(), 87 | /// access_type: AccessType { read: true, write: false, mknod: true }, 88 | /// } 89 | /// ); 90 | /// 91 | /// let access = "a *:* rwm".parse::().unwrap(); 92 | /// assert_eq!( 93 | /// access, 94 | /// Access { 95 | /// device_type: DeviceType::All, 96 | /// device_number: Device { major: DeviceNumber::Any, minor: DeviceNumber::Any }, 97 | /// access_type: AccessType { read: true, write: true, mknod: true }, 98 | /// } 99 | /// ); 100 | /// 101 | /// let access = "a".parse::().unwrap(); // equivalent to "a *:* rwm" 102 | /// assert_eq!( 103 | /// access, 104 | /// Access { 105 | /// device_type: DeviceType::All, 106 | /// device_number: Device { major: DeviceNumber::Any, minor: DeviceNumber::Any }, 107 | /// access_type: AccessType { read: true, write: true, mknod: true }, 108 | /// } 109 | /// ); 110 | /// ``` 111 | /// 112 | /// ``` 113 | /// use controlgroup::{Device, DeviceNumber, v1::devices::{Access, AccessType, DeviceType}}; 114 | /// 115 | /// let access = Access { 116 | /// device_type: DeviceType::Char, 117 | /// device_number: Device { major: DeviceNumber::Number(1), minor: DeviceNumber::Number(3) }, 118 | /// access_type: AccessType { read: true, write: false, mknod: true }, 119 | /// }; 120 | /// assert_eq!(access.to_string(), "c 1:3 rm"); 121 | /// 122 | /// let access = Access { 123 | /// device_type: DeviceType::All, 124 | /// device_number: Device { major: DeviceNumber::Any, minor: DeviceNumber::Any }, 125 | /// access_type: AccessType { read: true, write: true, mknod: true }, 126 | /// }; 127 | /// assert_eq!(access.to_string(), "a *:* rwm"); 128 | /// ``` 129 | /// 130 | /// [`FromStr`]: https://doc.rust-lang.org/std/str/trait.FromStr.html 131 | /// [`Display`]: https://doc.rust-lang.org/std/fmt/trait.Display.html 132 | /// [`ErrorKind::Parse`]: ../../enum.ErrorKind.html#variant.Parse 133 | #[derive(Debug, Clone, PartialEq, Eq)] 134 | pub struct Access { 135 | /// Type of device for which access is permitted or denied. 136 | pub device_type: DeviceType, 137 | /// Number of device for which access is permitted or denied. 138 | pub device_number: crate::Device, 139 | /// What kinds of access is permitted or denied. 140 | pub access_type: AccessType, 141 | } 142 | 143 | /// Device type. 144 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 145 | pub enum DeviceType { 146 | /// Both character and block devices, and all major and minor numbers. 147 | All, 148 | /// Character device. 149 | Char, 150 | /// Block device. 151 | Block, 152 | } 153 | 154 | /// Type of device access. 155 | #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] 156 | pub struct AccessType { 157 | /// Read access. 158 | pub read: bool, 159 | /// Write access. 160 | pub write: bool, 161 | /// Make-node access 162 | pub mknod: bool, 163 | } 164 | 165 | impl_cgroup! { 166 | Subsystem, Devices, 167 | 168 | /// Applies `resources.devices`. `deny` list is applied first, and then `allow` list is. 169 | fn apply(&mut self, resources: &v1::Resources) -> Result<()> { 170 | for denied in &resources.devices.deny { 171 | self.deny(denied)?; 172 | } 173 | 174 | for allowed in &resources.devices.allow { 175 | self.allow(allowed)?; 176 | } 177 | 178 | Ok(()) 179 | } 180 | } 181 | 182 | macro_rules! _gen_setter { 183 | ($desc: literal, $field: ident) => { with_doc! { concat!( 184 | $desc, " this cgroup to perform a type of access to devices with specific type and number,", 185 | " by writing to `devices.", stringify!($field), "` file.\n\n", 186 | gen_doc!(see; $field), 187 | gen_doc!(err_write; subsys_file!(devices, $field)), 188 | gen_doc!(eg_write; devices, $field, &"c 8:0 rm".parse::()?)), 189 | pub fn $field(&mut self, access: &Access) -> Result<()> { 190 | self.write_file(subsys_file!(devices, $field), access) 191 | } 192 | } }; 193 | } 194 | 195 | impl Subsystem { 196 | gen_getter!( 197 | devices, 198 | "allowed device access of this cgroup", 199 | list, 200 | Vec, 201 | parse_list 202 | ); 203 | 204 | _gen_setter!("Denies", deny); 205 | _gen_setter!("Allows", allow); 206 | } 207 | 208 | fn parse_list(reader: impl std::io::Read) -> Result> { 209 | use std::io::{BufRead, BufReader}; 210 | 211 | let mut result = Vec::new(); 212 | 213 | for line in BufReader::new(reader).lines() { 214 | let line = line?; 215 | result.push(line.parse::()?); 216 | } 217 | 218 | Ok(result) 219 | } 220 | 221 | impl Into for Resources { 222 | fn into(self) -> v1::Resources { 223 | v1::Resources { 224 | devices: self, 225 | ..v1::Resources::default() 226 | } 227 | } 228 | } 229 | 230 | impl FromStr for Access { 231 | type Err = Error; 232 | 233 | fn from_str(s: &str) -> Result { 234 | let mut entry = s.split_whitespace(); 235 | 236 | let device_type = parse_next(&mut entry)?; 237 | 238 | if let Some(device_number) = entry.next() { 239 | let device_number = device_number.parse()?; 240 | let access_type = parse_next(&mut entry)?; 241 | 242 | if entry.next().is_some() { 243 | bail_parse!(); 244 | } 245 | 246 | Ok(Self { 247 | device_type, 248 | device_number, 249 | access_type, 250 | }) 251 | } else if device_type == DeviceType::All { 252 | use crate::{Device, DeviceNumber}; 253 | 254 | Ok(Self { 255 | device_type, 256 | device_number: Device { 257 | major: DeviceNumber::Any, 258 | minor: DeviceNumber::Any, 259 | }, 260 | access_type: AccessType { 261 | read: true, 262 | write: true, 263 | mknod: true, 264 | }, 265 | }) 266 | } else { 267 | bail_parse!(); 268 | } 269 | } 270 | } 271 | 272 | impl fmt::Display for Access { 273 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 274 | write!( 275 | f, 276 | "{} {} {}", 277 | self.device_type, self.device_number, self.access_type 278 | ) 279 | } 280 | } 281 | 282 | impl FromStr for DeviceType { 283 | type Err = Error; 284 | 285 | fn from_str(s: &str) -> Result { 286 | match s { 287 | "a" => Ok(Self::All), 288 | "c" => Ok(Self::Char), 289 | "b" => Ok(Self::Block), 290 | _ => { 291 | bail_parse!(); 292 | } 293 | } 294 | } 295 | } 296 | 297 | impl fmt::Display for DeviceType { 298 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 299 | use std::fmt::Write; 300 | 301 | f.write_char(match self { 302 | Self::All => 'a', 303 | Self::Char => 'c', 304 | Self::Block => 'b', 305 | }) 306 | } 307 | } 308 | 309 | impl FromStr for AccessType { 310 | type Err = Error; 311 | 312 | fn from_str(s: &str) -> Result { 313 | let mut access = AccessType::default(); 314 | 315 | macro_rules! s { 316 | ($r: ident) => {{ 317 | if access.$r { 318 | bail_parse!(); 319 | } 320 | access.$r = true; 321 | }}; 322 | } 323 | 324 | for c in s.chars() { 325 | match c { 326 | 'r' => s!(read), 327 | 'w' => s!(write), 328 | 'm' => s!(mknod), 329 | _ => { 330 | bail_parse!(); 331 | } 332 | } 333 | } 334 | 335 | Ok(access) 336 | } 337 | } 338 | 339 | impl fmt::Display for AccessType { 340 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 341 | use std::fmt::Write; 342 | 343 | if self.read { 344 | f.write_char('r')?; 345 | } 346 | if self.write { 347 | f.write_char('w')?; 348 | } 349 | if self.mknod { 350 | f.write_char('m')?; 351 | } 352 | 353 | Ok(()) 354 | } 355 | } 356 | 357 | #[cfg(test)] 358 | mod tests { 359 | use super::*; 360 | use crate::{Device, DeviceNumber, ErrorKind}; 361 | 362 | #[test] 363 | fn test_subsystem_create_file_exists() -> Result<()> { 364 | gen_subsystem_test!(Devices, ["allow", "deny", "list"]) 365 | } 366 | 367 | #[test] 368 | fn test_subsystem_apply() -> Result<()> { 369 | gen_subsystem_test!( 370 | Devices, 371 | Resources { 372 | deny: vec!["a".parse::().unwrap()], 373 | allow: vec!["c 1:3 rm".parse::().unwrap()], 374 | }, 375 | (list, vec!["c 1:3 rm".parse::().unwrap()]), 376 | ) 377 | } 378 | 379 | #[test] 380 | fn test_subsystem_list() -> Result<()> { 381 | let allowed_all = Access { 382 | device_type: DeviceType::All, 383 | device_number: Device { 384 | major: DeviceNumber::Any, 385 | minor: DeviceNumber::Any, 386 | }, 387 | access_type: AccessType { 388 | read: true, 389 | write: true, 390 | mknod: true, 391 | }, 392 | }; 393 | 394 | gen_subsystem_test!(Devices, list, vec![allowed_all]) 395 | } 396 | 397 | #[test] 398 | fn test_subsystem_deny_allow() -> Result<()> { 399 | let mut cgroup = Subsystem::new(CgroupPath::new( 400 | v1::SubsystemKind::Devices, 401 | gen_cgroup_name!(), 402 | )); 403 | cgroup.create()?; 404 | 405 | let all = "a".parse::().unwrap(); 406 | cgroup.deny(&all)?; 407 | assert!(cgroup.list()?.is_empty()); 408 | 409 | let c_1_3_rm = "c 1:3 rm".parse::().unwrap(); 410 | cgroup.allow(&c_1_3_rm)?; 411 | assert_eq!(cgroup.list()?, vec![c_1_3_rm]); 412 | 413 | cgroup.delete() 414 | } 415 | 416 | #[test] 417 | fn err_parse_access() { 418 | for case in &[ 419 | "c", 420 | "d *:* rwm", 421 | "a *:* invalid", 422 | "a invalid rwm", 423 | "a rwm", 424 | "a 1:3", 425 | "a 1:3 rwm invalid", 426 | ] { 427 | assert_eq!(case.parse::().unwrap_err().kind(), ErrorKind::Parse); 428 | } 429 | } 430 | 431 | #[test] 432 | fn test_parse_list() -> Result<()> { 433 | const CONTENT_OK: &str = "\ 434 | c 1:3 rm 435 | b 8:0 rw 436 | "; 437 | assert_eq!( 438 | parse_list(CONTENT_OK.as_bytes())?, 439 | vec![ 440 | Access { 441 | device_type: DeviceType::Char, 442 | device_number: Device { 443 | major: DeviceNumber::Number(1), 444 | minor: DeviceNumber::Number(3) 445 | }, 446 | access_type: AccessType { 447 | read: true, 448 | write: false, 449 | mknod: true 450 | } 451 | }, 452 | Access { 453 | device_type: DeviceType::Block, 454 | device_number: Device { 455 | major: DeviceNumber::Number(8), 456 | minor: DeviceNumber::Number(0) 457 | }, 458 | access_type: AccessType { 459 | read: true, 460 | write: true, 461 | mknod: false 462 | } 463 | }, 464 | ] 465 | ); 466 | 467 | assert_eq!(parse_list("".as_bytes())?, vec![]); 468 | 469 | Ok(()) 470 | } 471 | } 472 | -------------------------------------------------------------------------------- /src/v1/cpuacct.rs: -------------------------------------------------------------------------------- 1 | //! Operations on a cpuacct (CPU accounting) subsystem. 2 | //! 3 | //! [`Subsystem`] implements [`Cgroup`] trait and subsystem-specific operations. 4 | //! 5 | //! For more information about this subsystem, see the kernel's documentation 6 | //! [Documentation/cgroup-v1/cpuacct.txt]. 7 | //! 8 | //! # Examples 9 | //! 10 | //! ```no_run 11 | //! # fn main() -> controlgroup::Result<()> { 12 | //! use std::path::PathBuf; 13 | //! use controlgroup::{Pid, v1::{cpuacct, Cgroup, CgroupPath, SubsystemKind}}; 14 | //! 15 | //! let mut cpuacct_cgroup = cpuacct::Subsystem::new( 16 | //! CgroupPath::new(SubsystemKind::Cpuacct, PathBuf::from("students/charlie"))); 17 | //! cpuacct_cgroup.create()?; 18 | //! 19 | //! // Add a task to this cgroup to monitor CPU usage. 20 | //! let pid = Pid::from(std::process::id()); 21 | //! cpuacct_cgroup.add_task(pid)?; 22 | //! 23 | //! // Do something ... 24 | //! 25 | //! // Get the statistics about CPU usage. 26 | //! let stat_hz = cpuacct_cgroup.stat()?; 27 | //! println!( 28 | //! "cgroup used {} USER_HZ in system mode, {} USER_HZ in user mode.", 29 | //! stat_hz.system, stat_hz.user 30 | //! ); 31 | //! 32 | //! cpuacct_cgroup.remove_task(pid)?; 33 | //! cpuacct_cgroup.delete()?; 34 | //! # Ok(()) 35 | //! # } 36 | //! ``` 37 | //! 38 | //! [`Subsystem`]: struct.Subsystem.html 39 | //! [`Cgroup`]: ../trait.Cgroup.html 40 | //! 41 | //! [Documentation/cgroup-v1/cpuacct.txt]: https://www.kernel.org/doc/Documentation/cgroup-v1/cpuacct.txt 42 | 43 | use std::{ 44 | io::{self, BufRead}, 45 | path::PathBuf, 46 | }; 47 | 48 | use crate::{ 49 | parse::{parse, parse_next, parse_vec}, 50 | v1::{self, cgroup::CgroupHelper, Cgroup, CgroupPath}, 51 | Error, Result, 52 | }; 53 | 54 | /// Handler of a Cpuacct subsystem. 55 | #[derive(Debug)] 56 | pub struct Subsystem { 57 | path: CgroupPath, 58 | } 59 | 60 | /// Statistics about how much CPU time is consumed by tasks in a cgroup. 61 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 62 | pub struct Stat { 63 | /// CPU time consumed in the system (kernel) mode. 64 | pub system: u64, 65 | /// CPU time consumed in the user mode. 66 | pub user: u64, 67 | } 68 | 69 | impl_cgroup! { 70 | Subsystem, Cpuacct, 71 | 72 | /// Does nothing as a Cpuacct subsystem is basically read-only. 73 | fn apply(&mut self, _resources: &v1::Resources) -> Result<()> { 74 | Ok(()) 75 | } 76 | } 77 | 78 | macro_rules! _gen_getter { 79 | ($desc: literal $( : $detail: literal )?, $field: ident, $ty: ty, $parser: ident) => { 80 | gen_getter!(cpuacct, $desc $( : $detail )?, $field, $ty, $parser); 81 | }; 82 | } 83 | 84 | impl Subsystem { 85 | _gen_getter!( 86 | "the statistics about how much CPU time is consumed by this cgroup (in `USER_HZ` unit)" 87 | : "The CPU time is further divided into user and system times.", 88 | stat, Stat, parse_stat 89 | ); 90 | 91 | _gen_getter!( 92 | "the total CPU time consumed by this cgroup (in nanoseconds)", 93 | usage, 94 | u64, 95 | parse 96 | ); 97 | 98 | _gen_getter!( 99 | "the per-CPU total CPU time consumed by this cgroup (in nanoseconds)" : 100 | "The CPU time is further divided into user and system times.", 101 | usage_all, Vec, parse_usage_all 102 | ); 103 | 104 | _gen_getter!( 105 | "the per-CPU total CPU times consumed by this cgroup (in nanoseconds)", 106 | usage_percpu, 107 | Vec, 108 | parse_vec 109 | ); 110 | 111 | _gen_getter!( 112 | "the per-CPU total CPU times consumed by this cgroup 113 | in the system (kernel) mode (in nanoseconds)", 114 | usage_percpu_sys, 115 | Vec, 116 | parse_vec 117 | ); 118 | 119 | _gen_getter!( 120 | "the per-CPU total CPU times consumed by this cgroup in the user mode (in nanoseconds)", 121 | usage_percpu_user, 122 | Vec, 123 | parse_vec 124 | ); 125 | 126 | _gen_getter!( 127 | "the total CPU time consumed by this cgroup in the system (kernel) mode (in nanoseconds)", 128 | usage_sys, 129 | u64, 130 | parse 131 | ); 132 | 133 | _gen_getter!( 134 | "the total CPU time consumed by this cgroup in the user mode (in nanoseconds)", 135 | usage_user, 136 | u64, 137 | parse 138 | ); 139 | 140 | with_doc! { concat!( 141 | "Resets the accounted CPU time of this cgroup by writing to `cpuacct.usage` file.\n\n", 142 | gen_doc!(err_write; "cpuacct.usage"), 143 | gen_doc!(eg_write; cpuacct, reset)), 144 | pub fn reset(&mut self) -> Result<()> { 145 | self.write_file("cpuacct.usage", 0) 146 | } 147 | } 148 | } 149 | 150 | fn parse_stat(reader: impl io::Read) -> Result { 151 | let (mut system, mut user) = (None, None); 152 | let buf = io::BufReader::new(reader); 153 | 154 | for line in buf.lines() { 155 | let line = line?; 156 | let mut entry = line.split_whitespace(); 157 | 158 | match entry.next() { 159 | Some("system") => { 160 | if system.is_some() { 161 | bail_parse!(); 162 | } 163 | system = Some(parse_next(&mut entry)?); 164 | } 165 | Some("user") => { 166 | if user.is_some() { 167 | bail_parse!(); 168 | } 169 | user = Some(parse_next(&mut entry)?); 170 | } 171 | _ => { 172 | bail_parse!(); 173 | } 174 | } 175 | 176 | if entry.next().is_some() { 177 | bail_parse!(); 178 | } 179 | } 180 | 181 | match (system, user) { 182 | (Some(system), Some(user)) => Ok(Stat { system, user }), 183 | _ => { 184 | bail_parse!(); 185 | } 186 | } 187 | } 188 | 189 | fn parse_usage_all(reader: impl io::Read) -> Result> { 190 | let mut buf = io::BufReader::new(reader); 191 | 192 | let mut header = String::new(); 193 | buf.read_line(&mut header).map_err(Error::parse)?; 194 | let mut header = header.split_whitespace(); 195 | 196 | if header.next() != Some("cpu") { 197 | bail_parse!(); 198 | } 199 | 200 | // FIXME: is column order guaranteed? 201 | let system_column = match (header.next(), header.next()) { 202 | (Some("system"), Some("user")) => 0, 203 | (Some("user"), Some("system")) => 1, 204 | _ => { 205 | bail_parse!(); 206 | } 207 | }; 208 | 209 | let mut stats = Vec::new(); 210 | for line in buf.lines() { 211 | let line = line?; 212 | 213 | let mut entry = line.split_whitespace(); 214 | 215 | // FIXME: are IDs guaranteed to be sorted ? 216 | let _id: u32 = parse_next(&mut entry)?; 217 | 218 | if system_column == 0 { 219 | stats.push(Stat { 220 | system: parse_next(&mut entry)?, 221 | user: parse_next(&mut entry)?, 222 | }); 223 | } else { 224 | stats.push(Stat { 225 | user: parse_next(&mut entry)?, 226 | system: parse_next(&mut entry)?, 227 | }); 228 | } 229 | 230 | if entry.next().is_some() { 231 | bail_parse!(); 232 | } 233 | } 234 | 235 | Ok(stats) 236 | } 237 | 238 | #[cfg(test)] 239 | mod tests { 240 | use super::*; 241 | use crate::ErrorKind; 242 | 243 | #[test] 244 | #[rustfmt::skip] 245 | fn test_subsystem_create_file_exists() -> Result<()> { 246 | gen_subsystem_test!( 247 | Cpuacct, 248 | [ 249 | "stat", "usage", "usage_all", "usage_percpu", "usage_percpu_sys", 250 | "usage_percpu_user", "usage_sys", "usage_user" 251 | ] 252 | ) 253 | } 254 | 255 | #[test] 256 | fn test_subsystem_stat() -> Result<()> { 257 | gen_subsystem_test!(Cpuacct, stat, Stat { system: 0, user: 0 }) 258 | } 259 | 260 | #[test] 261 | fn test_subsystem_usage() -> Result<()> { 262 | gen_subsystem_test!(Cpuacct, usage, 0) 263 | } 264 | 265 | #[test] 266 | fn test_subsystem_usage_all() -> Result<()> { 267 | gen_subsystem_test!( 268 | Cpuacct, 269 | usage_all, 270 | vec![Stat { system: 0, user: 0 }; num_cpus::get()] 271 | ) 272 | } 273 | 274 | #[test] 275 | fn test_subsystem_usage_percpu() -> Result<()> { 276 | gen_subsystem_test!(Cpuacct, usage_percpu, vec![0; num_cpus::get()]) 277 | } 278 | 279 | #[test] 280 | fn test_subsystem_usage_percpu_sys() -> Result<()> { 281 | gen_subsystem_test!(Cpuacct, usage_percpu_sys, vec![0; num_cpus::get()]) 282 | } 283 | 284 | #[test] 285 | fn test_subsystem_usage_percpu_user() -> Result<()> { 286 | gen_subsystem_test!(Cpuacct, usage_percpu_user, vec![0; num_cpus::get()]) 287 | } 288 | 289 | #[test] 290 | fn test_subsystem_usage_sys() -> Result<()> { 291 | gen_subsystem_test!(Cpuacct, usage_sys, 0) 292 | } 293 | 294 | #[test] 295 | fn test_subsystem_usage_user() -> Result<()> { 296 | gen_subsystem_test!(Cpuacct, usage_user, 0) 297 | } 298 | 299 | #[test] 300 | fn test_subsystem_reset() -> Result<()> { 301 | let mut cgroup = Subsystem::new(CgroupPath::new( 302 | v1::SubsystemKind::Cpuacct, 303 | gen_cgroup_name!(), 304 | )); 305 | cgroup.create()?; 306 | 307 | cgroup.reset()?; 308 | assert_eq!(cgroup.stat()?, Stat { system: 0, user: 0 }); 309 | 310 | cgroup.delete() 311 | } 312 | 313 | #[test] 314 | #[ignore] // must not be executed in parallel 315 | fn test_subsystem_stat_updated() -> Result<()> { 316 | fn wait(millis: u64) { 317 | std::thread::sleep(std::time::Duration::from_millis(millis)); 318 | } 319 | 320 | let mut cgroup = Subsystem::new(CgroupPath::new( 321 | v1::SubsystemKind::Cpuacct, 322 | gen_cgroup_name!(), 323 | )); 324 | cgroup.create()?; 325 | 326 | let pid = crate::Pid::from(std::process::id()); 327 | cgroup.add_proc(pid)?; 328 | 329 | crate::consume_cpu_until(|| cgroup.usage().unwrap() > 0, 30); 330 | wait(100); 331 | // dbg!(cgroup.max_usage_all()?); 332 | 333 | let usage = cgroup.usage()?; 334 | let usage_all = cgroup.usage_all()?; 335 | let usage_percpu = cgroup.usage_percpu()?; 336 | let usage_percpu_sys = cgroup.usage_percpu_sys()?; 337 | let usage_percpu_user = cgroup.usage_percpu_user()?; 338 | let usage_sys = cgroup.usage_sys()?; 339 | let usage_user = cgroup.usage_user()?; 340 | 341 | let usage_all_sys_sum = usage_all.iter().map(|u| u.system).sum::(); 342 | let usage_all_user_sum = usage_all.iter().map(|u| u.user).sum::(); 343 | 344 | let mut failed = false; 345 | 346 | macro_rules! assert_sign { 347 | ($left: expr, $right: expr) => {{ 348 | let line = line!(); 349 | match ($left, $right) { 350 | (left, right) if (left == 0) != (right == 0) => { 351 | eprintln!("sign-assertion failed at line {}", line); 352 | eprintln!("{}: {}", stringify!($left), left); 353 | eprintln!("{}: {}", stringify!($right), right); 354 | failed = true; 355 | } 356 | _ => {} 357 | } 358 | }}; 359 | 360 | (vec; $left: expr, $right: expr) => { 361 | let line = line!(); 362 | match ($left, $right) { 363 | (left, right) => { 364 | if left.iter().all(|e| *e == 0) != right.iter().all(|e| *e == 0) { 365 | eprintln!("sign-assertion failed at line {}", line); 366 | eprintln!("{}: {:?}", stringify!($left), left); 367 | eprintln!("{}: {:?}", stringify!($right), right); 368 | failed = true; 369 | } 370 | } 371 | } 372 | }; 373 | } 374 | 375 | assert_sign!(usage, usage_all_sys_sum + usage_all_user_sum); 376 | assert_sign!(usage_sys, usage_all_sys_sum); 377 | assert_sign!(usage_user, usage_all_user_sum); 378 | assert_sign!( 379 | vec; 380 | usage_percpu_sys, 381 | usage_all.iter().map(|u| u.system).collect::>() 382 | ); 383 | assert_sign!( 384 | vec; 385 | usage_percpu_user, 386 | usage_all.iter().map(|u| u.user).collect::>() 387 | ); 388 | assert_sign!( 389 | vec; 390 | usage_percpu, 391 | usage_all 392 | .iter() 393 | .map(|u| u.system + u.user) 394 | .collect::>() 395 | ); 396 | 397 | if failed { 398 | panic!("sign-assertion failed"); 399 | } 400 | 401 | wait(100); 402 | cgroup.remove_proc(pid)?; 403 | 404 | cgroup.reset()?; 405 | assert_eq!(cgroup.usage()?, 0); 406 | 407 | cgroup.delete() 408 | } 409 | 410 | #[test] 411 | fn tets_parse_stat() -> Result<()> { 412 | #![allow(clippy::unreadable_literal)] 413 | 414 | const CONTENT_OK: &str = "\ 415 | user 9434783 416 | system 2059970 417 | "; 418 | 419 | assert_eq!( 420 | parse_stat(CONTENT_OK.as_bytes())?, 421 | Stat { 422 | system: 2059970, 423 | user: 9434783 424 | } 425 | ); 426 | 427 | assert_eq!( 428 | parse_stat("".as_bytes()).unwrap_err().kind(), 429 | ErrorKind::Parse 430 | ); 431 | 432 | const CONTENT_NG_NOT_INT: &str = "\ 433 | user 9434783 434 | system invalid 435 | "; 436 | 437 | const CONTENT_NG_MISSING_DATA: &str = "\ 438 | user 9434783 439 | "; 440 | 441 | const CONTENT_NG_EXTRA_DATA: &str = "\ 442 | user 9434783 256 443 | system 2059970 444 | "; 445 | 446 | const CONTENT_NG_EXTRA_ROW: &str = "\ 447 | user 9434783 256 448 | system 2059970 449 | user 9434783 256 450 | "; 451 | 452 | for case in &[ 453 | CONTENT_NG_NOT_INT, 454 | CONTENT_NG_MISSING_DATA, 455 | CONTENT_NG_EXTRA_DATA, 456 | CONTENT_NG_EXTRA_ROW, 457 | ] { 458 | assert_eq!( 459 | parse_stat(case.as_bytes()).unwrap_err().kind(), 460 | ErrorKind::Parse 461 | ); 462 | } 463 | 464 | Ok(()) 465 | } 466 | 467 | #[test] 468 | fn test_parse_usage_all() -> Result<()> { 469 | #![allow(clippy::unreadable_literal)] 470 | 471 | const CONTENT_OK: &str = "\ 472 | cpu user system 473 | 0 29308474949876 365961153038 474 | 1 29360907385495 300617395557 475 | 2 29097088553941 333686385015 476 | 3 28649065680082 311282670956 477 | "; 478 | 479 | assert_eq!( 480 | parse_usage_all(CONTENT_OK.as_bytes())?, 481 | vec![ 482 | Stat { 483 | user: 29308474949876, 484 | system: 365961153038 485 | }, 486 | Stat { 487 | user: 29360907385495, 488 | system: 300617395557 489 | }, 490 | Stat { 491 | user: 29097088553941, 492 | system: 333686385015 493 | }, 494 | Stat { 495 | user: 28649065680082, 496 | system: 311282670956 497 | }, 498 | ] 499 | ); 500 | 501 | const CONTENT_NG_NOT_INT_0: &str = "\ 502 | cpu user system 503 | 0 29308474949876 365961153038 504 | 1 29360907385495 300617395557 505 | 2 29097088553941 invalid 506 | 3 28649065680082 311282670956 507 | "; 508 | 509 | const CONTENT_NG_NOT_INT_1: &str = "\ 510 | cpu user system 511 | invalid 29308474949876 365961153038 512 | 1 29360907385495 300617395557 513 | 2 29097088553941 333686385015 514 | 3 28649065680082 311282670956 515 | "; 516 | 517 | const CONTENT_NG_MISSING_DATA: &str = "\ 518 | cpu user system 519 | 0 29308474949876 365961153038 520 | 1 29360907385495 521 | 2 29097088553941 333686385015 522 | 3 28649065680082 311282670956 523 | "; 524 | 525 | const CONTENT_NG_EXTRA_DATA: &str = "\ 526 | cpu user system 527 | 0 29308474949876 365961153038 528 | 1 29360907385495 300617395557 256 529 | 2 29097088553941 333686385015 530 | 3 28649065680082 311282670956 531 | "; 532 | 533 | for case in &[ 534 | CONTENT_NG_NOT_INT_0, 535 | CONTENT_NG_NOT_INT_1, 536 | CONTENT_NG_MISSING_DATA, 537 | CONTENT_NG_EXTRA_DATA, 538 | ] { 539 | assert_eq!( 540 | parse_usage_all(case.as_bytes()).unwrap_err().kind(), 541 | ErrorKind::Parse 542 | ); 543 | } 544 | 545 | Ok(()) 546 | } 547 | } 548 | -------------------------------------------------------------------------------- /src/v1/hugetlb.rs: -------------------------------------------------------------------------------- 1 | //! Operations on a HugeTLB subsystem. 2 | //! 3 | //! [`Subsystem`] implements [`Cgroup`] trait and subsystem-specific operations. 4 | //! 5 | //! For more information about this subsystem, see the kernel's documentation 6 | //! [Documentation/cgroup-v1/hugetlb.txt]. 7 | //! 8 | //! # Examples 9 | //! 10 | //! ```no_run 11 | //! # fn main() -> controlgroup::Result<()> { 12 | //! use std::path::PathBuf; 13 | //! use controlgroup::{ 14 | //! Pid, 15 | //! v1::{self, hugetlb::{self, HugepageSize, Limit}, Cgroup, CgroupPath, SubsystemKind}, 16 | //! }; 17 | //! 18 | //! let mut hugetlb_cgroup = hugetlb::Subsystem::new( 19 | //! CgroupPath::new(SubsystemKind::HugeTlb, PathBuf::from("students/charlie"))); 20 | //! hugetlb_cgroup.create()?; 21 | //! 22 | //! // Define a resource limit about how many hugepage TLB a cgroup can use. 23 | //! let resources = hugetlb::Resources { 24 | //! limits: [ 25 | //! (hugetlb::HugepageSize::Mb2, Limit::Pages(1)), 26 | //! (hugetlb::HugepageSize::Gb1, Limit::Pages(1)), 27 | //! ].iter().copied().collect(), 28 | //! }; 29 | //! 30 | //! // Apply the resource limit. 31 | //! hugetlb_cgroup.apply(&resources.into())?; 32 | //! 33 | //! // Add tasks to this cgroup. 34 | //! let pid = Pid::from(std::process::id()); 35 | //! hugetlb_cgroup.add_task(pid)?; 36 | //! 37 | //! // Do something ... 38 | //! 39 | //! hugetlb_cgroup.remove_task(pid)?; 40 | //! hugetlb_cgroup.delete()?; 41 | //! # Ok(()) 42 | //! # } 43 | //! ``` 44 | //! 45 | //! [`Subsystem`]: struct.Subsystem.html 46 | //! [`Cgroup`]: ../trait.Cgroup.html 47 | //! 48 | //! [Documentation/cgroup-v1/hugetlb.txt]: https://www.kernel.org/doc/Documentation/cgroup-v1/hugetlb.txt 49 | 50 | use std::{collections::HashMap, fmt, path::PathBuf}; 51 | 52 | use crate::{ 53 | parse::parse, 54 | v1::{self, cgroup::CgroupHelper, Cgroup, CgroupPath}, 55 | Result, 56 | }; 57 | 58 | /// Handler of a HugeTLB subsystem. 59 | #[derive(Debug)] 60 | pub struct Subsystem { 61 | path: CgroupPath, 62 | } 63 | 64 | /// Resource limit no how many hugepage TLBs a cgroup can use. 65 | /// 66 | /// See the kernel's documentation for more information about the fields. 67 | #[derive(Debug, Default, Clone, PartialEq, Eq)] 68 | pub struct Resources { 69 | /// How many hugepage TLBs this cgroup can use for each hugepage size. 70 | pub limits: HashMap, 71 | } 72 | 73 | /// Limit on hugepage TLB usage in different units. 74 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 75 | pub enum Limit { 76 | /// Limit hugepage TLB usage in bytes. 77 | Bytes(u64), 78 | /// Limit hugepage TLB usage in pages. 79 | Pages(u64), 80 | } 81 | 82 | /// Hugepage sizes. 83 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 84 | pub enum HugepageSize { 85 | /// 8 KB hugepage. 86 | Kb8, 87 | /// 64 KB hugepage. 88 | Kb64, 89 | /// 256 KB hugepage. 90 | Kb256, 91 | /// 1 MB hugepage. 92 | Mb1, 93 | /// 2 MB hugepage. 94 | Mb2, 95 | /// 4 MB hugepage. 96 | Mb4, 97 | /// 16 MB hugepage. 98 | Mb16, 99 | /// 256 MB hugepage. 100 | Mb256, 101 | /// 1 GB hugepage. 102 | Gb1, 103 | } 104 | 105 | impl_cgroup! { 106 | Subsystem, HugeTlb, 107 | 108 | /// Applies `resources.hugetlb.limits` if it is not empty. 109 | fn apply(&mut self, resources: &v1::Resources) -> Result<()> { 110 | for (&size, &limit) in &resources.hugetlb.limits { 111 | self.set_limit(size, limit)?; 112 | } 113 | 114 | Ok(()) 115 | } 116 | } 117 | 118 | macro_rules! _gen_getter { 119 | ($desc: literal, $in_bytes: ident, $in_pages: ident) => { 120 | with_doc! { concat!( 121 | gen_doc!(reads; subsys_file!("hugetlb.", $in_bytes), $desc), 122 | gen_doc!(see), 123 | gen_doc!(err_read; subsys_file!("hugetlb.", $in_bytes)), 124 | gen_doc!(eg_read; hugetlb, $in_bytes, hugetlb::HugepageSize::Mb2)), 125 | pub fn $in_bytes(&self, size: HugepageSize) -> Result { 126 | self.open_file_read(&format!("hugetlb.{}.{}", size, stringify!($in_bytes))) 127 | .and_then(parse) 128 | } 129 | } 130 | 131 | with_doc! { concat!( 132 | "Reads ", $desc, " in pages. See [`", stringify!($in_bytes), "`](#method.", 133 | stringify!($in_bytes), ") method for more information."), 134 | pub fn $in_pages(&self, size: HugepageSize) -> Result { 135 | self.$in_bytes(size).map(|b| size.bytes_to_pages(b)) 136 | } 137 | } 138 | }; 139 | } 140 | 141 | const LIMIT_IN_BYTES: &str = "limit_in_bytes"; 142 | const USAGE_IN_BYTES: &str = "usage_in_bytes"; 143 | const MAX_USAGE_IN_BYTES: &str = "max_usage_in_bytes"; 144 | const FAILCNT: &str = "failcnt"; 145 | 146 | impl Subsystem { 147 | /// Returns whether the system supports hugepage in `size`. 148 | /// 149 | /// Note that this method returns `false` if the directory of this cgroup is not created yet. 150 | /// 151 | /// # Example 152 | /// 153 | /// ```no_run 154 | /// # fn main() -> controlgroup::Result<()> { 155 | /// use std::path::PathBuf; 156 | /// use controlgroup::v1::{hugetlb::{self, HugepageSize}, Cgroup, CgroupPath, SubsystemKind}; 157 | /// 158 | /// let mut cgroup = hugetlb::Subsystem::new( 159 | /// CgroupPath::new(SubsystemKind::HugeTlb, PathBuf::from("students/charlie"))); 160 | /// cgroup.create()?; 161 | /// 162 | /// let support_2mb = cgroup.size_supported(HugepageSize::Mb2); 163 | /// let support_1gb = cgroup.size_supported(HugepageSize::Gb1); 164 | /// # Ok(()) 165 | /// # } 166 | /// ``` 167 | pub fn size_supported(&self, size: HugepageSize) -> bool { 168 | self.file_exists(&format!("hugetlb.{}.{}", size, LIMIT_IN_BYTES)) 169 | && self.file_exists(&format!("hugetlb.{}.{}", size, USAGE_IN_BYTES)) 170 | && self.file_exists(&format!("hugetlb.{}.{}", size, MAX_USAGE_IN_BYTES)) 171 | && self.file_exists(&format!("hugetlb.{}.{}", size, FAILCNT)) 172 | } 173 | 174 | _gen_getter!( 175 | "the limit of hugepage TLB usage in bytes", 176 | limit_in_bytes, 177 | limit_in_pages 178 | ); 179 | 180 | with_doc! { concat!( 181 | gen_doc!( 182 | sets; 183 | "hugetlb..limit_in_bytes", 184 | "a limit of hugepage TLB usage" 185 | ), 186 | gen_doc!(see), 187 | gen_doc!(err_write; "hugetlb..limit_in_bytes"), 188 | gen_doc!( 189 | eg_write; hugetlb, 190 | set_limit, hugetlb::HugepageSize::Mb2, hugetlb::Limit::Pages(4) 191 | )), 192 | pub fn set_limit(&mut self, size: HugepageSize, limit: Limit) -> Result<()> { 193 | match limit { 194 | Limit::Bytes(bytes) => self.set_limit_in_bytes(size, bytes), 195 | Limit::Pages(pages) => self.set_limit_in_pages(size, pages), 196 | } 197 | } 198 | } 199 | 200 | /// Sets a limit of hugepage TLB usage in bytes. See [`set_limit`] method for more information. 201 | /// 202 | /// [`set_limit`]: #method.set_limit 203 | pub fn set_limit_in_bytes(&mut self, size: HugepageSize, bytes: u64) -> Result<()> { 204 | self.write_file(&format!("hugetlb.{}.{}", size, LIMIT_IN_BYTES), bytes) 205 | } 206 | 207 | /// Sets a limit of hugepage TLB usage in pages. See [`set_limit`] method for more information. 208 | /// 209 | /// [`set_limit`]: #method.set_limit 210 | pub fn set_limit_in_pages(&mut self, size: HugepageSize, pages: u64) -> Result<()> { 211 | self.set_limit_in_bytes(size, size.pages_to_bytes(pages)) 212 | } 213 | 214 | _gen_getter!( 215 | "the current usage of hugepage TLB in bytes", 216 | usage_in_bytes, 217 | usage_in_pages 218 | ); 219 | 220 | _gen_getter!( 221 | "the maximum recorded usage of hugepage TLB in bytes", 222 | max_usage_in_bytes, 223 | max_usage_in_pages 224 | ); 225 | 226 | with_doc! { concat!( 227 | gen_doc!( 228 | reads; 229 | "hugetlb..failcnt", 230 | "the number of allocation failure due to the limit," 231 | ), 232 | gen_doc!(see), 233 | gen_doc!(err_read; "hugetlb..failcnt"), 234 | gen_doc!(eg_read; hugetlb, failcnt, hugetlb::HugepageSize::Mb2)), 235 | pub fn failcnt(&self, size: HugepageSize) -> Result { 236 | self.open_file_read(&format!("hugetlb.{}.{}", size, FAILCNT)) 237 | .and_then(parse) 238 | } 239 | } 240 | } 241 | 242 | impl Into for Resources { 243 | fn into(self) -> v1::Resources { 244 | v1::Resources { 245 | hugetlb: self, 246 | ..v1::Resources::default() 247 | } 248 | } 249 | } 250 | 251 | impl HugepageSize { 252 | fn bytes_to_pages(self, bytes: u64) -> u64 { 253 | match self { 254 | Self::Kb8 => bytes / (8 << 10), 255 | Self::Kb64 => bytes / (64 << 10), 256 | Self::Kb256 => bytes / (256 << 10), 257 | 258 | Self::Mb1 => bytes / (1 << 20), 259 | Self::Mb2 => bytes / (2 << 20), 260 | Self::Mb4 => bytes / (4 << 20), 261 | Self::Mb16 => bytes / (16 << 20), 262 | Self::Mb256 => bytes / (256 << 20), 263 | 264 | Self::Gb1 => bytes / (1 << 30), 265 | } 266 | } 267 | 268 | fn pages_to_bytes(self, pages: u64) -> u64 { 269 | match self { 270 | Self::Kb8 => pages * (8 << 10), 271 | Self::Kb64 => pages * (64 << 10), 272 | Self::Kb256 => pages * (256 << 10), 273 | 274 | Self::Mb1 => pages * (1 << 20), 275 | Self::Mb2 => pages * (2 << 20), 276 | Self::Mb4 => pages * (4 << 20), 277 | Self::Mb16 => pages * (16 << 20), 278 | Self::Mb256 => pages * (256 << 20), 279 | 280 | Self::Gb1 => pages * (1 << 30), 281 | } 282 | } 283 | } 284 | 285 | impl fmt::Display for HugepageSize { 286 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 287 | write!( 288 | f, 289 | "{}", 290 | match self { 291 | Self::Kb8 => "8KB", 292 | Self::Kb64 => "64KB", 293 | Self::Kb256 => "256KB", 294 | Self::Mb1 => "1MB", 295 | Self::Mb2 => "2MB", 296 | Self::Mb4 => "4MB", 297 | Self::Mb16 => "16MB", 298 | Self::Mb256 => "256MB", 299 | Self::Gb1 => "1GB", 300 | } 301 | ) 302 | } 303 | } 304 | 305 | #[cfg(test)] 306 | mod tests { 307 | use super::*; 308 | use v1::SubsystemKind; 309 | use HugepageSize::*; 310 | 311 | const LIMIT_2MB_BYTES_DEFAULT: u64 = 0x7FFF_FFFF_FFE0_0000; 312 | const LIMIT_1GB_BYTES_DEFAULT: u64 = 0x7FFF_FFFF_C000_0000; 313 | 314 | #[test] 315 | #[rustfmt::skip] 316 | fn test_subsystem_create_file_exists() -> Result<()> { 317 | gen_subsystem_test!( 318 | HugeTlb, 319 | [ 320 | "2MB.limit_in_bytes", "2MB.usage_in_bytes", "2MB.max_usage_in_bytes", "2MB.failcnt", 321 | "1GB.limit_in_bytes", "1GB.usage_in_bytes", "1GB.max_usage_in_bytes", "1GB.failcnt" 322 | ] 323 | ) 324 | } 325 | 326 | #[test] 327 | fn test_subsystem_apply() -> Result<()> { 328 | let mut cgroup = Subsystem::new(CgroupPath::new( 329 | v1::SubsystemKind::HugeTlb, 330 | gen_cgroup_name!(), 331 | )); 332 | cgroup.create()?; 333 | 334 | cgroup.apply( 335 | &Resources { 336 | limits: [(Mb2, Limit::Pages(4)), (Gb1, Limit::Pages(2))] 337 | .iter() 338 | .copied() 339 | .collect(), 340 | } 341 | .into(), 342 | )?; 343 | 344 | assert_eq!(cgroup.limit_in_pages(HugepageSize::Mb2)?, 4); 345 | assert_eq!(cgroup.limit_in_pages(HugepageSize::Gb1)?, 2); 346 | 347 | cgroup.delete() 348 | } 349 | 350 | #[test] 351 | fn test_subsystem_size_supported() -> Result<()> { 352 | let mut cgroup = 353 | Subsystem::new(CgroupPath::new(SubsystemKind::HugeTlb, gen_cgroup_name!())); 354 | 355 | assert!(!cgroup.size_supported(Mb2)); 356 | assert!(!cgroup.size_supported(Gb1)); 357 | 358 | cgroup.create()?; 359 | 360 | assert!(cgroup.size_supported(Mb2)); 361 | assert!(cgroup.size_supported(Gb1)); 362 | 363 | cgroup.delete() 364 | } 365 | 366 | macro_rules! gen_test { 367 | ($field: ident, $mb2: expr, $gb1: expr) => {{ 368 | let mut cgroup = Subsystem::new(CgroupPath::new( 369 | v1::SubsystemKind::HugeTlb, 370 | gen_cgroup_name!(), 371 | )); 372 | 373 | cgroup.create()?; 374 | assert_eq!(cgroup.$field(Mb2)?, $mb2); 375 | assert_eq!(cgroup.$field(Gb1)?, $gb1); 376 | 377 | cgroup.delete() 378 | }}; 379 | 380 | ( 381 | $field: ident, 382 | $setter: ident, 383 | $dfl_mb2: expr, 384 | $dfl_gb1: expr, 385 | $val_mb2: expr, 386 | $val_gb1: expr 387 | ) => {{ 388 | let mut cgroup = Subsystem::new(CgroupPath::new( 389 | v1::SubsystemKind::HugeTlb, 390 | gen_cgroup_name!(), 391 | )); 392 | 393 | cgroup.create()?; 394 | assert_eq!(cgroup.$field(Mb2)?, $dfl_mb2); 395 | assert_eq!(cgroup.$field(Gb1)?, $dfl_gb1); 396 | 397 | cgroup.$setter(Mb2, $val_mb2)?; 398 | cgroup.$setter(Gb1, $val_gb1)?; 399 | 400 | assert_eq!(cgroup.$field(Mb2)?, $val_mb2); 401 | assert_eq!(cgroup.$field(Gb1)?, $val_gb1); 402 | 403 | cgroup.delete() 404 | }}; 405 | } 406 | 407 | #[test] 408 | fn test_subsystem_limit_in_bytes() -> Result<()> { 409 | gen_test!( 410 | limit_in_bytes, 411 | set_limit_in_bytes, 412 | LIMIT_2MB_BYTES_DEFAULT, 413 | LIMIT_1GB_BYTES_DEFAULT, 414 | 4 * (1 << 21), 415 | 2 * (1 << 30) 416 | ) 417 | } 418 | 419 | #[test] 420 | fn test_subsystem_limit_in_pages() -> Result<()> { 421 | gen_test!( 422 | limit_in_pages, 423 | set_limit_in_pages, 424 | LIMIT_2MB_BYTES_DEFAULT >> 21, 425 | LIMIT_1GB_BYTES_DEFAULT >> 30, 426 | 4, 427 | 2 428 | ) 429 | } 430 | 431 | #[test] 432 | fn test_subsystem_set_limit() -> Result<()> { 433 | let mut cgroup = 434 | Subsystem::new(CgroupPath::new(SubsystemKind::HugeTlb, gen_cgroup_name!())); 435 | cgroup.create()?; 436 | 437 | cgroup.set_limit(Mb2, Limit::Bytes(4 * (1 << 21)))?; 438 | assert_eq!(cgroup.limit_in_bytes(Mb2)?, 4 * (1 << 21)); 439 | 440 | cgroup.set_limit(Mb2, Limit::Pages(4))?; 441 | assert_eq!(cgroup.limit_in_pages(Mb2)?, 4); 442 | 443 | cgroup.set_limit(Gb1, Limit::Bytes(4 * (1 << 30)))?; 444 | assert_eq!(cgroup.limit_in_bytes(Gb1)?, 4 * (1 << 30)); 445 | 446 | cgroup.set_limit(Gb1, Limit::Pages(4))?; 447 | assert_eq!(cgroup.limit_in_pages(Gb1)?, 4); 448 | 449 | cgroup.delete() 450 | } 451 | 452 | #[test] 453 | fn test_subsystem_usage() -> Result<()> { 454 | gen_test!(usage_in_bytes, 0, 0)?; 455 | gen_test!(usage_in_pages, 0, 0) 456 | } 457 | 458 | #[test] 459 | fn test_subsystem_max_usage() -> Result<()> { 460 | gen_test!(max_usage_in_bytes, 0, 0)?; 461 | gen_test!(max_usage_in_pages, 0, 0) 462 | } 463 | 464 | #[test] 465 | fn test_subsystem_failcnt() -> Result<()> { 466 | gen_test!(failcnt, 0, 0) 467 | } 468 | 469 | #[test] 470 | fn test_bytes_to_pages() { 471 | #![allow(clippy::identity_op)] 472 | 473 | assert_eq!(Mb2.bytes_to_pages(1 * (1 << 20)), 0); 474 | assert_eq!(Mb2.bytes_to_pages(1 * (1 << 21)), 1); 475 | assert_eq!(Mb2.bytes_to_pages(4 * (1 << 21) - 1), 3); 476 | assert_eq!(Mb2.bytes_to_pages(4 * (1 << 21)), 4); 477 | assert_eq!(Mb2.bytes_to_pages(4 * (1 << 21) + 1), 4); 478 | 479 | assert_eq!(Gb1.bytes_to_pages(1 * (1 << 29)), 0); 480 | assert_eq!(Gb1.bytes_to_pages(1 * (1 << 30)), 1); 481 | assert_eq!(Gb1.bytes_to_pages(4 * (1 << 30) - 1), 3); 482 | assert_eq!(Gb1.bytes_to_pages(4 * (1 << 30)), 4); 483 | assert_eq!(Gb1.bytes_to_pages(4 * (1 << 30) + 1), 4); 484 | } 485 | 486 | #[test] 487 | fn test_pages_to_bytes() { 488 | #![allow(clippy::identity_op)] 489 | 490 | assert_eq!(Mb2.pages_to_bytes(0), 0); 491 | assert_eq!(Mb2.pages_to_bytes(1), 1 * (1 << 21)); 492 | assert_eq!(Mb2.pages_to_bytes(4), 4 * (1 << 21)); 493 | 494 | assert_eq!(Gb1.pages_to_bytes(0), 0); 495 | assert_eq!(Gb1.pages_to_bytes(1), 1 * (1 << 30)); 496 | assert_eq!(Gb1.pages_to_bytes(4), 4 * (1 << 30)); 497 | } 498 | } 499 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg(target_os = "linux")] 2 | #![warn( 3 | future_incompatible, 4 | missing_docs, 5 | missing_debug_implementations, 6 | nonstandard_style, 7 | rust_2018_idioms, 8 | trivial_casts, 9 | trivial_numeric_casts, 10 | unused 11 | )] 12 | // Clippy's suggestion causes many compile error 13 | #![allow(clippy::string_lit_as_bytes)] 14 | #![doc(html_root_url = "https://docs.rs/controlgroup/0.3.0")] 15 | 16 | //! Native Rust crate for cgroup operations. 17 | //! 18 | //! Currently this crate supports only cgroup v1 hierarchy, implemented in [`v1`] module. 19 | //! 20 | //! ## Examples for v1 hierarchy 21 | //! 22 | //! ### Create a cgroup controlled by the CPU subsystem 23 | //! 24 | //! ```no_run 25 | //! # fn main() -> controlgroup::Result<()> { 26 | //! use std::path::PathBuf; 27 | //! use controlgroup::{Pid, v1::{cpu, Cgroup, CgroupPath, SubsystemKind, Resources}}; 28 | //! 29 | //! // Define and create a new cgroup controlled by the CPU subsystem. 30 | //! let mut cgroup = cpu::Subsystem::new( 31 | //! CgroupPath::new(SubsystemKind::Cpu, PathBuf::from("students/charlie"))); 32 | //! cgroup.create()?; 33 | //! 34 | //! // Attach the self process to the cgroup. 35 | //! let pid = Pid::from(std::process::id()); 36 | //! cgroup.add_task(pid)?; 37 | //! 38 | //! // Define resource limits and constraints for this cgroup. 39 | //! // Here we just use the default for an example. 40 | //! let resources = Resources::default(); 41 | //! 42 | //! // Apply the resource limits. 43 | //! cgroup.apply(&resources)?; 44 | //! 45 | //! // Low-level file operations are also supported. 46 | //! let stat_file = cgroup.open_file_read("cpu.stat")?; 47 | //! 48 | //! // Do something ... 49 | //! 50 | //! // Now, remove self process from the cgroup. 51 | //! cgroup.remove_task(pid)?; 52 | //! 53 | //! // ... and delete the cgroup. 54 | //! cgroup.delete()?; 55 | //! 56 | //! // Note that subsystem handlers does not implement `Drop` and therefore when the 57 | //! // handler is dropped, the cgroup will stay around. 58 | //! # Ok(()) 59 | //! # } 60 | //! ``` 61 | //! 62 | //! ### Create a set of cgroups controlled by multiple subsystems 63 | //! 64 | //! [`v1::Builder`] provides a way to configure cgroups in the builder pattern. 65 | //! 66 | //! ```no_run 67 | //! # fn main() -> controlgroup::Result<()> { 68 | //! use std::path::PathBuf; 69 | //! use controlgroup::{ 70 | //! Max, 71 | //! v1::{devices, hugetlb::{self, HugepageSize}, net_cls, rdma, Builder, SubsystemKind}, 72 | //! }; 73 | //! 74 | //! let mut cgroups = 75 | //! // Start building a (set of) cgroup(s). 76 | //! Builder::new(PathBuf::from("students/charlie")) 77 | //! // Start configuring the CPU resource limits. 78 | //! .cpu() 79 | //! .shares(1000) 80 | //! .cfs_quota_us(500 * 1000) 81 | //! .cfs_period_us(1000 * 1000) 82 | //! // Finish configuring the CPU resource limits. 83 | //! .done() 84 | //! // Start configuring the cpuset resource limits. 85 | //! .cpuset() 86 | //! .cpus([0].iter().copied().collect()) 87 | //! .mems([0].iter().copied().collect()) 88 | //! .memory_migrate(true) 89 | //! .done() 90 | //! .memory() 91 | //! .limit_in_bytes(4 * (1 << 30)) 92 | //! .soft_limit_in_bytes(3 * (1 << 30)) 93 | //! .use_hierarchy(true) 94 | //! .done() 95 | //! .hugetlb() 96 | //! .limits( 97 | //! [ 98 | //! (HugepageSize::Mb2, hugetlb::Limit::Pages(4)), 99 | //! (HugepageSize::Gb1, hugetlb::Limit::Pages(2)), 100 | //! ].iter().copied() 101 | //! ) 102 | //! .done() 103 | //! .devices() 104 | //! .deny(vec!["a *:* rwm".parse::().unwrap()]) 105 | //! .allow(vec!["c 1:3 mr".parse::().unwrap()]) 106 | //! .done() 107 | //! .blkio() 108 | //! .weight(1000) 109 | //! .weight_device([([8, 0].into(), 100)].iter().copied()) 110 | //! .read_bps_device([([8, 0].into(), 10 * (1 << 20))].iter().copied()) 111 | //! .write_iops_device([([8, 0].into(), 100)].iter().copied()) 112 | //! .done() 113 | //! .rdma() 114 | //! .max( 115 | //! [( 116 | //! "mlx4_0".to_string(), 117 | //! rdma::Limit { 118 | //! hca_handle: 2.into(), 119 | //! hca_object: Max::Max, 120 | //! }, 121 | //! )].iter().cloned(), 122 | //! ) 123 | //! .done() 124 | //! .net_prio() 125 | //! .ifpriomap( 126 | //! [("lo".to_string(), 0), ("wlp1s0".to_string(), 1)].iter().cloned(), 127 | //! ) 128 | //! .done() 129 | //! .net_cls() 130 | //! .classid([0x10, 0x1].into()) 131 | //! .done() 132 | //! .pids() 133 | //! .max(42.into()) 134 | //! .done() 135 | //! .freezer() 136 | //! // Tasks in this cgroup will be frozen. 137 | //! .freeze() 138 | //! .done() 139 | //! // Enable CPU accounting for this cgroup. 140 | //! // Cpuacct subsystem has no parameter, so this method does not return a subsystem builder, 141 | //! // just enables the accounting. 142 | //! .cpuacct() 143 | //! // Enable monitoring this cgroup via `perf` tool. 144 | //! // Like `cpuacct()` method, this method does not return a subsystem builder. 145 | //! .perf_event() 146 | //! // Skip creating directories for Cpuacct subsystem and net_cls subsystem. 147 | //! // This is useful when some subsystems share hierarchy with others. 148 | //! .skip_create(vec![SubsystemKind::Cpuacct, SubsystemKind::NetCls]) 149 | //! // Actually build cgroups with the configuration. 150 | //! .build()?; 151 | //! 152 | //! let pid = std::process::id().into(); 153 | //! cgroups.add_task(pid)?; 154 | //! 155 | //! // Do something ... 156 | //! 157 | //! cgroups.remove_task(pid)?; 158 | //! cgroups.delete()?; 159 | //! # Ok(()) 160 | //! # } 161 | //! ``` 162 | //! 163 | //! ### Spawn a process within one or more cgroups 164 | //! 165 | //! [`v1::CommandExt`] extends the [`std::process::Command`] builder to attach a command process to 166 | //! one or more cgroups on start. 167 | //! 168 | //! ```no_run 169 | //! # fn main() -> controlgroup::Result<()> { 170 | //! use std::path::PathBuf; 171 | //! use controlgroup::v1::{cpu, Cgroup, CgroupPath, SubsystemKind}; 172 | //! // Import extension trait 173 | //! use controlgroup::v1::CommandExt as _; 174 | //! 175 | //! let mut cgroup = cpu::Subsystem::new( 176 | //! CgroupPath::new(SubsystemKind::Cpu, PathBuf::from("students/charlie"))); 177 | //! cgroup.create()?; 178 | //! 179 | //! let mut child = std::process::Command::new("sleep") 180 | //! .arg("1") 181 | //! // Attach this command process to a cgroup on start 182 | //! .cgroup(&mut cgroup) 183 | //! // This process will run within the cgroup 184 | //! .spawn() 185 | //! .unwrap(); 186 | //! 187 | //! println!("{:?}", cgroup.stat()?); 188 | //! 189 | //! child.wait().unwrap(); 190 | //! cgroup.delete()?; 191 | //! # Ok(()) 192 | //! # } 193 | //! ``` 194 | //! 195 | //! [`v1`]: v1/index.html 196 | //! [`v1::Builder`]: v1/builder/struct.Builder.html 197 | //! [`v1::CommandExt`]: v1/trait.CommandExt.html 198 | //! [`std::process::Command`]: https://doc.rust-lang.org/std/process/struct.Command.html 199 | 200 | #[macro_use] 201 | mod macros; 202 | mod error; 203 | mod parse; 204 | pub mod v1; 205 | 206 | use std::{ 207 | fmt::{self, Display}, 208 | str::FromStr, 209 | }; 210 | 211 | pub use error::{Error, ErrorKind, Result}; 212 | 213 | /// PID or thread ID for attaching a task to a cgroup. 214 | /// 215 | /// `Pid` can be converted from [`u32`] and [`&std::process::Child`]. 216 | /// 217 | /// ``` 218 | /// use controlgroup::Pid; 219 | /// 220 | /// let pid = Pid::from(42_u32); 221 | /// 222 | /// let child = std::process::Command::new("sleep").arg("1").spawn().unwrap(); 223 | /// let pid = Pid::from(&child); 224 | /// ``` 225 | /// 226 | /// [`u32`]: https://doc.rust-lang.org/std/primitive.u32.html 227 | /// [`&std::process::Child`]: https://doc.rust-lang.org/std/process/struct.Child.html 228 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 229 | pub struct Pid(u32); // Max PID is 2^15 on 32-bit systems, 2^22 on 64-bit systems 230 | // FIXME: ^ also true for thread IDs? 231 | 232 | impl From for Pid { 233 | fn from(pid: u32) -> Self { 234 | Self(pid) 235 | } 236 | } 237 | 238 | impl From<&std::process::Child> for Pid { 239 | fn from(child: &std::process::Child) -> Self { 240 | Self(child.id()) 241 | } 242 | } 243 | 244 | impl Into for Pid { 245 | fn into(self) -> u32 { 246 | self.0 247 | } 248 | } 249 | 250 | impl FromStr for Pid { 251 | type Err = Error; 252 | 253 | fn from_str(s: &str) -> Result { 254 | let n = s.parse::()?; 255 | Ok(Self(n)) 256 | } 257 | } 258 | 259 | impl Display for Pid { 260 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 261 | write!(f, "{}", self.0) 262 | } 263 | } 264 | 265 | /// Limits the maximum number or amount of a resource, or not limits. 266 | /// 267 | /// `Max` implements [`FromStr`] and [`Display`]. You can convert a string into a `Max` and vice 268 | /// versa. [`parse`] returns an error with kind [`ErrorKind::Parse`] if failed. 269 | /// 270 | /// ``` 271 | /// use controlgroup::Max; 272 | /// 273 | /// let max = "max".parse::().unwrap(); 274 | /// assert_eq!(max, Max::Max); 275 | /// 276 | /// let num = "42".parse::().unwrap(); 277 | /// assert_eq!(num, Max::Limit(42)); 278 | /// 279 | /// assert_eq!(Max::Max.to_string(), "max"); 280 | /// assert_eq!(Max::Limit(42).to_string(), "42"); 281 | /// ``` 282 | /// 283 | /// `Max` also implements [`Default`], which yields `Max::Max`. 284 | /// 285 | /// ``` 286 | /// use controlgroup::Max; 287 | /// 288 | /// assert_eq!(Max::default(), Max::Max); 289 | /// ``` 290 | /// 291 | /// [`FromStr`]: https://doc.rust-lang.org/std/str/trait.FromStr.html 292 | /// [`Display`]: https://doc.rust-lang.org/std/fmt/trait.Display.html 293 | /// [`parse`]: https://doc.rust-lang.org/std/primitive.str.html#method.parse 294 | /// [`ErrorKind::Parse`]: enum.ErrorKind.html#variant.Parse 295 | /// 296 | /// [`Default`]: https://doc.rust-lang.org/std/default/trait.Default.html 297 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 298 | pub enum Max { 299 | /// Not limit the maximum number or amount of a resource. 300 | Max, 301 | /// Limits the maximum number or amount of a resource to this value. 302 | Limit(u32), // only `u32` is used for the integer type of `Max` in this crate 303 | } 304 | 305 | impl Default for Max { 306 | fn default() -> Self { 307 | Self::Max 308 | } 309 | } 310 | 311 | impl From for Max { 312 | fn from(n: u32) -> Self { 313 | Self::Limit(n) 314 | } 315 | } 316 | 317 | impl FromStr for Max { 318 | type Err = Error; 319 | 320 | fn from_str(s: &str) -> Result { 321 | match s { 322 | "max" => Ok(Self::Max), 323 | n => Ok(Self::Limit(n.parse()?)), 324 | } 325 | } 326 | } 327 | 328 | impl Display for Max { 329 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 330 | match self { 331 | Self::Max => write!(f, "max"), 332 | Self::Limit(n) => write!(f, "{}", n), 333 | } 334 | } 335 | } 336 | 337 | /// Linux device number. 338 | /// 339 | /// `Device` implements [`FromStr`] and [`Display`]. You can convert a string into a `Device` and 340 | /// vice versa. [`parse`] returns an error with kind [`ErrorKind::Parse`] if failed. 341 | /// 342 | /// ``` 343 | /// use controlgroup::{Device, DeviceNumber}; 344 | /// 345 | /// let dev = "8:16".parse::().unwrap(); 346 | /// assert_eq!(dev, Device { major: DeviceNumber::Number(8), minor: DeviceNumber::Number(16) }); 347 | /// 348 | /// let dev = "8:*".parse::().unwrap(); 349 | /// assert_eq!(dev, Device { major: DeviceNumber::Number(8), minor: DeviceNumber::Any }); 350 | /// ``` 351 | /// 352 | /// ``` 353 | /// use controlgroup::{Device, DeviceNumber}; 354 | /// 355 | /// let dev = Device { major: DeviceNumber::Number(8), minor: DeviceNumber::Number(16) }; 356 | /// assert_eq!(dev.to_string(), "8:16"); 357 | /// 358 | /// let dev = Device { major: DeviceNumber::Number(8), minor: DeviceNumber::Any }; 359 | /// assert_eq!(dev.to_string(), "8:*"); 360 | /// ``` 361 | /// 362 | /// `Device` also implements [`From`]`<[u16; 2]>` and `From<[DeviceNumber; 2]>`. 363 | /// 364 | /// ``` 365 | /// use controlgroup::{Device, DeviceNumber}; 366 | /// 367 | /// assert_eq!( 368 | /// Device::from([8, 16]), 369 | /// Device { major: DeviceNumber::Number(8), minor: DeviceNumber::Number(16) } 370 | /// ); 371 | /// 372 | /// assert_eq!( 373 | /// Device::from([DeviceNumber::Number(1), DeviceNumber::Any]), 374 | /// Device { major: DeviceNumber::Number(1), minor: DeviceNumber::Any } 375 | /// ); 376 | /// ``` 377 | /// 378 | /// [`FromStr`]: https://doc.rust-lang.org/std/str/trait.FromStr.html 379 | /// [`Display`]: https://doc.rust-lang.org/std/fmt/trait.Display.html 380 | /// [`parse`]: https://doc.rust-lang.org/std/primitive.str.html#method.parse 381 | /// [`ErrorKind::Parse`]: enum.ErrorKind.html#variant.Parse 382 | /// 383 | /// [`From`]: https://doc.rust-lang.org/std/convert/trait.From.html 384 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 385 | pub struct Device { 386 | /// Major number. 387 | pub major: DeviceNumber, 388 | /// Minor number. 389 | pub minor: DeviceNumber, 390 | } 391 | 392 | impl From<[u16; 2]> for Device { 393 | fn from(n: [u16; 2]) -> Self { 394 | Self { 395 | major: n[0].into(), 396 | minor: n[1].into(), 397 | } 398 | } 399 | } 400 | 401 | impl From<[DeviceNumber; 2]> for Device { 402 | fn from(n: [DeviceNumber; 2]) -> Self { 403 | Self { 404 | major: n[0], 405 | minor: n[1], 406 | } 407 | } 408 | } 409 | 410 | impl FromStr for Device { 411 | type Err = Error; 412 | 413 | fn from_str(s: &str) -> Result { 414 | let mut parts = s.split(':'); 415 | let major = parse::parse_next(&mut parts)?; 416 | let minor = parse::parse_next(&mut parts)?; 417 | 418 | Ok(Device { major, minor }) 419 | } 420 | } 421 | 422 | impl Display for Device { 423 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 424 | write!(f, "{}:{}", self.major, self.minor) 425 | } 426 | } 427 | 428 | /// Device major/minor number. 429 | /// 430 | /// `DeviceNumber` implements [`FromStr`] and [`Display`]. You can convert a string into a 431 | /// `DeviceNumber` and vice versa. [`parse`] returns an error with kind [`ErrorKind::Parse`] if 432 | /// failed. 433 | /// 434 | /// ``` 435 | /// use controlgroup::DeviceNumber; 436 | /// 437 | /// let n = "8".parse::().unwrap(); 438 | /// assert_eq!(n, DeviceNumber::Number(8)); 439 | /// 440 | /// let n = "*".parse::().unwrap(); 441 | /// assert_eq!(n, DeviceNumber::Any); 442 | /// ``` 443 | /// 444 | /// ``` 445 | /// use controlgroup::DeviceNumber; 446 | /// 447 | /// assert_eq!(DeviceNumber::Number(8).to_string(), "8"); 448 | /// assert_eq!(DeviceNumber::Any.to_string(), "*"); 449 | /// ``` 450 | /// 451 | /// `DeviceNumber` also implements [`From`]``, which results in `DeviceNumber::Number`. 452 | /// 453 | /// ``` 454 | /// use controlgroup::DeviceNumber; 455 | /// 456 | /// assert_eq!(DeviceNumber::from(8), DeviceNumber::Number(8)); 457 | /// ``` 458 | /// 459 | /// [`FromStr`]: https://doc.rust-lang.org/std/str/trait.FromStr.html 460 | /// [`Display`]: https://doc.rust-lang.org/std/fmt/trait.Display.html 461 | /// [`parse`]: https://doc.rust-lang.org/std/primitive.str.html#method.parse 462 | /// [`ErrorKind::Parse`]: enum.ErrorKind.html#variant.Parse 463 | /// 464 | /// [`From`]: https://doc.rust-lang.org/std/convert/trait.From.html 465 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 466 | pub enum DeviceNumber { 467 | /// Any number matches. 468 | Any, 469 | /// Specific number. 470 | Number(u16), 471 | } 472 | 473 | impl From for DeviceNumber { 474 | fn from(n: u16) -> Self { 475 | Self::Number(n) 476 | } 477 | } 478 | 479 | impl FromStr for DeviceNumber { 480 | type Err = Error; 481 | 482 | fn from_str(s: &str) -> Result { 483 | if s == "*" { 484 | Ok(Self::Any) 485 | } else { 486 | let n = s.parse::()?; 487 | Ok(Self::Number(n)) 488 | } 489 | } 490 | } 491 | 492 | impl Display for DeviceNumber { 493 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 494 | use fmt::Write; 495 | 496 | match self { 497 | Self::Any => f.write_char('*'), 498 | Self::Number(n) => write!(f, "{}", n), 499 | } 500 | } 501 | } 502 | 503 | /// Yields a pair of a references, each of which points to a key and a value. 504 | /// 505 | /// This trait is used to convert a reference to a pair `&(K, V)` into a pair of references 506 | /// `(&K, &V)`. 507 | pub trait RefKv { 508 | /// Yields a pair of a references, each of which points to a key and a value. 509 | fn ref_kv(&self) -> (&K, &V); 510 | } 511 | 512 | impl RefKv for (&K, &V) { 513 | fn ref_kv(&self) -> (&K, &V) { 514 | *self 515 | } 516 | } 517 | 518 | impl RefKv for &(K, V) { 519 | fn ref_kv(&self) -> (&K, &V) { 520 | (&self.0, &self.1) 521 | } 522 | } 523 | 524 | // Consume CPU time on the all logical cores until a condition holds. Panics if the condition does 525 | // not hold in the given timeout. 526 | // 527 | // FIXME: consume system time 528 | #[cfg(test)] 529 | pub(crate) fn consume_cpu_until(condition: impl Fn() -> bool, timeout_secs: u64) { 530 | use std::{ 531 | sync::{ 532 | atomic::{AtomicBool, Ordering}, 533 | Arc, 534 | }, 535 | thread, time, 536 | }; 537 | 538 | let finished = Arc::new(AtomicBool::new(false)); 539 | 540 | let handlers = (0..(num_cpus::get() - 1)) 541 | .map(|_| { 542 | let fin = finished.clone(); 543 | thread::spawn(move || { 544 | while !fin.load(Ordering::Relaxed) { 545 | // spin 546 | } 547 | }) 548 | }) 549 | .collect::>(); 550 | 551 | let start = time::Instant::now(); 552 | while start.elapsed() < time::Duration::from_secs(timeout_secs) { 553 | if condition() { 554 | finished.store(true, Ordering::Relaxed); 555 | for handler in handlers { 556 | handler.join().expect("Failed to join a thread"); 557 | } 558 | 559 | return; 560 | } 561 | 562 | // spin 563 | } 564 | 565 | panic!("consume_cpu_until timeout") 566 | } 567 | -------------------------------------------------------------------------------- /src/v1/unified_repr.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, path::PathBuf}; 2 | 3 | use crate::{ 4 | v1::{self, Cgroup, CgroupPath, SubsystemKind}, 5 | Pid, Result, 6 | }; 7 | 8 | macro_rules! gen_unified_repr { 9 | ( $( ($subsystem: ident, $subsystem_mut: ident, $kind: ident, $name: literal) ),* $(, )? ) => { 10 | 11 | use v1::{$( $subsystem ),*}; 12 | 13 | /// Unified representation of a set of cgroups sharing the same name. 14 | /// 15 | /// In cgroup v1, a system has multiple directory hierarchies for different sets of subsystems 16 | /// (typically one subsystem). Each cgroup belongs to a hierarchy, and subsystems attached to that 17 | /// hierarchy control the resources of that cgroup. 18 | /// 19 | /// In cgroup v2 (not yet fully implemented in the Linux kernel), on the other hand, a system has 20 | /// only a single unified hierarchy, and subsystems are differently enabled for each cgroup. This 21 | /// design is suitable for cases such as containers, where each cgroup should be controlled by 22 | /// multiple subsystems simultaneously. 23 | /// 24 | /// `UnifiedRepr` provides an access to a set of cgroups in the v1 hierarchies as if it is in the v2 25 | /// hierarchy. A unified representation of a set of cgroups appears to have multiple subsystems, 26 | /// and the set is controlled by the subsystems simultaneously by calling a single method of 27 | /// `UnifiedRepr`. 28 | /// 29 | /// For more information about cgroup v2, see the kernel's documentation 30 | /// [Documentation/cgroup-v2.txt](https://www.kernel.org/doc/Documentation/cgroup-v2.txt). 31 | /// 32 | /// # Examples 33 | /// 34 | /// ```no_run 35 | /// # fn main() -> controlgroup::Result<()> { 36 | /// use std::path::PathBuf; 37 | /// use controlgroup::{Pid, v1::{Resources, UnifiedRepr}}; 38 | /// 39 | /// // Define and create a new unified representation of a set of cgroups. 40 | /// let mut cgroups = UnifiedRepr::new(PathBuf::from("students/charlie")); 41 | /// cgroups.create()?; 42 | /// 43 | /// // Attach the self process to the cgroup set. 44 | /// let pid = Pid::from(std::process::id()); 45 | /// cgroups.add_task(pid)?; 46 | /// 47 | /// // Define resource limits and constraints for this cgroup set. 48 | /// // Here we just use the default for an example. 49 | /// let resources = Resources::default(); 50 | /// 51 | /// // Apply the resource limits. 52 | /// cgroups.apply(&resources)?; 53 | /// 54 | /// // Do something ... 55 | /// 56 | /// // Now, remove self from the cgroup set. 57 | /// cgroups.remove_task(pid)?; 58 | /// 59 | /// // ... and delete the cgroup set. 60 | /// cgroups.delete()?; 61 | /// # Ok(()) 62 | /// # } 63 | /// ``` 64 | #[derive(Debug)] 65 | pub struct UnifiedRepr { 66 | $( $subsystem: Option> ),* 67 | } 68 | 69 | #[derive(Debug)] 70 | struct Subsys { 71 | subsystem: T, 72 | create: bool, 73 | } 74 | 75 | impl UnifiedRepr { 76 | /// Defines a new unified representation of a set of cgroups with all subsystems available in 77 | /// this crate. 78 | /// 79 | /// For the directory name of the each subsystem, the standard name (e.g. `SubsystemKind::Cpu` 80 | /// => `cpu`) are used. 81 | /// 82 | /// See [`SubsystemKind`] for the available subsystems. 83 | /// 84 | /// # Examples 85 | /// 86 | /// ``` 87 | /// use std::path::PathBuf; 88 | /// use controlgroup::v1::UnifiedRepr; 89 | /// 90 | /// let cgroups = UnifiedRepr::new(PathBuf::from("students/charlie")); 91 | /// ``` 92 | /// 93 | /// [`SubsystemKind`]: enum.SubsystemKind.html 94 | pub fn new(name: PathBuf) -> Self { 95 | Self::with_subsystems(name, &[$(SubsystemKind::$kind),*]) 96 | } 97 | 98 | /// Defines a new unified representation of a set of cgroups with the given subsystem kinds. 99 | /// 100 | /// For the directory name of the each subsystem, the standard name (e.g. `SubsystemKind::Cpu` 101 | /// => `cpu`) are used. 102 | /// 103 | /// # Examples 104 | /// 105 | /// ``` 106 | /// use std::path::PathBuf; 107 | /// use controlgroup::v1::{SubsystemKind, UnifiedRepr}; 108 | /// 109 | /// let cgroups = UnifiedRepr::with_subsystems( 110 | /// PathBuf::from("students/charlie"), &[SubsystemKind::Cpu]); 111 | /// ``` 112 | pub fn with_subsystems(name: PathBuf, subsystems: &[SubsystemKind]) -> Self { 113 | Self::with_custom_name_subsystems( 114 | subsystems.iter().map(|k| (*k, CgroupPath::new(*k, name.clone()))) 115 | ) 116 | } 117 | 118 | /// Defines a new unified representation of a set of cgroups with the given subsystem kinds and 119 | /// their paths. 120 | /// 121 | /// # Examples 122 | /// 123 | /// ``` 124 | /// use std::path::PathBuf; 125 | /// use controlgroup::v1::{CgroupPath, SubsystemKind, UnifiedRepr}; 126 | /// 127 | /// let name = PathBuf::from("students/charlie"); 128 | /// let cgroups = UnifiedRepr::with_custom_name_subsystems( 129 | /// [ 130 | /// (SubsystemKind::Cpu, CgroupPath::new(SubsystemKind::Cpu, name.clone())), 131 | /// (SubsystemKind::Cpuset, CgroupPath::with_subsystem_name("custom", name)), 132 | /// ].iter().cloned() 133 | /// ); 134 | /// ``` 135 | pub fn with_custom_name_subsystems( 136 | subsystems: impl IntoIterator, 137 | ) -> Self { 138 | $( let mut $subsystem = None; )* 139 | for (kind, path) in subsystems { 140 | match kind { 141 | $( 142 | SubsystemKind::$kind => { 143 | $subsystem = Some(Subsys { 144 | subsystem: $subsystem::Subsystem::new(path), 145 | create: true, 146 | }); 147 | } 148 | )* 149 | } 150 | } 151 | Self { $( $subsystem ),* } 152 | } 153 | 154 | /// Returns whether a subsystem is supported by this unified representation, i.e. included in 155 | /// this set of cgroups. 156 | /// 157 | /// # Examples 158 | /// 159 | /// ``` 160 | /// use std::path::PathBuf; 161 | /// use controlgroup::v1::{SubsystemKind, UnifiedRepr}; 162 | /// 163 | /// let cgroups = UnifiedRepr::with_subsystems( 164 | /// PathBuf::from("students/charlie"), &[SubsystemKind::Cpu]); 165 | /// 166 | /// assert!(cgroups.supports(SubsystemKind::Cpu)); 167 | /// assert!(!cgroups.supports(SubsystemKind::Cpuset)); 168 | /// ``` 169 | pub fn supports(&self, subsystem_kind: SubsystemKind) -> bool { 170 | match subsystem_kind { 171 | $(SubsystemKind::$kind => self.$subsystem.is_some()),* 172 | } 173 | } 174 | 175 | /// Skips creating and deleting the directories for some subsystems. 176 | /// 177 | /// This method is useful when multiple subsystems share the same hierarchy (including via 178 | /// symbolic links), and thus [`create`]/[`delete`] method tries to create/delete the same 179 | /// directory multiple times. 180 | /// 181 | /// [`create`]: #method.create 182 | /// [`delete`]: #method.delete 183 | /// 184 | /// # Examples 185 | /// 186 | /// ```no_run 187 | /// # fn main() -> controlgroup::Result<()> { 188 | /// use std::path::PathBuf; 189 | /// use controlgroup::v1::{CgroupPath, SubsystemKind, UnifiedRepr}; 190 | /// 191 | /// let mut cgroups = UnifiedRepr::with_subsystems( 192 | /// PathBuf::from("students/charlie"), &[SubsystemKind::Cpu, SubsystemKind::Cpuset]); 193 | /// 194 | /// cgroups.skip_create(&[SubsystemKind::Cpuset]); 195 | /// 196 | /// cgroups.create()?; // Creates only a directory for the CPU subsystem 197 | /// # Ok(()) 198 | /// # } 199 | /// ``` 200 | pub fn skip_create(&mut self, skip_subsystems: &[SubsystemKind]) { 201 | for kind in skip_subsystems { 202 | match kind { 203 | $( 204 | SubsystemKind::$kind => { 205 | if let Some(ref mut s) = self.$subsystem { 206 | s.create = false; 207 | } 208 | } 209 | )* 210 | } 211 | } 212 | } 213 | 214 | /// Creates new directories for each cgroup of the all supported subsystems except for ones that 215 | /// was skipped by [`skip_create`] method. 216 | /// 217 | /// See [`Cgroup::create`] for more information. 218 | /// 219 | /// [`skip_create`]: #method.skip_create 220 | /// [`Cgroup::create`]: trait.Cgroup.html#method.create 221 | pub fn create(&mut self) -> Result<()> { 222 | $( 223 | if let Some(ref mut s) = self.$subsystem { 224 | if s.create { 225 | s.subsystem.create()?; 226 | } 227 | } 228 | )* 229 | Ok(()) 230 | } 231 | 232 | /// Applies resource limits and constraints to all cgroups of the all supported subsystems. 233 | pub fn apply(&mut self, resources: &v1::Resources) -> Result<()> { 234 | $( 235 | if let Some(ref mut s) = self.$subsystem { 236 | s.subsystem.apply(&resources)?; 237 | } 238 | )* 239 | Ok(()) 240 | } 241 | 242 | /// Deletes directories for each cgroup of the all supported subsystems except for ones that 243 | /// was skipped by [`skip_create`] method. 244 | /// 245 | /// See [`Cgroup::delete`] for more information. 246 | /// 247 | /// [`skip_create`]: #method.skip_create 248 | /// [`Cgroup::delete`]: trait.Cgroup.html#method.delete 249 | pub fn delete(&mut self) -> Result<()> { 250 | $( 251 | if let Some(ref mut s) = self.$subsystem { 252 | if s.create { 253 | s.subsystem.delete()?; 254 | } 255 | } 256 | )* 257 | Ok(()) 258 | } 259 | 260 | /// Reads a list of tasks attached to each cgroup of the all supported subsystems. 261 | /// 262 | /// See [`Cgroup::tasks`] for more information. 263 | /// 264 | /// [`Cgroup::tasks`]: trait.Cgroup.html#method.tasks 265 | pub fn tasks(&self) -> Result>> { 266 | let mut tasks = HashMap::new(); 267 | $( 268 | if let Some(ref s) = self.$subsystem { 269 | tasks.insert(SubsystemKind::$kind, s.subsystem.tasks()?); 270 | } 271 | )* 272 | Ok(tasks) 273 | } 274 | 275 | /// Attaches a task to all cgroups of the all supported subsystems. 276 | /// 277 | /// See [`Cgroup::add_task`] for more information. 278 | /// 279 | /// [`Cgroup::add_task`]: trait.Cgroup.html#method.add_task 280 | pub fn add_task(&mut self, pid: Pid) -> Result<()> { 281 | $( 282 | if let Some(ref mut s) = self.$subsystem { 283 | s.subsystem.add_task(pid)?; 284 | } 285 | )* 286 | Ok(()) 287 | } 288 | 289 | /// Removes a task from all cgroups of the all supported subsystems. 290 | /// 291 | /// See [`Cgroup::remove_task`] for more information. 292 | /// 293 | /// [`Cgroup::remove_task`]: trait.Cgroup.html#method.remove_task 294 | pub fn remove_task(&mut self, pid: Pid) -> Result<()> { 295 | $( 296 | if let Some(ref mut s) = self.$subsystem { 297 | s.subsystem.remove_task(pid)?; 298 | } 299 | )* 300 | Ok(()) 301 | } 302 | 303 | /// Reads a list of processes attached to each cgroup of the all supported subsystems. 304 | /// 305 | /// See [`Cgroup::procs`] for more information. 306 | /// 307 | /// [`Cgroup::procs`]: trait.Cgroup.html#method.procs 308 | pub fn procs(&self) -> Result>> { 309 | let mut procs = HashMap::new(); 310 | $( 311 | if let Some(ref s) = self.$subsystem { 312 | procs.insert(SubsystemKind::$kind, s.subsystem.procs()?); 313 | } 314 | )* 315 | Ok(procs) 316 | } 317 | 318 | /// Attaches a process to all cgroups of the all supported subsystems. 319 | /// 320 | /// See [`Cgroup::add_proc`] for more information. 321 | /// 322 | /// [`Cgroup::add_proc`]: trait.Cgroup.html#method.add_proc 323 | pub fn add_proc(&mut self, pid: Pid) -> Result<()> { 324 | $( 325 | if let Some(ref mut s) = self.$subsystem { 326 | s.subsystem.add_proc(pid)?; 327 | } 328 | )* 329 | Ok(()) 330 | } 331 | 332 | /// Removes a process from all cgroups of the all supported subsystems. 333 | /// 334 | /// See [`Cgroup::remove_proc`] for more information. 335 | /// 336 | /// [`Cgroup::remove_proc`]: trait.Cgroup.html#method.remove_proc 337 | pub fn remove_proc(&mut self, pid: Pid) -> Result<()> { 338 | $( 339 | if let Some(ref mut s) = self.$subsystem { 340 | s.subsystem.remove_proc(pid)?; 341 | } 342 | )* 343 | Ok(()) 344 | } 345 | 346 | $( 347 | with_doc!( 348 | concat!("Returns a reference to the ", $name, " subsystem."), 349 | pub fn $subsystem(&self) -> Option<&$subsystem::Subsystem> { 350 | self.$subsystem.as_ref().map(|s| &s.subsystem) 351 | } 352 | ); 353 | 354 | with_doc!( 355 | concat!("Returns a mutable reference to the ", $name, " subsystem."), 356 | pub fn $subsystem_mut(&mut self) -> Option<&mut $subsystem::Subsystem> { 357 | self.$subsystem.as_mut().map(|s| &mut s.subsystem) 358 | } 359 | ); 360 | )* 361 | } 362 | }; 363 | } 364 | 365 | gen_unified_repr! { 366 | (cpu, cpu_mut, Cpu, "CPU"), 367 | (cpuset, cpuset_mut, Cpuset, "cpuset"), 368 | (cpuacct, cpuacct_mut, Cpuacct, "cpuacct"), 369 | (memory, memory_mut, Memory, "memory"), 370 | (hugetlb, hugetlb_mut, HugeTlb, "hugetlb"), 371 | (devices, devices_mut, Devices, "devices"), 372 | (blkio, blkio_mut, BlkIo, "blkio"), 373 | (rdma, rdma_mut, Rdma, "RDMA"), 374 | (net_prio, net_prio_mut, NetPrio, "net_prio"), 375 | (net_cls, net_cls_mut, NetCls, "net_cls"), 376 | (pids, pids_mut, Pids, "pids"), 377 | (freezer, freezer_mut, Freezer, "freezer"), 378 | (perf_event, perf_event_mut, PerfEvent, "perf_event"), 379 | } 380 | 381 | #[cfg(test)] 382 | mod tests { 383 | use super::*; 384 | 385 | #[test] 386 | fn test_unified_repr_subsystems() { 387 | // with all subsystems 388 | let cgroups = UnifiedRepr::new(gen_cgroup_name!()); 389 | 390 | assert!(cgroups.supports(SubsystemKind::Cpu)); 391 | assert!(cgroups.cpu().is_some()); 392 | 393 | assert!(cgroups.supports(SubsystemKind::Cpuset)); 394 | assert!(cgroups.cpuset().is_some()); 395 | 396 | // without any subsystems 397 | let cgroups = UnifiedRepr::with_subsystems(gen_cgroup_name!(), &[]); 398 | 399 | assert!(!cgroups.supports(SubsystemKind::Cpu)); 400 | assert!(cgroups.cpu().is_none()); 401 | 402 | assert!(!cgroups.supports(SubsystemKind::Cpuset)); 403 | assert!(cgroups.cpuset().is_none()); 404 | 405 | // with only CPU subsystem 406 | let cgroups = UnifiedRepr::with_subsystems(gen_cgroup_name!(), &[SubsystemKind::Cpu]); 407 | 408 | assert!(cgroups.supports(SubsystemKind::Cpu)); 409 | assert!(cgroups.cpu().is_some()); 410 | 411 | assert!(!cgroups.supports(SubsystemKind::Cpuset)); 412 | assert!(cgroups.cpuset().is_none()); 413 | } 414 | 415 | #[test] 416 | fn test_unified_repr_create_delete() -> Result<()> { 417 | { 418 | // with CPU and Cpuset subsystems 419 | 420 | let mut cgroups = UnifiedRepr::with_subsystems( 421 | gen_cgroup_name!(), 422 | &[SubsystemKind::Cpu, SubsystemKind::Cpuset], 423 | ); 424 | cgroups.create()?; 425 | 426 | assert!(cgroups.cpu().unwrap().path().exists()); 427 | assert!(cgroups.cpuset().unwrap().path().exists()); 428 | 429 | cgroups.delete()?; 430 | 431 | assert!(!cgroups.cpu().unwrap().path().exists()); 432 | assert!(!cgroups.cpuset().unwrap().path().exists()); 433 | } 434 | 435 | { 436 | // without any subsystems 437 | 438 | let name = gen_cgroup_name!(); 439 | let mut cgroups = UnifiedRepr::with_subsystems(name.clone(), &[]); 440 | cgroups.create()?; 441 | 442 | let cpu = cpu::Subsystem::new(CgroupPath::new(SubsystemKind::Cpu, name.clone())); 443 | assert!(!cpu.path().exists()); 444 | 445 | let cpuset = cpuset::Subsystem::new(CgroupPath::new(SubsystemKind::Cpuset, name)); 446 | assert!(!cpuset.path().exists()); 447 | 448 | cgroups.delete()?; 449 | } 450 | 451 | { 452 | // with only CPU subsystem 453 | 454 | let name = gen_cgroup_name!(); 455 | let mut cgroups = UnifiedRepr::with_subsystems(name.clone(), &[SubsystemKind::Cpu]); 456 | cgroups.create()?; 457 | 458 | let cpu = cpu::Subsystem::new(CgroupPath::new(SubsystemKind::Cpu, name.clone())); 459 | assert!(cpu.path().exists()); 460 | 461 | let cpuset = cpuset::Subsystem::new(CgroupPath::new(SubsystemKind::Cpuset, name)); 462 | assert!(!cpuset.path().exists()); 463 | 464 | cgroups.delete()?; 465 | 466 | assert!(!cpu.path().exists()); 467 | assert!(!cpuset.path().exists()); 468 | } 469 | 470 | Ok(()) 471 | } 472 | 473 | #[test] 474 | fn test_unified_repr_skip_create() -> Result<()> { 475 | let mut cgroups = UnifiedRepr::with_subsystems( 476 | gen_cgroup_name!(), 477 | &[SubsystemKind::Cpu, SubsystemKind::Cpuset], 478 | ); 479 | 480 | cgroups.skip_create(&[SubsystemKind::Cpuset]); 481 | cgroups.create()?; 482 | 483 | assert!(cgroups.cpu().unwrap().path().exists()); 484 | assert!(!cgroups.cpuset().unwrap().path().exists()); 485 | 486 | cgroups.delete()?; 487 | 488 | assert!(!cgroups.cpu().unwrap().path().exists()); 489 | assert!(!cgroups.cpuset().unwrap().path().exists()); 490 | 491 | Ok(()) 492 | } 493 | 494 | #[test] 495 | #[ignore] // must not be executed in parallel 496 | fn test_unified_repr_add_get_remove_tasks() -> Result<()> { 497 | let mut cgroups = UnifiedRepr::with_subsystems(gen_cgroup_name!(), &[SubsystemKind::Cpu]); 498 | cgroups.create()?; 499 | 500 | let pid = Pid::from(std::process::id()); 501 | 502 | cgroups.add_task(pid)?; 503 | assert_eq!(cgroups.cpu().unwrap().tasks()?, vec![pid]); 504 | assert_eq!( 505 | cgroups.tasks()?, 506 | hashmap! { (SubsystemKind::Cpu, vec![pid]) } 507 | ); 508 | 509 | cgroups.remove_task(pid)?; 510 | assert!(cgroups.cpu().unwrap().tasks()?.is_empty()); 511 | assert!(cgroups.tasks()?[&SubsystemKind::Cpu].is_empty()); 512 | 513 | cgroups.delete() 514 | } 515 | 516 | #[test] 517 | #[ignore] // must not be executed in parallel 518 | fn test_unified_repr_add_get_remove_procs() -> Result<()> { 519 | use std::process::{self, Command}; 520 | 521 | let mut cgroups = UnifiedRepr::with_subsystems(gen_cgroup_name!(), &[SubsystemKind::Cpu]); 522 | cgroups.create()?; 523 | 524 | let pid = Pid::from(process::id()); 525 | 526 | cgroups.add_proc(pid)?; 527 | assert_eq!(cgroups.cpu().unwrap().procs()?, vec![pid]); 528 | assert_eq!( 529 | cgroups.procs()?, 530 | hashmap! { (SubsystemKind::Cpu, vec![pid]) } 531 | ); 532 | 533 | // automatically added to the cgroup 534 | let mut child = Command::new("sleep").arg("1").spawn().unwrap(); 535 | let child_pid = Pid::from(&child); 536 | assert!( 537 | cgroups.cpu().unwrap().procs()? == vec![pid, child_pid] 538 | || cgroups.cpu().unwrap().procs()? == vec![child_pid, pid] 539 | ); 540 | assert!( 541 | cgroups.procs()? == hashmap! { (SubsystemKind::Cpu, vec![pid, child_pid]) } 542 | || cgroups.procs()? == hashmap! { (SubsystemKind::Cpu, vec![child_pid, pid]) } 543 | ); 544 | 545 | child.wait()?; 546 | assert!(cgroups.cpu().unwrap().procs()? == vec![pid]); 547 | assert!(cgroups.procs()? == hashmap! { (SubsystemKind::Cpu, vec![pid]) }); 548 | 549 | cgroups.remove_proc(pid)?; 550 | assert!(cgroups.cpu().unwrap().procs()?.is_empty()); 551 | assert!(cgroups.procs()?[&SubsystemKind::Cpu].is_empty()); 552 | 553 | cgroups.delete() 554 | } 555 | 556 | #[test] 557 | fn test_unified_repr_apply() -> Result<()> { 558 | #![allow(clippy::identity_op)] 559 | 560 | const GB: u64 = 1 << 30; 561 | 562 | let mut cgroups = UnifiedRepr::new(gen_cgroup_name!()); 563 | cgroups.skip_create(&[SubsystemKind::Cpuacct, SubsystemKind::NetCls]); 564 | cgroups.create()?; 565 | 566 | let id_set = [0].iter().copied().collect::(); 567 | let class_id = [0x10, 0x1].into(); 568 | let pids_max = crate::Max::Limit(42); 569 | 570 | let mut resources = v1::Resources::default(); 571 | resources.cpu.shares = Some(1000); 572 | resources.cpuset.cpus = Some(id_set.clone()); 573 | resources.memory.limit_in_bytes = Some(1 * GB as i64); 574 | resources.hugetlb.limits = [(hugetlb::HugepageSize::Mb2, hugetlb::Limit::Pages(1))] 575 | .iter() 576 | .copied() 577 | .collect(); 578 | resources.devices.deny = vec!["a".parse::().unwrap()]; 579 | resources.blkio.weight = Some(1000); 580 | // resources.rdma.max = 581 | resources.net_prio.ifpriomap = hashmap! { ("lo".to_string(), 1)}; 582 | resources.net_cls.classid = Some(class_id); 583 | resources.pids.max = Some(pids_max); 584 | resources.freezer.state = Some(freezer::State::Frozen); 585 | 586 | cgroups.apply(&resources)?; 587 | 588 | assert_eq!(cgroups.cpu().unwrap().shares()?, 1000); 589 | assert_eq!(cgroups.cpuset().unwrap().cpus()?, id_set); 590 | assert_eq!(cgroups.memory().unwrap().limit_in_bytes()?, 1 * GB); 591 | assert_eq!( 592 | cgroups 593 | .hugetlb() 594 | .unwrap() 595 | .limit_in_pages(hugetlb::HugepageSize::Mb2)?, 596 | 1 597 | ); 598 | assert!(cgroups.devices().unwrap().list()?.is_empty()); 599 | assert_eq!(cgroups.blkio().unwrap().weight()?, 1000); 600 | assert_eq!(cgroups.net_prio().unwrap().ifpriomap()?["lo"], 1); 601 | assert_eq!(cgroups.net_cls().unwrap().classid()?, class_id); 602 | assert_eq!(cgroups.pids().unwrap().max()?, pids_max); 603 | assert_eq!(cgroups.freezer().unwrap().state()?, freezer::State::Frozen); 604 | 605 | cgroups.delete() 606 | } 607 | } 608 | -------------------------------------------------------------------------------- /src/v1/builder.rs: -------------------------------------------------------------------------------- 1 | //! Configuring a set of cgroups using the builder pattern. 2 | //! 3 | //! [`Builder`] struct is the entry point of the pattern. See its documentation. 4 | //! 5 | //! [`Builder`]: struct.Builder.html 6 | 7 | use std::path::PathBuf; 8 | 9 | use crate::{ 10 | v1::{cpuset, devices, freezer, hugetlb, net_cls, rdma, Resources, SubsystemKind, UnifiedRepr}, 11 | Device, Result, 12 | }; 13 | 14 | // NOTE: Keep the example below in sync with `README.md` and `lib.rs` 15 | 16 | /// Cgroup builder. 17 | /// 18 | /// By using `Builder`, you can configure a (set of) cgroup(s) in the builder pattern. This 19 | /// builder creates directories for the cgroups, but only for the configured subsystems. e.g. If 20 | /// you call only [`cpu`] method, only one cgroup directory is created for the CPU subsystem. 21 | /// 22 | /// ```no_run 23 | /// # fn main() -> controlgroup::Result<()> { 24 | /// use std::path::PathBuf; 25 | /// use controlgroup::{ 26 | /// Max, 27 | /// v1::{devices, hugetlb::{self, HugepageSize}, net_cls, rdma, Builder, SubsystemKind}, 28 | /// }; 29 | /// 30 | /// let mut cgroups = 31 | /// // Start building a (set of) cgroup(s). 32 | /// Builder::new(PathBuf::from("students/charlie")) 33 | /// // Start configuring the CPU resource limits. 34 | /// .cpu() 35 | /// .shares(1000) 36 | /// .cfs_quota_us(500 * 1000) 37 | /// .cfs_period_us(1000 * 1000) 38 | /// // Finish configuring the CPU resource limits. 39 | /// .done() 40 | /// // Start configuring the cpuset resource limits. 41 | /// .cpuset() 42 | /// .cpus([0].iter().copied().collect()) 43 | /// .mems([0].iter().copied().collect()) 44 | /// .memory_migrate(true) 45 | /// .done() 46 | /// .memory() 47 | /// .limit_in_bytes(4 * (1 << 30)) 48 | /// .soft_limit_in_bytes(3 * (1 << 30)) 49 | /// .use_hierarchy(true) 50 | /// .done() 51 | /// .hugetlb() 52 | /// .limits( 53 | /// [ 54 | /// (HugepageSize::Mb2, hugetlb::Limit::Pages(4)), 55 | /// (HugepageSize::Gb1, hugetlb::Limit::Pages(2)), 56 | /// ].iter().copied() 57 | /// ) 58 | /// .done() 59 | /// .devices() 60 | /// .deny(vec!["a *:* rwm".parse::().unwrap()]) 61 | /// .allow(vec!["c 1:3 mr".parse::().unwrap()]) 62 | /// .done() 63 | /// .blkio() 64 | /// .weight(1000) 65 | /// .weight_device([([8, 0].into(), 100)].iter().copied()) 66 | /// .read_bps_device([([8, 0].into(), 10 * (1 << 20))].iter().copied()) 67 | /// .write_iops_device([([8, 0].into(), 100)].iter().copied()) 68 | /// .done() 69 | /// .rdma() 70 | /// .max( 71 | /// [( 72 | /// "mlx4_0".to_string(), 73 | /// rdma::Limit { 74 | /// hca_handle: 2.into(), 75 | /// hca_object: Max::Max, 76 | /// }, 77 | /// )].iter().cloned(), 78 | /// ) 79 | /// .done() 80 | /// .net_prio() 81 | /// .ifpriomap( 82 | /// [("lo".to_string(), 0), ("wlp1s0".to_string(), 1)].iter().cloned(), 83 | /// ) 84 | /// .done() 85 | /// .net_cls() 86 | /// .classid([0x10, 0x1].into()) 87 | /// .done() 88 | /// .pids() 89 | /// .max(42.into()) 90 | /// .done() 91 | /// .freezer() 92 | /// // Tasks in this cgroup will be frozen. 93 | /// .freeze() 94 | /// .done() 95 | /// // Enable CPU accounting for this cgroup. 96 | /// // Cpuacct subsystem has no parameter, so this method does not return a subsystem builder, 97 | /// // just enables the accounting. 98 | /// .cpuacct() 99 | /// // Enable monitoring this cgroup via `perf` tool. 100 | /// // Like `cpuacct()` method, this method does not return a subsystem builder. 101 | /// .perf_event() 102 | /// // Skip creating directories for Cpuacct subsystem and net_cls subsystem. 103 | /// // This is useful when some subsystems share hierarchy with others. 104 | /// .skip_create(vec![SubsystemKind::Cpuacct, SubsystemKind::NetCls]) 105 | /// // Actually build cgroups with the configuration. 106 | /// .build()?; 107 | /// 108 | /// let pid = std::process::id().into(); 109 | /// cgroups.add_task(pid)?; 110 | /// 111 | /// // Do something ... 112 | /// 113 | /// cgroups.remove_task(pid)?; 114 | /// cgroups.delete()?; 115 | /// # Ok(()) 116 | /// # } 117 | /// ``` 118 | /// 119 | /// Note that calling the same method of the same subsystem builder twice overrides the previous 120 | /// configuration if set. 121 | /// 122 | /// ```no_run 123 | /// # fn main() -> controlgroup::Result<()> { 124 | /// # use std::path::PathBuf; 125 | /// # use controlgroup::v1::Builder; 126 | /// let mut cgroups = Builder::new(PathBuf::from("students/charlie")) 127 | /// .cpu() 128 | /// .shares(1000) 129 | /// .shares(2000) // Override. 130 | /// .done() 131 | /// .build()?; 132 | /// 133 | /// assert_eq!(cgroups.cpu().unwrap().shares()?, 2000); 134 | /// # Ok(()) 135 | /// # } 136 | /// ``` 137 | /// 138 | /// But building the same subsystem twice does not reset the subsystem configuration. 139 | /// 140 | /// ```no_run 141 | /// # fn main() -> controlgroup::Result<()> { 142 | /// # use std::path::PathBuf; 143 | /// # use controlgroup::v1::Builder; 144 | /// let mut cgroups = Builder::new(PathBuf::from("students/charlie")) 145 | /// .cpu() 146 | /// .shares(1000) 147 | /// .done() 148 | /// .cpu() // Not reset shares. 149 | /// .cfs_quota_us(500 * 1000) 150 | /// .cfs_period_us(1000 * 1000) 151 | /// .done() 152 | /// .build()?; 153 | /// 154 | /// assert_eq!(cgroups.cpu().unwrap().shares()?, 1000); 155 | /// # Ok(()) 156 | /// # } 157 | /// ``` 158 | /// 159 | /// [`cpu`]: struct.Builder.html#method.cpu 160 | #[derive(Debug)] 161 | pub struct Builder { 162 | name: PathBuf, 163 | subsystems: Vec, 164 | skips: Vec, 165 | resources: Resources, 166 | } 167 | 168 | macro_rules! gen_subsystem_builder_calls { 169 | ( $( ($subsystem: ident, $kind: ident, $builder: ident, $name: literal) ),* $(, )? ) => { $( 170 | with_doc! { 171 | concat!("Starts configuring the ", $name, " subsystem."), 172 | pub fn $subsystem(mut self) -> $builder { 173 | self.subsystems.push(SubsystemKind::$kind); 174 | $builder { builder: self } 175 | } 176 | } 177 | )* } 178 | } 179 | 180 | impl Builder { 181 | /// Creates a new cgroup builder. 182 | /// 183 | /// The resulting (set of) cgroup(s) will have the given name. For the directory name of each 184 | /// subsystem, the standard name (e.g. `cpu` for the CPU subsystem) is used. 185 | pub fn new(name: PathBuf) -> Self { 186 | Self { 187 | name, 188 | subsystems: Vec::new(), 189 | skips: Vec::new(), 190 | resources: Resources::default(), 191 | } 192 | } 193 | 194 | /// Skips creating and deleting the directories for some subsystems. 195 | /// 196 | /// This method is useful when multiple subsystems share the same hierarchy (including via 197 | /// symbolic links), and thus [`build`] method tries to create the same directory multiple 198 | /// times. 199 | /// 200 | /// [`build`]: #method.build 201 | pub fn skip_create(mut self, skip_subsystems: impl IntoIterator) -> Self { 202 | self.skips = skip_subsystems.into_iter().collect(); 203 | self 204 | } 205 | 206 | gen_subsystem_builder_calls! { 207 | (cpu, Cpu, CpuBuilder, "CPU"), 208 | (cpuset, Cpuset, CpusetBuilder, "cpuset"), 209 | (memory, Memory, MemoryBuilder, "memory"), 210 | (hugetlb, HugeTlb, HugeTlbBuilder, "hugetlb"), 211 | (devices, Devices, DevicesBuilder, "devices"), 212 | (blkio, BlkIo, BlkIoBuilder, "blkio"), 213 | (rdma, Rdma, RdmaBuilder, "RDMA"), 214 | (net_prio, NetPrio, NetPrioBuilder, "net_prio"), 215 | (net_cls, NetCls, NetClsBuilder, "net_cls"), 216 | (pids, Pids, PidsBuilder, "pids"), 217 | (freezer, Freezer, FreezerBuilder, "freezer"), 218 | } 219 | 220 | // Calling e.g. `cpu()` twice will push duplicated `SubsystemKind::Cpu`, but it is not a problem 221 | // for `UnifiedRepr::with_subsystems()`. 222 | 223 | /// Enables CPU accounting for this cgroup. 224 | pub fn cpuacct(mut self) -> Self { 225 | self.subsystems.push(SubsystemKind::Cpuacct); 226 | self 227 | } 228 | 229 | /// Enables monitoring this cgroup via `perf` tool. 230 | pub fn perf_event(mut self) -> Self { 231 | self.subsystems.push(SubsystemKind::PerfEvent); 232 | self 233 | } 234 | 235 | /// Builds a (set of) cgroup(s) with the configuration. 236 | /// 237 | /// This method creates directories for the cgroups, but only for the configured subsystems. 238 | /// i.e. if you called only [`cpu`] method, only one cgroup directory is created for the CPU 239 | /// subsystem. 240 | /// 241 | /// Also, this method does not create subsystems that are skipped by [`skip_create`] method. 242 | /// 243 | /// [`cpu`]: #method.cpu 244 | /// [`skip_create`]: #method.skip_create 245 | pub fn build(self) -> Result { 246 | let mut unified_repr = UnifiedRepr::with_subsystems(self.name, &self.subsystems); 247 | 248 | unified_repr.skip_create(&self.skips); 249 | unified_repr.create()?; 250 | 251 | unified_repr.apply(&self.resources)?; 252 | 253 | Ok(unified_repr) 254 | } 255 | } 256 | 257 | macro_rules! gen_subsystem_builder { 258 | ($subsystem: ident, $builder: ident, $name: literal, $( $tt: tt )*) => { 259 | with_doc! { concat!( 260 | $name, " subsystem builder.\n\n", 261 | "This struct is crated by [`Builder::", stringify!($subsystem), "`]", 262 | "(struct.Builder.html#method.", stringify!($subsystem), ") method."), 263 | #[derive(Debug)] 264 | pub struct $builder { 265 | builder: Builder, 266 | } 267 | } 268 | 269 | impl $builder { 270 | $( $tt )* 271 | 272 | with_doc! { 273 | concat!("Finishes configuring this ", $name, " subsystem."), 274 | pub fn done(self) -> Builder { 275 | self.builder 276 | } 277 | } 278 | } 279 | }; 280 | } 281 | 282 | macro_rules! _gen_setter { 283 | ($subsystem: ident, $desc: literal, $field: ident, $ty: ty) => { 284 | _gen_setter!($subsystem, $desc, $field, $field, $ty); 285 | }; 286 | 287 | ($subsystem: ident, $desc: literal, $field: ident, $arg: ident, $ty: ty) => { with_doc! { 288 | _gen_setter!(_doc; $desc, $subsystem, $field), 289 | pub fn $field(mut self, $arg: $ty) -> Self { 290 | self.builder.resources.$subsystem.$field = $arg; 291 | self 292 | } 293 | } }; 294 | 295 | (some; $subsystem: ident, $desc: literal, $field: ident, $ty: ty $( as $as: ty )?) => { 296 | _gen_setter!(some; $subsystem, $desc, $field, $field, $ty $( as $as )?); 297 | }; 298 | 299 | ( 300 | some; 301 | $subsystem: ident, 302 | $desc: literal, 303 | $field: ident, 304 | $arg: ident, 305 | $ty: ty $( as $as: ty )? 306 | ) => { with_doc! { 307 | _gen_setter!(_doc; $desc, $subsystem, $field), 308 | pub fn $field(mut self, $arg: $ty) -> Self { 309 | self.builder.resources.$subsystem.$field = Some($arg $( as $as )*); 310 | self 311 | } 312 | } }; 313 | 314 | (into_iter; $subsystem: ident, $desc: literal, $field: ident, $arg: ident, $ty: ty) => { 315 | with_doc! { 316 | _gen_setter!(_doc; $desc, $subsystem, $field), 317 | pub fn $field(mut self, $arg: impl IntoIterator) -> Self { 318 | self.builder.resources.$subsystem.$field = $arg.into_iter().collect(); 319 | self 320 | } 321 | } 322 | }; 323 | 324 | (_doc; $desc: literal, $subsys: ident, $field: ident) => { concat!( 325 | "Sets ", $desc, ".\n\n", 326 | "See [`", stringify!($subsys), "::Subsystem::set_", stringify!($field), "`]", 327 | "(../", stringify!($subsys), "/struct.Subsystem.html#method.set_", stringify!($field), ")", 328 | " for more information." 329 | ) }; 330 | } 331 | 332 | gen_subsystem_builder! { 333 | cpu, CpuBuilder, "CPU", 334 | 335 | _gen_setter!(some; cpu, "CPU time shares", shares, u64); 336 | 337 | _gen_setter!( 338 | some; cpu, "total available CPU time within a period (in microseconds)", cfs_quota_us, i64 339 | ); 340 | _gen_setter!(some; cpu, "length of period (in microseconds)", cfs_period_us, u64); 341 | 342 | _gen_setter!( 343 | some; 344 | cpu, 345 | "total available CPU time for realtime tasks within a period (in microseconds)", 346 | rt_runtime_us, 347 | i64 348 | ); 349 | _gen_setter!( 350 | some; cpu, "length of period for realtime tasks (in microseconds)", rt_period_us, u64 351 | ); 352 | } 353 | 354 | gen_subsystem_builder! { 355 | cpuset, CpusetBuilder, "cpuset", 356 | 357 | _gen_setter!( 358 | some; cpuset, 359 | "a set of CPUs this cgroup can run", 360 | cpus, 361 | cpuset::IdSet 362 | ); 363 | 364 | _gen_setter!( 365 | some; cpuset, 366 | "a set of memory nodes this cgroup can use", 367 | mems, 368 | cpuset::IdSet 369 | ); 370 | 371 | _gen_setter!( 372 | some; cpuset, 373 | "whether the memory used by this cgroup 374 | should be migrated when memory selection is updated", 375 | memory_migrate, 376 | enable, 377 | bool 378 | ); 379 | 380 | _gen_setter!( 381 | some; cpuset, 382 | "whether the selected CPUs should be exclusive to this cgroup", 383 | cpu_exclusive, 384 | exclusive, 385 | bool 386 | ); 387 | 388 | _gen_setter!( 389 | some; cpuset, 390 | "whether the selected memory nodes should be exclusive to this cgroup", 391 | mem_exclusive, 392 | exclusive, 393 | bool 394 | ); 395 | 396 | _gen_setter!( 397 | some; cpuset, 398 | "whether this cgroup is \"hardwalled\"", 399 | mem_hardwall, 400 | enable, 401 | bool 402 | ); 403 | 404 | /// Sets whether the kernel computes the memory pressure of this cgroup. 405 | /// 406 | /// This field is valid only for the root cgroup. Building a non-root cgroup with memory 407 | /// pressure computation enabled will raise an error with kind [`ErrorKind::InvalidOperation`]. 408 | /// 409 | /// [`ErrorKind::InvalidOperation`]: ../../enum.ErrorKind.html#variant.InvalidOperation 410 | pub fn memory_pressure_enabled(mut self, enable: bool) -> Self { 411 | self.builder.resources.cpuset.memory_pressure_enabled = Some(enable); 412 | self 413 | } 414 | 415 | _gen_setter!( 416 | some; cpuset, 417 | "whether file system buffers are spread across the selected memory nodes", 418 | memory_spread_page, 419 | enable, 420 | bool 421 | ); 422 | 423 | _gen_setter!( 424 | some; cpuset, 425 | "whether file system buffers are spread across the selected memory nodes", 426 | memory_spread_slab, 427 | enable, 428 | bool 429 | ); 430 | 431 | _gen_setter!( 432 | some; cpuset, 433 | "whether the kernel balances the load across the selected CPUs", 434 | sched_load_balance, 435 | enable, 436 | bool 437 | ); 438 | 439 | _gen_setter!( 440 | some; cpuset, 441 | "how much work the kernel do to balance the load on this cgroup", 442 | sched_relax_domain_level, 443 | level, 444 | i32 445 | ); 446 | } 447 | 448 | gen_subsystem_builder! { 449 | memory, MemoryBuilder, "memory", 450 | 451 | _gen_setter!( 452 | some; memory, 453 | "limit on memory usage by this cgroup", 454 | limit_in_bytes, 455 | limit, 456 | u64 as i64 // not i64 because setting -1 to a new cgroup does not make sense 457 | ); 458 | 459 | _gen_setter!( 460 | some; memory, 461 | "limit on total of memory and swap usage by this cgroup", 462 | memsw_limit_in_bytes, 463 | limit, 464 | u64 as i64 465 | ); 466 | 467 | _gen_setter!( 468 | some; memory, 469 | "limit on kernel memory usage by this cgroup", 470 | kmem_limit_in_bytes, 471 | limit, 472 | u64 as i64 473 | ); 474 | 475 | _gen_setter!( 476 | some; memory, 477 | "limit on kernel memory usage for TCP by this cgroup", 478 | kmem_tcp_limit_in_bytes, 479 | limit, 480 | u64 as i64 481 | ); 482 | 483 | _gen_setter!( 484 | some; memory, 485 | "soft limit on memory usage by this cgroup", 486 | soft_limit_in_bytes, 487 | limit, 488 | u64 as i64 489 | ); 490 | 491 | _gen_setter!( 492 | some; memory, 493 | "whether pages may be recharged to the new cgroup when a task is moved", 494 | move_charge_at_immigrate, 495 | enable, 496 | bool 497 | ); 498 | 499 | _gen_setter!( 500 | some; memory, 501 | "the kernel's tendency to swap out pages consumed by this cgroup", 502 | swappiness, 503 | u64 504 | ); 505 | 506 | _gen_setter!( 507 | some; memory, 508 | "whether the OOM killer tries to reclaim memory from the self and descendant cgroups", 509 | use_hierarchy, 510 | use_, 511 | bool 512 | ); 513 | } 514 | 515 | gen_subsystem_builder! { 516 | hugetlb, HugeTlbBuilder, "hugetlb", 517 | 518 | _gen_setter!( 519 | into_iter; hugetlb, 520 | "a map of limits on hugepage TLB usage", 521 | limits, 522 | limits, 523 | (hugetlb::HugepageSize, hugetlb::Limit) 524 | ); 525 | } 526 | 527 | gen_subsystem_builder! { 528 | devices, DevicesBuilder, "devices", 529 | 530 | _gen_setter!( 531 | into_iter; devices, 532 | "a list of allowed device accesses. `deny` list is applied first, and then `allow` list is", 533 | allow, 534 | allowed_devices, 535 | devices::Access 536 | ); 537 | 538 | _gen_setter!( 539 | into_iter; devices, 540 | "a list of denied device accesses. `deny` list is applied first, and then `allow` list is", 541 | deny, 542 | denied_devices, 543 | devices::Access 544 | ); 545 | } 546 | 547 | gen_subsystem_builder! { 548 | blkio, BlkIoBuilder, "blkio", 549 | 550 | _gen_setter!( 551 | some; blkio, 552 | "a relative weight of block I/O performed by this cgroup", 553 | weight, 554 | u16 555 | ); 556 | 557 | _gen_setter!( 558 | into_iter; blkio, 559 | "overriding weights for each device", 560 | weight_device, 561 | weight_map, 562 | (Device, u16) 563 | ); 564 | 565 | _gen_setter!( 566 | some; blkio, 567 | "a weight this cgroup has while competing against descendant cgroups", 568 | leaf_weight, 569 | u16 570 | ); 571 | 572 | _gen_setter!( 573 | into_iter; blkio, 574 | "overriding leaf weights for each device", 575 | leaf_weight_device, 576 | weight_map, 577 | (Device, u16) 578 | ); 579 | 580 | _gen_setter!( 581 | into_iter; blkio, 582 | "a throttling on read access in terms of bytes/s for each device", 583 | read_bps_device, 584 | bps_map, 585 | (Device, u64) 586 | ); 587 | _gen_setter!( 588 | into_iter; blkio, 589 | "a throttling on write access in terms of bytes/s for each device", 590 | write_bps_device, 591 | bps_map, 592 | (Device, u64) 593 | ); 594 | _gen_setter!( 595 | into_iter; blkio, 596 | "a throttling on read access in terms of ops/s for each device", 597 | read_iops_device, 598 | iops_map, 599 | (Device, u64) 600 | ); 601 | _gen_setter!( 602 | into_iter; blkio, 603 | "a throttling on write access in terms of ops/s for each device", 604 | write_iops_device, 605 | iops_map, 606 | (Device, u64) 607 | ); 608 | } 609 | 610 | gen_subsystem_builder! { 611 | rdma, RdmaBuilder, "RDMA", 612 | 613 | _gen_setter!( 614 | into_iter; rdma, 615 | "limits on the usage of RDMA/IB devices", 616 | max, 617 | max_map, 618 | (String, rdma::Limit) 619 | ); 620 | } 621 | 622 | gen_subsystem_builder! { 623 | net_prio, NetPrioBuilder, "net_prio", 624 | 625 | _gen_setter!( 626 | into_iter; net_prio, 627 | "a map of priorities assigned to traffic originating from this cgroup", 628 | ifpriomap, 629 | if_prio_map, 630 | (String, u32) 631 | ); 632 | } 633 | 634 | gen_subsystem_builder! { 635 | net_cls, NetClsBuilder, "net_cls", 636 | 637 | /// Tags network packet from this cgroup with a class ID. 638 | /// 639 | /// See [`net_cls::Subsystem::set_classid`](../net_cls/struct.Subsystem.html#method.set_classid) 640 | /// for more information. 641 | pub fn classid(mut self, id: net_cls::ClassId) -> Self { 642 | self.builder.resources.net_cls.classid = Some(id); 643 | self 644 | } 645 | } 646 | 647 | gen_subsystem_builder! { 648 | pids, PidsBuilder, "pids", 649 | 650 | _gen_setter!( 651 | some; pids, 652 | "a maximum number of tasks this cgroup can have", 653 | max, 654 | crate::Max 655 | ); 656 | } 657 | 658 | gen_subsystem_builder! { 659 | freezer, FreezerBuilder, "freezer", 660 | 661 | /// Freezes tasks in this cgroup. 662 | /// 663 | /// See [`freezer::Subsystem::freeze`](../freezer/struct.Subsystem.html#method.freeze) for more 664 | /// information. 665 | pub fn freeze(mut self) -> Self { 666 | self.builder.resources.freezer.state = Some(freezer::State::Frozen); 667 | self 668 | } 669 | } 670 | 671 | #[cfg(test)] 672 | mod tests { 673 | use super::*; 674 | use crate::{ 675 | v1::{cpuset, Cgroup, CgroupPath}, 676 | ErrorKind, 677 | }; 678 | 679 | #[test] 680 | fn test_builder() -> Result<()> { 681 | #![allow(clippy::identity_op)] 682 | 683 | use crate::v1::hugetlb::HugepageSize; 684 | 685 | const GB: u64 = 1 << 30; 686 | 687 | let id_set = [0].iter().copied().collect::(); 688 | let class_id = [0x01, 0x10].into(); 689 | let pids_max = crate::Max::Limit(21); 690 | 691 | #[rustfmt::skip] 692 | let mut cgroups = Builder::new(gen_cgroup_name!()) 693 | .cpu() 694 | .cfs_quota_us(500_000) 695 | .done() 696 | .cpuset() 697 | .mems(id_set.clone()) 698 | .done() 699 | .memory() 700 | .soft_limit_in_bytes(1 * GB) 701 | .done() 702 | .hugetlb() 703 | .limits( 704 | [ 705 | (HugepageSize::Mb2, hugetlb::Limit::Pages(4)), 706 | (HugepageSize::Gb1, hugetlb::Limit::Pages(2)), 707 | ].iter().copied() 708 | ) 709 | .done() 710 | .devices() 711 | .deny(vec!["a".parse::().unwrap()]) 712 | .done() 713 | .blkio() 714 | .leaf_weight(1000) 715 | .done() 716 | .rdma() 717 | // .max() 718 | .done() 719 | .net_prio() 720 | .ifpriomap([("lo".to_string(), 1)].iter().cloned()) 721 | .done() 722 | .net_cls() 723 | .classid(class_id) 724 | .done() 725 | .pids() 726 | .max(pids_max) 727 | .done() 728 | .freezer() 729 | .freeze() 730 | .done() 731 | // .cpuacct() 732 | .perf_event() 733 | .skip_create(vec![SubsystemKind::NetPrio]) 734 | .build()?; 735 | 736 | assert_eq!(cgroups.cpu().unwrap().cfs_quota_us()?, 500 * 1000); 737 | assert_eq!(cgroups.cpuset().unwrap().mems()?, id_set); 738 | assert_eq!(cgroups.memory().unwrap().soft_limit_in_bytes()?, 1 * GB); 739 | assert_eq!( 740 | cgroups 741 | .hugetlb() 742 | .unwrap() 743 | .limit_in_pages(hugetlb::HugepageSize::Mb2)?, 744 | 4, 745 | ); 746 | assert_eq!( 747 | cgroups 748 | .hugetlb() 749 | .unwrap() 750 | .limit_in_pages(hugetlb::HugepageSize::Gb1)?, 751 | 2, 752 | ); 753 | assert!(cgroups.devices().unwrap().list()?.is_empty()); 754 | assert_eq!(cgroups.blkio().unwrap().leaf_weight()?, 1000); 755 | // assert_eq!(cgroups.rdma().unwrap().max()?, ); 756 | assert_eq!(cgroups.net_prio().unwrap().ifpriomap()?["lo"], 1); 757 | assert_eq!(cgroups.net_cls().unwrap().classid()?, class_id); 758 | assert_eq!(cgroups.pids().unwrap().max()?, pids_max); 759 | assert_eq!(cgroups.freezer().unwrap().state()?, freezer::State::Frozen); 760 | 761 | // assert!(cgroups.cpuacct().unwrap().path().exists()); 762 | assert!(cgroups.perf_event().unwrap().path().exists()); 763 | 764 | cgroups.delete() 765 | } 766 | 767 | #[test] 768 | fn test_builder_skip_create() -> Result<()> { 769 | #[rustfmt::skip] 770 | let mut cgroups = Builder::new(gen_cgroup_name!()) 771 | .cpu() 772 | .done() 773 | .cpuset() 774 | .done() 775 | .skip_create(vec![SubsystemKind::Cpuset]) 776 | .build()?; 777 | 778 | assert!(cgroups.cpu().unwrap().path().exists()); 779 | assert!(!cgroups.cpuset().unwrap().path().exists()); 780 | 781 | cgroups.delete() 782 | } 783 | 784 | #[test] 785 | fn err_builder() -> Result<()> { 786 | let name = gen_cgroup_name!(); 787 | 788 | #[rustfmt::skip] 789 | let cgroups = Builder::new(name.clone()) 790 | .cpuset() 791 | .memory_pressure_enabled(true) 792 | .done() 793 | .build(); 794 | 795 | assert_eq!(cgroups.unwrap_err().kind(), ErrorKind::InvalidOperation); 796 | 797 | // cleanup the created directories 798 | let mut cgroup = cpuset::Subsystem::new(CgroupPath::new(SubsystemKind::Cpuset, name)); 799 | cgroup.delete() 800 | } 801 | 802 | #[test] 803 | fn test_builder_not_create_unused_subsystem_directory() -> Result<()> { 804 | let name = gen_cgroup_name!(); 805 | 806 | #[rustfmt::skip] 807 | let mut cgroups = Builder::new(name.clone()) 808 | .cpu() 809 | .done() 810 | .build()?; 811 | 812 | let cpuset = cpuset::Subsystem::new(CgroupPath::new(SubsystemKind::Cpuset, name)); 813 | assert!(!cpuset.path().exists()); 814 | 815 | cgroups.delete() 816 | } 817 | 818 | #[test] 819 | fn test_builder_override() -> Result<()> { 820 | #[rustfmt::skip] 821 | let mut cgroup = Builder::new(gen_cgroup_name!()) 822 | .cpu() 823 | .shares(1000) 824 | .shares(2000) 825 | .done() 826 | .build()?; 827 | 828 | let cpu = cgroup.cpu().unwrap(); 829 | assert_eq!(cpu.shares()?, 2000); 830 | 831 | cgroup.delete()?; 832 | 833 | #[rustfmt::skip] 834 | let mut cgroup = Builder::new(gen_cgroup_name!()) 835 | .cpu() 836 | .shares(1000) 837 | .done() 838 | .cpu() 839 | .shares(2000) 840 | .done() 841 | .build()?; 842 | 843 | let cpu = cgroup.cpu().unwrap(); 844 | assert_eq!(cpu.shares()?, 2000); 845 | 846 | cgroup.delete() 847 | } 848 | 849 | #[test] 850 | fn test_builder_not_reset() -> Result<()> { 851 | #[rustfmt::skip] 852 | let mut cgroup = Builder::new(gen_cgroup_name!()) 853 | .cpu() 854 | .shares(1000) 855 | .done() 856 | .cpu() 857 | .cfs_quota_us(500 * 1000) 858 | .done() 859 | .build()?; 860 | 861 | let cpu = cgroup.cpu().unwrap(); 862 | assert_eq!(cpu.shares()?, 1000); 863 | assert_eq!(cpu.cfs_quota_us()?, 500 * 1000); 864 | 865 | cgroup.delete() 866 | } 867 | } 868 | --------------------------------------------------------------------------------