├── .cargo └── config ├── .clippy.toml ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── enhancement-request.md │ └── feature_request.md └── workflows │ ├── PR-wip-checks.yaml │ ├── bvt.yaml │ └── commit-message-check.yaml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── LICENSE-Apache-2.0 ├── LICENSE-MIT ├── Makefile ├── README.md ├── src ├── blkio.rs ├── cgroup.rs ├── cgroup_builder.rs ├── cpu.rs ├── cpuacct.rs ├── cpuset.rs ├── devices.rs ├── error.rs ├── events.rs ├── freezer.rs ├── hierarchies.rs ├── hugetlb.rs ├── lib.rs ├── memory.rs ├── net_cls.rs ├── net_prio.rs ├── perf_event.rs ├── pid.rs ├── rdma.rs └── systemd.rs ├── tests ├── builder.rs ├── cgroup.rs ├── cpu.rs ├── cpuset.rs ├── devices.rs ├── hugetlb.rs ├── memory.rs ├── pids.rs └── resources.rs └── tools ├── create_cgroup.sh └── delete_cgroup.sh /.cargo/config: -------------------------------------------------------------------------------- 1 | [target.x86_64-unknown-linux-gnu] 2 | runner = 'sudo -E' 3 | -------------------------------------------------------------------------------- /.clippy.toml: -------------------------------------------------------------------------------- 1 | upper-case-acronyms-aggressive = true 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: 'bug, needs-review' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **Expected behavior** 14 | A clear and concise description of what you expected to happen. 15 | 16 | **Additional context** 17 | Add any other context about the problem here. 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/enhancement-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Enhancement request 3 | about: Suggest an improvement to an existing feature 4 | title: '' 5 | labels: enhancement, needs-review 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Which feature do you think can be improved?** 11 | 12 | Specify the feature you think could be made better. 13 | 14 | **How can it be improved?** 15 | 16 | Describe how specifically you think it could be improved. 17 | 18 | **Additional Information** 19 | 20 | Anything else to add? 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: 'feature, needs-review' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/PR-wip-checks.yaml: -------------------------------------------------------------------------------- 1 | name: Pull request WIP checks 2 | on: 3 | pull_request: 4 | types: 5 | - opened 6 | - synchronize 7 | - reopened 8 | - edited 9 | - labeled 10 | - unlabeled 11 | 12 | jobs: 13 | pr_wip_check: 14 | runs-on: ubuntu-latest 15 | name: WIP Check 16 | steps: 17 | - name: WIP Check 18 | uses: tim-actions/wip-check@1c2a1ca6c110026b3e2297bb2ef39e1747b5a755 19 | with: 20 | labels: '["do-not-merge", "wip", "rfc"]' 21 | keywords: '["WIP", "wip", "RFC", "rfc", "dnm", "DNM", "do-not-merge"]' 22 | -------------------------------------------------------------------------------- /.github/workflows/bvt.yaml: -------------------------------------------------------------------------------- 1 | name: BVT 2 | on: [pull_request] 3 | env: 4 | RUST_VERSION: 1.69.0 5 | jobs: 6 | build: 7 | name: Build 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - run: rustup install ${{ env.RUST_VERSION }} && rustup default ${{ env.RUST_VERSION }} 12 | - run: make debug 13 | 14 | fmt: 15 | name: Format Check 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v2 19 | - run: rustup install ${{ env.RUST_VERSION }} && rustup default ${{ env.RUST_VERSION }} 20 | - run: rustup component add rustfmt 21 | - run: make fmt 22 | clippy: 23 | name: Clippy Check 24 | runs-on: ubuntu-latest 25 | steps: 26 | - uses: actions/checkout@v2 27 | - run: rustup install ${{ env.RUST_VERSION }} && rustup default ${{ env.RUST_VERSION }} 28 | - run: rustup component add clippy 29 | - run: make clippy 30 | test: 31 | name: Run Unit Test 32 | runs-on: ubuntu-latest 33 | steps: 34 | - uses: actions/checkout@v2 35 | - run: rustup install ${{ env.RUST_VERSION }} && rustup default ${{ env.RUST_VERSION }} 36 | - run: make test 37 | 38 | 39 | -------------------------------------------------------------------------------- /.github/workflows/commit-message-check.yaml: -------------------------------------------------------------------------------- 1 | name: Commit Message Check 2 | on: 3 | pull_request: 4 | types: 5 | - opened 6 | - reopened 7 | - synchronize 8 | 9 | env: 10 | error_msg: |+ 11 | See the document below for help on formatting commits for the project. 12 | 13 | https://github.com/kata-containers/community/blob/master/CONTRIBUTING.md#patch-forma 14 | 15 | jobs: 16 | commit-message-check: 17 | runs-on: ubuntu-latest 18 | name: Commit Message Check 19 | steps: 20 | - name: Get PR Commits 21 | id: 'get-pr-commits' 22 | uses: tim-actions/get-pr-commits@v1.0.0 23 | with: 24 | token: ${{ secrets.GITHUB_TOKEN }} 25 | 26 | - name: DCO Check 27 | uses: tim-actions/dco@2fd0504dc0d27b33f542867c300c60840c6dcb20 28 | with: 29 | commits: ${{ steps.get-pr-commits.outputs.commits }} 30 | 31 | - name: Commit Body Missing Check 32 | if: ${{ success() || failure() }} 33 | uses: tim-actions/commit-body-check@v1.0.2 34 | with: 35 | commits: ${{ steps.get-pr-commits.outputs.commits }} 36 | 37 | - name: Check Subject Line Length 38 | if: ${{ success() || failure() }} 39 | uses: tim-actions/commit-message-checker-with-regex@v0.3.1 40 | with: 41 | commits: ${{ steps.get-pr-commits.outputs.commits }} 42 | pattern: '^.{0,75}(\n.*)*$' 43 | error: 'Subject too long (max 75)' 44 | post_error: ${{ env.error_msg }} 45 | 46 | - name: Check Body Line Length 47 | if: ${{ success() || failure() }} 48 | uses: tim-actions/commit-message-checker-with-regex@v0.3.1 49 | with: 50 | commits: ${{ steps.get-pr-commits.outputs.commits }} 51 | pattern: '^.+(\n([a-zA-Z].{0,149}|[^a-zA-Z\n].*|Signed-off-by:.*|))+$' 52 | error: 'Body line too long (max 72)' 53 | post_error: ${{ env.error_msg }} 54 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cgroups-rs" 3 | description = "Native Rust crate for managing control groups on Linux" 4 | repository = "https://github.com/kata-containers/cgroups-rs" 5 | keywords = ["linux", "cgroup", "containers", "isolation"] 6 | categories = ["os", "api-bindings", "os::unix-apis"] 7 | license = "MIT OR Apache-2.0" 8 | version = "0.3.5" 9 | authors = ["The Kata Containers community ", "Levente Kurusa ", "Sam Wilson "] 10 | edition = "2018" 11 | homepage = "https://github.com/kata-containers/cgroups-rs" 12 | readme = "README.md" 13 | 14 | [dependencies] 15 | log = "0.4" 16 | regex = "1.1" 17 | nix = { version = "0.25.0", default-features = false, features = ["event", "fs", "process"] } 18 | libc = "0.2" 19 | serde = { version = "1.0", features = ["derive"], optional = true } 20 | thiserror = "1" 21 | 22 | [dev-dependencies] 23 | libc = "0.2.76" 24 | 25 | [features] 26 | default = [] 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This crate is licensed under either of 2 | 3 | - "Apache License, Version 2.0, (See LICENSE-Apache-2.0 file); or 4 | - "MIT license" (See LICENSE-MIT file), 5 | 6 | at your option. 7 | -------------------------------------------------------------------------------- /LICENSE-Apache-2.0: -------------------------------------------------------------------------------- 1 | Copyright 2018 Levente Kurusa 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 | http://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 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Levente Kurusa 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: debug fmt test 2 | 3 | # 4 | # Build 5 | # 6 | 7 | .PHONY: debug 8 | debug: 9 | RUSTFLAGS="--deny warnings" cargo build 10 | 11 | .PHONY: release 12 | release: 13 | cargo build --release 14 | 15 | .PHONY: build 16 | build: debug 17 | 18 | # 19 | # Tests and linters 20 | # 21 | 22 | .PHONY: test 23 | test: 24 | cargo test -- --color always --nocapture 25 | 26 | .PHONY: check 27 | check: fmt clippy 28 | 29 | 30 | .PHONY: fmt 31 | fmt: 32 | cargo fmt --all -- --check 33 | 34 | .PHONY: clippy 35 | clippy: 36 | cargo clippy --all-targets --all-features -- -D warnings 37 | 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cgroups-rs ![Build](https://travis-ci.org/kata-containers/cgroups-rs.svg?branch=master) 2 | Native Rust library for managing control groups under Linux 3 | 4 | Both v1 and v2 of cgroups are supported. 5 | 6 | # Examples 7 | 8 | ## Create a control group using the builder pattern 9 | 10 | ``` rust 11 | 12 | 13 | use cgroups_rs::*; 14 | use cgroups_rs::cgroup_builder::*; 15 | 16 | // Acquire a handle for the cgroup hierarchy. 17 | let hier = cgroups_rs::hierarchies::auto(); 18 | 19 | // Use the builder pattern (see the documentation to create the control group) 20 | // 21 | // This creates a control group named "example" in the V1 hierarchy. 22 | let cg: Cgroup = CgroupBuilder::new("example") 23 | .cpu() 24 | .shares(85) 25 | .done() 26 | .build(hier); 27 | 28 | // Now `cg` is a control group that gets 85% of the CPU time in relative to 29 | // other control groups. 30 | 31 | // Get a handle to the CPU controller. 32 | let cpus: &cgroups_rs::cpu::CpuController = cg.controller_of().unwrap(); 33 | cpus.add_task(&CgroupPid::from(1234u64)); 34 | 35 | // [...] 36 | 37 | // Finally, clean up and delete the control group. 38 | cg.delete(); 39 | 40 | // Note that `Cgroup` does not implement `Drop` and therefore when the 41 | // structure is dropped, the Cgroup will stay around. This is because, later 42 | // you can then re-create the `Cgroup` using `load()`. We aren't too set on 43 | // this behavior, so it might change in the feature. Rest assured, it will be a 44 | // major version change. 45 | ``` 46 | 47 | # Disclaimer 48 | 49 | This crate is licensed under: 50 | 51 | - MIT License (see LICENSE-MIT); or 52 | - Apache 2.0 License (see LICENSE-Apache-2.0), 53 | 54 | at your option. 55 | 56 | Please note that this crate is under heavy development, we will use sematic 57 | versioning, but during the `0.0.*` phase, no guarantees are made about 58 | backwards compatibility. 59 | 60 | Regardless, check back often and thanks for taking a look! 61 | -------------------------------------------------------------------------------- /src/cgroup.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Levente Kurusa 2 | // Copyright (c) 2020 Ant Group 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 or MIT 5 | // 6 | 7 | //! This module handles cgroup operations. Start here! 8 | 9 | use crate::error::ErrorKind::*; 10 | use crate::error::*; 11 | 12 | use crate::hierarchies::V1; 13 | use crate::{CgroupPid, ControllIdentifier, Controller, Hierarchy, Resources, Subsystem}; 14 | 15 | use std::collections::HashMap; 16 | use std::convert::From; 17 | use std::fs; 18 | use std::path::{Path, PathBuf}; 19 | 20 | pub const CGROUP_MODE_DOMAIN: &str = "domain"; 21 | pub const CGROUP_MODE_DOMAIN_THREADED: &str = "domain threaded"; 22 | pub const CGROUP_MODE_DOMAIN_INVALID: &str = "domain invalid"; 23 | pub const CGROUP_MODE_THREADED: &str = "threaded"; 24 | 25 | /// A control group is the central structure to this crate. 26 | /// 27 | /// 28 | /// # What are control groups? 29 | /// 30 | /// Lifting over from the Linux kernel sources: 31 | /// 32 | /// > Control Groups provide a mechanism for aggregating/partitioning sets of 33 | /// > tasks, and all their future children, into hierarchical groups with 34 | /// > specialized behaviour. 35 | /// 36 | /// This crate is an attempt at providing a Rust-native way of managing these cgroups. 37 | #[derive(Debug)] 38 | pub struct Cgroup { 39 | /// The list of subsystems that control this cgroup 40 | subsystems: Vec, 41 | 42 | /// The hierarchy. 43 | hier: Box, 44 | path: String, 45 | 46 | /// List of controllers specifically enabled in the control group. 47 | specified_controllers: Option>, 48 | } 49 | 50 | impl Clone for Cgroup { 51 | fn clone(&self) -> Self { 52 | Cgroup { 53 | subsystems: self.subsystems.clone(), 54 | hier: crate::hierarchies::auto(), 55 | path: self.path.clone(), 56 | specified_controllers: None, 57 | } 58 | } 59 | } 60 | 61 | impl Default for Cgroup { 62 | fn default() -> Self { 63 | Cgroup { 64 | subsystems: Vec::new(), 65 | hier: crate::hierarchies::auto(), 66 | path: "".to_string(), 67 | specified_controllers: None, 68 | } 69 | } 70 | } 71 | 72 | impl Cgroup { 73 | pub fn v2(&self) -> bool { 74 | self.hier.v2() 75 | } 76 | 77 | /// Return the path the cgroup is located at. 78 | pub fn path(&self) -> &str { 79 | &self.path 80 | } 81 | 82 | /// Create this control group. 83 | pub fn create(&self) -> Result<()> { 84 | if self.hier.v2() { 85 | create_v2_cgroup(self.hier.root(), &self.path, &self.specified_controllers) 86 | } else { 87 | for subsystem in &self.subsystems { 88 | subsystem.to_controller().create(); 89 | } 90 | Ok(()) 91 | } 92 | } 93 | 94 | /// Create a new control group in the hierarchy `hier`, with name `path`. 95 | /// 96 | /// Returns a handle to the control group that can be used to manipulate it. 97 | pub fn new>(hier: Box, path: P) -> Result { 98 | let cg = Cgroup::load(hier, path); 99 | cg.create()?; 100 | Ok(cg) 101 | } 102 | 103 | /// Create a new control group in the hierarchy `hier`, with name `path`. 104 | /// 105 | /// Returns a handle to the control group that can be used to manipulate it. 106 | pub fn new_with_specified_controllers>( 107 | hier: Box, 108 | path: P, 109 | specified_controllers: Option>, 110 | ) -> Result { 111 | let cg = if let Some(sc) = specified_controllers { 112 | Cgroup::load_with_specified_controllers(hier, path, sc) 113 | } else { 114 | Cgroup::load(hier, path) 115 | }; 116 | cg.create()?; 117 | Ok(cg) 118 | } 119 | 120 | /// Create a new control group in the hierarchy `hier`, with name `path` and `relative_paths` 121 | /// 122 | /// Returns a handle to the control group that can be used to manipulate it. 123 | /// 124 | /// Note that this method is only meaningful for cgroup v1, call it is equivalent to call `new` in the v2 mode. 125 | pub fn new_with_relative_paths>( 126 | hier: Box, 127 | path: P, 128 | relative_paths: HashMap, 129 | ) -> Result { 130 | let cg = Cgroup::load_with_relative_paths(hier, path, relative_paths); 131 | cg.create()?; 132 | Ok(cg) 133 | } 134 | 135 | /// Create a handle for a control group in the hierarchy `hier`, with name `path`. 136 | /// 137 | /// Returns a handle to the control group (that possibly does not exist until `create()` has 138 | /// been called on the cgroup. 139 | pub fn load>(hier: Box, path: P) -> Cgroup { 140 | let path = path.as_ref(); 141 | let mut subsystems = hier.subsystems(); 142 | if path.as_os_str() != "" { 143 | subsystems = subsystems 144 | .into_iter() 145 | .map(|x| x.enter(path)) 146 | .collect::>(); 147 | } 148 | 149 | Cgroup { 150 | path: path.to_str().unwrap().to_string(), 151 | subsystems, 152 | hier, 153 | specified_controllers: None, 154 | } 155 | } 156 | 157 | /// Create a handle for a specified control group in the hierarchy `hier`, with name `path`. 158 | /// 159 | /// Returns a handle to the control group (that possibly does not exist until `create()` has 160 | /// been called on the cgroup. 161 | pub fn load_with_specified_controllers>( 162 | hier: Box, 163 | path: P, 164 | specified_controllers: Vec, 165 | ) -> Cgroup { 166 | let path = path.as_ref(); 167 | let mut subsystems = hier.subsystems(); 168 | if path.as_os_str() != "" { 169 | subsystems = subsystems 170 | .into_iter() 171 | .filter(|x| specified_controllers.contains(&x.controller_name())) 172 | .map(|x| x.enter(path)) 173 | .collect::>(); 174 | } 175 | 176 | Cgroup { 177 | path: path.to_str().unwrap().to_string(), 178 | subsystems, 179 | hier, 180 | specified_controllers: Some(specified_controllers), 181 | } 182 | } 183 | 184 | /// Create a handle for a control group in the hierarchy `hier`, with name `path` and `relative_paths` 185 | /// 186 | /// Returns a handle to the control group (that possibly does not exist until `create()` has 187 | /// been called on the cgroup. 188 | /// 189 | /// Note that this method is only meaningful for cgroup v1, call it is equivalent to call `load` in the v2 mode 190 | pub fn load_with_relative_paths>( 191 | hier: Box, 192 | path: P, 193 | relative_paths: HashMap, 194 | ) -> Cgroup { 195 | // relative_paths only valid for cgroup v1 196 | if hier.v2() { 197 | return Self::load(hier, path); 198 | } 199 | 200 | let path = path.as_ref(); 201 | let mut subsystems = hier.subsystems(); 202 | if path.as_os_str() != "" { 203 | subsystems = subsystems 204 | .into_iter() 205 | .map(|x| { 206 | let cn = x.controller_name(); 207 | if relative_paths.contains_key(&cn) { 208 | let rp = relative_paths.get(&cn).unwrap(); 209 | let valid_path = rp.trim_start_matches('/').to_string(); 210 | let mut p = PathBuf::from(valid_path); 211 | p.push(path); 212 | x.enter(p.as_ref()) 213 | } else { 214 | x.enter(path) 215 | } 216 | }) 217 | .collect::>(); 218 | } 219 | 220 | Cgroup { 221 | subsystems, 222 | hier, 223 | path: path.to_str().unwrap().to_string(), 224 | specified_controllers: None, 225 | } 226 | } 227 | 228 | /// The list of subsystems that this control group supports. 229 | pub fn subsystems(&self) -> &Vec { 230 | &self.subsystems 231 | } 232 | 233 | /// Deletes the control group. 234 | /// 235 | /// Note that this function makes no effort in cleaning up the descendant and the underlying 236 | /// system call will fail if there are any descendants. Thus, one should check whether it was 237 | /// actually removed, and remove the descendants first if not. In the future, this behavior 238 | /// will change. 239 | pub fn delete(&self) -> Result<()> { 240 | if self.v2() { 241 | if !self.path.is_empty() { 242 | let mut p = self.hier.root(); 243 | p.push(self.path.clone()); 244 | return fs::remove_dir(p).map_err(|e| Error::with_cause(RemoveFailed, e)); 245 | } 246 | return Ok(()); 247 | } 248 | 249 | self.subsystems.iter().try_for_each(|sub| match sub { 250 | Subsystem::Pid(pidc) => pidc.delete(), 251 | Subsystem::Mem(c) => c.delete(), 252 | Subsystem::CpuSet(c) => c.delete(), 253 | Subsystem::CpuAcct(c) => c.delete(), 254 | Subsystem::Cpu(c) => c.delete(), 255 | Subsystem::Devices(c) => c.delete(), 256 | Subsystem::Freezer(c) => c.delete(), 257 | Subsystem::NetCls(c) => c.delete(), 258 | Subsystem::BlkIo(c) => c.delete(), 259 | Subsystem::PerfEvent(c) => c.delete(), 260 | Subsystem::NetPrio(c) => c.delete(), 261 | Subsystem::HugeTlb(c) => c.delete(), 262 | Subsystem::Rdma(c) => c.delete(), 263 | Subsystem::Systemd(c) => c.delete(), 264 | }) 265 | } 266 | 267 | /// Apply a set of resource limits to the control group. 268 | pub fn apply(&self, res: &Resources) -> Result<()> { 269 | self.subsystems 270 | .iter() 271 | .try_fold((), |_, e| e.to_controller().apply(res)) 272 | } 273 | 274 | /// Retrieve a container based on type inference. 275 | /// 276 | /// ## Example: 277 | /// 278 | /// ```text 279 | /// let pids: &PidController = control_group.controller_of() 280 | /// .expect("No pids controller attached!"); 281 | /// let cpu: &CpuController = control_group.controller_of() 282 | /// .expect("No cpu controller attached!"); 283 | /// ``` 284 | pub fn controller_of<'a, T>(&'a self) -> Option<&'a T> 285 | where 286 | &'a T: From<&'a Subsystem>, 287 | T: Controller + ControllIdentifier, 288 | { 289 | for i in &self.subsystems { 290 | if i.to_controller().control_type() == T::controller_type() { 291 | // N.B.: 292 | // https://play.rust-lang.org/?gist=978b2846bacebdaa00be62374f4f4334&version=stable&mode=debug&edition=2015 293 | return Some(i.into()); 294 | } 295 | } 296 | None 297 | } 298 | 299 | /// Removes tasks from the control group by thread group id. 300 | /// 301 | /// Note that this means that the task will be moved back to the root control group in the 302 | /// hierarchy and any rules applied to that control group will _still_ apply to the proc. 303 | pub fn remove_task_by_tgid(&self, tgid: CgroupPid) -> Result<()> { 304 | self.hier.root_control_group().add_task_by_tgid(tgid) 305 | } 306 | 307 | /// Removes a task from the control group. 308 | /// 309 | /// Note that this means that the task will be moved back to the root control group in the 310 | /// hierarchy and any rules applied to that control group will _still_ apply to the task. 311 | pub fn remove_task(&self, tid: CgroupPid) -> Result<()> { 312 | self.hier.root_control_group().add_task(tid) 313 | } 314 | 315 | /// Moves tasks to the parent control group by thread group id. 316 | pub fn move_task_to_parent_by_tgid(&self, tgid: CgroupPid) -> Result<()> { 317 | self.hier 318 | .parent_control_group(&self.path) 319 | .add_task_by_tgid(tgid) 320 | } 321 | 322 | /// Moves a task to the parent control group. 323 | pub fn move_task_to_parent(&self, tid: CgroupPid) -> Result<()> { 324 | self.hier.parent_control_group(&self.path).add_task(tid) 325 | } 326 | 327 | /// Return a handle to the parent control group in the hierarchy. 328 | pub fn parent_control_group(&self) -> Cgroup { 329 | self.hier.parent_control_group(&self.path) 330 | } 331 | 332 | /// Kill every process in the control group. Only supported for v2 cgroups and on 333 | /// kernels 5.14+. This will fail with InvalidOperation if the 'cgroup.kill' file does 334 | /// not exist. 335 | pub fn kill(&self) -> Result<()> { 336 | if !self.v2() { 337 | return Err(Error::new(CgroupVersion)); 338 | } 339 | 340 | let val = "1"; 341 | let file_name = "cgroup.kill"; 342 | let p = self.hier.root().join(self.path.clone()).join(file_name); 343 | 344 | // If cgroup.kill doesn't exist they're not on 5.14+ so lets 345 | // surface some error the caller can check against. 346 | if !p.exists() { 347 | return Err(Error::new(InvalidOperation)); 348 | } 349 | 350 | fs::write(p, val) 351 | .map_err(|e| Error::with_cause(WriteFailed(file_name.to_string(), val.to_string()), e)) 352 | } 353 | 354 | /// Attach a task to the control group. 355 | pub fn add_task(&self, tid: CgroupPid) -> Result<()> { 356 | if self.v2() { 357 | let subsystems = self.subsystems(); 358 | if !subsystems.is_empty() { 359 | let c = subsystems[0].to_controller(); 360 | let cgroup_type = self.get_cgroup_type()?; 361 | // In cgroup v2, writing to the cgroup.threads file is only supported in thread mode. 362 | if cgroup_type == *CGROUP_MODE_DOMAIN_THREADED 363 | || cgroup_type == *CGROUP_MODE_THREADED 364 | { 365 | // It is used to move the threads of a process into a cgroup in thread mode. 366 | c.add_task(&tid) 367 | } else { 368 | // When the cgroup type is domain or domain invalid, 369 | // cgroup.threads cannot be written. 370 | Err(Error::new(CgroupMode)) 371 | } 372 | } else { 373 | Err(Error::new(SubsystemsEmpty)) 374 | } 375 | } else { 376 | self.subsystems() 377 | .iter() 378 | .try_for_each(|sub| sub.to_controller().add_task(&tid)) 379 | } 380 | } 381 | 382 | /// Attach tasks to the control group by thread group id. 383 | pub fn add_task_by_tgid(&self, tgid: CgroupPid) -> Result<()> { 384 | if self.v2() { 385 | let subsystems = self.subsystems(); 386 | if !subsystems.is_empty() { 387 | let c = subsystems[0].to_controller(); 388 | // It is used to move a thread of the process to a cgroup, 389 | // and other threads of the process will also move together. 390 | c.add_task_by_tgid(&tgid) 391 | } else { 392 | Err(Error::new(SubsystemsEmpty)) 393 | } 394 | } else { 395 | self.subsystems() 396 | .iter() 397 | .try_for_each(|sub| sub.to_controller().add_task_by_tgid(&tgid)) 398 | } 399 | } 400 | 401 | /// set cgroup.type 402 | pub fn set_cgroup_type(&self, cgroup_type: &str) -> Result<()> { 403 | if self.v2() { 404 | let subsystems = self.subsystems(); 405 | if !subsystems.is_empty() { 406 | let c = subsystems[0].to_controller(); 407 | c.set_cgroup_type(cgroup_type) 408 | } else { 409 | Err(Error::new(SubsystemsEmpty)) 410 | } 411 | } else { 412 | Err(Error::new(CgroupVersion)) 413 | } 414 | } 415 | 416 | /// get cgroup.type 417 | pub fn get_cgroup_type(&self) -> Result { 418 | if self.v2() { 419 | let subsystems = self.subsystems(); 420 | if !subsystems.is_empty() { 421 | let c = subsystems[0].to_controller(); 422 | let cgroup_type = c.get_cgroup_type()?; 423 | Ok(cgroup_type) 424 | } else { 425 | Err(Error::new(SubsystemsEmpty)) 426 | } 427 | } else { 428 | Err(Error::new(CgroupVersion)) 429 | } 430 | } 431 | 432 | /// Set notify_on_release to the control group. 433 | pub fn set_notify_on_release(&self, enable: bool) -> Result<()> { 434 | self.subsystems() 435 | .iter() 436 | .try_for_each(|sub| sub.to_controller().set_notify_on_release(enable)) 437 | } 438 | 439 | /// Set release_agent 440 | pub fn set_release_agent(&self, path: &str) -> Result<()> { 441 | self.hier 442 | .root_control_group() 443 | .subsystems() 444 | .iter() 445 | .try_for_each(|sub| sub.to_controller().set_release_agent(path)) 446 | } 447 | 448 | /// Returns an Iterator that can be used to iterate over the procs that are currently in the 449 | /// control group. 450 | pub fn procs(&self) -> Vec { 451 | // Collect the procs from all subsystems 452 | let mut v = if self.v2() { 453 | let subsystems = self.subsystems(); 454 | if !subsystems.is_empty() { 455 | let c = subsystems[0].to_controller(); 456 | c.procs() 457 | } else { 458 | vec![] 459 | } 460 | } else { 461 | self.subsystems() 462 | .iter() 463 | .map(|x| x.to_controller().procs()) 464 | .fold(vec![], |mut acc, mut x| { 465 | acc.append(&mut x); 466 | acc 467 | }) 468 | }; 469 | 470 | v.sort(); 471 | v.dedup(); 472 | v 473 | } 474 | 475 | /// Returns an Iterator that can be used to iterate over the tasks that are currently in the 476 | /// control group. 477 | pub fn tasks(&self) -> Vec { 478 | // Collect the tasks from all subsystems 479 | let mut v = if self.v2() { 480 | let subsystems = self.subsystems(); 481 | if !subsystems.is_empty() { 482 | let c = subsystems[0].to_controller(); 483 | c.tasks() 484 | } else { 485 | vec![] 486 | } 487 | } else { 488 | self.subsystems() 489 | .iter() 490 | .map(|x| x.to_controller().tasks()) 491 | .fold(vec![], |mut acc, mut x| { 492 | acc.append(&mut x); 493 | acc 494 | }) 495 | }; 496 | 497 | v.sort(); 498 | v.dedup(); 499 | v 500 | } 501 | 502 | /// Checks if the cgroup exists. 503 | /// 504 | /// Returns true if at least one subsystem exists. 505 | pub fn exists(&self) -> bool { 506 | self.subsystems().iter().any(|e| e.to_controller().exists()) 507 | } 508 | } 509 | 510 | pub const UNIFIED_MOUNTPOINT: &str = "/sys/fs/cgroup"; 511 | 512 | fn enable_controllers(controllers: &[String], path: &Path) { 513 | let f = path.join("cgroup.subtree_control"); 514 | for c in controllers { 515 | let body = format!("+{}", c); 516 | let _rest = fs::write(f.as_path(), body.as_bytes()); 517 | } 518 | } 519 | 520 | fn supported_controllers() -> Vec { 521 | let p = format!("{}/{}", UNIFIED_MOUNTPOINT, "cgroup.controllers"); 522 | let ret = fs::read_to_string(p.as_str()); 523 | ret.unwrap_or_default() 524 | .split(' ') 525 | .map(|x| x.trim().to_string()) 526 | .collect::>() 527 | } 528 | 529 | fn create_v2_cgroup( 530 | root: PathBuf, 531 | path: &str, 532 | specified_controllers: &Option>, 533 | ) -> Result<()> { 534 | // controler list ["memory", "cpu"] 535 | let controllers = if let Some(s_controllers) = specified_controllers.clone() { 536 | if verify_supported_controllers(s_controllers.as_ref()) { 537 | s_controllers 538 | } else { 539 | return Err(Error::new(ErrorKind::SpecifiedControllers)); 540 | } 541 | } else { 542 | supported_controllers() 543 | }; 544 | 545 | let mut fp = root; 546 | 547 | // enable for root 548 | enable_controllers(&controllers, &fp); 549 | 550 | // path: "a/b/c" 551 | let elements = path.split('/').collect::>(); 552 | let last_index = elements.len() - 1; 553 | for (i, ele) in elements.iter().enumerate() { 554 | // ROOT/a 555 | fp.push(ele); 556 | // create dir, need not check if is a file or directory 557 | if !fp.exists() { 558 | if let Err(e) = std::fs::create_dir(fp.clone()) { 559 | return Err(Error::with_cause(ErrorKind::FsError, e)); 560 | } 561 | } 562 | 563 | if i < last_index { 564 | // enable controllers for substree 565 | enable_controllers(&controllers, &fp); 566 | } 567 | } 568 | 569 | Ok(()) 570 | } 571 | 572 | pub fn verify_supported_controllers(controllers: &[String]) -> bool { 573 | let sc = supported_controllers(); 574 | for controller in controllers.iter() { 575 | if !sc.contains(controller) { 576 | return false; 577 | } 578 | } 579 | true 580 | } 581 | 582 | pub fn get_cgroups_relative_paths() -> Result> { 583 | let path = "/proc/self/cgroup".to_string(); 584 | get_cgroups_relative_paths_by_path(path) 585 | } 586 | 587 | pub fn get_cgroups_relative_paths_by_pid(pid: u32) -> Result> { 588 | let path = format!("/proc/{}/cgroup", pid); 589 | get_cgroups_relative_paths_by_path(path) 590 | } 591 | 592 | fn get_cgroup_destination(mut mount_root: String, pidpath: String) -> String { 593 | if mount_root == "/" { 594 | mount_root = String::from(""); 595 | } 596 | pidpath.trim_start_matches(&mount_root).to_string() 597 | } 598 | 599 | pub fn existing_path(paths: HashMap) -> Result> { 600 | let mount_roots_v1 = V1::new(); 601 | let mut mount_roots_subsystems_map = HashMap::new(); 602 | 603 | for s in mount_roots_v1.subsystems().iter() { 604 | let controller_name = s.controller_name(); 605 | let path_from_cgroup = paths 606 | .get(&controller_name) 607 | .ok_or(Error::new(Common(format!( 608 | "controller {} found in mountinfo, but not found in cgroup.", 609 | controller_name 610 | ))))?; 611 | let path_from_mountinfo = s.to_controller().base().to_string_lossy().to_string(); 612 | 613 | let des_path = get_cgroup_destination(path_from_mountinfo, path_from_cgroup.to_owned()); 614 | mount_roots_subsystems_map.insert(controller_name, des_path); 615 | } 616 | Ok(mount_roots_subsystems_map) 617 | } 618 | 619 | fn get_cgroups_relative_paths_by_path(path: String) -> Result> { 620 | let mut m = HashMap::new(); 621 | let content = 622 | fs::read_to_string(path.clone()).map_err(|e| Error::with_cause(ReadFailed(path), e))?; 623 | // cgroup path may have ":" , likes 624 | // "2:cpu,cpuacct:/system.slice/containerd.service/test.slice:cri-containerd:96b37a2edf84351487f42039e137427f1812f678850675fac214caf597ee5e4a" 625 | for line in content.lines() { 626 | if let Some((first_value_part, remaining_path)) = 627 | line.split_once(':').unwrap_or_default().1.split_once(':') 628 | { 629 | let keys: Vec<&str> = first_value_part.split(',').collect(); 630 | keys.iter().for_each(|key| { 631 | m.insert(key.to_string(), remaining_path.to_string()); 632 | }); 633 | } 634 | } 635 | Ok(m) 636 | } 637 | -------------------------------------------------------------------------------- /src/cgroup_builder.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Levente Kurusa 2 | // Copyright (c) 2020 Ant Group 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 or MIT 5 | // 6 | 7 | //! This module allows the user to create a control group using the Builder pattern. 8 | //! # Example 9 | //! 10 | //! The following example demonstrates how the control group builder looks like. The user 11 | //! specifies the name of the control group (here: "hello") and the hierarchy it belongs to (here: 12 | //! a V1 hierarchy). Next, the user selects a subsystem by calling functions like `memory()`, 13 | //! `cpu()` and `devices()`. The user can then add restrictions and details via subsystem-specific 14 | //! calls. To finalize a subsystem, the user may call `done()`. Finally, if the control group build 15 | //! is done and all requirements/restrictions have been specified, the control group can be created 16 | //! by a call to `build()`. 17 | //! 18 | //! ```rust,no_run 19 | //! # use cgroups_rs::*; 20 | //! # use cgroups_rs::devices::*; 21 | //! # use cgroups_rs::cgroup_builder::*; 22 | //! let h = cgroups_rs::hierarchies::auto(); 23 | //! let cgroup: Cgroup = CgroupBuilder::new("hello") 24 | //! .memory() 25 | //! .kernel_memory_limit(1024 * 1024) 26 | //! .memory_hard_limit(1024 * 1024) 27 | //! .done() 28 | //! .cpu() 29 | //! .shares(100) 30 | //! .done() 31 | //! .devices() 32 | //! .device(1000, 10, DeviceType::Block, true, 33 | //! vec![DevicePermissions::Read, 34 | //! DevicePermissions::Write, 35 | //! DevicePermissions::MkNod]) 36 | //! .device(6, 1, DeviceType::Char, false, vec![]) 37 | //! .done() 38 | //! .network() 39 | //! .class_id(1337) 40 | //! .priority("eth0".to_string(), 100) 41 | //! .priority("wl0".to_string(), 200) 42 | //! .done() 43 | //! .hugepages() 44 | //! .limit("2M".to_string(), 0) 45 | //! .limit("4M".to_string(), 4 * 1024 * 1024 * 100) 46 | //! .limit("2G".to_string(), 2 * 1024 * 1024 * 1024) 47 | //! .done() 48 | //! .blkio() 49 | //! .weight(123) 50 | //! .leaf_weight(99) 51 | //! .weight_device(6, 1, Some(100), Some(55)) 52 | //! .weight_device(6, 1, Some(100), Some(55)) 53 | //! .throttle_iops() 54 | //! .read(6, 1, 10) 55 | //! .write(11, 1, 100) 56 | //! .throttle_bps() 57 | //! .read(6, 1, 10) 58 | //! .write(11, 1, 100) 59 | //! .done() 60 | //! .build(h).unwrap(); 61 | //! ``` 62 | 63 | use crate::{ 64 | BlkIoDeviceResource, BlkIoDeviceThrottleResource, Cgroup, DeviceResource, Error, Hierarchy, 65 | HugePageResource, MaxValue, NetworkPriority, Resources, 66 | }; 67 | 68 | macro_rules! gen_setter { 69 | ($res:ident, $cont:ident, $func:ident, $name:ident, $ty:ty) => { 70 | /// See the similarly named function in the respective controller. 71 | pub fn $name(mut self, $name: $ty) -> Self { 72 | self.cgroup.resources.$res.$name = Some($name); 73 | self 74 | } 75 | }; 76 | } 77 | 78 | /// A control group builder instance 79 | pub struct CgroupBuilder { 80 | name: String, 81 | /// Internal, unsupported field: use the associated builders instead. 82 | resources: Resources, 83 | /// List of controllers specifically enabled in the control group. 84 | specified_controllers: Option>, 85 | } 86 | 87 | impl CgroupBuilder { 88 | /// Start building a control group with the supplied hierarchy and name pair. 89 | /// 90 | /// Note that this does not actually create the control group until `build()` is called. 91 | pub fn new(name: &str) -> CgroupBuilder { 92 | CgroupBuilder { 93 | name: name.to_owned(), 94 | resources: Resources::default(), 95 | specified_controllers: None, 96 | } 97 | } 98 | 99 | /// Builds the memory resources of the control group. 100 | pub fn memory(self) -> MemoryResourceBuilder { 101 | MemoryResourceBuilder { cgroup: self } 102 | } 103 | 104 | /// Builds the pid resources of the control group. 105 | pub fn pid(self) -> PidResourceBuilder { 106 | PidResourceBuilder { cgroup: self } 107 | } 108 | 109 | /// Builds the cpu resources of the control group. 110 | pub fn cpu(self) -> CpuResourceBuilder { 111 | CpuResourceBuilder { cgroup: self } 112 | } 113 | 114 | /// Builds the devices resources of the control group, disallowing or 115 | /// allowing access to certain devices in the system. 116 | pub fn devices(self) -> DeviceResourceBuilder { 117 | DeviceResourceBuilder { cgroup: self } 118 | } 119 | 120 | /// Builds the network resources of the control group, setting class id, or 121 | /// various priorities on networking interfaces. 122 | pub fn network(self) -> NetworkResourceBuilder { 123 | NetworkResourceBuilder { cgroup: self } 124 | } 125 | 126 | /// Builds the hugepage/hugetlb resources available to the control group. 127 | pub fn hugepages(self) -> HugepagesResourceBuilder { 128 | HugepagesResourceBuilder { cgroup: self } 129 | } 130 | 131 | /// Builds the block I/O resources available for the control group. 132 | pub fn blkio(self) -> BlkIoResourcesBuilder { 133 | BlkIoResourcesBuilder { 134 | cgroup: self, 135 | throttling_iops: false, 136 | } 137 | } 138 | 139 | /// Finalize the control group, consuming the builder and creating the control group. 140 | pub fn build(self, hier: Box) -> Result { 141 | if let Some(controllers) = self.specified_controllers { 142 | let cg = Cgroup::new_with_specified_controllers(hier, self.name, Some(controllers))?; 143 | cg.apply(&self.resources)?; 144 | Ok(cg) 145 | } else { 146 | let cg = Cgroup::new(hier, self.name)?; 147 | cg.apply(&self.resources)?; 148 | Ok(cg) 149 | } 150 | } 151 | 152 | /// Specifically enable some controllers in the control group. 153 | pub fn set_specified_controllers(mut self, specified_controllers: Vec) -> Self { 154 | self.specified_controllers = Some(specified_controllers); 155 | self 156 | } 157 | } 158 | 159 | /// A builder that configures the memory controller of a control group. 160 | pub struct MemoryResourceBuilder { 161 | cgroup: CgroupBuilder, 162 | } 163 | 164 | impl MemoryResourceBuilder { 165 | gen_setter!( 166 | memory, 167 | MemController, 168 | set_kmem_limit, 169 | kernel_memory_limit, 170 | i64 171 | ); 172 | gen_setter!(memory, MemController, set_limit, memory_hard_limit, i64); 173 | gen_setter!( 174 | memory, 175 | MemController, 176 | set_soft_limit, 177 | memory_soft_limit, 178 | i64 179 | ); 180 | gen_setter!( 181 | memory, 182 | MemController, 183 | set_tcp_limit, 184 | kernel_tcp_memory_limit, 185 | i64 186 | ); 187 | gen_setter!( 188 | memory, 189 | MemController, 190 | set_memswap_limit, 191 | memory_swap_limit, 192 | i64 193 | ); 194 | gen_setter!(memory, MemController, set_swappiness, swappiness, u64); 195 | 196 | /// Finish the construction of the memory resources of a control group. 197 | pub fn done(self) -> CgroupBuilder { 198 | self.cgroup 199 | } 200 | } 201 | 202 | /// A builder that configures the pid controller of a control group. 203 | pub struct PidResourceBuilder { 204 | cgroup: CgroupBuilder, 205 | } 206 | 207 | impl PidResourceBuilder { 208 | gen_setter!( 209 | pid, 210 | PidController, 211 | set_pid_max, 212 | maximum_number_of_processes, 213 | MaxValue 214 | ); 215 | 216 | /// Finish the construction of the pid resources of a control group. 217 | pub fn done(self) -> CgroupBuilder { 218 | self.cgroup 219 | } 220 | } 221 | 222 | /// A builder that configures the cpuset & cpu controllers of a control group. 223 | pub struct CpuResourceBuilder { 224 | cgroup: CgroupBuilder, 225 | } 226 | 227 | impl CpuResourceBuilder { 228 | gen_setter!(cpu, CpuSetController, set_cpus, cpus, String); 229 | gen_setter!(cpu, CpuSetController, set_mems, mems, String); 230 | gen_setter!(cpu, CpuController, set_shares, shares, u64); 231 | gen_setter!(cpu, CpuController, set_cfs_quota, quota, i64); 232 | gen_setter!(cpu, CpuController, set_cfs_period, period, u64); 233 | gen_setter!(cpu, CpuController, set_rt_runtime, realtime_runtime, i64); 234 | gen_setter!(cpu, CpuController, set_rt_period, realtime_period, u64); 235 | 236 | /// Finish the construction of the cpu resources of a control group. 237 | pub fn done(self) -> CgroupBuilder { 238 | self.cgroup 239 | } 240 | } 241 | 242 | /// A builder that configures the devices controller of a control group. 243 | pub struct DeviceResourceBuilder { 244 | cgroup: CgroupBuilder, 245 | } 246 | 247 | impl DeviceResourceBuilder { 248 | /// Restrict (or allow) a device to the tasks inside the control group. 249 | pub fn device( 250 | mut self, 251 | major: i64, 252 | minor: i64, 253 | devtype: crate::devices::DeviceType, 254 | allow: bool, 255 | access: Vec, 256 | ) -> DeviceResourceBuilder { 257 | self.cgroup.resources.devices.devices.push(DeviceResource { 258 | allow, 259 | devtype, 260 | major, 261 | minor, 262 | access, 263 | }); 264 | self 265 | } 266 | 267 | /// Finish the construction of the devices resources of a control group. 268 | pub fn done(self) -> CgroupBuilder { 269 | self.cgroup 270 | } 271 | } 272 | 273 | /// A builder that configures the net_cls & net_prio controllers of a control group. 274 | pub struct NetworkResourceBuilder { 275 | cgroup: CgroupBuilder, 276 | } 277 | 278 | impl NetworkResourceBuilder { 279 | gen_setter!(network, NetclsController, set_class, class_id, u64); 280 | 281 | /// Set the priority of the tasks when operating on a networking device defined by `name` to be 282 | /// `priority`. 283 | pub fn priority(mut self, name: String, priority: u64) -> NetworkResourceBuilder { 284 | self.cgroup 285 | .resources 286 | .network 287 | .priorities 288 | .push(NetworkPriority { name, priority }); 289 | self 290 | } 291 | 292 | /// Finish the construction of the network resources of a control group. 293 | pub fn done(self) -> CgroupBuilder { 294 | self.cgroup 295 | } 296 | } 297 | 298 | /// A builder that configures the hugepages controller of a control group. 299 | pub struct HugepagesResourceBuilder { 300 | cgroup: CgroupBuilder, 301 | } 302 | 303 | impl HugepagesResourceBuilder { 304 | /// Limit the usage of certain hugepages (determined by `size`) to be at most `limit` bytes. 305 | pub fn limit(mut self, size: String, limit: u64) -> HugepagesResourceBuilder { 306 | self.cgroup 307 | .resources 308 | .hugepages 309 | .limits 310 | .push(HugePageResource { size, limit }); 311 | self 312 | } 313 | 314 | /// Finish the construction of the network resources of a control group. 315 | pub fn done(self) -> CgroupBuilder { 316 | self.cgroup 317 | } 318 | } 319 | 320 | /// A builder that configures the blkio controller of a control group. 321 | pub struct BlkIoResourcesBuilder { 322 | cgroup: CgroupBuilder, 323 | throttling_iops: bool, 324 | } 325 | 326 | impl BlkIoResourcesBuilder { 327 | gen_setter!(blkio, BlkIoController, set_weight, weight, u16); 328 | gen_setter!(blkio, BlkIoController, set_leaf_weight, leaf_weight, u16); 329 | 330 | /// Set the weight of a certain device. 331 | pub fn weight_device( 332 | mut self, 333 | major: u64, 334 | minor: u64, 335 | weight: Option, 336 | leaf_weight: Option, 337 | ) -> BlkIoResourcesBuilder { 338 | self.cgroup 339 | .resources 340 | .blkio 341 | .weight_device 342 | .push(BlkIoDeviceResource { 343 | major, 344 | minor, 345 | weight, 346 | leaf_weight, 347 | }); 348 | self 349 | } 350 | 351 | /// Start configuring the I/O operations per second metric. 352 | pub fn throttle_iops(mut self) -> BlkIoResourcesBuilder { 353 | self.throttling_iops = true; 354 | self 355 | } 356 | 357 | /// Start configuring the bytes per second metric. 358 | pub fn throttle_bps(mut self) -> BlkIoResourcesBuilder { 359 | self.throttling_iops = false; 360 | self 361 | } 362 | 363 | /// Limit the read rate of the current metric for a certain device. 364 | pub fn read(mut self, major: u64, minor: u64, rate: u64) -> BlkIoResourcesBuilder { 365 | let throttle = BlkIoDeviceThrottleResource { major, minor, rate }; 366 | if self.throttling_iops { 367 | self.cgroup 368 | .resources 369 | .blkio 370 | .throttle_read_iops_device 371 | .push(throttle); 372 | } else { 373 | self.cgroup 374 | .resources 375 | .blkio 376 | .throttle_read_bps_device 377 | .push(throttle); 378 | } 379 | self 380 | } 381 | 382 | /// Limit the write rate of the current metric for a certain device. 383 | pub fn write(mut self, major: u64, minor: u64, rate: u64) -> BlkIoResourcesBuilder { 384 | let throttle = BlkIoDeviceThrottleResource { major, minor, rate }; 385 | if self.throttling_iops { 386 | self.cgroup 387 | .resources 388 | .blkio 389 | .throttle_write_iops_device 390 | .push(throttle); 391 | } else { 392 | self.cgroup 393 | .resources 394 | .blkio 395 | .throttle_write_bps_device 396 | .push(throttle); 397 | } 398 | self 399 | } 400 | 401 | /// Finish the construction of the blkio resources of a control group. 402 | pub fn done(self) -> CgroupBuilder { 403 | self.cgroup 404 | } 405 | } 406 | -------------------------------------------------------------------------------- /src/cpu.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Levente Kurusa 2 | // Copyright (c) 2020 Ant Group 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 or MIT 5 | // 6 | 7 | //! This module contains the implementation of the `cpu` cgroup subsystem. 8 | //! 9 | //! See the Kernel's documentation for more information about this subsystem, found at: 10 | //! [Documentation/scheduler/sched-design-CFS.txt](https://www.kernel.org/doc/Documentation/scheduler/sched-design-CFS.txt) 11 | //! paragraph 7 ("GROUP SCHEDULER EXTENSIONS TO CFS"). 12 | use std::fs::File; 13 | use std::io::{Read, Write}; 14 | use std::path::PathBuf; 15 | 16 | use crate::error::ErrorKind::*; 17 | use crate::error::*; 18 | use crate::{parse_max_value, read_i64_from, read_u64_from}; 19 | 20 | use crate::{ 21 | ControllIdentifier, ControllerInternal, Controllers, CpuResources, CustomizedAttribute, 22 | MaxValue, Resources, Subsystem, 23 | }; 24 | 25 | /// A controller that allows controlling the `cpu` subsystem of a Cgroup. 26 | /// 27 | /// In essence, it allows gathering information about how much the tasks inside the control group 28 | /// are using the CPU and creating rules that limit their usage. Note that this crate does not yet 29 | /// support managing realtime tasks. 30 | #[derive(Debug, Clone)] 31 | pub struct CpuController { 32 | base: PathBuf, 33 | path: PathBuf, 34 | v2: bool, 35 | } 36 | 37 | /// The current state of the control group and its processes. 38 | #[derive(Debug)] 39 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 40 | pub struct Cpu { 41 | /// Reports CPU time statistics. 42 | /// 43 | /// Corresponds the `cpu.stat` file in `cpu` control group. 44 | pub stat: String, 45 | } 46 | 47 | /// The current state of the control group and its processes. 48 | #[derive(Debug)] 49 | struct CfsQuotaAndPeriod { 50 | quota: MaxValue, 51 | period: u64, 52 | } 53 | 54 | impl ControllerInternal for CpuController { 55 | fn control_type(&self) -> Controllers { 56 | Controllers::Cpu 57 | } 58 | 59 | fn get_path(&self) -> &PathBuf { 60 | &self.path 61 | } 62 | fn get_path_mut(&mut self) -> &mut PathBuf { 63 | &mut self.path 64 | } 65 | 66 | fn get_base(&self) -> &PathBuf { 67 | &self.base 68 | } 69 | 70 | fn is_v2(&self) -> bool { 71 | self.v2 72 | } 73 | 74 | fn apply(&self, res: &Resources) -> Result<()> { 75 | // get the resources that apply to this controller 76 | let res: &CpuResources = &res.cpu; 77 | 78 | update_and_test!(self, set_shares, res.shares, shares); 79 | update_and_test!(self, set_cfs_period, res.period, cfs_period); 80 | update_and_test!(self, set_cfs_quota, res.quota, cfs_quota); 81 | 82 | res.attrs.iter().for_each(|(k, v)| { 83 | let _ = self.set(k, v); 84 | }); 85 | 86 | // TODO: rt properties (CONFIG_RT_GROUP_SCHED) are not yet supported 87 | 88 | Ok(()) 89 | } 90 | } 91 | 92 | impl ControllIdentifier for CpuController { 93 | fn controller_type() -> Controllers { 94 | Controllers::Cpu 95 | } 96 | } 97 | 98 | impl<'a> From<&'a Subsystem> for &'a CpuController { 99 | fn from(sub: &'a Subsystem) -> &'a CpuController { 100 | unsafe { 101 | match sub { 102 | Subsystem::Cpu(c) => c, 103 | _ => { 104 | assert_eq!(1, 0); 105 | let v = std::mem::MaybeUninit::uninit(); 106 | v.assume_init() 107 | } 108 | } 109 | } 110 | } 111 | } 112 | 113 | impl CpuController { 114 | /// Contructs a new `CpuController` with `root` serving as the root of the control group. 115 | pub fn new(point: PathBuf, root: PathBuf, v2: bool) -> Self { 116 | Self { 117 | base: root, 118 | path: point, 119 | v2, 120 | } 121 | } 122 | 123 | /// Returns CPU time statistics based on the processes in the control group. 124 | pub fn cpu(&self) -> Cpu { 125 | Cpu { 126 | stat: self 127 | .open_path("cpu.stat", false) 128 | .and_then(|mut file| { 129 | let mut s = String::new(); 130 | let res = file.read_to_string(&mut s); 131 | match res { 132 | Ok(_) => Ok(s), 133 | Err(e) => Err(Error::with_cause(ReadFailed("cpu.stat".to_string()), e)), 134 | } 135 | }) 136 | .unwrap_or_default(), 137 | } 138 | } 139 | 140 | /// Configures the CPU bandwidth (in relative relation to other control groups and this control 141 | /// group's parent). 142 | /// 143 | /// For example, setting control group `A`'s `shares` to `100`, and control group `B`'s 144 | /// `shares` to `200` ensures that control group `B` receives twice as much as CPU bandwidth. 145 | /// (Assuming both `A` and `B` are of the same parent) 146 | pub fn set_shares(&self, shares: u64) -> Result<()> { 147 | let mut file_name = "cpu.shares"; 148 | if self.v2 { 149 | file_name = "cpu.weight"; 150 | } 151 | // NOTE: .CpuShares is not used here. Conversion is the caller's responsibility. 152 | self.open_path(file_name, true).and_then(|mut file| { 153 | file.write_all(shares.to_string().as_ref()).map_err(|e| { 154 | Error::with_cause(WriteFailed(file_name.to_string(), shares.to_string()), e) 155 | }) 156 | }) 157 | } 158 | 159 | /// Retrieve the CPU bandwidth that this control group (relative to other control groups and 160 | /// this control group's parent) can use. 161 | pub fn shares(&self) -> Result { 162 | let mut file = "cpu.shares"; 163 | if self.v2 { 164 | file = "cpu.weight"; 165 | } 166 | self.open_path(file, false).and_then(read_u64_from) 167 | } 168 | 169 | /// Specify a period (when using the CFS scheduler) of time in microseconds for how often this 170 | /// control group's access to the CPU should be reallocated. 171 | pub fn set_cfs_period(&self, us: u64) -> Result<()> { 172 | if self.v2 { 173 | return self.set_cfs_quota_and_period(None, Some(us)); 174 | } 175 | self.open_path("cpu.cfs_period_us", true) 176 | .and_then(|mut file| { 177 | file.write_all(us.to_string().as_ref()).map_err(|e| { 178 | Error::with_cause( 179 | WriteFailed("cpu.cfs_period_us".to_string(), us.to_string()), 180 | e, 181 | ) 182 | }) 183 | }) 184 | } 185 | 186 | /// Retrieve the period of time of how often this cgroup's access to the CPU should be 187 | /// reallocated in microseconds. 188 | pub fn cfs_period(&self) -> Result { 189 | if self.v2 { 190 | let current_value = self 191 | .open_path("cpu.max", false) 192 | .and_then(parse_cfs_quota_and_period)?; 193 | return Ok(current_value.period); 194 | } 195 | self.open_path("cpu.cfs_period_us", false) 196 | .and_then(read_u64_from) 197 | } 198 | 199 | /// Specify a quota (when using the CFS scheduler) of time in microseconds for which all tasks 200 | /// in this control group can run during one period (see: `set_cfs_period()`). 201 | pub fn set_cfs_quota(&self, us: i64) -> Result<()> { 202 | if self.v2 { 203 | return self.set_cfs_quota_and_period(Some(us), None); 204 | } 205 | self.open_path("cpu.cfs_quota_us", true) 206 | .and_then(|mut file| { 207 | file.write_all(us.to_string().as_ref()).map_err(|e| { 208 | Error::with_cause( 209 | WriteFailed("cpu.cfs_quota_us".to_string(), us.to_string()), 210 | e, 211 | ) 212 | }) 213 | }) 214 | } 215 | 216 | /// Retrieve the quota of time for which all tasks in this cgroup can run during one period, in 217 | /// microseconds. 218 | pub fn cfs_quota(&self) -> Result { 219 | if self.v2 { 220 | let current_value = self 221 | .open_path("cpu.max", false) 222 | .and_then(parse_cfs_quota_and_period)?; 223 | return Ok(current_value.quota.to_i64()); 224 | } 225 | 226 | self.open_path("cpu.cfs_quota_us", false) 227 | .and_then(read_i64_from) 228 | } 229 | 230 | pub fn set_cfs_quota_and_period(&self, quota: Option, period: Option) -> Result<()> { 231 | if !self.v2 { 232 | if let Some(q) = quota { 233 | self.set_cfs_quota(q)?; 234 | } 235 | if let Some(p) = period { 236 | self.set_cfs_period(p)?; 237 | } 238 | return Ok(()); 239 | } 240 | 241 | // https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html 242 | 243 | // cpu.max 244 | // A read-write two value file which exists on non-root cgroups. The default is “max 100000”. 245 | // The maximum bandwidth limit. It’s in the following format: 246 | // $MAX $PERIOD 247 | // which indicates that the group may consume upto $MAX in each $PERIOD duration. 248 | // “max” for $MAX indicates no limit. If only one number is written, $MAX is updated. 249 | 250 | let current_value = self 251 | .open_path("cpu.max", false) 252 | .and_then(parse_cfs_quota_and_period)?; 253 | 254 | let new_quota = if let Some(q) = quota { 255 | if q > 0 { 256 | q.to_string() 257 | } else { 258 | "max".to_string() 259 | } 260 | } else { 261 | current_value.quota.to_string() 262 | }; 263 | 264 | let new_period = if let Some(p) = period { 265 | p.to_string() 266 | } else { 267 | current_value.period.to_string() 268 | }; 269 | 270 | let line = format!("{} {}", new_quota, new_period); 271 | self.open_path("cpu.max", true).and_then(|mut file| { 272 | file.write_all(line.as_ref()) 273 | .map_err(|e| Error::with_cause(WriteFailed("cpu.max".to_string(), line), e)) 274 | }) 275 | } 276 | 277 | pub fn set_rt_runtime(&self, us: i64) -> Result<()> { 278 | self.open_path("cpu.rt_runtime_us", true) 279 | .and_then(|mut file| { 280 | file.write_all(us.to_string().as_ref()).map_err(|e| { 281 | Error::with_cause( 282 | WriteFailed("cpu.rt_runtime_us".to_string(), us.to_string()), 283 | e, 284 | ) 285 | }) 286 | }) 287 | } 288 | 289 | pub fn set_rt_period_us(&self, us: u64) -> Result<()> { 290 | self.open_path("cpu.rt_period_us", true) 291 | .and_then(|mut file| { 292 | file.write_all(us.to_string().as_ref()).map_err(|e| { 293 | Error::with_cause( 294 | WriteFailed("cpu.rt_period_us".to_string(), us.to_string()), 295 | e, 296 | ) 297 | }) 298 | }) 299 | } 300 | } 301 | 302 | impl CustomizedAttribute for CpuController {} 303 | 304 | fn parse_cfs_quota_and_period(mut file: File) -> Result { 305 | let mut content = String::new(); 306 | file.read_to_string(&mut content) 307 | .map_err(|e| Error::with_cause(ReadFailed("cpu.max".to_string()), e))?; 308 | 309 | let fields = content.trim().split(' ').collect::>(); 310 | if fields.len() != 2 { 311 | return Err(Error::from_string(format!("invaild format: {}", content))); 312 | } 313 | 314 | let quota = parse_max_value(fields[0])?; 315 | let period = fields[1] 316 | .parse::() 317 | .map_err(|e| Error::with_cause(ParseError, e))?; 318 | 319 | Ok(CfsQuotaAndPeriod { quota, period }) 320 | } 321 | -------------------------------------------------------------------------------- /src/cpuacct.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Levente Kurusa 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 or MIT 4 | // 5 | 6 | //! This module contains the implementation of the `cpuacct` cgroup subsystem. 7 | //! 8 | //! See the Kernel's documentation for more information about this subsystem, found at: 9 | //! [Documentation/cgroup-v1/cpuacct.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/cpuacct.txt) 10 | use std::io::Write; 11 | use std::path::PathBuf; 12 | 13 | use crate::error::ErrorKind::*; 14 | use crate::error::*; 15 | 16 | use crate::{read_string_from, read_u64_from}; 17 | use crate::{ControllIdentifier, ControllerInternal, Controllers, Resources, Subsystem}; 18 | 19 | /// A controller that allows controlling the `cpuacct` subsystem of a Cgroup. 20 | /// 21 | /// In essence, this control group provides accounting (hence the name `cpuacct`) for CPU usage of 22 | /// the tasks in the control group. 23 | #[derive(Debug, Clone)] 24 | pub struct CpuAcctController { 25 | base: PathBuf, 26 | path: PathBuf, 27 | } 28 | 29 | /// Represents the statistics retrieved from the control group. 30 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 31 | pub struct CpuAcct { 32 | /// Divides the time used by the tasks into `user` time and `system` time. 33 | pub stat: String, 34 | /// Total CPU time (in nanoseconds) spent by the tasks. 35 | pub usage: u64, 36 | /// Total CPU time (in nanoseconds) spent by the tasks, broken down by CPU and by whether the 37 | /// time spent is `user` time or `system` time. 38 | /// 39 | /// An example is as follows: 40 | /// ```text 41 | /// cpu user system 42 | /// 0 8348363768 0 43 | /// 1 8324369100 0 44 | /// 2 8598185449 0 45 | /// 3 8648262473 0 46 | /// ``` 47 | pub usage_all: String, 48 | /// CPU time (in nanoseconds) spent by the tasks, broken down by each CPU. 49 | /// Times spent in each CPU are separated by a space. 50 | pub usage_percpu: String, 51 | /// As for `usage_percpu`, but the `system` time spent. 52 | pub usage_percpu_sys: String, 53 | /// As for `usage_percpu`, but the `user` time spent. 54 | pub usage_percpu_user: String, 55 | /// CPU time (in nanoseconds) spent by the tasks that counted for `system` time. 56 | pub usage_sys: u64, 57 | /// CPU time (in nanoseconds) spent by the tasks that counted for `user` time. 58 | pub usage_user: u64, 59 | } 60 | 61 | impl ControllerInternal for CpuAcctController { 62 | fn control_type(&self) -> Controllers { 63 | Controllers::CpuAcct 64 | } 65 | fn get_path(&self) -> &PathBuf { 66 | &self.path 67 | } 68 | fn get_path_mut(&mut self) -> &mut PathBuf { 69 | &mut self.path 70 | } 71 | fn get_base(&self) -> &PathBuf { 72 | &self.base 73 | } 74 | 75 | fn apply(&self, _res: &Resources) -> Result<()> { 76 | Ok(()) 77 | } 78 | } 79 | 80 | impl ControllIdentifier for CpuAcctController { 81 | fn controller_type() -> Controllers { 82 | Controllers::CpuAcct 83 | } 84 | } 85 | 86 | impl<'a> From<&'a Subsystem> for &'a CpuAcctController { 87 | fn from(sub: &'a Subsystem) -> &'a CpuAcctController { 88 | unsafe { 89 | match sub { 90 | Subsystem::CpuAcct(c) => c, 91 | _ => { 92 | assert_eq!(1, 0); 93 | let v = std::mem::MaybeUninit::uninit(); 94 | v.assume_init() 95 | } 96 | } 97 | } 98 | } 99 | } 100 | 101 | impl CpuAcctController { 102 | /// Contructs a new `CpuAcctController` with `root` serving as the root of the control group. 103 | pub fn new(point: PathBuf, root: PathBuf) -> Self { 104 | Self { 105 | base: root, 106 | path: point, 107 | } 108 | } 109 | 110 | /// Gathers the statistics that are available in the control group into a `CpuAcct` structure. 111 | pub fn cpuacct(&self) -> CpuAcct { 112 | CpuAcct { 113 | stat: self 114 | .open_path("cpuacct.stat", false) 115 | .and_then(read_string_from) 116 | .unwrap_or_default(), 117 | usage: self 118 | .open_path("cpuacct.usage", false) 119 | .and_then(read_u64_from) 120 | .unwrap_or(0), 121 | usage_all: self 122 | .open_path("cpuacct.usage_all", false) 123 | .and_then(read_string_from) 124 | .unwrap_or_default(), 125 | usage_percpu: self 126 | .open_path("cpuacct.usage_percpu", false) 127 | .and_then(read_string_from) 128 | .unwrap_or_default(), 129 | usage_percpu_sys: self 130 | .open_path("cpuacct.usage_percpu_sys", false) 131 | .and_then(read_string_from) 132 | .unwrap_or_default(), 133 | usage_percpu_user: self 134 | .open_path("cpuacct.usage_percpu_user", false) 135 | .and_then(read_string_from) 136 | .unwrap_or_default(), 137 | usage_sys: self 138 | .open_path("cpuacct.usage_sys", false) 139 | .and_then(read_u64_from) 140 | .unwrap_or(0), 141 | usage_user: self 142 | .open_path("cpuacct.usage_user", false) 143 | .and_then(read_u64_from) 144 | .unwrap_or(0), 145 | } 146 | } 147 | 148 | /// Reset the statistics the kernel has gathered about the control group. 149 | pub fn reset(&self) -> Result<()> { 150 | self.open_path("cpuacct.usage", true).and_then(|mut file| { 151 | file.write_all(b"0").map_err(|e| { 152 | Error::with_cause(WriteFailed("cpuacct.usage".to_string(), "0".to_string()), e) 153 | }) 154 | }) 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/cpuset.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Levente Kurusa 2 | // Copyright (c) 2020 Ant Group 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 or MIT 5 | // 6 | 7 | //! This module contains the implementation of the `cpuset` cgroup subsystem. 8 | //! 9 | //! See the Kernel's documentation for more information about this subsystem, found at: 10 | //! [Documentation/cgroup-v1/cpusets.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/cpusets.txt) 11 | 12 | use log::*; 13 | use std::io::Write; 14 | use std::path::PathBuf; 15 | 16 | use crate::error::ErrorKind::*; 17 | use crate::error::*; 18 | 19 | use crate::{read_string_from, read_u64_from}; 20 | use crate::{ 21 | ControllIdentifier, ControllerInternal, Controllers, CpuResources, Resources, Subsystem, 22 | }; 23 | 24 | /// A controller that allows controlling the `cpuset` subsystem of a Cgroup. 25 | /// 26 | /// In essence, this controller is responsible for restricting the tasks in the control group to a 27 | /// set of CPUs and/or memory nodes. 28 | #[derive(Debug, Clone)] 29 | pub struct CpuSetController { 30 | base: PathBuf, 31 | path: PathBuf, 32 | v2: bool, 33 | } 34 | 35 | /// The current state of the `cpuset` controller for this control group. 36 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 37 | pub struct CpuSet { 38 | /// If true, no other control groups can share the CPUs listed in the `cpus` field. 39 | pub cpu_exclusive: bool, 40 | /// The list of CPUs the tasks of the control group can run on. 41 | /// 42 | /// This is a vector of `(start, end)` tuples, where each tuple is a range of CPUs where the 43 | /// control group is allowed to run on. Both sides of the range are inclusive. 44 | pub cpus: Vec<(u64, u64)>, 45 | /// The list of CPUs that the tasks can effectively run on. This removes the list of CPUs that 46 | /// the parent (and all of its parents) cannot run on from the `cpus` field of this control 47 | /// group. 48 | pub effective_cpus: Vec<(u64, u64)>, 49 | /// The list of memory nodes that the tasks can effectively use. This removes the list of nodes that 50 | /// the parent (and all of its parents) cannot use from the `mems` field of this control 51 | /// group. 52 | pub effective_mems: Vec<(u64, u64)>, 53 | /// If true, no other control groups can share the memory nodes listed in the `mems` field. 54 | pub mem_exclusive: bool, 55 | /// If true, the control group is 'hardwalled'. Kernel memory allocations (except for a few 56 | /// minor exceptions) are made from the memory nodes designated in the `mems` field. 57 | pub mem_hardwall: bool, 58 | /// If true, whenever `mems` is changed via `set_mems()`, the memory stored on the previous 59 | /// nodes are migrated to the new nodes selected by the new `mems`. 60 | pub memory_migrate: bool, 61 | /// Running average of the memory pressured faced by the tasks in the control group. 62 | pub memory_pressure: u64, 63 | /// This field is only at the root control group and controls whether the kernel will compute 64 | /// the memory pressure for control groups or not. 65 | pub memory_pressure_enabled: Option, 66 | /// If true, filesystem buffers are spread across evenly between the nodes specified in `mems`. 67 | pub memory_spread_page: bool, 68 | /// If true, kernel slab caches for file I/O are spread across evenly between the nodes 69 | /// specified in `mems`. 70 | pub memory_spread_slab: bool, 71 | /// The list of memory nodes the tasks of the control group can use. 72 | /// 73 | /// The format is the same as the `cpus`, `effective_cpus` and `effective_mems` fields. 74 | pub mems: Vec<(u64, u64)>, 75 | /// If true, the kernel will attempt to rebalance the load between the CPUs specified in the 76 | /// `cpus` field of this control group. 77 | pub sched_load_balance: bool, 78 | /// Represents how much work the kernel should do to rebalance this cpuset. 79 | /// 80 | /// | `sched_load_balance` | Effect | 81 | /// | -------------------- | ------ | 82 | /// | -1 | Use the system default value | 83 | /// | 0 | Only balance loads periodically | 84 | /// | 1 | Immediately balance the load across tasks on the same core | 85 | /// | 2 | Immediately balance the load across cores in the same CPU package | 86 | /// | 4 | Immediately balance the load across CPUs on the same node | 87 | /// | 5 | Immediately balance the load between CPUs even if the system is NUMA | 88 | /// | 6 | Immediately balance the load between all CPUs | 89 | pub sched_relax_domain_level: u64, 90 | } 91 | 92 | impl ControllerInternal for CpuSetController { 93 | fn control_type(&self) -> Controllers { 94 | Controllers::CpuSet 95 | } 96 | fn get_path(&self) -> &PathBuf { 97 | &self.path 98 | } 99 | fn get_path_mut(&mut self) -> &mut PathBuf { 100 | &mut self.path 101 | } 102 | fn get_base(&self) -> &PathBuf { 103 | &self.base 104 | } 105 | 106 | fn is_v2(&self) -> bool { 107 | self.v2 108 | } 109 | 110 | fn apply(&self, res: &Resources) -> Result<()> { 111 | // get the resources that apply to this controller 112 | let res: &CpuResources = &res.cpu; 113 | 114 | update!(self, set_cpus, res.cpus.as_ref()); 115 | update!(self, set_mems, res.mems.as_ref()); 116 | 117 | Ok(()) 118 | } 119 | 120 | fn post_create(&self) { 121 | if self.is_v2() { 122 | return; 123 | } 124 | let current = self.get_path(); 125 | 126 | if current != self.get_base() { 127 | match copy_from_parent(current.to_str().unwrap(), "cpuset.cpus") { 128 | Ok(_) => (), 129 | Err(err) => error!("error create_dir for cpuset.cpus {:?}", err), 130 | } 131 | match copy_from_parent(current.to_str().unwrap(), "cpuset.mems") { 132 | Ok(_) => (), 133 | Err(err) => error!("error create_dir for cpuset.mems {:?}", err), 134 | } 135 | } 136 | } 137 | } 138 | 139 | fn find_no_empty_parent(from: &str, file: &str) -> Result<(String, Vec)> { 140 | let mut current_path = ::std::path::Path::new(from).to_path_buf(); 141 | let mut v = vec![]; 142 | 143 | loop { 144 | let current_value = 145 | match ::std::fs::read_to_string(current_path.clone().join(file).to_str().unwrap()) { 146 | Ok(cpus) => String::from(cpus.trim()), 147 | Err(e) => { 148 | return Err(Error::with_cause( 149 | ReadFailed(current_path.display().to_string()), 150 | e, 151 | )) 152 | } 153 | }; 154 | 155 | if !current_value.is_empty() { 156 | return Ok((current_value, v)); 157 | } 158 | v.push(current_path.clone()); 159 | 160 | let parent = match current_path.parent() { 161 | Some(p) => p, 162 | None => return Ok(("".to_string(), v)), 163 | }; 164 | 165 | // next loop, find parent 166 | current_path = parent.to_path_buf(); 167 | } 168 | } 169 | 170 | /// copy_from_parent copy the cpuset.cpus and cpuset.mems from the parent 171 | /// directory to the current directory if the file's contents are 0 172 | fn copy_from_parent(current: &str, file: &str) -> Result<()> { 173 | // find not empty cpus/memes from current directory. 174 | let (value, parents) = find_no_empty_parent(current, file)?; 175 | 176 | if value.is_empty() || parents.is_empty() { 177 | return Ok(()); 178 | } 179 | 180 | for p in parents.iter().rev() { 181 | let mut pb = p.clone(); 182 | pb.push(file); 183 | match ::std::fs::write(pb.to_str().unwrap(), value.as_bytes()) { 184 | Ok(_) => (), 185 | Err(e) => { 186 | return Err(Error::with_cause( 187 | WriteFailed(pb.display().to_string(), pb.display().to_string()), 188 | e, 189 | )) 190 | } 191 | } 192 | } 193 | 194 | Ok(()) 195 | } 196 | 197 | impl ControllIdentifier for CpuSetController { 198 | fn controller_type() -> Controllers { 199 | Controllers::CpuSet 200 | } 201 | } 202 | 203 | impl<'a> From<&'a Subsystem> for &'a CpuSetController { 204 | fn from(sub: &'a Subsystem) -> &'a CpuSetController { 205 | unsafe { 206 | match sub { 207 | Subsystem::CpuSet(c) => c, 208 | _ => { 209 | assert_eq!(1, 0); 210 | let v = std::mem::MaybeUninit::uninit(); 211 | v.assume_init() 212 | } 213 | } 214 | } 215 | } 216 | } 217 | 218 | /// Parse a string like "1,2,4-5,8" into a list of (start, end) tuples. 219 | fn parse_range(s: String) -> Result> { 220 | let mut fin = Vec::new(); 221 | 222 | if s.is_empty() { 223 | return Ok(fin); 224 | } 225 | 226 | // first split by commas 227 | let comma_split = s.split(','); 228 | 229 | for sp in comma_split { 230 | if sp.contains('-') { 231 | // this is a true range 232 | let dash_split = sp.split('-').collect::>(); 233 | if dash_split.len() != 2 { 234 | return Err(Error::new(ParseError)); 235 | } 236 | let first = dash_split[0].parse::(); 237 | let second = dash_split[1].parse::(); 238 | if first.is_err() || second.is_err() { 239 | return Err(Error::new(ParseError)); 240 | } 241 | fin.push((first.unwrap(), second.unwrap())); 242 | } else { 243 | // this is just a single number 244 | let num = sp.parse::(); 245 | if num.is_err() { 246 | return Err(Error::new(ParseError)); 247 | } 248 | fin.push((num.clone().unwrap(), num.clone().unwrap())); 249 | } 250 | } 251 | 252 | Ok(fin) 253 | } 254 | 255 | impl CpuSetController { 256 | /// Contructs a new `CpuSetController` with `root` serving as the root of the control group. 257 | pub fn new(point: PathBuf, root: PathBuf, v2: bool) -> Self { 258 | Self { 259 | base: root, 260 | path: point, 261 | v2, 262 | } 263 | } 264 | 265 | /// Returns the statistics gathered by the kernel for this control group. See the struct for 266 | /// more information on what information this entails. 267 | pub fn cpuset(&self) -> CpuSet { 268 | CpuSet { 269 | cpu_exclusive: { 270 | self.open_path("cpuset.cpu_exclusive", false) 271 | .and_then(read_u64_from) 272 | .map(|x| x == 1) 273 | .unwrap_or(false) 274 | }, 275 | cpus: { 276 | self.open_path("cpuset.cpus", false) 277 | .and_then(read_string_from) 278 | .and_then(parse_range) 279 | .unwrap_or_default() 280 | }, 281 | effective_cpus: { 282 | self.open_path("cpuset.effective_cpus", false) 283 | .and_then(read_string_from) 284 | .and_then(parse_range) 285 | .unwrap_or_default() 286 | }, 287 | effective_mems: { 288 | self.open_path("cpuset.effective_mems", false) 289 | .and_then(read_string_from) 290 | .and_then(parse_range) 291 | .unwrap_or_default() 292 | }, 293 | mem_exclusive: { 294 | self.open_path("cpuset.mem_exclusive", false) 295 | .and_then(read_u64_from) 296 | .map(|x| x == 1) 297 | .unwrap_or(false) 298 | }, 299 | mem_hardwall: { 300 | self.open_path("cpuset.mem_hardwall", false) 301 | .and_then(read_u64_from) 302 | .map(|x| x == 1) 303 | .unwrap_or(false) 304 | }, 305 | memory_migrate: { 306 | self.open_path("cpuset.memory_migrate", false) 307 | .and_then(read_u64_from) 308 | .map(|x| x == 1) 309 | .unwrap_or(false) 310 | }, 311 | memory_pressure: { 312 | self.open_path("cpuset.memory_pressure", false) 313 | .and_then(read_u64_from) 314 | .unwrap_or(0) 315 | }, 316 | memory_pressure_enabled: { 317 | self.open_path("cpuset.memory_pressure_enabled", false) 318 | .and_then(read_u64_from) 319 | .map(|x| x == 1) 320 | .ok() 321 | }, 322 | memory_spread_page: { 323 | self.open_path("cpuset.memory_spread_page", false) 324 | .and_then(read_u64_from) 325 | .map(|x| x == 1) 326 | .unwrap_or(false) 327 | }, 328 | memory_spread_slab: { 329 | self.open_path("cpuset.memory_spread_slab", false) 330 | .and_then(read_u64_from) 331 | .map(|x| x == 1) 332 | .unwrap_or(false) 333 | }, 334 | mems: { 335 | self.open_path("cpuset.mems", false) 336 | .and_then(read_string_from) 337 | .and_then(parse_range) 338 | .unwrap_or_default() 339 | }, 340 | sched_load_balance: { 341 | self.open_path("cpuset.sched_load_balance", false) 342 | .and_then(read_u64_from) 343 | .map(|x| x == 1) 344 | .unwrap_or(false) 345 | }, 346 | sched_relax_domain_level: { 347 | self.open_path("cpuset.sched_relax_domain_level", false) 348 | .and_then(read_u64_from) 349 | .unwrap_or(0) 350 | }, 351 | } 352 | } 353 | 354 | /// Control whether the CPUs selected via `set_cpus()` should be exclusive to this control 355 | /// group or not. 356 | pub fn set_cpu_exclusive(&self, b: bool) -> Result<()> { 357 | self.open_path("cpuset.cpu_exclusive", true) 358 | .and_then(|mut file| { 359 | if b { 360 | file.write_all(b"1").map_err(|e| { 361 | Error::with_cause( 362 | WriteFailed("cpuset.cpu_exclusive".to_string(), "1".to_string()), 363 | e, 364 | ) 365 | }) 366 | } else { 367 | file.write_all(b"0").map_err(|e| { 368 | Error::with_cause( 369 | WriteFailed("cpuset.cpu_exclusive".to_string(), "0".to_string()), 370 | e, 371 | ) 372 | }) 373 | } 374 | }) 375 | } 376 | 377 | /// Control whether the memory nodes selected via `set_memss()` should be exclusive to this control 378 | /// group or not. 379 | pub fn set_mem_exclusive(&self, b: bool) -> Result<()> { 380 | self.open_path("cpuset.mem_exclusive", true) 381 | .and_then(|mut file| { 382 | if b { 383 | file.write_all(b"1").map_err(|e| { 384 | Error::with_cause( 385 | WriteFailed("cpuset.mem_exclusive".to_string(), "1".to_string()), 386 | e, 387 | ) 388 | }) 389 | } else { 390 | file.write_all(b"0").map_err(|e| { 391 | Error::with_cause( 392 | WriteFailed("cpuset.mem_exclusive".to_string(), "0".to_string()), 393 | e, 394 | ) 395 | }) 396 | } 397 | }) 398 | } 399 | 400 | /// Set the CPUs that the tasks in this control group can run on. 401 | /// 402 | /// Syntax is a comma separated list of CPUs, with an additional extension that ranges can 403 | /// be represented via dashes. 404 | pub fn set_cpus(&self, cpus: &str) -> Result<()> { 405 | self.open_path("cpuset.cpus", true).and_then(|mut file| { 406 | file.write_all(cpus.as_ref()).map_err(|e| { 407 | Error::with_cause(WriteFailed("cpuset.cpus".to_string(), cpus.to_string()), e) 408 | }) 409 | }) 410 | } 411 | 412 | /// Set the memory nodes that the tasks in this control group can use. 413 | /// 414 | /// Syntax is the same as with `set_cpus()`. 415 | pub fn set_mems(&self, mems: &str) -> Result<()> { 416 | self.open_path("cpuset.mems", true).and_then(|mut file| { 417 | file.write_all(mems.as_ref()).map_err(|e| { 418 | Error::with_cause(WriteFailed("cpuset.mems".to_string(), mems.to_string()), e) 419 | }) 420 | }) 421 | } 422 | 423 | /// Controls whether the control group should be "hardwalled", i.e., whether kernel allocations 424 | /// should exclusively use the memory nodes set via `set_mems()`. 425 | /// 426 | /// Note that some kernel allocations, most notably those that are made in interrupt handlers 427 | /// may disregard this. 428 | pub fn set_hardwall(&self, b: bool) -> Result<()> { 429 | self.open_path("cpuset.mem_hardwall", true) 430 | .and_then(|mut file| { 431 | if b { 432 | file.write_all(b"1").map_err(|e| { 433 | Error::with_cause( 434 | WriteFailed("cpuset.mem_hardwall".to_string(), "1".to_string()), 435 | e, 436 | ) 437 | }) 438 | } else { 439 | file.write_all(b"0").map_err(|e| { 440 | Error::with_cause( 441 | WriteFailed("cpuset.mem_hardwall".to_string(), "0".to_string()), 442 | e, 443 | ) 444 | }) 445 | } 446 | }) 447 | } 448 | 449 | /// Controls whether the kernel should attempt to rebalance the load between the CPUs specified in the 450 | /// `cpus` field of this control group. 451 | pub fn set_load_balancing(&self, b: bool) -> Result<()> { 452 | self.open_path("cpuset.sched_load_balance", true) 453 | .and_then(|mut file| { 454 | if b { 455 | file.write_all(b"1").map_err(|e| { 456 | Error::with_cause( 457 | WriteFailed("cpuset.sched_load_balance".to_string(), "1".to_string()), 458 | e, 459 | ) 460 | }) 461 | } else { 462 | file.write_all(b"0").map_err(|e| { 463 | Error::with_cause( 464 | WriteFailed("cpuset.sched_load_balance".to_string(), "0".to_string()), 465 | e, 466 | ) 467 | }) 468 | } 469 | }) 470 | } 471 | 472 | /// Contorl how much effort the kernel should invest in rebalacing the control group. 473 | /// 474 | /// See @CpuSet 's similar field for more information. 475 | pub fn set_rebalance_relax_domain_level(&self, i: i64) -> Result<()> { 476 | self.open_path("cpuset.sched_relax_domain_level", true) 477 | .and_then(|mut file| { 478 | file.write_all(i.to_string().as_ref()).map_err(|e| { 479 | Error::with_cause( 480 | WriteFailed("cpuset.sched_relax_domain_level".to_string(), i.to_string()), 481 | e, 482 | ) 483 | }) 484 | }) 485 | } 486 | 487 | /// Control whether when using `set_mems()` the existing memory used by the tasks should be 488 | /// migrated over to the now-selected nodes. 489 | pub fn set_memory_migration(&self, b: bool) -> Result<()> { 490 | self.open_path("cpuset.memory_migrate", true) 491 | .and_then(|mut file| { 492 | if b { 493 | file.write_all(b"1").map_err(|e| { 494 | Error::with_cause( 495 | WriteFailed("cpuset.memory_migrate".to_string(), "1".to_string()), 496 | e, 497 | ) 498 | }) 499 | } else { 500 | file.write_all(b"0").map_err(|e| { 501 | Error::with_cause( 502 | WriteFailed("cpuset.memory_migrate".to_string(), "0".to_string()), 503 | e, 504 | ) 505 | }) 506 | } 507 | }) 508 | } 509 | 510 | /// Control whether filesystem buffers should be evenly split across the nodes selected via 511 | /// `set_mems()`. 512 | pub fn set_memory_spread_page(&self, b: bool) -> Result<()> { 513 | self.open_path("cpuset.memory_spread_page", true) 514 | .and_then(|mut file| { 515 | if b { 516 | file.write_all(b"1").map_err(|e| { 517 | Error::with_cause( 518 | WriteFailed("cpuset.memory_spread_page".to_string(), "1".to_string()), 519 | e, 520 | ) 521 | }) 522 | } else { 523 | file.write_all(b"0").map_err(|e| { 524 | Error::with_cause( 525 | WriteFailed("cpuset.memory_spread_page".to_string(), "0".to_string()), 526 | e, 527 | ) 528 | }) 529 | } 530 | }) 531 | } 532 | 533 | /// Control whether the kernel's slab cache for file I/O should be evenly split across the 534 | /// nodes selected via `set_mems()`. 535 | pub fn set_memory_spread_slab(&self, b: bool) -> Result<()> { 536 | self.open_path("cpuset.memory_spread_slab", true) 537 | .and_then(|mut file| { 538 | if b { 539 | file.write_all(b"1").map_err(|e| { 540 | Error::with_cause( 541 | WriteFailed("cpuset.memory_spread_slab".to_string(), "1".to_string()), 542 | e, 543 | ) 544 | }) 545 | } else { 546 | file.write_all(b"0").map_err(|e| { 547 | Error::with_cause( 548 | WriteFailed("cpuset.memory_spread_slab".to_string(), "0".to_string()), 549 | e, 550 | ) 551 | }) 552 | } 553 | }) 554 | } 555 | 556 | /// Control whether the kernel should collect information to calculate memory pressure for 557 | /// control groups. 558 | /// 559 | /// Note: This will fail with `InvalidOperation` if the current congrol group is not the root 560 | /// control group. 561 | pub fn set_enable_memory_pressure(&self, b: bool) -> Result<()> { 562 | if !self.path_exists("cpuset.memory_pressure_enabled") { 563 | return Err(Error::new(InvalidOperation)); 564 | } 565 | self.open_path("cpuset.memory_pressure_enabled", true) 566 | .and_then(|mut file| { 567 | if b { 568 | file.write_all(b"1").map_err(|e| { 569 | Error::with_cause( 570 | WriteFailed( 571 | "cpuset.memory_pressure_enabled".to_string(), 572 | "1".to_string(), 573 | ), 574 | e, 575 | ) 576 | }) 577 | } else { 578 | file.write_all(b"0").map_err(|e| { 579 | Error::with_cause( 580 | WriteFailed( 581 | "cpuset.memory_pressure_enabled".to_string(), 582 | "0".to_string(), 583 | ), 584 | e, 585 | ) 586 | }) 587 | } 588 | }) 589 | } 590 | } 591 | 592 | #[cfg(test)] 593 | mod tests { 594 | use crate::cpuset; 595 | #[test] 596 | fn test_parse_range() { 597 | let test_cases = vec![ 598 | "1,2,4-6,9".to_string(), 599 | "".to_string(), 600 | "1".to_string(), 601 | "1-111".to_string(), 602 | "1,2,3,4".to_string(), 603 | "1-5,6-7,8-9".to_string(), 604 | ]; 605 | let expecteds = vec![ 606 | vec![(1, 1), (2, 2), (4, 6), (9, 9)], 607 | vec![], 608 | vec![(1, 1)], 609 | vec![(1, 111)], 610 | vec![(1, 1), (2, 2), (3, 3), (4, 4)], 611 | vec![(1, 5), (6, 7), (8, 9)], 612 | ]; 613 | 614 | for (i, case) in test_cases.into_iter().enumerate() { 615 | let range = cpuset::parse_range(case.clone()); 616 | println!("{:?} => {:?}", case, range); 617 | assert!(range.is_ok()); 618 | assert_eq!(range.unwrap(), expecteds[i]); 619 | } 620 | } 621 | } 622 | -------------------------------------------------------------------------------- /src/devices.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Levente Kurusa 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 or MIT 4 | // 5 | 6 | //! This module contains the implementation of the `devices` cgroup subsystem. 7 | //! 8 | //! See the Kernel's documentation for more information about this subsystem, found at: 9 | //! [Documentation/cgroup-v1/devices.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/devices.txt) 10 | use std::io::{Read, Write}; 11 | use std::path::PathBuf; 12 | 13 | use log::*; 14 | 15 | use crate::error::ErrorKind::*; 16 | use crate::error::*; 17 | 18 | use crate::{ 19 | ControllIdentifier, ControllerInternal, Controllers, DeviceResource, DeviceResources, 20 | Resources, Subsystem, 21 | }; 22 | 23 | /// A controller that allows controlling the `devices` subsystem of a Cgroup. 24 | /// 25 | /// In essence, using the devices controller, it is possible to allow or disallow sets of devices to 26 | /// be used by the control group's tasks. 27 | #[derive(Debug, Clone)] 28 | pub struct DevicesController { 29 | base: PathBuf, 30 | path: PathBuf, 31 | } 32 | 33 | /// An enum holding the different types of devices that can be manipulated using this controller. 34 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] 35 | #[cfg_attr( 36 | feature = "serde", 37 | derive(serde::Serialize, serde::Deserialize), 38 | serde(rename_all = "snake_case") 39 | )] 40 | pub enum DeviceType { 41 | /// The rule applies to all devices. 42 | All, 43 | /// The rule only applies to character devices. 44 | Char, 45 | /// The rule only applies to block devices. 46 | Block, 47 | } 48 | 49 | #[allow(clippy::derivable_impls)] 50 | impl Default for DeviceType { 51 | fn default() -> Self { 52 | DeviceType::All 53 | } 54 | } 55 | 56 | impl DeviceType { 57 | /// Convert a DeviceType into the character that the kernel recognizes. 58 | #[allow(clippy::should_implement_trait, clippy::wrong_self_convention)] 59 | pub fn to_char(&self) -> char { 60 | match self { 61 | DeviceType::All => 'a', 62 | DeviceType::Char => 'c', 63 | DeviceType::Block => 'b', 64 | } 65 | } 66 | 67 | /// Convert the kenrel's representation into the DeviceType type. 68 | pub fn from_char(c: Option) -> Option { 69 | match c { 70 | Some('a') => Some(DeviceType::All), 71 | Some('c') => Some(DeviceType::Char), 72 | Some('b') => Some(DeviceType::Block), 73 | _ => None, 74 | } 75 | } 76 | } 77 | 78 | /// An enum with the permissions that can be allowed/denied to the control group. 79 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] 80 | #[cfg_attr( 81 | feature = "serde", 82 | derive(serde::Serialize, serde::Deserialize), 83 | serde(rename_all = "snake_case") 84 | )] 85 | pub enum DevicePermissions { 86 | /// Permission to read from the device. 87 | Read, 88 | /// Permission to write to the device. 89 | Write, 90 | /// Permission to execute the `mknod(2)` system call with the device's major and minor numbers. 91 | /// That is, the permission to create a special file that refers to the device node. 92 | MkNod, 93 | } 94 | 95 | impl DevicePermissions { 96 | /// Convert a DevicePermissions into the character that the kernel recognizes. 97 | #[allow(clippy::should_implement_trait, clippy::wrong_self_convention)] 98 | pub fn to_char(&self) -> char { 99 | match self { 100 | DevicePermissions::Read => 'r', 101 | DevicePermissions::Write => 'w', 102 | DevicePermissions::MkNod => 'm', 103 | } 104 | } 105 | 106 | /// Convert a char to a DevicePermission if there is such a mapping. 107 | pub fn from_char(c: char) -> Option { 108 | match c { 109 | 'r' => Some(DevicePermissions::Read), 110 | 'w' => Some(DevicePermissions::Write), 111 | 'm' => Some(DevicePermissions::MkNod), 112 | _ => None, 113 | } 114 | } 115 | 116 | /// Checks whether the string is a valid descriptor of DevicePermissions. 117 | pub fn is_valid(s: &str) -> bool { 118 | if s.is_empty() { 119 | return false; 120 | } 121 | for i in s.chars() { 122 | if i != 'r' && i != 'w' && i != 'm' { 123 | return false; 124 | } 125 | } 126 | true 127 | } 128 | 129 | /// Returns a Vec will all the permissions that a device can have. 130 | pub fn all() -> Vec { 131 | vec![ 132 | DevicePermissions::Read, 133 | DevicePermissions::Write, 134 | DevicePermissions::MkNod, 135 | ] 136 | } 137 | 138 | /// Convert a string into DevicePermissions. 139 | #[allow(clippy::should_implement_trait)] 140 | pub fn from_str(s: &str) -> Result> { 141 | let mut v = Vec::new(); 142 | if s.is_empty() { 143 | return Ok(v); 144 | } 145 | for e in s.chars() { 146 | let perm = DevicePermissions::from_char(e).ok_or_else(|| Error::new(ParseError))?; 147 | v.push(perm); 148 | } 149 | 150 | Ok(v) 151 | } 152 | } 153 | 154 | impl ControllerInternal for DevicesController { 155 | fn control_type(&self) -> Controllers { 156 | Controllers::Devices 157 | } 158 | fn get_path(&self) -> &PathBuf { 159 | &self.path 160 | } 161 | fn get_path_mut(&mut self) -> &mut PathBuf { 162 | &mut self.path 163 | } 164 | fn get_base(&self) -> &PathBuf { 165 | &self.base 166 | } 167 | 168 | fn apply(&self, res: &Resources) -> Result<()> { 169 | // get the resources that apply to this controller 170 | let res: &DeviceResources = &res.devices; 171 | 172 | for i in &res.devices { 173 | if i.allow { 174 | self.allow_device(i.devtype, i.major, i.minor, &i.access)?; 175 | } else { 176 | self.deny_device(i.devtype, i.major, i.minor, &i.access)?; 177 | } 178 | } 179 | 180 | Ok(()) 181 | } 182 | } 183 | 184 | impl ControllIdentifier for DevicesController { 185 | fn controller_type() -> Controllers { 186 | Controllers::Devices 187 | } 188 | } 189 | 190 | impl<'a> From<&'a Subsystem> for &'a DevicesController { 191 | fn from(sub: &'a Subsystem) -> &'a DevicesController { 192 | unsafe { 193 | match sub { 194 | Subsystem::Devices(c) => c, 195 | _ => { 196 | assert_eq!(1, 0); 197 | let v = std::mem::MaybeUninit::uninit(); 198 | v.assume_init() 199 | } 200 | } 201 | } 202 | } 203 | } 204 | 205 | impl DevicesController { 206 | /// Constructs a new `DevicesController` with `root` serving as the root of the control group. 207 | pub fn new(point: PathBuf, root: PathBuf) -> Self { 208 | Self { 209 | base: root, 210 | path: point, 211 | } 212 | } 213 | 214 | /// Allow a (possibly, set of) device(s) to be used by the tasks in the control group. 215 | /// 216 | /// When `-1` is passed as `major` or `minor`, the kernel interprets that value as "any", 217 | /// meaning that it will match any device. 218 | pub fn allow_device( 219 | &self, 220 | devtype: DeviceType, 221 | major: i64, 222 | minor: i64, 223 | perm: &[DevicePermissions], 224 | ) -> Result<()> { 225 | let perms = perm 226 | .iter() 227 | .map(DevicePermissions::to_char) 228 | .collect::(); 229 | let minor = if minor == -1 { 230 | "*".to_string() 231 | } else { 232 | format!("{}", minor) 233 | }; 234 | let major = if major == -1 { 235 | "*".to_string() 236 | } else { 237 | format!("{}", major) 238 | }; 239 | let final_str = format!("{} {}:{} {}", devtype.to_char(), major, minor, perms); 240 | self.open_path("devices.allow", true).and_then(|mut file| { 241 | file.write_all(final_str.as_ref()).map_err(|e| { 242 | Error::with_cause( 243 | WriteFailed( 244 | self.get_path().join("devices.allow").display().to_string(), 245 | final_str, 246 | ), 247 | e, 248 | ) 249 | }) 250 | }) 251 | } 252 | 253 | /// Deny the control group's tasks access to the devices covered by `dev`. 254 | /// 255 | /// When `-1` is passed as `major` or `minor`, the kernel interprets that value as "any", 256 | /// meaning that it will match any device. 257 | pub fn deny_device( 258 | &self, 259 | devtype: DeviceType, 260 | major: i64, 261 | minor: i64, 262 | perm: &[DevicePermissions], 263 | ) -> Result<()> { 264 | let perms = perm 265 | .iter() 266 | .map(DevicePermissions::to_char) 267 | .collect::(); 268 | let minor = if minor == -1 { 269 | "*".to_string() 270 | } else { 271 | format!("{}", minor) 272 | }; 273 | let major = if major == -1 { 274 | "*".to_string() 275 | } else { 276 | format!("{}", major) 277 | }; 278 | let final_str = format!("{} {}:{} {}", devtype.to_char(), major, minor, perms); 279 | self.open_path("devices.deny", true).and_then(|mut file| { 280 | file.write_all(final_str.as_ref()).map_err(|e| { 281 | Error::with_cause( 282 | WriteFailed( 283 | self.get_path().join("devices.deny").display().to_string(), 284 | final_str, 285 | ), 286 | e, 287 | ) 288 | }) 289 | }) 290 | } 291 | 292 | /// Get the current list of allowed devices. 293 | pub fn allowed_devices(&self) -> Result> { 294 | self.open_path("devices.list", false).and_then(|mut file| { 295 | let mut s = String::new(); 296 | let res = file.read_to_string(&mut s); 297 | match res { 298 | Ok(_) => { 299 | s.lines().fold(Ok(Vec::new()), |acc, line| { 300 | let ls = line.split(|c| c == ' ' || c == ':').map(|x| x.to_string()).collect::>(); 301 | if acc.is_err() || ls.len() != 4 { 302 | error!("allowed_devices: acc: {:?}, ls: {:?}", acc, ls); 303 | Err(Error::new(ParseError)) 304 | } else { 305 | let devtype = DeviceType::from_char(ls[0].chars().next()); 306 | let mut major = ls[1].parse::(); 307 | let mut minor = ls[2].parse::(); 308 | if major.is_err() && ls[1] == "*" { 309 | major = Ok(-1); 310 | } 311 | if minor.is_err() && ls[2] == "*" { 312 | minor = Ok(-1); 313 | } 314 | if devtype.is_none() || major.is_err() || minor.is_err() || !DevicePermissions::is_valid(&ls[3]) { 315 | error!("allowed_devices: acc: {:?}, ls: {:?}, devtype: {:?}, major {:?} minor {:?} ls3 {:?}", 316 | acc, ls, devtype, major, minor, &ls[3]); 317 | Err(Error::new(ParseError)) 318 | } else { 319 | let access = DevicePermissions::from_str(&ls[3])?; 320 | let mut acc = acc.unwrap(); 321 | acc.push(DeviceResource { 322 | allow: true, 323 | devtype: devtype.unwrap(), 324 | major: major.unwrap(), 325 | minor: minor.unwrap(), 326 | access, 327 | }); 328 | Ok(acc) 329 | } 330 | } 331 | }) 332 | }, 333 | Err(e) => Err(Error::with_cause(ReadFailed("devices.list".to_string()), e)), 334 | } 335 | }) 336 | } 337 | } 338 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Levente Kurusa 2 | // Copyright (c) 2020 Ant Group 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 or MIT 5 | // 6 | 7 | use std::error::Error as StdError; 8 | use std::fmt; 9 | 10 | /// The different types of errors that can occur while manipulating control groups. 11 | #[derive(thiserror::Error, Debug, Eq, PartialEq)] 12 | pub enum ErrorKind { 13 | #[error("fs error")] 14 | FsError, 15 | 16 | #[error("common error: {0}")] 17 | Common(String), 18 | 19 | /// An error occured while writing to a control group file. 20 | #[error("unable to write to a control group file {0}, value {1}")] 21 | WriteFailed(String, String), 22 | 23 | /// An error occured while trying to read from a control group file. 24 | #[error("unable to read a control group file {0}")] 25 | ReadFailed(String), 26 | 27 | /// An error occured while trying to remove a control group. 28 | #[error("unable to remove a control group")] 29 | RemoveFailed, 30 | 31 | /// An error occured while trying to parse a value from a control group file. 32 | /// 33 | /// In the future, there will be some information attached to this field. 34 | #[error("unable to parse control group file")] 35 | ParseError, 36 | 37 | /// You tried to do something invalid. 38 | /// 39 | /// This could be because you tried to set a value in a control group that is not a root 40 | /// control group. Or, when using unified hierarchy, you tried to add a task in a leaf node. 41 | #[error("the requested operation is invalid")] 42 | InvalidOperation, 43 | 44 | /// The path of the control group was invalid. 45 | /// 46 | /// This could be caused by trying to escape the control group filesystem via a string of "..". 47 | /// This crate checks against this and operations will fail with this error. 48 | #[error("the given path is invalid")] 49 | InvalidPath, 50 | 51 | #[error("invalid bytes size")] 52 | InvalidBytesSize, 53 | 54 | /// The specified controller is not in the list of supported controllers. 55 | #[error("specified controller is not in the list of supported controllers")] 56 | SpecifiedControllers, 57 | 58 | /// Using method in wrong cgroup version. 59 | #[error("using method in wrong cgroup version")] 60 | CgroupVersion, 61 | 62 | /// Using method in wrong cgroup mode. 63 | #[error("using method in wrong cgroup mode.")] 64 | CgroupMode, 65 | 66 | /// Subsystems is empty. 67 | #[error("subsystems is empty")] 68 | SubsystemsEmpty, 69 | 70 | /// An unknown error has occured. 71 | #[error("an unknown error")] 72 | Other, 73 | } 74 | 75 | #[derive(Debug)] 76 | pub struct Error { 77 | kind: ErrorKind, 78 | cause: Option>, 79 | } 80 | 81 | impl fmt::Display for Error { 82 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 83 | if let Some(cause) = &self.cause { 84 | write!(f, "{} caused by: {:?}", &self.kind, cause) 85 | } else { 86 | write!(f, "{}", &self.kind) 87 | } 88 | } 89 | } 90 | 91 | impl StdError for Error { 92 | fn source(&self) -> Option<&(dyn StdError + 'static)> { 93 | #[allow(clippy::manual_map)] 94 | match self.cause { 95 | Some(ref x) => Some(&**x), 96 | None => None, 97 | } 98 | } 99 | } 100 | 101 | impl Error { 102 | pub(crate) fn from_string(s: String) -> Self { 103 | Self { 104 | kind: ErrorKind::Common(s), 105 | cause: None, 106 | } 107 | } 108 | pub(crate) fn new(kind: ErrorKind) -> Self { 109 | Self { kind, cause: None } 110 | } 111 | 112 | pub(crate) fn with_cause(kind: ErrorKind, cause: E) -> Self 113 | where 114 | E: 'static + Send + Sync + StdError, 115 | { 116 | Self { 117 | kind, 118 | cause: Some(Box::new(cause)), 119 | } 120 | } 121 | 122 | pub fn kind(&self) -> &ErrorKind { 123 | &self.kind 124 | } 125 | } 126 | 127 | pub type Result = ::std::result::Result; 128 | -------------------------------------------------------------------------------- /src/events.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Ant Group 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 or MIT 4 | // 5 | 6 | use eventfd::{eventfd, EfdFlags}; 7 | use nix::sys::eventfd; 8 | use std::fs::{self, File}; 9 | use std::io::Read; 10 | use std::os::unix::io::{AsRawFd, FromRawFd}; 11 | use std::path::Path; 12 | use std::sync::mpsc::{self, Receiver}; 13 | use std::thread; 14 | 15 | use crate::error::ErrorKind::*; 16 | use crate::error::*; 17 | 18 | // notify_on_oom returns channel on which you can expect event about OOM, 19 | // if process died without OOM this channel will be closed. 20 | pub fn notify_on_oom_v2(key: &str, dir: &Path) -> Result> { 21 | register_memory_event(key, dir, "memory.oom_control", "") 22 | } 23 | 24 | // notify_on_oom returns channel on which you can expect event about OOM, 25 | // if process died without OOM this channel will be closed. 26 | pub fn notify_on_oom_v1(key: &str, dir: &Path) -> Result> { 27 | register_memory_event(key, dir, "memory.oom_control", "") 28 | } 29 | 30 | // level is one of "low", "medium", or "critical" 31 | pub fn notify_memory_pressure(key: &str, dir: &Path, level: &str) -> Result> { 32 | if level != "low" && level != "medium" && level != "critical" { 33 | return Err(Error::from_string(format!( 34 | "invalid pressure level {}", 35 | level 36 | ))); 37 | } 38 | 39 | register_memory_event(key, dir, "memory.pressure_level", level) 40 | } 41 | 42 | fn register_memory_event( 43 | key: &str, 44 | cg_dir: &Path, 45 | event_name: &str, 46 | arg: &str, 47 | ) -> Result> { 48 | let path = cg_dir.join(event_name); 49 | let event_file = File::open(path.clone()) 50 | .map_err(|e| Error::with_cause(ReadFailed(path.display().to_string()), e))?; 51 | 52 | let eventfd = eventfd(0, EfdFlags::EFD_CLOEXEC) 53 | .map_err(|e| Error::with_cause(ReadFailed("eventfd".to_string()), e))?; 54 | 55 | let event_control_path = cg_dir.join("cgroup.event_control"); 56 | let data = if arg.is_empty() { 57 | format!("{} {}", eventfd, event_file.as_raw_fd()) 58 | } else { 59 | format!("{} {} {}", eventfd, event_file.as_raw_fd(), arg) 60 | }; 61 | 62 | // write to file and set mode to 0700(FIXME) 63 | fs::write(&event_control_path, data.clone()).map_err(|e| { 64 | Error::with_cause( 65 | WriteFailed(event_control_path.display().to_string(), data), 66 | e, 67 | ) 68 | })?; 69 | 70 | let mut eventfd_file = unsafe { File::from_raw_fd(eventfd) }; 71 | 72 | let (sender, receiver) = mpsc::channel(); 73 | let key = key.to_string(); 74 | 75 | thread::spawn(move || { 76 | loop { 77 | let mut buf = [0; 8]; 78 | if eventfd_file.read(&mut buf).is_err() { 79 | return; 80 | } 81 | 82 | // When a cgroup is destroyed, an event is sent to eventfd. 83 | // So if the control path is gone, return instead of notifying. 84 | if !Path::new(&event_control_path).exists() { 85 | return; 86 | } 87 | sender.send(key.clone()).unwrap(); 88 | } 89 | }); 90 | 91 | Ok(receiver) 92 | } 93 | -------------------------------------------------------------------------------- /src/freezer.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Levente Kurusa 2 | // Copyright (c) 2020 Ant Group 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 or MIT 5 | // 6 | 7 | //! This module contains the implementation of the `freezer` cgroup subsystem. 8 | //! 9 | //! See the Kernel's documentation for more information about this subsystem, found at: 10 | //! [Documentation/cgroup-v1/freezer-subsystem.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/freezer-subsystem.txt) 11 | use std::io::{Read, Write}; 12 | use std::path::PathBuf; 13 | 14 | use crate::error::ErrorKind::*; 15 | use crate::error::*; 16 | 17 | use crate::{ControllIdentifier, ControllerInternal, Controllers, Resources, Subsystem}; 18 | 19 | /// A controller that allows controlling the `freezer` subsystem of a Cgroup. 20 | /// 21 | /// In essence, this subsystem allows the user to freeze and thaw (== "un-freeze") the processes in 22 | /// the control group. This is done _transparently_ so that neither the parent, nor the children of 23 | /// the processes can observe the freeze. 24 | /// 25 | /// Note that if the control group is currently in the `Frozen` or `Freezing` state, then no 26 | /// processes can be added to it. 27 | #[derive(Debug, Clone)] 28 | pub struct FreezerController { 29 | base: PathBuf, 30 | path: PathBuf, 31 | v2: bool, 32 | } 33 | 34 | /// The current state of the control group 35 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 36 | pub enum FreezerState { 37 | /// The processes in the control group are _not_ frozen. 38 | Thawed, 39 | /// The processes in the control group are in the processes of being frozen. 40 | Freezing, 41 | /// The processes in the control group are frozen. 42 | Frozen, 43 | } 44 | 45 | impl ControllerInternal for FreezerController { 46 | fn control_type(&self) -> Controllers { 47 | Controllers::Freezer 48 | } 49 | fn get_path(&self) -> &PathBuf { 50 | &self.path 51 | } 52 | fn get_path_mut(&mut self) -> &mut PathBuf { 53 | &mut self.path 54 | } 55 | fn get_base(&self) -> &PathBuf { 56 | &self.base 57 | } 58 | 59 | fn apply(&self, _res: &Resources) -> Result<()> { 60 | Ok(()) 61 | } 62 | } 63 | 64 | impl ControllIdentifier for FreezerController { 65 | fn controller_type() -> Controllers { 66 | Controllers::Freezer 67 | } 68 | } 69 | 70 | impl<'a> From<&'a Subsystem> for &'a FreezerController { 71 | fn from(sub: &'a Subsystem) -> &'a FreezerController { 72 | unsafe { 73 | match sub { 74 | Subsystem::Freezer(c) => c, 75 | _ => { 76 | assert_eq!(1, 0); 77 | let v = std::mem::MaybeUninit::uninit(); 78 | v.assume_init() 79 | } 80 | } 81 | } 82 | } 83 | } 84 | 85 | impl FreezerController { 86 | /// Contructs a new `FreezerController` with `root` serving as the root of the control group. 87 | pub fn new(point: PathBuf, root: PathBuf, v2: bool) -> Self { 88 | Self { 89 | base: root, 90 | path: point, 91 | v2, 92 | } 93 | } 94 | /// Freezes the processes in the control group. 95 | pub fn freeze(&self) -> Result<()> { 96 | let mut file_name = "freezer.state"; 97 | let mut content = "FROZEN".to_string(); 98 | if self.v2 { 99 | file_name = "cgroup.freeze"; 100 | content = "1".to_string(); 101 | } 102 | 103 | self.open_path(file_name, true).and_then(|mut file| { 104 | file.write_all(content.as_ref()) 105 | .map_err(|e| Error::with_cause(WriteFailed(file_name.to_string(), content), e)) 106 | }) 107 | } 108 | 109 | /// Thaws, that is, unfreezes the processes in the control group. 110 | pub fn thaw(&self) -> Result<()> { 111 | let mut file_name = "freezer.state"; 112 | let mut content = "THAWED".to_string(); 113 | if self.v2 { 114 | file_name = "cgroup.freeze"; 115 | content = "0".to_string(); 116 | } 117 | self.open_path(file_name, true).and_then(|mut file| { 118 | file.write_all(content.as_ref()) 119 | .map_err(|e| Error::with_cause(WriteFailed(file_name.to_string(), content), e)) 120 | }) 121 | } 122 | 123 | /// Retrieve the state of processes in the control group. 124 | pub fn state(&self) -> Result { 125 | let mut file_name = "freezer.state"; 126 | if self.v2 { 127 | file_name = "cgroup.freeze"; 128 | } 129 | self.open_path(file_name, false).and_then(|mut file| { 130 | let mut s = String::new(); 131 | let res = file.read_to_string(&mut s); 132 | match res { 133 | Ok(_) => match s.trim() { 134 | "FROZEN" => Ok(FreezerState::Frozen), 135 | "THAWED" => Ok(FreezerState::Thawed), 136 | "1" => Ok(FreezerState::Frozen), 137 | "0" => Ok(FreezerState::Thawed), 138 | "FREEZING" => Ok(FreezerState::Freezing), 139 | _ => Err(Error::new(ParseError)), 140 | }, 141 | Err(e) => Err(Error::with_cause(ReadFailed(file_name.to_string()), e)), 142 | } 143 | }) 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/hierarchies.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Levente Kurusa 2 | // Copyright (c) 2020 Ant Group 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 or MIT 5 | // 6 | 7 | //! This module represents the various control group hierarchies the Linux kernel supports. 8 | 9 | use std::fs; 10 | use std::fs::File; 11 | use std::io::{BufRead, BufReader}; 12 | use std::path::{Path, PathBuf}; 13 | 14 | use crate::blkio::BlkIoController; 15 | use crate::cpu::CpuController; 16 | use crate::cpuacct::CpuAcctController; 17 | use crate::cpuset::CpuSetController; 18 | use crate::devices::DevicesController; 19 | use crate::freezer::FreezerController; 20 | use crate::hugetlb::HugeTlbController; 21 | use crate::memory::MemController; 22 | use crate::net_cls::NetClsController; 23 | use crate::net_prio::NetPrioController; 24 | use crate::perf_event::PerfEventController; 25 | use crate::pid::PidController; 26 | use crate::rdma::RdmaController; 27 | use crate::systemd::SystemdController; 28 | use crate::{Controllers, Hierarchy, Subsystem}; 29 | 30 | use crate::cgroup::Cgroup; 31 | 32 | /// Process mounts information. 33 | /// 34 | /// See `proc(5)` for format details. 35 | #[derive(Debug, PartialEq, Eq, Hash, Clone)] 36 | pub struct Mountinfo { 37 | /// Mount root directory of the file system. 38 | pub mount_root: PathBuf, 39 | /// Mount pathname relative to the process's root. 40 | pub mount_point: PathBuf, 41 | /// Filesystem type (main type with optional sub-type). 42 | pub fs_type: (String, Option), 43 | /// Superblock options. 44 | pub super_opts: Vec, 45 | } 46 | 47 | pub(crate) fn parse_mountinfo_for_line(line: &str) -> Option { 48 | let s_values: Vec<_> = line.split(" - ").collect(); 49 | if s_values.len() != 2 { 50 | return None; 51 | } 52 | 53 | let s0_values: Vec<_> = s_values[0].trim().split(' ').collect(); 54 | let s1_values: Vec<_> = s_values[1].trim().split(' ').collect(); 55 | if s0_values.len() < 6 || s1_values.len() < 3 { 56 | return None; 57 | } 58 | let mount_point = PathBuf::from(s0_values[4]); 59 | let mount_root = PathBuf::from(s0_values[3]); 60 | let fs_type_values: Vec<_> = s1_values[0].trim().split('.').collect(); 61 | let fs_type = match fs_type_values.len() { 62 | 1 => (fs_type_values[0].to_string(), None), 63 | 2 => ( 64 | fs_type_values[0].to_string(), 65 | Some(fs_type_values[1].to_string()), 66 | ), 67 | _ => return None, 68 | }; 69 | 70 | let super_opts: Vec = s1_values[2].trim().split(',').map(String::from).collect(); 71 | Some(Mountinfo { 72 | mount_root, 73 | mount_point, 74 | fs_type, 75 | super_opts, 76 | }) 77 | } 78 | 79 | /// Parses the provided mountinfo file. 80 | fn mountinfo_file(file: &mut File) -> Vec { 81 | let mut r = Vec::new(); 82 | for line in BufReader::new(file).lines() { 83 | match line { 84 | Ok(line) => { 85 | if let Some(mi) = parse_mountinfo_for_line(&line) { 86 | if mi.fs_type.0 == "cgroup" { 87 | r.push(mi); 88 | } 89 | } 90 | } 91 | Err(_) => break, 92 | } 93 | } 94 | r 95 | } 96 | 97 | /// Returns mounts information for the current process. 98 | pub fn mountinfo_self() -> Vec { 99 | match File::open("/proc/self/mountinfo") { 100 | Ok(mut file) => mountinfo_file(&mut file), 101 | Err(_) => vec![], 102 | } 103 | } 104 | 105 | /// The standard, original cgroup implementation. Often referred to as "cgroupv1". 106 | #[derive(Debug, Clone)] 107 | pub struct V1 { 108 | mountinfo: Vec, 109 | } 110 | 111 | #[derive(Debug, Clone)] 112 | pub struct V2 { 113 | root: String, 114 | } 115 | 116 | impl Hierarchy for V1 { 117 | fn v2(&self) -> bool { 118 | false 119 | } 120 | 121 | fn subsystems(&self) -> Vec { 122 | let mut subs = vec![]; 123 | 124 | // The cgroup writeback feature requires cooperation between memcgs and blkcgs 125 | // To avoid exceptions, we should add_task for blkcg before memcg(push BlkIo before Mem) 126 | // For more Information: https://www.alibabacloud.com/help/doc-detail/155509.htm 127 | if let Some((point, root)) = self.get_mount_point(Controllers::BlkIo) { 128 | subs.push(Subsystem::BlkIo(BlkIoController::new(point, root, false))); 129 | } 130 | if let Some((point, root)) = self.get_mount_point(Controllers::Mem) { 131 | subs.push(Subsystem::Mem(MemController::new(point, root, false))); 132 | } 133 | if let Some((point, root)) = self.get_mount_point(Controllers::Pids) { 134 | subs.push(Subsystem::Pid(PidController::new(point, root, false))); 135 | } 136 | if let Some((point, root)) = self.get_mount_point(Controllers::CpuSet) { 137 | subs.push(Subsystem::CpuSet(CpuSetController::new(point, root, false))); 138 | } 139 | if let Some((point, root)) = self.get_mount_point(Controllers::CpuAcct) { 140 | subs.push(Subsystem::CpuAcct(CpuAcctController::new(point, root))); 141 | } 142 | if let Some((point, root)) = self.get_mount_point(Controllers::Cpu) { 143 | subs.push(Subsystem::Cpu(CpuController::new(point, root, false))); 144 | } 145 | if let Some((point, root)) = self.get_mount_point(Controllers::Devices) { 146 | subs.push(Subsystem::Devices(DevicesController::new(point, root))); 147 | } 148 | if let Some((point, root)) = self.get_mount_point(Controllers::Freezer) { 149 | subs.push(Subsystem::Freezer(FreezerController::new( 150 | point, root, false, 151 | ))); 152 | } 153 | if let Some((point, root)) = self.get_mount_point(Controllers::NetCls) { 154 | subs.push(Subsystem::NetCls(NetClsController::new(point, root))); 155 | } 156 | if let Some((point, root)) = self.get_mount_point(Controllers::PerfEvent) { 157 | subs.push(Subsystem::PerfEvent(PerfEventController::new(point, root))); 158 | } 159 | if let Some((point, root)) = self.get_mount_point(Controllers::NetPrio) { 160 | subs.push(Subsystem::NetPrio(NetPrioController::new(point, root))); 161 | } 162 | if let Some((point, root)) = self.get_mount_point(Controllers::HugeTlb) { 163 | subs.push(Subsystem::HugeTlb(HugeTlbController::new( 164 | point, root, false, 165 | ))); 166 | } 167 | if let Some((point, root)) = self.get_mount_point(Controllers::Rdma) { 168 | subs.push(Subsystem::Rdma(RdmaController::new(point, root))); 169 | } 170 | if let Some((point, root)) = self.get_mount_point(Controllers::Systemd) { 171 | subs.push(Subsystem::Systemd(SystemdController::new( 172 | point, root, false, 173 | ))); 174 | } 175 | 176 | subs 177 | } 178 | 179 | fn root_control_group(&self) -> Cgroup { 180 | Cgroup::load(auto(), "") 181 | } 182 | 183 | fn parent_control_group(&self, path: &str) -> Cgroup { 184 | let path = Path::new(path); 185 | let parent_path = path.parent().unwrap().to_string_lossy().to_string(); 186 | Cgroup::load(auto(), parent_path) 187 | } 188 | 189 | fn root(&self) -> PathBuf { 190 | self.mountinfo 191 | .iter() 192 | .find_map(|m| { 193 | if m.fs_type.0 == "cgroup" { 194 | return Some(m.mount_point.parent().unwrap()); 195 | } 196 | None 197 | }) 198 | .unwrap() 199 | .to_path_buf() 200 | } 201 | } 202 | 203 | impl Hierarchy for V2 { 204 | fn v2(&self) -> bool { 205 | true 206 | } 207 | 208 | fn subsystems(&self) -> Vec { 209 | let p = format!("{}/{}", UNIFIED_MOUNTPOINT, "cgroup.controllers"); 210 | let ret = fs::read_to_string(p.as_str()); 211 | if ret.is_err() { 212 | return vec![]; 213 | } 214 | 215 | let mut subs = vec![]; 216 | 217 | let controllers = ret.unwrap().trim().to_string(); 218 | let mut controller_list: Vec<&str> = controllers.split(' ').collect(); 219 | 220 | // The freezer functionality is present in V2, but not as a controller, 221 | // but apparently as a core functionality. FreezerController supports 222 | // that, but we must explicitly fake the controller here. 223 | controller_list.push("freezer"); 224 | 225 | for s in controller_list { 226 | match s { 227 | "cpu" => { 228 | subs.push(Subsystem::Cpu(CpuController::new( 229 | self.root(), 230 | PathBuf::from(""), 231 | true, 232 | ))); 233 | } 234 | "io" => { 235 | subs.push(Subsystem::BlkIo(BlkIoController::new( 236 | self.root(), 237 | PathBuf::from(""), 238 | true, 239 | ))); 240 | } 241 | "cpuset" => { 242 | subs.push(Subsystem::CpuSet(CpuSetController::new( 243 | self.root(), 244 | PathBuf::from(""), 245 | true, 246 | ))); 247 | } 248 | "memory" => { 249 | subs.push(Subsystem::Mem(MemController::new( 250 | self.root(), 251 | PathBuf::from(""), 252 | true, 253 | ))); 254 | } 255 | "pids" => { 256 | subs.push(Subsystem::Pid(PidController::new( 257 | self.root(), 258 | PathBuf::from(""), 259 | true, 260 | ))); 261 | } 262 | "freezer" => { 263 | subs.push(Subsystem::Freezer(FreezerController::new( 264 | self.root(), 265 | PathBuf::from(""), 266 | true, 267 | ))); 268 | } 269 | "hugetlb" => { 270 | subs.push(Subsystem::HugeTlb(HugeTlbController::new( 271 | self.root(), 272 | PathBuf::from(""), 273 | true, 274 | ))); 275 | } 276 | _ => {} 277 | } 278 | } 279 | 280 | subs 281 | } 282 | 283 | fn root_control_group(&self) -> Cgroup { 284 | Cgroup::load(auto(), "") 285 | } 286 | 287 | fn parent_control_group(&self, path: &str) -> Cgroup { 288 | let path = Path::new(path); 289 | let parent_path = path.parent().unwrap().to_string_lossy().to_string(); 290 | Cgroup::load(auto(), parent_path) 291 | } 292 | 293 | fn root(&self) -> PathBuf { 294 | PathBuf::from(self.root.clone()) 295 | } 296 | } 297 | 298 | impl V1 { 299 | /// Finds where control groups are mounted to and returns a hierarchy in which control groups 300 | /// can be created. 301 | pub fn new() -> V1 { 302 | V1 { 303 | mountinfo: mountinfo_self(), 304 | } 305 | } 306 | 307 | pub fn get_mount_point(&self, controller: Controllers) -> Option<(PathBuf, PathBuf)> { 308 | self.mountinfo.iter().find_map(|m| { 309 | if m.fs_type.0 == "cgroup" && m.super_opts.contains(&controller.to_string()) { 310 | return Some((m.mount_point.to_owned(), m.mount_root.to_owned())); 311 | } 312 | None 313 | }) 314 | } 315 | } 316 | 317 | impl Default for V1 { 318 | fn default() -> Self { 319 | Self::new() 320 | } 321 | } 322 | 323 | impl V2 { 324 | /// Finds where control groups are mounted to and returns a hierarchy in which control groups 325 | /// can be created. 326 | pub fn new() -> V2 { 327 | V2 { 328 | root: String::from(UNIFIED_MOUNTPOINT), 329 | } 330 | } 331 | } 332 | 333 | impl Default for V2 { 334 | fn default() -> Self { 335 | Self::new() 336 | } 337 | } 338 | 339 | pub const UNIFIED_MOUNTPOINT: &str = "/sys/fs/cgroup"; 340 | 341 | pub fn is_cgroup2_unified_mode() -> bool { 342 | use nix::sys::statfs; 343 | 344 | let path = std::path::Path::new(UNIFIED_MOUNTPOINT); 345 | let fs_stat = match statfs::statfs(path) { 346 | Ok(fs_stat) => fs_stat, 347 | Err(_) => return false, 348 | }; 349 | 350 | fs_stat.filesystem_type() == statfs::CGROUP2_SUPER_MAGIC 351 | } 352 | 353 | pub fn auto() -> Box { 354 | if is_cgroup2_unified_mode() { 355 | Box::new(V2::new()) 356 | } else { 357 | Box::new(V1::new()) 358 | } 359 | } 360 | 361 | #[cfg(test)] 362 | mod tests { 363 | use super::*; 364 | 365 | #[test] 366 | fn test_parse_mount() { 367 | let mountinfo = vec![ 368 | ("29 26 0:26 / /sys/fs/cgroup/cpuset,cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:10 - cgroup cgroup rw,cpuset,cpu,cpuacct", 369 | Mountinfo{mount_root: PathBuf::from("/"), mount_point: PathBuf::from("/sys/fs/cgroup/cpuset,cpu,cpuacct"), fs_type: ("cgroup".to_string(), None), super_opts: vec![ 370 | "rw".to_string(), 371 | "cpuset".to_string(), 372 | "cpu".to_string(), 373 | "cpuacct".to_string(), 374 | ]}), 375 | ("121 1731 0:42 / /shm rw,nosuid,nodev,noexec,relatime shared:68 master:66 - tmpfs shm rw,size=65536k", 376 | Mountinfo{mount_root: PathBuf::from("/"), mount_point: PathBuf::from("/shm"), fs_type: ("tmpfs".to_string(), None), super_opts: vec![ 377 | "rw".to_string(), 378 | "size=65536k".to_string(), 379 | ]}), 380 | ("121 1731 0:42 / /shm rw,nosuid,nodev,noexec,relatime shared:68 master:66 - tmpfs.123 shm rw,size=65536k", 381 | Mountinfo{mount_root: PathBuf::from("/"), mount_point: PathBuf::from("/shm"), fs_type: ("tmpfs".to_string(), Some("123".to_string())), super_opts: vec![ 382 | "rw".to_string(), 383 | "size=65536k".to_string(), 384 | ]}), 385 | ]; 386 | 387 | for mi in mountinfo { 388 | let info = parse_mountinfo_for_line(mi.0).unwrap(); 389 | assert_eq!(info, mi.1) 390 | } 391 | } 392 | } 393 | -------------------------------------------------------------------------------- /src/hugetlb.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Levente Kurusa 2 | // Copyright (c) 2020 Ant Group 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 or MIT 5 | // 6 | 7 | //! This module contains the implementation of the `hugetlb` cgroup subsystem. 8 | //! 9 | //! See the Kernel's documentation for more information about this subsystem, found at: 10 | //! [Documentation/cgroup-v1/hugetlb.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/hugetlb.txt) 11 | use log::warn; 12 | use std::io::Write; 13 | use std::path::PathBuf; 14 | 15 | use crate::error::ErrorKind::*; 16 | use crate::error::*; 17 | use crate::{flat_keyed_to_vec, read_u64_from}; 18 | 19 | use crate::{ 20 | ControllIdentifier, ControllerInternal, Controllers, HugePageResources, Resources, Subsystem, 21 | }; 22 | 23 | /// A controller that allows controlling the `hugetlb` subsystem of a Cgroup. 24 | /// 25 | /// In essence, using this controller it is possible to limit the use of hugepages in the tasks of 26 | /// the control group. 27 | #[derive(Debug, Clone)] 28 | pub struct HugeTlbController { 29 | base: PathBuf, 30 | path: PathBuf, 31 | sizes: Vec, 32 | v2: bool, 33 | } 34 | 35 | impl ControllerInternal for HugeTlbController { 36 | fn control_type(&self) -> Controllers { 37 | Controllers::HugeTlb 38 | } 39 | fn get_path(&self) -> &PathBuf { 40 | &self.path 41 | } 42 | fn get_path_mut(&mut self) -> &mut PathBuf { 43 | &mut self.path 44 | } 45 | fn get_base(&self) -> &PathBuf { 46 | &self.base 47 | } 48 | 49 | fn is_v2(&self) -> bool { 50 | self.v2 51 | } 52 | 53 | fn apply(&self, res: &Resources) -> Result<()> { 54 | // get the resources that apply to this controller 55 | let res: &HugePageResources = &res.hugepages; 56 | 57 | for i in &res.limits { 58 | let _ = self.set_limit_in_bytes(&i.size, i.limit); 59 | if self.limit_in_bytes(&i.size)? != i.limit { 60 | return Err(Error::new(Other)); 61 | } 62 | } 63 | 64 | Ok(()) 65 | } 66 | } 67 | 68 | impl ControllIdentifier for HugeTlbController { 69 | fn controller_type() -> Controllers { 70 | Controllers::HugeTlb 71 | } 72 | } 73 | 74 | impl<'a> From<&'a Subsystem> for &'a HugeTlbController { 75 | fn from(sub: &'a Subsystem) -> &'a HugeTlbController { 76 | unsafe { 77 | match sub { 78 | Subsystem::HugeTlb(c) => c, 79 | _ => { 80 | assert_eq!(1, 0); 81 | let v = std::mem::MaybeUninit::uninit(); 82 | v.assume_init() 83 | } 84 | } 85 | } 86 | } 87 | } 88 | 89 | impl HugeTlbController { 90 | /// Constructs a new `HugeTlbController` with `root` serving as the root of the control group. 91 | pub fn new(point: PathBuf, root: PathBuf, v2: bool) -> Self { 92 | let sizes = get_hugepage_sizes(); 93 | Self { 94 | base: root, 95 | path: point, 96 | sizes, 97 | v2, 98 | } 99 | } 100 | 101 | /// Whether the system supports `hugetlb_size` hugepages. 102 | pub fn size_supported(&self, hugetlb_size: &str) -> bool { 103 | for s in &self.sizes { 104 | if s == hugetlb_size { 105 | return true; 106 | } 107 | } 108 | false 109 | } 110 | 111 | pub fn get_sizes(&self) -> Vec { 112 | self.sizes.clone() 113 | } 114 | 115 | fn failcnt_v2(&self, hugetlb_size: &str) -> Result { 116 | self.open_path(&format!("hugetlb.{}.events", hugetlb_size), false) 117 | .and_then(flat_keyed_to_vec) 118 | .and_then(|x| { 119 | if x.is_empty() { 120 | return Err(Error::from_string(format!( 121 | "get empty from hugetlb.{}.events", 122 | hugetlb_size 123 | ))); 124 | } 125 | Ok(x[0].1 as u64) 126 | }) 127 | } 128 | 129 | /// Check how many times has the limit of `hugetlb_size` hugepages been hit. 130 | pub fn failcnt(&self, hugetlb_size: &str) -> Result { 131 | if self.v2 { 132 | return self.failcnt_v2(hugetlb_size); 133 | } 134 | self.open_path(&format!("hugetlb.{}.failcnt", hugetlb_size), false) 135 | .and_then(read_u64_from) 136 | } 137 | 138 | /// Get the limit (in bytes) of how much memory can be backed by hugepages of a certain size 139 | /// (`hugetlb_size`). 140 | pub fn limit_in_bytes(&self, hugetlb_size: &str) -> Result { 141 | let mut file_name = format!("hugetlb.{}.limit_in_bytes", hugetlb_size); 142 | if self.v2 { 143 | file_name = format!("hugetlb.{}.max", hugetlb_size); 144 | } 145 | self.open_path(&file_name, false).and_then(read_u64_from) 146 | } 147 | 148 | /// Get the current usage of memory that is backed by hugepages of a certain size 149 | /// (`hugetlb_size`). 150 | pub fn usage_in_bytes(&self, hugetlb_size: &str) -> Result { 151 | let mut file = format!("hugetlb.{}.usage_in_bytes", hugetlb_size); 152 | if self.v2 { 153 | file = format!("hugetlb.{}.current", hugetlb_size); 154 | } 155 | self.open_path(&file, false).and_then(read_u64_from) 156 | } 157 | 158 | /// Get the maximum observed usage of memory that is backed by hugepages of a certain size 159 | /// (`hugetlb_size`). 160 | pub fn max_usage_in_bytes(&self, hugetlb_size: &str) -> Result { 161 | self.open_path( 162 | &format!("hugetlb.{}.max_usage_in_bytes", hugetlb_size), 163 | false, 164 | ) 165 | .and_then(read_u64_from) 166 | } 167 | 168 | /// Set the limit (in bytes) of how much memory can be backed by hugepages of a certain size 169 | /// (`hugetlb_size`). 170 | pub fn set_limit_in_bytes(&self, hugetlb_size: &str, limit: u64) -> Result<()> { 171 | let mut file_name = format!("hugetlb.{}.limit_in_bytes", hugetlb_size); 172 | if self.v2 { 173 | file_name = format!("hugetlb.{}.max", hugetlb_size); 174 | } 175 | self.open_path(&file_name, true).and_then(|mut file| { 176 | file.write_all(limit.to_string().as_ref()).map_err(|e| { 177 | Error::with_cause(WriteFailed(file_name.to_string(), limit.to_string()), e) 178 | }) 179 | }) 180 | } 181 | } 182 | 183 | pub const HUGEPAGESIZE_DIR: &str = "/sys/kernel/mm/hugepages"; 184 | use regex::Regex; 185 | use std::collections::HashMap; 186 | use std::fs; 187 | 188 | fn get_hugepage_sizes() -> Vec { 189 | let dirs = fs::read_dir(HUGEPAGESIZE_DIR); 190 | if dirs.is_err() { 191 | return Vec::new(); 192 | } 193 | 194 | dirs.unwrap() 195 | .filter_map(|e| { 196 | let entry = e.map_err(|e| warn!("readdir error: {:?}", e)).ok()?; 197 | let name = entry.file_name().into_string().unwrap(); 198 | let parts: Vec<&str> = name.split('-').collect(); 199 | if parts.len() != 2 { 200 | return None; 201 | } 202 | let bmap = get_binary_size_map(); 203 | let size = parse_size(parts[1], &bmap) 204 | .map_err(|e| warn!("parse_size error: {:?}", e)) 205 | .ok()?; 206 | let dabbrs = get_decimal_abbrs(); 207 | 208 | Some(custom_size(size as f64, 1024.0, &dabbrs)) 209 | }) 210 | .collect() 211 | } 212 | 213 | pub const KB: u128 = 1000; 214 | pub const MB: u128 = 1000 * KB; 215 | pub const GB: u128 = 1000 * MB; 216 | pub const TB: u128 = 1000 * GB; 217 | pub const PB: u128 = 1000 * TB; 218 | 219 | #[allow(non_upper_case_globals)] 220 | pub const KiB: u128 = 1024; 221 | #[allow(non_upper_case_globals)] 222 | pub const MiB: u128 = 1024 * KiB; 223 | #[allow(non_upper_case_globals)] 224 | pub const GiB: u128 = 1024 * MiB; 225 | #[allow(non_upper_case_globals)] 226 | pub const TiB: u128 = 1024 * GiB; 227 | #[allow(non_upper_case_globals)] 228 | pub const PiB: u128 = 1024 * TiB; 229 | 230 | pub fn get_binary_size_map() -> HashMap { 231 | let mut m = HashMap::new(); 232 | m.insert("k".to_string(), KiB); 233 | m.insert("m".to_string(), MiB); 234 | m.insert("g".to_string(), GiB); 235 | m.insert("t".to_string(), TiB); 236 | m.insert("p".to_string(), PiB); 237 | m 238 | } 239 | 240 | pub fn get_decimal_size_map() -> HashMap { 241 | let mut m = HashMap::new(); 242 | m.insert("k".to_string(), KB); 243 | m.insert("m".to_string(), MB); 244 | m.insert("g".to_string(), GB); 245 | m.insert("t".to_string(), TB); 246 | m.insert("p".to_string(), PB); 247 | m 248 | } 249 | 250 | pub fn get_decimal_abbrs() -> Vec { 251 | let m = vec![ 252 | "B".to_string(), 253 | "KB".to_string(), 254 | "MB".to_string(), 255 | "GB".to_string(), 256 | "TB".to_string(), 257 | "PB".to_string(), 258 | "EB".to_string(), 259 | "ZB".to_string(), 260 | "YB".to_string(), 261 | ]; 262 | m 263 | } 264 | 265 | fn parse_size(s: &str, m: &HashMap) -> Result { 266 | let re = Regex::new(r"(?P\d+)(?P[kKmMgGtTpP]?)[bB]?$"); 267 | 268 | if re.is_err() { 269 | return Err(Error::new(InvalidBytesSize)); 270 | } 271 | let caps = re.unwrap().captures(s).unwrap(); 272 | 273 | let num = caps.name("num"); 274 | let size: u128 = if let Some(num) = num { 275 | let n = num.as_str().trim().parse::(); 276 | if n.is_err() { 277 | return Err(Error::new(InvalidBytesSize)); 278 | } 279 | n.unwrap() 280 | } else { 281 | return Err(Error::new(InvalidBytesSize)); 282 | }; 283 | 284 | let q = caps.name("mul"); 285 | let mul: u128 = if let Some(q) = q { 286 | let t = m.get(q.as_str()); 287 | if let Some(t) = t { 288 | *t 289 | } else { 290 | return Err(Error::new(InvalidBytesSize)); 291 | } 292 | } else { 293 | return Err(Error::new(InvalidBytesSize)); 294 | }; 295 | 296 | Ok(size * mul) 297 | } 298 | 299 | fn custom_size(mut size: f64, base: f64, m: &[String]) -> String { 300 | let mut i = 0; 301 | while size >= base && i < m.len() - 1 { 302 | size /= base; 303 | i += 1; 304 | } 305 | 306 | format!("{}{}", size, m[i].as_str()) 307 | } 308 | -------------------------------------------------------------------------------- /src/net_cls.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Levente Kurusa 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 or MIT 4 | // 5 | 6 | //! This module contains the implementation of the `net_cls` cgroup subsystem. 7 | //! 8 | //! See the Kernel's documentation for more information about this subsystem, found at: 9 | //! [Documentation/cgroup-v1/net_cls.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/net_cls.txt) 10 | use std::io::Write; 11 | use std::path::PathBuf; 12 | 13 | use crate::error::ErrorKind::*; 14 | use crate::error::*; 15 | 16 | use crate::read_u64_from; 17 | use crate::{ 18 | ControllIdentifier, ControllerInternal, Controllers, NetworkResources, Resources, Subsystem, 19 | }; 20 | 21 | /// A controller that allows controlling the `net_cls` subsystem of a Cgroup. 22 | /// 23 | /// In esssence, using the `net_cls` controller, one can attach a custom class to the network 24 | /// packets emitted by the control group's tasks. This can then later be used in iptables to have 25 | /// custom firewall rules, QoS, etc. 26 | #[derive(Debug, Clone)] 27 | pub struct NetClsController { 28 | base: PathBuf, 29 | path: PathBuf, 30 | } 31 | 32 | impl ControllerInternal for NetClsController { 33 | fn control_type(&self) -> Controllers { 34 | Controllers::NetCls 35 | } 36 | fn get_path(&self) -> &PathBuf { 37 | &self.path 38 | } 39 | fn get_path_mut(&mut self) -> &mut PathBuf { 40 | &mut self.path 41 | } 42 | fn get_base(&self) -> &PathBuf { 43 | &self.base 44 | } 45 | 46 | fn apply(&self, res: &Resources) -> Result<()> { 47 | // get the resources that apply to this controller 48 | let res: &NetworkResources = &res.network; 49 | 50 | update_and_test!(self, set_class, res.class_id, get_class); 51 | 52 | Ok(()) 53 | } 54 | } 55 | 56 | impl ControllIdentifier for NetClsController { 57 | fn controller_type() -> Controllers { 58 | Controllers::NetCls 59 | } 60 | } 61 | 62 | impl<'a> From<&'a Subsystem> for &'a NetClsController { 63 | fn from(sub: &'a Subsystem) -> &'a NetClsController { 64 | unsafe { 65 | match sub { 66 | Subsystem::NetCls(c) => c, 67 | _ => { 68 | assert_eq!(1, 0); 69 | let v = std::mem::MaybeUninit::uninit(); 70 | v.assume_init() 71 | } 72 | } 73 | } 74 | } 75 | } 76 | 77 | impl NetClsController { 78 | /// Constructs a new `NetClsController` with `root` serving as the root of the control group. 79 | pub fn new(point: PathBuf, root: PathBuf) -> Self { 80 | Self { 81 | base: root, 82 | path: point, 83 | } 84 | } 85 | 86 | /// Set the network class id of the outgoing packets of the control group's tasks. 87 | pub fn set_class(&self, class: u64) -> Result<()> { 88 | self.open_path("net_cls.classid", true) 89 | .and_then(|mut file| { 90 | let s = format!("{:#08X}", class); 91 | file.write_all(s.as_ref()).map_err(|e| { 92 | Error::with_cause(WriteFailed("net_cls.classid".to_string(), s), e) 93 | }) 94 | }) 95 | } 96 | 97 | /// Get the network class id of the outgoing packets of the control group's tasks. 98 | pub fn get_class(&self) -> Result { 99 | self.open_path("net_cls.classid", false) 100 | .and_then(read_u64_from) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/net_prio.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Levente Kurusa 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 or MIT 4 | // 5 | 6 | //! This module contains the implementation of the `net_prio` cgroup subsystem. 7 | //! 8 | //! See the Kernel's documentation for more information about this subsystem, found at: 9 | //! [Documentation/cgroup-v1/net_prio.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/net_prio.txt) 10 | use std::collections::HashMap; 11 | use std::io::{BufRead, BufReader, Write}; 12 | use std::path::PathBuf; 13 | 14 | use crate::error::ErrorKind::*; 15 | use crate::error::*; 16 | 17 | use crate::read_u64_from; 18 | use crate::{ 19 | ControllIdentifier, ControllerInternal, Controllers, NetworkResources, Resources, Subsystem, 20 | }; 21 | 22 | /// A controller that allows controlling the `net_prio` subsystem of a Cgroup. 23 | /// 24 | /// In essence, using `net_prio` one can set the priority of the packets emitted from the control 25 | /// group's tasks. This can then be used to have QoS restrictions on certain control groups and 26 | /// thus, prioritizing certain tasks. 27 | #[derive(Debug, Clone)] 28 | pub struct NetPrioController { 29 | base: PathBuf, 30 | path: PathBuf, 31 | } 32 | 33 | impl ControllerInternal for NetPrioController { 34 | fn control_type(&self) -> Controllers { 35 | Controllers::NetPrio 36 | } 37 | fn get_path(&self) -> &PathBuf { 38 | &self.path 39 | } 40 | fn get_path_mut(&mut self) -> &mut PathBuf { 41 | &mut self.path 42 | } 43 | fn get_base(&self) -> &PathBuf { 44 | &self.base 45 | } 46 | 47 | fn apply(&self, res: &Resources) -> Result<()> { 48 | // get the resources that apply to this controller 49 | let res: &NetworkResources = &res.network; 50 | 51 | for i in &res.priorities { 52 | let _ = self.set_if_prio(&i.name, i.priority); 53 | } 54 | 55 | Ok(()) 56 | } 57 | } 58 | 59 | impl ControllIdentifier for NetPrioController { 60 | fn controller_type() -> Controllers { 61 | Controllers::NetPrio 62 | } 63 | } 64 | 65 | impl<'a> From<&'a Subsystem> for &'a NetPrioController { 66 | fn from(sub: &'a Subsystem) -> &'a NetPrioController { 67 | unsafe { 68 | match sub { 69 | Subsystem::NetPrio(c) => c, 70 | _ => { 71 | assert_eq!(1, 0); 72 | let v = std::mem::MaybeUninit::uninit(); 73 | v.assume_init() 74 | } 75 | } 76 | } 77 | } 78 | } 79 | 80 | impl NetPrioController { 81 | /// Constructs a new `NetPrioController` with `root` serving as the root of the control group. 82 | pub fn new(point: PathBuf, root: PathBuf) -> Self { 83 | Self { 84 | base: root, 85 | path: point, 86 | } 87 | } 88 | 89 | /// Retrieves the current priority of the emitted packets. 90 | pub fn prio_idx(&self) -> u64 { 91 | self.open_path("net_prio.prioidx", false) 92 | .and_then(read_u64_from) 93 | .unwrap_or(0) 94 | } 95 | 96 | /// A map of priorities for each network interface. 97 | #[allow(clippy::iter_nth_zero, clippy::unnecessary_unwrap)] 98 | pub fn ifpriomap(&self) -> Result> { 99 | self.open_path("net_prio.ifpriomap", false) 100 | .and_then(|file| { 101 | let bf = BufReader::new(file); 102 | bf.lines().fold(Ok(HashMap::new()), |acc, line| { 103 | if acc.is_err() { 104 | acc 105 | } else { 106 | let mut acc = acc.unwrap(); 107 | let l = line.unwrap(); 108 | let mut sp = l.split_whitespace(); 109 | 110 | let ifname = sp.nth(0); 111 | let ifprio = sp.nth(1); 112 | if ifname.is_none() || ifprio.is_none() { 113 | Err(Error::new(ParseError)) 114 | } else { 115 | let ifname = ifname.unwrap(); 116 | let ifprio = ifprio.unwrap().trim().parse(); 117 | match ifprio { 118 | Err(e) => Err(Error::with_cause(ParseError, e)), 119 | Ok(_) => { 120 | acc.insert(ifname.to_string(), ifprio.unwrap()); 121 | Ok(acc) 122 | } 123 | } 124 | } 125 | } 126 | }) 127 | }) 128 | } 129 | 130 | /// Set the priority of the network traffic on `eif` to be `prio`. 131 | pub fn set_if_prio(&self, eif: &str, prio: u64) -> Result<()> { 132 | self.open_path("net_prio.ifpriomap", true) 133 | .and_then(|mut file| { 134 | file.write_all(format!("{} {}", eif, prio).as_ref()) 135 | .map_err(|e| { 136 | Error::with_cause( 137 | WriteFailed( 138 | "net_prio.ifpriomap".to_string(), 139 | format!("{} {}", eif, prio), 140 | ), 141 | e, 142 | ) 143 | }) 144 | }) 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/perf_event.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Levente Kurusa 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 or MIT 4 | // 5 | 6 | //! This module contains the implementation of the `perf_event` cgroup subsystem. 7 | //! 8 | //! See the Kernel's documentation for more information about this subsystem, found at: 9 | //! [tools/perf/Documentation/perf-record.txt](https://raw.githubusercontent.com/torvalds/linux/master/tools/perf/Documentation/perf-record.txt) 10 | use std::path::PathBuf; 11 | 12 | use crate::error::*; 13 | 14 | use crate::{ControllIdentifier, ControllerInternal, Controllers, Resources, Subsystem}; 15 | 16 | /// A controller that allows controlling the `perf_event` subsystem of a Cgroup. 17 | /// 18 | /// In essence, when processes belong to the same `perf_event` controller, they can be monitored 19 | /// together using the `perf` performance monitoring and reporting tool. 20 | #[derive(Debug, Clone)] 21 | pub struct PerfEventController { 22 | base: PathBuf, 23 | path: PathBuf, 24 | } 25 | 26 | impl ControllerInternal for PerfEventController { 27 | fn control_type(&self) -> Controllers { 28 | Controllers::PerfEvent 29 | } 30 | fn get_path(&self) -> &PathBuf { 31 | &self.path 32 | } 33 | fn get_path_mut(&mut self) -> &mut PathBuf { 34 | &mut self.path 35 | } 36 | fn get_base(&self) -> &PathBuf { 37 | &self.base 38 | } 39 | 40 | fn apply(&self, _res: &Resources) -> Result<()> { 41 | Ok(()) 42 | } 43 | } 44 | 45 | impl ControllIdentifier for PerfEventController { 46 | fn controller_type() -> Controllers { 47 | Controllers::PerfEvent 48 | } 49 | } 50 | 51 | impl<'a> From<&'a Subsystem> for &'a PerfEventController { 52 | fn from(sub: &'a Subsystem) -> &'a PerfEventController { 53 | unsafe { 54 | match sub { 55 | Subsystem::PerfEvent(c) => c, 56 | _ => { 57 | assert_eq!(1, 0); 58 | let v = std::mem::MaybeUninit::uninit(); 59 | v.assume_init() 60 | } 61 | } 62 | } 63 | } 64 | } 65 | 66 | impl PerfEventController { 67 | /// Constructs a new `PerfEventController` with `root` serving as the root of the control group. 68 | pub fn new(point: PathBuf, root: PathBuf) -> Self { 69 | Self { 70 | base: root, 71 | path: point, 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/pid.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Levente Kurusa 2 | // Copyright (c) 2020 Ant Group 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 or MIT 5 | // 6 | 7 | //! This module contains the implementation of the `pids` cgroup subsystem. 8 | //! 9 | //! See the Kernel's documentation for more information about this subsystem, found at: 10 | //! [Documentation/cgroups-v1/pids.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/pids.txt) 11 | use std::io::{Read, Write}; 12 | use std::path::PathBuf; 13 | 14 | use crate::error::ErrorKind::*; 15 | use crate::error::*; 16 | 17 | use crate::read_u64_from; 18 | use crate::{ 19 | parse_max_value, ControllIdentifier, ControllerInternal, Controllers, MaxValue, PidResources, 20 | Resources, Subsystem, 21 | }; 22 | 23 | /// A controller that allows controlling the `pids` subsystem of a Cgroup. 24 | #[derive(Debug, Clone)] 25 | pub struct PidController { 26 | base: PathBuf, 27 | path: PathBuf, 28 | v2: bool, 29 | } 30 | 31 | impl ControllerInternal for PidController { 32 | fn control_type(&self) -> Controllers { 33 | Controllers::Pids 34 | } 35 | fn get_path(&self) -> &PathBuf { 36 | &self.path 37 | } 38 | fn get_path_mut(&mut self) -> &mut PathBuf { 39 | &mut self.path 40 | } 41 | fn get_base(&self) -> &PathBuf { 42 | &self.base 43 | } 44 | 45 | fn is_v2(&self) -> bool { 46 | self.v2 47 | } 48 | 49 | fn apply(&self, res: &Resources) -> Result<()> { 50 | // get the resources that apply to this controller 51 | let pidres: &PidResources = &res.pid; 52 | 53 | // apply pid_max 54 | update_and_test!( 55 | self, 56 | set_pid_max, 57 | pidres.maximum_number_of_processes, 58 | get_pid_max 59 | ); 60 | 61 | Ok(()) 62 | } 63 | } 64 | 65 | // impl<'a> ControllIdentifier for &'a PidController { 66 | // fn controller_type() -> Controllers { 67 | // Controllers::Pids 68 | // } 69 | // } 70 | 71 | impl ControllIdentifier for PidController { 72 | fn controller_type() -> Controllers { 73 | Controllers::Pids 74 | } 75 | } 76 | 77 | impl<'a> From<&'a Subsystem> for &'a PidController { 78 | fn from(sub: &'a Subsystem) -> &'a PidController { 79 | unsafe { 80 | match sub { 81 | Subsystem::Pid(c) => c, 82 | _ => { 83 | assert_eq!(1, 0); 84 | let v = std::mem::MaybeUninit::uninit(); 85 | v.assume_init() 86 | } 87 | } 88 | } 89 | } 90 | } 91 | 92 | impl PidController { 93 | /// Constructors a new `PidController` instance, with `root` serving as the controller's root 94 | /// directory. 95 | pub fn new(point: PathBuf, root: PathBuf, v2: bool) -> Self { 96 | Self { 97 | base: root, 98 | path: point, 99 | v2, 100 | } 101 | } 102 | 103 | /// The number of times `fork` failed because the limit was hit. 104 | pub fn get_pid_events(&self) -> Result { 105 | self.open_path("pids.events", false).and_then(|mut file| { 106 | let mut string = String::new(); 107 | match file.read_to_string(&mut string) { 108 | Ok(_) => match string.split_whitespace().nth(1) { 109 | Some(elem) => match elem.parse() { 110 | Ok(val) => Ok(val), 111 | Err(e) => Err(Error::with_cause(ParseError, e)), 112 | }, 113 | None => Err(Error::new(ParseError)), 114 | }, 115 | Err(e) => Err(Error::with_cause(ReadFailed("pids.events".to_string()), e)), 116 | } 117 | }) 118 | } 119 | 120 | /// The number of processes currently. 121 | pub fn get_pid_current(&self) -> Result { 122 | self.open_path("pids.current", false) 123 | .and_then(read_u64_from) 124 | } 125 | 126 | /// The maximum number of processes that can exist at one time in the control group. 127 | pub fn get_pid_max(&self) -> Result { 128 | self.open_path("pids.max", false).and_then(|mut file| { 129 | let mut string = String::new(); 130 | let res = file.read_to_string(&mut string); 131 | match res { 132 | Ok(_) => parse_max_value(&string), 133 | Err(e) => Err(Error::with_cause(ReadFailed("pids.max".to_string()), e)), 134 | } 135 | }) 136 | } 137 | 138 | /// Set the maximum number of processes that can exist in this control group. 139 | /// 140 | /// Note that if `get_pid_current()` returns a higher number than what you 141 | /// are about to set (`max_pid`), then no processess will be killed. Additonally, attaching 142 | /// extra processes to a control group disregards the limit. 143 | pub fn set_pid_max(&self, max_pid: MaxValue) -> Result<()> { 144 | self.open_path("pids.max", true).and_then(|mut file| { 145 | let string_to_write = max_pid.to_string(); 146 | match file.write_all(string_to_write.as_ref()) { 147 | Ok(_) => Ok(()), 148 | Err(e) => Err(Error::with_cause( 149 | WriteFailed("pids.max".to_string(), format!("{:?}", max_pid)), 150 | e, 151 | )), 152 | } 153 | }) 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/rdma.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Levente Kurusa 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 or MIT 4 | // 5 | 6 | //! This module contains the implementation of the `rdma` cgroup subsystem. 7 | //! 8 | //! See the Kernel's documentation for more information about this subsystem, found at: 9 | //! [Documentation/cgroup-v1/rdma.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/rdma.txt) 10 | use std::io::Write; 11 | use std::path::PathBuf; 12 | 13 | use crate::error::ErrorKind::*; 14 | use crate::error::*; 15 | 16 | use crate::read_string_from; 17 | use crate::{ControllIdentifier, ControllerInternal, Controllers, Resources, Subsystem}; 18 | 19 | /// A controller that allows controlling the `rdma` subsystem of a Cgroup. 20 | /// 21 | /// In essence, using this controller one can limit the RDMA/IB specific resources that the tasks 22 | /// in the control group can use. 23 | #[derive(Debug, Clone)] 24 | pub struct RdmaController { 25 | base: PathBuf, 26 | path: PathBuf, 27 | } 28 | 29 | impl ControllerInternal for RdmaController { 30 | fn control_type(&self) -> Controllers { 31 | Controllers::Rdma 32 | } 33 | fn get_path(&self) -> &PathBuf { 34 | &self.path 35 | } 36 | fn get_path_mut(&mut self) -> &mut PathBuf { 37 | &mut self.path 38 | } 39 | fn get_base(&self) -> &PathBuf { 40 | &self.base 41 | } 42 | 43 | fn apply(&self, _res: &Resources) -> Result<()> { 44 | Ok(()) 45 | } 46 | } 47 | 48 | impl ControllIdentifier for RdmaController { 49 | fn controller_type() -> Controllers { 50 | Controllers::Rdma 51 | } 52 | } 53 | 54 | impl<'a> From<&'a Subsystem> for &'a RdmaController { 55 | fn from(sub: &'a Subsystem) -> &'a RdmaController { 56 | unsafe { 57 | match sub { 58 | Subsystem::Rdma(c) => c, 59 | _ => { 60 | assert_eq!(1, 0); 61 | let v = std::mem::MaybeUninit::uninit(); 62 | v.assume_init() 63 | } 64 | } 65 | } 66 | } 67 | } 68 | 69 | impl RdmaController { 70 | /// Constructs a new `RdmaController` with `root` serving as the root of the control group. 71 | pub fn new(point: PathBuf, root: PathBuf) -> Self { 72 | Self { 73 | base: root, 74 | path: point, 75 | } 76 | } 77 | 78 | /// Returns the current usage of RDMA/IB specific resources. 79 | pub fn current(&self) -> Result { 80 | self.open_path("rdma.current", false) 81 | .and_then(read_string_from) 82 | } 83 | 84 | /// Returns the max usage of RDMA/IB specific resources. 85 | pub fn max(&self) -> Result { 86 | self.open_path("rdma.max", false).and_then(read_string_from) 87 | } 88 | 89 | /// Set a maximum usage for each RDMA/IB resource. 90 | pub fn set_max(&self, max: &str) -> Result<()> { 91 | self.open_path("rdma.max", true).and_then(|mut file| { 92 | file.write_all(max.as_ref()).map_err(|e| { 93 | Error::with_cause(WriteFailed("rdma.max".to_string(), max.to_string()), e) 94 | }) 95 | }) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/systemd.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Ant Group 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 or MIT 4 | // 5 | 6 | //! This module contains the implementation of the `systemd` cgroup subsystem. 7 | //! 8 | use std::path::PathBuf; 9 | 10 | use crate::error::*; 11 | 12 | use crate::{ControllIdentifier, ControllerInternal, Controllers, Resources, Subsystem}; 13 | 14 | /// A controller that allows controlling the `systemd` subsystem of a Cgroup. 15 | /// 16 | #[derive(Debug, Clone)] 17 | pub struct SystemdController { 18 | base: PathBuf, 19 | path: PathBuf, 20 | _v2: bool, 21 | } 22 | 23 | impl ControllerInternal for SystemdController { 24 | fn control_type(&self) -> Controllers { 25 | Controllers::Systemd 26 | } 27 | fn get_path(&self) -> &PathBuf { 28 | &self.path 29 | } 30 | fn get_path_mut(&mut self) -> &mut PathBuf { 31 | &mut self.path 32 | } 33 | fn get_base(&self) -> &PathBuf { 34 | &self.base 35 | } 36 | 37 | fn apply(&self, _res: &Resources) -> Result<()> { 38 | Ok(()) 39 | } 40 | } 41 | 42 | impl ControllIdentifier for SystemdController { 43 | fn controller_type() -> Controllers { 44 | Controllers::Systemd 45 | } 46 | } 47 | 48 | impl<'a> From<&'a Subsystem> for &'a SystemdController { 49 | fn from(sub: &'a Subsystem) -> &'a SystemdController { 50 | unsafe { 51 | match sub { 52 | Subsystem::Systemd(c) => c, 53 | _ => { 54 | assert_eq!(1, 0); 55 | let v = std::mem::MaybeUninit::uninit(); 56 | v.assume_init() 57 | } 58 | } 59 | } 60 | } 61 | } 62 | 63 | impl SystemdController { 64 | /// Constructs a new `SystemdController` with `root` serving as the root of the control group. 65 | pub fn new(point: PathBuf, root: PathBuf, v2: bool) -> Self { 66 | Self { 67 | base: root, 68 | path: point, 69 | _v2: v2, 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /tests/builder.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Levente Kurusa 2 | // Copyright (c) 2020 And Group 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 or MIT 5 | // 6 | 7 | //! Some simple tests covering the builder pattern for control groups. 8 | use cgroups_rs::blkio::*; 9 | use cgroups_rs::cgroup_builder::*; 10 | use cgroups_rs::cpu::*; 11 | use cgroups_rs::devices::*; 12 | use cgroups_rs::hugetlb::*; 13 | use cgroups_rs::memory::*; 14 | use cgroups_rs::net_cls::*; 15 | use cgroups_rs::pid::*; 16 | use cgroups_rs::*; 17 | 18 | #[test] 19 | pub fn test_cpu_res_build() { 20 | let h = cgroups_rs::hierarchies::auto(); 21 | let cg: Cgroup = CgroupBuilder::new("test_cpu_res_build") 22 | .cpu() 23 | .shares(85) 24 | .done() 25 | .build(h) 26 | .unwrap(); 27 | 28 | { 29 | let cpu: &CpuController = cg.controller_of().unwrap(); 30 | assert!(cpu.shares().is_ok()); 31 | assert_eq!(cpu.shares().unwrap(), 85); 32 | } 33 | 34 | cg.delete().unwrap(); 35 | } 36 | 37 | #[test] 38 | pub fn test_memory_res_build() { 39 | let h = cgroups_rs::hierarchies::auto(); 40 | let cg: Cgroup = CgroupBuilder::new("test_memory_res_build") 41 | .memory() 42 | .kernel_memory_limit(128 * 1024 * 1024) 43 | .swappiness(70) 44 | .memory_hard_limit(1024 * 1024 * 1024) 45 | .done() 46 | .build(h) 47 | .unwrap(); 48 | 49 | { 50 | let c: &MemController = cg.controller_of().unwrap(); 51 | if !c.v2() { 52 | // Note: we don't tests the value of c.kmem_stat().limit_in_bytes because on Linux 53 | // kernel >= 5.16 setting this value is unsupported. 54 | assert_eq!(c.memory_stat().swappiness, 70); 55 | } 56 | assert_eq!(c.memory_stat().limit_in_bytes, 1024 * 1024 * 1024); 57 | } 58 | 59 | cg.delete().unwrap(); 60 | } 61 | 62 | #[test] 63 | pub fn test_pid_res_build() { 64 | let h = cgroups_rs::hierarchies::auto(); 65 | let cg: Cgroup = CgroupBuilder::new("test_pid_res_build") 66 | .pid() 67 | .maximum_number_of_processes(MaxValue::Value(123)) 68 | .done() 69 | .build(h) 70 | .unwrap(); 71 | 72 | { 73 | let c: &PidController = cg.controller_of().unwrap(); 74 | assert!(c.get_pid_max().is_ok()); 75 | assert_eq!(c.get_pid_max().unwrap(), MaxValue::Value(123)); 76 | } 77 | 78 | cg.delete().unwrap(); 79 | } 80 | 81 | #[test] 82 | #[ignore] // ignore this test for now, not sure why my kernel doesn't like it 83 | pub fn test_devices_res_build() { 84 | let h = cgroups_rs::hierarchies::auto(); 85 | let cg: Cgroup = CgroupBuilder::new("test_devices_res_build") 86 | .devices() 87 | .device(1, 6, DeviceType::Char, true, vec![DevicePermissions::Read]) 88 | .done() 89 | .build(h) 90 | .unwrap(); 91 | 92 | { 93 | let c: &DevicesController = cg.controller_of().unwrap(); 94 | assert!(c.allowed_devices().is_ok()); 95 | assert_eq!( 96 | c.allowed_devices().unwrap(), 97 | vec![DeviceResource { 98 | allow: true, 99 | devtype: DeviceType::Char, 100 | major: 1, 101 | minor: 6, 102 | access: vec![DevicePermissions::Read], 103 | }] 104 | ); 105 | } 106 | cg.delete().unwrap(); 107 | } 108 | 109 | #[test] 110 | pub fn test_network_res_build() { 111 | let h = cgroups_rs::hierarchies::auto(); 112 | if h.v2() { 113 | // FIXME add cases for v2 114 | return; 115 | } 116 | let cg: Cgroup = CgroupBuilder::new("test_network_res_build") 117 | .network() 118 | .class_id(1337) 119 | .done() 120 | .build(h) 121 | .unwrap(); 122 | 123 | { 124 | let c: &NetClsController = cg.controller_of().unwrap(); 125 | assert!(c.get_class().is_ok()); 126 | assert_eq!(c.get_class().unwrap(), 1337); 127 | } 128 | cg.delete().unwrap(); 129 | } 130 | 131 | #[test] 132 | pub fn test_hugepages_res_build() { 133 | let h = cgroups_rs::hierarchies::auto(); 134 | if h.v2() { 135 | // FIXME add cases for v2 136 | return; 137 | } 138 | let cg: Cgroup = CgroupBuilder::new("test_hugepages_res_build") 139 | .hugepages() 140 | .limit("2MB".to_string(), 4 * 2 * 1024 * 1024) 141 | .done() 142 | .build(h) 143 | .unwrap(); 144 | 145 | { 146 | let c: &HugeTlbController = cg.controller_of().unwrap(); 147 | assert!(c.limit_in_bytes("2MB").is_ok()); 148 | assert_eq!(c.limit_in_bytes("2MB").unwrap(), 4 * 2 * 1024 * 1024); 149 | } 150 | cg.delete().unwrap(); 151 | } 152 | 153 | #[test] 154 | #[ignore] // high version kernel not support `blkio.weight` 155 | pub fn test_blkio_res_build() { 156 | let h = cgroups_rs::hierarchies::auto(); 157 | let cg: Cgroup = CgroupBuilder::new("test_blkio_res_build") 158 | .blkio() 159 | .weight(100) 160 | .done() 161 | .build(h) 162 | .unwrap(); 163 | 164 | { 165 | let c: &BlkIoController = cg.controller_of().unwrap(); 166 | assert_eq!(c.blkio().weight, 100); 167 | } 168 | cg.delete().unwrap(); 169 | } 170 | -------------------------------------------------------------------------------- /tests/cgroup.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Levente Kurusa 2 | // Copyright (c) 2020 And Group 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 or MIT 5 | // 6 | 7 | //! Simple unit tests about the control groups system. 8 | use cgroups_rs::cgroup::{ 9 | CGROUP_MODE_DOMAIN, CGROUP_MODE_DOMAIN_INVALID, CGROUP_MODE_DOMAIN_THREADED, 10 | CGROUP_MODE_THREADED, 11 | }; 12 | use cgroups_rs::memory::MemController; 13 | use cgroups_rs::Controller; 14 | use cgroups_rs::{Cgroup, CgroupPid, Subsystem}; 15 | use std::process::Command; 16 | use std::thread::sleep; 17 | use std::time::Duration; 18 | 19 | #[test] 20 | fn test_procs_iterator_cgroup() { 21 | let h = cgroups_rs::hierarchies::auto(); 22 | let pid = libc::pid_t::from(nix::unistd::getpid()) as u64; 23 | let cg = Cgroup::new(h, String::from("test_procs_iterator_cgroup")).unwrap(); 24 | { 25 | // Add a task to the control group. 26 | cg.add_task_by_tgid(CgroupPid::from(pid)).unwrap(); 27 | 28 | let mut procs = cg.procs().into_iter(); 29 | // Verify that the task is indeed in the xcontrol group 30 | assert_eq!(procs.next(), Some(CgroupPid::from(pid))); 31 | assert_eq!(procs.next(), None); 32 | 33 | // Now, try removing it. 34 | cg.remove_task_by_tgid(CgroupPid::from(pid)).unwrap(); 35 | procs = cg.procs().into_iter(); 36 | 37 | // Verify that it was indeed removed. 38 | assert_eq!(procs.next(), None); 39 | } 40 | cg.delete().unwrap(); 41 | } 42 | 43 | #[test] 44 | fn test_tasks_iterator_cgroup_v1() { 45 | if cgroups_rs::hierarchies::is_cgroup2_unified_mode() { 46 | return; 47 | } 48 | let h = cgroups_rs::hierarchies::auto(); 49 | let pid = libc::pid_t::from(nix::unistd::getpid()) as u64; 50 | let cg = Cgroup::new(h, String::from("test_tasks_iterator_cgroup_v1")).unwrap(); 51 | { 52 | // Add a task to the control group. 53 | cg.add_task(CgroupPid::from(pid)).unwrap(); 54 | 55 | let mut tasks = cg.tasks().into_iter(); 56 | // Verify that the task is indeed in the xcontrol group 57 | assert_eq!(tasks.next(), Some(CgroupPid::from(pid))); 58 | assert_eq!(tasks.next(), None); 59 | 60 | // Now, try removing it. 61 | cg.remove_task(CgroupPid::from(pid)).unwrap(); 62 | tasks = cg.tasks().into_iter(); 63 | 64 | // Verify that it was indeed removed. 65 | assert_eq!(tasks.next(), None); 66 | } 67 | cg.delete().unwrap(); 68 | } 69 | 70 | #[test] 71 | fn test_tasks_iterator_cgroup_threaded_mode() { 72 | if !cgroups_rs::hierarchies::is_cgroup2_unified_mode() { 73 | return; 74 | } 75 | let pid = libc::pid_t::from(nix::unistd::getpid()) as u64; 76 | let cg = Cgroup::new( 77 | cgroups_rs::hierarchies::auto(), 78 | String::from("test_tasks_iterator_cgroup_threaded_mode"), 79 | ) 80 | .unwrap(); 81 | let cg_threaded_sub1 = Cgroup::new_with_specified_controllers( 82 | cgroups_rs::hierarchies::auto(), 83 | String::from("test_tasks_iterator_cgroup_threaded_mode/threaded_sub1"), 84 | Some(vec![String::from("cpuset"), String::from("cpu")]), 85 | ) 86 | .unwrap(); 87 | let cg_threaded_sub2 = Cgroup::new_with_specified_controllers( 88 | cgroups_rs::hierarchies::auto(), 89 | String::from("test_tasks_iterator_cgroup_threaded_mode/threaded_sub2"), 90 | Some(vec![String::from("cpuset"), String::from("cpu")]), 91 | ) 92 | .unwrap(); 93 | { 94 | // Verify that cgroup type of the control group is domain mode. 95 | assert_eq!(cg.get_cgroup_type().unwrap(), CGROUP_MODE_DOMAIN); 96 | 97 | // Set cgroup type of the sub-control group is thread mode. 98 | cg_threaded_sub1 99 | .set_cgroup_type(CGROUP_MODE_THREADED) 100 | .unwrap(); 101 | // Verify that cgroup type of the sub-control group is thread mode. 102 | assert_eq!( 103 | cg_threaded_sub1.get_cgroup_type().unwrap(), 104 | CGROUP_MODE_THREADED 105 | ); 106 | // Verify that the cgroup type of the sub-control group that does 107 | // not set the cgroup type is domain invalid mode. 108 | assert_eq!( 109 | cg_threaded_sub2.get_cgroup_type().unwrap(), 110 | CGROUP_MODE_DOMAIN_INVALID 111 | ); 112 | // Verify whether the cgroup type of the parent control group of 113 | // the control group whose cgroup type is set to thread mode is 114 | // domain thread mode. 115 | assert_eq!(cg.get_cgroup_type().unwrap(), CGROUP_MODE_DOMAIN_THREADED); 116 | 117 | // Set cgroup type of the sub-control group is thread mode. 118 | cg_threaded_sub2 119 | .set_cgroup_type(CGROUP_MODE_THREADED) 120 | .unwrap(); 121 | // Verify that cgroup type of the sub-control group is thread mode. 122 | assert_eq!( 123 | cg_threaded_sub2.get_cgroup_type().unwrap(), 124 | CGROUP_MODE_THREADED 125 | ); 126 | 127 | // Add a proc to the control group. 128 | cg.add_task_by_tgid(CgroupPid::from(pid)).unwrap(); 129 | 130 | let mut procs = cg.procs().into_iter(); 131 | // Verify that the task is indeed in the x control group 132 | assert_eq!(procs.next(), Some(CgroupPid::from(pid))); 133 | assert_eq!(procs.next(), None); 134 | 135 | // Add a task to the sub control group. 136 | cg_threaded_sub1.add_task(CgroupPid::from(pid)).unwrap(); 137 | 138 | let mut tasks = cg_threaded_sub1.tasks().into_iter(); 139 | // Verify that the task is indeed in the xcontrol group 140 | assert_eq!(tasks.next(), Some(CgroupPid::from(pid))); 141 | assert_eq!(tasks.next(), None); 142 | 143 | // Now, try move it to parent. 144 | cg_threaded_sub1 145 | .move_task_to_parent(CgroupPid::from(pid)) 146 | .unwrap(); 147 | tasks = cg_threaded_sub1.tasks().into_iter(); 148 | 149 | // Verify that it was indeed removed. 150 | assert_eq!(tasks.next(), None); 151 | 152 | // Now, try removing it. 153 | cg.remove_task_by_tgid(CgroupPid::from(pid)).unwrap(); 154 | procs = cg.procs().into_iter(); 155 | 156 | // Verify that it was indeed removed. 157 | assert_eq!(procs.next(), None); 158 | } 159 | cg_threaded_sub1.delete().unwrap(); 160 | cg_threaded_sub2.delete().unwrap(); 161 | cg.delete().unwrap(); 162 | } 163 | 164 | #[test] 165 | fn test_kill_cgroup() { 166 | if !cgroups_rs::hierarchies::is_cgroup2_unified_mode() { 167 | return; 168 | } 169 | let h = cgroups_rs::hierarchies::auto(); 170 | let cg = Cgroup::new(h, String::from("test_kill_cgroup")).unwrap(); 171 | { 172 | // Spawn a proc, don't want to getpid(2) here. 173 | let mut child = Command::new("sleep").arg("infinity").spawn().unwrap(); 174 | cg.add_task_by_tgid(CgroupPid::from(child.id() as u64)) 175 | .unwrap(); 176 | 177 | let cg_procs = cg.procs(); 178 | assert_eq!(cg_procs.len(), 1_usize); 179 | 180 | // Now kill and wait on the proc. 181 | cg.kill().unwrap(); 182 | 183 | let mut tries = 0; 184 | let status: Option = loop { 185 | match child.try_wait() { 186 | Ok(Some(status)) => { 187 | break Some(status); 188 | } 189 | Ok(None) => { 190 | if tries > 3 { 191 | break None; 192 | } 193 | sleep(Duration::from_millis(100)); 194 | tries += 1; 195 | } 196 | Err(e) => { 197 | child.kill().unwrap(); 198 | panic!("error attempting to wait: {}", e); 199 | } 200 | } 201 | }; 202 | assert!(status.is_some()); 203 | } 204 | cg.delete().unwrap(); 205 | } 206 | 207 | #[test] 208 | fn test_cgroup_with_relative_paths() { 209 | if cgroups_rs::hierarchies::is_cgroup2_unified_mode() { 210 | return; 211 | } 212 | let h = cgroups_rs::hierarchies::auto(); 213 | let cgroup_root = h.root(); 214 | let cgroup_name = "test_cgroup_with_relative_paths"; 215 | 216 | let cg = Cgroup::load(h, String::from(cgroup_name)); 217 | { 218 | let subsystems = cg.subsystems(); 219 | subsystems.iter().for_each(|sub| match sub { 220 | Subsystem::Pid(c) => { 221 | let cgroup_path = c.path().to_str().unwrap(); 222 | let relative_path = "/pids/"; 223 | // cgroup_path = cgroup_root + relative_path + cgroup_name 224 | assert_eq!( 225 | cgroup_path, 226 | format!( 227 | "{}{}{}", 228 | cgroup_root.to_str().unwrap(), 229 | relative_path, 230 | cgroup_name 231 | ) 232 | ); 233 | } 234 | Subsystem::Mem(c) => { 235 | let cgroup_path = c.path().to_str().unwrap(); 236 | // cgroup_path = cgroup_root + relative_path + cgroup_name 237 | assert_eq!( 238 | cgroup_path, 239 | format!("{}/memory/{}", cgroup_root.to_str().unwrap(), cgroup_name) 240 | ); 241 | } 242 | _ => {} 243 | }); 244 | } 245 | cg.delete().unwrap(); 246 | } 247 | 248 | #[test] 249 | fn test_cgroup_v2() { 250 | if !cgroups_rs::hierarchies::is_cgroup2_unified_mode() { 251 | return; 252 | } 253 | let h = cgroups_rs::hierarchies::auto(); 254 | let cg = Cgroup::new(h, String::from("test_v2")).unwrap(); 255 | 256 | let mem_controller: &MemController = cg.controller_of().unwrap(); 257 | let (mem, swp, rev) = (4 * 1024 * 1000, 2 * 1024 * 1000, 1024 * 1000); 258 | 259 | mem_controller.set_limit(mem).unwrap(); 260 | mem_controller.set_memswap_limit(swp).unwrap(); 261 | mem_controller.set_soft_limit(rev).unwrap(); 262 | 263 | let memory_stat = mem_controller.memory_stat(); 264 | println!("memory_stat {:?}", memory_stat); 265 | assert_eq!(mem, memory_stat.limit_in_bytes); 266 | assert_eq!(rev, memory_stat.soft_limit_in_bytes); 267 | 268 | let memswap = mem_controller.memswap(); 269 | println!("memswap {:?}", memswap); 270 | assert_eq!(swp, memswap.limit_in_bytes); 271 | 272 | cg.delete().unwrap(); 273 | } 274 | -------------------------------------------------------------------------------- /tests/cpu.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 And Group 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 or MIT 4 | // 5 | 6 | //! Simple unit tests about the CPU control groups system. 7 | use cgroups_rs::cpu::CpuController; 8 | use cgroups_rs::Cgroup; 9 | 10 | #[test] 11 | fn test_cfs_quota_and_periods() { 12 | let h = cgroups_rs::hierarchies::auto(); 13 | let cg = Cgroup::new(h, String::from("test_cfs_quota_and_periods")).unwrap(); 14 | 15 | let cpu_controller: &CpuController = cg.controller_of().unwrap(); 16 | 17 | let current_quota = cpu_controller.cfs_quota().unwrap(); 18 | let current_peroid = cpu_controller.cfs_period().unwrap(); 19 | 20 | // verify default value 21 | // The default is “max 100000”. 22 | assert_eq!(-1, current_quota); 23 | assert_eq!(100000, current_peroid); 24 | 25 | // case 1 set quota 26 | let _ = cpu_controller.set_cfs_quota(2000); 27 | 28 | let current_quota = cpu_controller.cfs_quota().unwrap(); 29 | let current_peroid = cpu_controller.cfs_period().unwrap(); 30 | assert_eq!(2000, current_quota); 31 | assert_eq!(100000, current_peroid); 32 | 33 | // case 2 set period 34 | cpu_controller.set_cfs_period(1000000).unwrap(); 35 | let current_quota = cpu_controller.cfs_quota().unwrap(); 36 | let current_peroid = cpu_controller.cfs_period().unwrap(); 37 | assert_eq!(2000, current_quota); 38 | assert_eq!(1000000, current_peroid); 39 | 40 | // case 3 set both quota and period 41 | cpu_controller 42 | .set_cfs_quota_and_period(Some(5000), Some(100000)) 43 | .unwrap(); 44 | 45 | let current_quota = cpu_controller.cfs_quota().unwrap(); 46 | let current_peroid = cpu_controller.cfs_period().unwrap(); 47 | assert_eq!(5000, current_quota); 48 | assert_eq!(100000, current_peroid); 49 | 50 | // case 4 set both quota and period, set quota to -1 51 | cpu_controller 52 | .set_cfs_quota_and_period(Some(-1), None) 53 | .unwrap(); 54 | 55 | let current_quota = cpu_controller.cfs_quota().unwrap(); 56 | let current_peroid = cpu_controller.cfs_period().unwrap(); 57 | assert_eq!(-1, current_quota); 58 | assert_eq!(100000, current_peroid); 59 | 60 | cg.delete().unwrap(); 61 | } 62 | -------------------------------------------------------------------------------- /tests/cpuset.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Levente Kurusa 2 | // Copyright (c) 2020 And Group 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 or MIT 5 | // 6 | 7 | use cgroups_rs::cpuset::CpuSetController; 8 | use cgroups_rs::error::ErrorKind; 9 | use cgroups_rs::{Cgroup, CgroupPid}; 10 | 11 | use std::fs; 12 | 13 | #[test] 14 | fn test_cpuset_memory_pressure_root_cg() { 15 | let h = cgroups_rs::hierarchies::auto(); 16 | let cg = Cgroup::new(h, String::from("test_cpuset_memory_pressure_root_cg")).unwrap(); 17 | { 18 | let cpuset: &CpuSetController = cg.controller_of().unwrap(); 19 | 20 | // This is not a root control group, so it should fail via InvalidOperation. 21 | let res = cpuset.set_enable_memory_pressure(true); 22 | assert_eq!(res.unwrap_err().kind(), &ErrorKind::InvalidOperation); 23 | } 24 | cg.delete().unwrap(); 25 | } 26 | 27 | #[test] 28 | fn test_cpuset_set_cpus() { 29 | let h = cgroups_rs::hierarchies::auto(); 30 | let cg = Cgroup::new(h, String::from("test_cpuset_set_cpus")).unwrap(); 31 | { 32 | let cpuset: &CpuSetController = cg.controller_of().unwrap(); 33 | 34 | let set = cpuset.cpuset(); 35 | if cg.v2() { 36 | assert_eq!(0, set.cpus.len()); 37 | } else { 38 | // for cgroup v1, cpuset is copied from parent. 39 | assert!(!set.cpus.is_empty()); 40 | } 41 | 42 | // 0 43 | let r = cpuset.set_cpus("0"); 44 | assert!(r.is_ok()); 45 | 46 | let set = cpuset.cpuset(); 47 | assert_eq!(1, set.cpus.len()); 48 | assert_eq!((0, 0), set.cpus[0]); 49 | 50 | // all cpus in system 51 | let cpus = fs::read_to_string("/sys/fs/cgroup/cpuset.cpus.effective").unwrap_or_default(); 52 | let cpus = cpus.trim(); 53 | if !cpus.is_empty() { 54 | let r = cpuset.set_cpus(cpus); 55 | assert!(r.is_ok()); 56 | let set = cpuset.cpuset(); 57 | assert_eq!(1, set.cpus.len()); 58 | assert_eq!(format!("{}-{}", set.cpus[0].0, set.cpus[0].1), cpus); 59 | } 60 | } 61 | cg.delete().unwrap(); 62 | } 63 | 64 | #[test] 65 | fn test_cpuset_set_cpus_add_task() { 66 | let h = cgroups_rs::hierarchies::auto(); 67 | let cg = Cgroup::new(h, String::from("test_cpuset_set_cpus_add_task/sub-dir")).unwrap(); 68 | 69 | let cpuset: &CpuSetController = cg.controller_of().unwrap(); 70 | let set = cpuset.cpuset(); 71 | if cg.v2() { 72 | assert_eq!(0, set.cpus.len()); 73 | } else { 74 | // for cgroup v1, cpuset is copied from parent. 75 | assert!(!set.cpus.is_empty()); 76 | } 77 | 78 | // Add a task to the control group. 79 | let pid_i = libc::pid_t::from(nix::unistd::getpid()) as u64; 80 | let _ = cg.add_task_by_tgid(CgroupPid::from(pid_i)); 81 | let tasks = cg.tasks(); 82 | assert!(!tasks.is_empty()); 83 | println!("tasks after added: {:?}", tasks); 84 | 85 | // remove task 86 | cg.remove_task_by_tgid(CgroupPid::from(pid_i)).unwrap(); 87 | let tasks = cg.tasks(); 88 | println!("tasks after deleted: {:?}", tasks); 89 | assert_eq!(0, tasks.len()); 90 | 91 | cg.delete().unwrap(); 92 | } 93 | -------------------------------------------------------------------------------- /tests/devices.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Levente Kurusa 2 | // Copyright (c) 2020 And Group 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 or MIT 5 | // 6 | 7 | //! Integration tests about the devices subsystem 8 | 9 | use cgroups_rs::devices::{DevicePermissions, DeviceType, DevicesController}; 10 | use cgroups_rs::{Cgroup, DeviceResource}; 11 | 12 | #[test] 13 | fn test_devices_parsing() { 14 | // now only v2 15 | if cgroups_rs::hierarchies::is_cgroup2_unified_mode() { 16 | return; 17 | } 18 | 19 | let h = cgroups_rs::hierarchies::auto(); 20 | let cg = Cgroup::new(h, String::from("test_devices_parsing")).unwrap(); 21 | { 22 | let devices: &DevicesController = cg.controller_of().unwrap(); 23 | 24 | // Deny access to all devices first 25 | devices 26 | .deny_device( 27 | DeviceType::All, 28 | -1, 29 | -1, 30 | &[ 31 | DevicePermissions::Read, 32 | DevicePermissions::Write, 33 | DevicePermissions::MkNod, 34 | ], 35 | ) 36 | .unwrap(); 37 | // Acquire the list of allowed devices after we denied all 38 | let allowed_devices = devices.allowed_devices(); 39 | // Verify that there are no devices that we can access. 40 | assert!(allowed_devices.is_ok()); 41 | assert_eq!(allowed_devices.unwrap(), Vec::new()); 42 | 43 | // Now add mknod access to /dev/null device 44 | devices 45 | .allow_device(DeviceType::Char, 1, 3, &[DevicePermissions::MkNod]) 46 | .unwrap(); 47 | let allowed_devices = devices.allowed_devices(); 48 | assert!(allowed_devices.is_ok()); 49 | let allowed_devices = allowed_devices.unwrap(); 50 | assert_eq!(allowed_devices.len(), 1); 51 | assert_eq!( 52 | allowed_devices[0], 53 | DeviceResource { 54 | allow: true, 55 | devtype: DeviceType::Char, 56 | major: 1, 57 | minor: 3, 58 | access: vec![DevicePermissions::MkNod], 59 | } 60 | ); 61 | 62 | // Now deny, this device explicitly. 63 | devices 64 | .deny_device(DeviceType::Char, 1, 3, &DevicePermissions::all()) 65 | .unwrap(); 66 | // Finally, check that. 67 | let allowed_devices = devices.allowed_devices(); 68 | // Verify that there are no devices that we can access. 69 | assert!(allowed_devices.is_ok()); 70 | assert_eq!(allowed_devices.unwrap(), Vec::new()); 71 | } 72 | cg.delete().unwrap(); 73 | } 74 | -------------------------------------------------------------------------------- /tests/hugetlb.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 And Group 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 or MIT 4 | // 5 | 6 | //! Integration tests about the hugetlb subsystem 7 | use cgroups_rs::error::*; 8 | use cgroups_rs::hugetlb::{self, HugeTlbController}; 9 | use cgroups_rs::Cgroup; 10 | use std::fs; 11 | 12 | #[test] 13 | fn test_hugetlb_sizes() { 14 | // now only v2 15 | if cgroups_rs::hierarchies::is_cgroup2_unified_mode() { 16 | return; 17 | } 18 | 19 | let h = cgroups_rs::hierarchies::auto(); 20 | let cg = Cgroup::new(h, String::from("test_hugetlb_sizes")).unwrap(); 21 | { 22 | let hugetlb_controller: &HugeTlbController = cg.controller_of().unwrap(); 23 | let _ = hugetlb_controller.get_sizes(); 24 | 25 | // test sizes count 26 | let sizes = hugetlb_controller.get_sizes(); 27 | let sizes_count = fs::read_dir(hugetlb::HUGEPAGESIZE_DIR).unwrap().count(); 28 | assert_eq!(sizes.len(), sizes_count); 29 | 30 | for size in sizes { 31 | let supported = hugetlb_controller.size_supported(&size); 32 | assert!(supported); 33 | assert_no_error(hugetlb_controller.failcnt(&size)); 34 | assert_no_error(hugetlb_controller.limit_in_bytes(&size)); 35 | assert_no_error(hugetlb_controller.usage_in_bytes(&size)); 36 | assert_no_error(hugetlb_controller.max_usage_in_bytes(&size)); 37 | } 38 | } 39 | cg.delete().unwrap(); 40 | } 41 | 42 | fn assert_no_error(r: Result) { 43 | assert!(r.is_ok()) 44 | } 45 | -------------------------------------------------------------------------------- /tests/memory.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 And Group 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 or MIT 4 | // 5 | 6 | //! Integration tests about the hugetlb subsystem 7 | use cgroups_rs::memory::{MemController, SetMemory}; 8 | use cgroups_rs::Controller; 9 | use cgroups_rs::{Cgroup, MaxValue}; 10 | 11 | #[test] 12 | fn test_disable_oom_killer() { 13 | let h = cgroups_rs::hierarchies::auto(); 14 | let cg = Cgroup::new(h, String::from("test_disable_oom_killer")).unwrap(); 15 | { 16 | let mem_controller: &MemController = cg.controller_of().unwrap(); 17 | 18 | // before disable 19 | let m = mem_controller.memory_stat(); 20 | assert!(!m.oom_control.oom_kill_disable); 21 | 22 | // now only v1 23 | if !mem_controller.v2() { 24 | // disable oom killer 25 | let r = mem_controller.disable_oom_killer(); 26 | assert!(r.is_ok()); 27 | 28 | // after disable 29 | let m = mem_controller.memory_stat(); 30 | assert!(m.oom_control.oom_kill_disable); 31 | } 32 | } 33 | cg.delete().unwrap(); 34 | } 35 | 36 | #[test] 37 | fn set_kmem_limit_v1() { 38 | let h = cgroups_rs::hierarchies::auto(); 39 | if h.v2() { 40 | return; 41 | } 42 | 43 | let cg = Cgroup::new(h, String::from("set_kmem_limit_v1")).unwrap(); 44 | { 45 | let mem_controller: &MemController = cg.controller_of().unwrap(); 46 | mem_controller.set_kmem_limit(1).unwrap(); 47 | } 48 | cg.delete().unwrap(); 49 | } 50 | 51 | #[test] 52 | fn set_mem_v2() { 53 | let h = cgroups_rs::hierarchies::auto(); 54 | if !h.v2() { 55 | return; 56 | } 57 | 58 | let cg = Cgroup::new(h, String::from("set_mem_v2")).unwrap(); 59 | { 60 | let mem_controller: &MemController = cg.controller_of().unwrap(); 61 | 62 | // before disable 63 | let m = mem_controller.get_mem().unwrap(); 64 | // case 1: get default value 65 | assert_eq!(m.low, Some(MaxValue::Value(0))); 66 | assert_eq!(m.min, Some(MaxValue::Value(0))); 67 | assert_eq!(m.high, Some(MaxValue::Max)); 68 | assert_eq!(m.max, Some(MaxValue::Max)); 69 | 70 | // case 2: set parts 71 | let m = SetMemory { 72 | low: Some(MaxValue::Value(1024 * 1024 * 2)), 73 | high: Some(MaxValue::Value(1024 * 1024 * 1024 * 2)), 74 | min: Some(MaxValue::Value(1024 * 1024 * 3)), 75 | max: None, 76 | }; 77 | let r = mem_controller.set_mem(m); 78 | assert!(r.is_ok()); 79 | 80 | let m = mem_controller.get_mem().unwrap(); 81 | // get 82 | assert_eq!(m.low, Some(MaxValue::Value(1024 * 1024 * 2))); 83 | assert_eq!(m.min, Some(MaxValue::Value(1024 * 1024 * 3))); 84 | assert_eq!(m.high, Some(MaxValue::Value(1024 * 1024 * 1024 * 2))); 85 | assert_eq!(m.max, Some(MaxValue::Max)); 86 | 87 | // case 3: set parts 88 | let m = SetMemory { 89 | max: Some(MaxValue::Value(1024 * 1024 * 1024 * 2)), 90 | min: Some(MaxValue::Value(1024 * 1024 * 4)), 91 | high: Some(MaxValue::Max), 92 | low: None, 93 | }; 94 | let r = mem_controller.set_mem(m); 95 | assert!(r.is_ok()); 96 | 97 | let m = mem_controller.get_mem().unwrap(); 98 | // get 99 | assert_eq!(m.low, Some(MaxValue::Value(1024 * 1024 * 2))); 100 | assert_eq!(m.min, Some(MaxValue::Value(1024 * 1024 * 4))); 101 | assert_eq!(m.max, Some(MaxValue::Value(1024 * 1024 * 1024 * 2))); 102 | assert_eq!(m.high, Some(MaxValue::Max)); 103 | } 104 | 105 | cg.delete().unwrap(); 106 | } 107 | -------------------------------------------------------------------------------- /tests/pids.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Levente Kurusa 2 | // Copyright (c) 2020 And Group 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 or MIT 5 | // 6 | 7 | //! Integration tests about the pids subsystem 8 | use cgroups_rs::pid::PidController; 9 | use cgroups_rs::Controller; 10 | use cgroups_rs::{Cgroup, MaxValue}; 11 | 12 | use nix::sys::wait::{waitpid, WaitStatus}; 13 | use nix::unistd::{fork, ForkResult}; 14 | 15 | use libc::pid_t; 16 | 17 | #[test] 18 | fn create_and_delete_cgroup() { 19 | let h = cgroups_rs::hierarchies::auto(); 20 | let cg = Cgroup::new(h, String::from("create_and_delete_cgroup")).unwrap(); 21 | { 22 | let pidcontroller: &PidController = cg.controller_of().unwrap(); 23 | pidcontroller.set_pid_max(MaxValue::Value(1337)).unwrap(); 24 | let max = pidcontroller.get_pid_max(); 25 | assert!(max.is_ok()); 26 | assert_eq!(max.unwrap(), MaxValue::Value(1337)); 27 | } 28 | cg.delete().unwrap(); 29 | } 30 | 31 | #[test] 32 | fn test_pids_current_is_zero() { 33 | let h = cgroups_rs::hierarchies::auto(); 34 | let cg = Cgroup::new(h, String::from("test_pids_current_is_zero")).unwrap(); 35 | { 36 | let pidcontroller: &PidController = cg.controller_of().unwrap(); 37 | let current = pidcontroller.get_pid_current(); 38 | assert_eq!(current.unwrap(), 0); 39 | } 40 | cg.delete().unwrap(); 41 | } 42 | 43 | #[test] 44 | fn test_pids_events_is_zero() { 45 | let h = cgroups_rs::hierarchies::auto(); 46 | let cg = Cgroup::new(h, String::from("test_pids_events_is_zero")).unwrap(); 47 | { 48 | let pidcontroller: &PidController = cg.controller_of().unwrap(); 49 | let events = pidcontroller.get_pid_events(); 50 | assert!(events.is_ok()); 51 | assert_eq!(events.unwrap(), 0); 52 | } 53 | cg.delete().unwrap(); 54 | } 55 | 56 | #[test] 57 | fn test_pid_events_is_not_zero() { 58 | let h = cgroups_rs::hierarchies::auto(); 59 | let cg = Cgroup::new(h, String::from("test_pid_events_is_not_zero")).unwrap(); 60 | { 61 | let pids: &PidController = cg.controller_of().unwrap(); 62 | let before = pids.get_pid_events(); 63 | let before = before.unwrap(); 64 | 65 | match unsafe { fork() } { 66 | Ok(ForkResult::Parent { child, .. }) => { 67 | // move the process into the control group 68 | let _ = pids.add_task_by_tgid(&(pid_t::from(child) as u64).into()); 69 | 70 | println!("added task to cg: {:?}", child); 71 | 72 | // Set limit to one 73 | let _ = pids.set_pid_max(MaxValue::Value(1)); 74 | println!("current pid.max = {:?}", pids.get_pid_max()); 75 | 76 | // wait on the child 77 | let res = waitpid(child, None); 78 | if let Ok(WaitStatus::Exited(_, e)) = res { 79 | assert_eq!(e, 0i32); 80 | } else { 81 | panic!("found result: {:?}", res); 82 | } 83 | 84 | // Check pids.events 85 | let events = pids.get_pid_events(); 86 | assert!(events.is_ok()); 87 | assert_eq!(events.unwrap(), before + 1); 88 | } 89 | Ok(ForkResult::Child) => loop { 90 | let pids_max = pids.get_pid_max(); 91 | if pids_max.is_ok() && pids_max.unwrap() == MaxValue::Value(1) { 92 | if unsafe { fork() }.is_err() { 93 | unsafe { libc::exit(0) }; 94 | } else { 95 | unsafe { libc::exit(1) }; 96 | } 97 | } 98 | }, 99 | Err(_) => panic!("failed to fork"), 100 | } 101 | } 102 | cg.delete().unwrap(); 103 | } 104 | -------------------------------------------------------------------------------- /tests/resources.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Levente Kurusa 2 | // Copyright (c) 2020 And Group 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 or MIT 5 | // 6 | 7 | //! Integration test about setting resources using `apply()` 8 | use cgroups_rs::pid::PidController; 9 | use cgroups_rs::{Cgroup, MaxValue, PidResources, Resources}; 10 | 11 | #[test] 12 | fn pid_resources() { 13 | let h = cgroups_rs::hierarchies::auto(); 14 | let cg = Cgroup::new(h, String::from("pid_resources")).unwrap(); 15 | { 16 | let res = Resources { 17 | pid: PidResources { 18 | maximum_number_of_processes: Some(MaxValue::Value(512)), 19 | }, 20 | ..Default::default() 21 | }; 22 | cg.apply(&res).unwrap(); 23 | 24 | // verify 25 | let pidcontroller: &PidController = cg.controller_of().unwrap(); 26 | let pid_max = pidcontroller.get_pid_max(); 27 | assert!(pid_max.is_ok()); 28 | assert_eq!(pid_max.unwrap(), MaxValue::Value(512)); 29 | } 30 | cg.delete().unwrap(); 31 | } 32 | -------------------------------------------------------------------------------- /tools/create_cgroup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Copyright (c) 2018 Levente Kurusa 4 | # 5 | # SPDX-License-Identifier: Apache-2.0 or MIT 6 | # 7 | 8 | CONTROL_GROUPS=`cargo test -- --list 2>/dev/null | egrep 'test$' | egrep -v '^src' | cut -d':' -f1` 9 | 10 | echo This script will create a control group in every subsystem of the V1 hierarchy. 11 | echo For this, we will need your sudo privileges. Please do not trust this shell script and have a look to check that it does something that you are okay with. 12 | sudo -v 13 | 14 | for i in ${CONTROL_GROUPS} 15 | do sudo mkdir -p /sys/fs/cgroup/{blkio,cpu,cpuacct,cpuset,devices,freezer,hugetlb,memory,net_cls,net_prio,perf_event,pids}/$i/ 16 | 17 | done 18 | echo 19 | echo We will now set up permissions... 20 | echo 21 | 22 | for i in ${CONTROL_GROUPS} 23 | do sudo chown -R ${USER} /sys/fs/cgroup/{blkio,cpu,cpuacct,cpuset,devices,freezer,hugetlb,memory,net_cls,net_prio,perf_event,pids}/$i/ 24 | done 25 | -------------------------------------------------------------------------------- /tools/delete_cgroup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Copyright (c) 2018 Levente Kurusa 4 | # 5 | # SPDX-License-Identifier: Apache-2.0 or MIT 6 | # 7 | 8 | CONTROL_GROUPS=`cargo test -- --list 2>/dev/null | egrep 'test$' | egrep -v '^src' | cut -d':' -f1` 9 | 10 | echo This script will delete the control groups created by the create_cgroup.sh shell script. 11 | echo 12 | echo It may spit out some errors, but that is fine. 13 | echo 14 | echo For this, we will need your sudo privileges. Please do not trust this shell script and have a look to check that it does something that you are okay with. 15 | sudo -v 16 | 17 | for i in ${CONTROL_GROUPS} 18 | do sudo rmdir /sys/fs/cgroup/{blkio,cpu,cpuacct,cpuset,devices,freezer,hugetlb,memory,net_cls,net_prio,perf_event,pids}/$i/ 19 | done 20 | --------------------------------------------------------------------------------