├── .cargo └── config ├── .gitignore ├── .travis.yml ├── Cargo.toml ├── LICENSE ├── LICENSE-Apache-2.0 ├── LICENSE-MIT ├── README.md ├── src ├── blkio.rs ├── cgroup.rs ├── cgroup_builder.rs ├── cpu.rs ├── cpuacct.rs ├── cpuset.rs ├── devices.rs ├── error.rs ├── freezer.rs ├── hierarchies.rs ├── hugetlb.rs ├── lib.rs ├── memory.rs ├── net_cls.rs ├── net_prio.rs ├── perf_event.rs ├── pid.rs └── rdma.rs ├── tests ├── builder.rs ├── cgroup.rs ├── cpuset.rs ├── devices.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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | /target 13 | **/*.rs.bk 14 | Cargo.lock 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - stable 4 | - beta 5 | - nightly 6 | matrix: 7 | allow_failures: 8 | - rust: nightly 9 | fast_finish: true 10 | script: 11 | - cargo build --verbose --all 12 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cgroups" 3 | description = "Native Rust crate for managing control groups on Linux" 4 | repository = "https://github.com/levex/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.1.1-alpha.0" 9 | authors = ["Levente Kurusa ", "Sam Wilson "] 10 | edition = "2018" 11 | 12 | [dependencies] 13 | log = "0.4" 14 | 15 | [dev-dependencies] 16 | nix = "0.11.0" 17 | libc = "0.2.43" 18 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # No Longer Maintained 2 | 3 | This repository is no longer actively maintained. 4 | 5 | Several forks of cgroups-rs exist: 6 | 7 | * [kata-containers][kata] - Roughly the same API 8 | * [controlgroup-rs][ordovicia] - Complete redesign 9 | 10 | None of these forks are endorsed by the authors of this crate, and are listed for informational purposes only. 11 | 12 | [kata]: https://github.com/kata-containers/cgroups-rs 13 | [ordovicia]: https://github.com/ordovicia/controlgroup-rs 14 | 15 | --- 16 | 17 | # cgroups-rs ![Build](https://travis-ci.org/levex/cgroups-rs.svg?branch=master) 18 | Native Rust library for managing control groups under Linux 19 | 20 | Right now the crate only support the original, V1 hierarchy, however support 21 | is planned for the Unified hierarchy. 22 | 23 | # Examples 24 | 25 | ## Create a control group using the builder pattern 26 | 27 | ``` rust 28 | // Acquire a handle for the V1 cgroup hierarchy. 29 | let hier = ::hierarchies::V1::new(); 30 | 31 | // Use the builder pattern (see the documentation to create the control group) 32 | // 33 | // This creates a control group named "example" in the V1 hierarchy. 34 | let cg: Cgroup = CgroupBuilder::new("example", &v1) 35 | .cpu() 36 | .shares(85) 37 | .done() 38 | .build(); 39 | 40 | // Now `cg` is a control group that gets 85% of the CPU time in relative to 41 | // other control groups. 42 | 43 | // Get a handle to the CPU controller. 44 | let cpus: &CpuController = cg.controller_of().unwrap(); 45 | cpus.add_task(1234u64); 46 | 47 | // [...] 48 | 49 | // Finally, clean up and delete the control group. 50 | cg.delete(); 51 | 52 | // Note that `Cgroup` does not implement `Drop` and therefore when the 53 | // structure is dropped, the Cgroup will stay around. This is because, later 54 | // you can then re-create the `Cgroup` using `load()`. We aren't too set on 55 | // this behavior, so it might change in the feature. Rest assured, it will be a 56 | // major version change. 57 | ``` 58 | 59 | # Disclaimer 60 | 61 | This crate is licensed under: 62 | 63 | - MIT License (see LICENSE-MIT); or 64 | - Apache 2.0 License (see LICENSE-Apache-2.0), 65 | 66 | at your option. 67 | 68 | Please note that this crate is under heavy development, we will use sematic 69 | versioning, but during the `0.0.*` phase, no guarantees are made about 70 | backwards compatibility. 71 | 72 | Regardless, check back often and thanks for taking a look! 73 | -------------------------------------------------------------------------------- /src/blkio.rs: -------------------------------------------------------------------------------- 1 | //! This module contains the implementation of the `blkio` cgroup subsystem. 2 | //! 3 | //! See the Kernel's documentation for more information about this subsystem, found at: 4 | //! [Documentation/cgroup-v1/blkio-controller.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/blkio-controller.txt) 5 | use std::fs::File; 6 | use std::io::{Read, Write}; 7 | use std::path::PathBuf; 8 | 9 | use crate::error::*; 10 | use crate::error::ErrorKind::*; 11 | 12 | use crate::{ 13 | BlkIoResources, ControllIdentifier, ControllerInternal, Controllers, Resources, Subsystem, 14 | }; 15 | 16 | /// A controller that allows controlling the `blkio` subsystem of a Cgroup. 17 | /// 18 | /// In essence, using the `blkio` controller one can limit and throttle the tasks' usage of block 19 | /// devices in the control group. 20 | #[derive(Debug, Clone)] 21 | pub struct BlkIoController { 22 | base: PathBuf, 23 | path: PathBuf, 24 | } 25 | 26 | #[derive(Eq, PartialEq, Debug)] 27 | /// Per-device information 28 | pub struct BlkIoData { 29 | /// The major number of the device. 30 | pub major: i16, 31 | /// The minor number of the device. 32 | pub minor: i16, 33 | /// The data that is associated with the device. 34 | pub data: u64, 35 | } 36 | 37 | #[derive(Eq, PartialEq, Debug)] 38 | /// Per-device activity from the control group. 39 | pub struct IoService { 40 | /// The major number of the device. 41 | pub major: i16, 42 | /// The minor number of the device. 43 | pub minor: i16, 44 | /// How many items were read from the device. 45 | pub read: u64, 46 | /// How many items were written to the device. 47 | pub write: u64, 48 | /// How many items were synchronously transferred. 49 | pub sync: u64, 50 | /// How many items were asynchronously transferred. 51 | pub r#async: u64, 52 | /// Total number of items transferred. 53 | pub total: u64, 54 | } 55 | 56 | fn parse_io_service(s: String) -> Result> { 57 | s.lines() 58 | .filter(|x| x.split_whitespace().collect::>().len() == 3) 59 | .map(|x| { 60 | let mut spl = x.split_whitespace(); 61 | (spl.nth(0).unwrap(), spl.nth(0).unwrap(), spl.nth(0).unwrap()) 62 | }) 63 | .map(|(a, b, c)| { 64 | let mut spl = a.split(":"); 65 | (spl.nth(0).unwrap(), spl.nth(0).unwrap(), b, c) 66 | }) 67 | .collect::>() 68 | .chunks(5) 69 | .map(|x| { 70 | match x { 71 | [(major, minor, "Read", read_val), (_, _, "Write", write_val), 72 | (_, _, "Sync", sync_val), (_, _, "Async", async_val), 73 | (_, _, "Total", total_val)] => 74 | Some(IoService { 75 | major: major.parse::().unwrap(), 76 | minor: minor.parse::().unwrap(), 77 | read: read_val.parse::().unwrap(), 78 | write: write_val.parse::().unwrap(), 79 | sync: sync_val.parse::().unwrap(), 80 | r#async: async_val.parse::().unwrap(), 81 | total: total_val.parse::().unwrap(), 82 | }), 83 | _ => None, 84 | } 85 | }) 86 | .fold(Ok(Vec::new()), |acc, x| { 87 | if acc.is_err() || x.is_none() { 88 | Err(Error::new(ParseError)) 89 | } else { 90 | let mut acc = acc.unwrap(); 91 | acc.push(x.unwrap()); 92 | Ok(acc) 93 | } 94 | }) 95 | } 96 | 97 | fn parse_io_service_total(s: String) -> Result { 98 | s.lines() 99 | .filter(|x| x.split_whitespace().collect::>().len() == 2) 100 | .fold(Err(Error::new(ParseError)), |_, x| { 101 | match x.split_whitespace().collect::>().as_slice() { 102 | ["Total", val] => val.parse::().map_err(|_| Error::new(ParseError)), 103 | _ => Err(Error::new(ParseError)), 104 | } 105 | }) 106 | } 107 | 108 | fn parse_blkio_data(s: String) -> Result> { 109 | let r = s 110 | .chars() 111 | .map(|x| if x == ':' { ' ' } else { x }) 112 | .collect::(); 113 | 114 | let r = r 115 | .lines() 116 | .flat_map(|x| x.split_whitespace()) 117 | .collect::>(); 118 | 119 | let r = r.chunks(3).collect::>(); 120 | 121 | let mut res = Vec::new(); 122 | 123 | let err = r.iter().try_for_each(|x| match x { 124 | [major, minor, data] => { 125 | res.push(BlkIoData { 126 | major: major.parse::().unwrap(), 127 | minor: minor.parse::().unwrap(), 128 | data: data.parse::().unwrap(), 129 | }); 130 | Ok(()) 131 | } 132 | _ => Err(Error::new(ParseError)), 133 | }); 134 | 135 | if err.is_err() { 136 | return Err(Error::new(ParseError)); 137 | } else { 138 | return Ok(res); 139 | } 140 | } 141 | 142 | /// Current state and statistics about how throttled are the block devices when accessed from the 143 | /// controller's control group. 144 | #[derive(Debug)] 145 | pub struct BlkIoThrottle { 146 | /// Statistics about the bytes transferred between the block devices by the tasks in this 147 | /// control group. 148 | pub io_service_bytes: Vec, 149 | /// Total amount of bytes transferred to and from the block devices. 150 | pub io_service_bytes_total: u64, 151 | /// Same as `io_service_bytes`, but contains all descendant control groups. 152 | pub io_service_bytes_recursive: Vec, 153 | /// Total amount of bytes transferred to and from the block devices, including all descendant 154 | /// control groups. 155 | pub io_service_bytes_recursive_total: u64, 156 | /// The number of I/O operations performed on the devices as seen by the throttling policy. 157 | pub io_serviced: Vec, 158 | /// The total number of I/O operations performed on the devices as seen by the throttling 159 | /// policy. 160 | pub io_serviced_total: u64, 161 | /// Same as `io_serviced`, but contains all descendant control groups. 162 | pub io_serviced_recursive: Vec, 163 | /// Same as `io_serviced`, but contains all descendant control groups and contains only the 164 | /// total amount. 165 | pub io_serviced_recursive_total: u64, 166 | /// The upper limit of bytes per second rate of read operation on the block devices by the 167 | /// control group's tasks. 168 | pub read_bps_device: Vec, 169 | /// The upper limit of I/O operation per second, when said operation is a read operation. 170 | pub read_iops_device: Vec, 171 | /// The upper limit of bytes per second rate of write operation on the block devices by the 172 | /// control group's tasks. 173 | pub write_bps_device: Vec, 174 | /// The upper limit of I/O operation per second, when said operation is a write operation. 175 | pub write_iops_device: Vec, 176 | } 177 | 178 | /// Statistics and state of the block devices. 179 | #[derive(Debug)] 180 | pub struct BlkIo { 181 | /// The number of BIOS requests merged into I/O requests by the control group's tasks. 182 | pub io_merged: Vec, 183 | /// Same as `io_merged`, but only reports the total number. 184 | pub io_merged_total: u64, 185 | /// Same as `io_merged`, but contains all descendant control groups. 186 | pub io_merged_recursive: Vec, 187 | /// Same as `io_merged_recursive`, but only reports the total number. 188 | pub io_merged_recursive_total: u64, 189 | /// The number of requests queued for I/O operations by the tasks of the control group. 190 | pub io_queued: Vec, 191 | /// Same as `io_queued`, but only reports the total number. 192 | pub io_queued_total: u64, 193 | /// Same as `io_queued`, but contains all descendant control groups. 194 | pub io_queued_recursive: Vec, 195 | /// Same as `io_queued_recursive`, but contains all descendant control groups. 196 | pub io_queued_recursive_total: u64, 197 | /// The number of bytes transferred from and to the block device (as seen by the CFQ I/O scheduler). 198 | pub io_service_bytes: Vec, 199 | /// Same as `io_service_bytes`, but contains all descendant control groups. 200 | pub io_service_bytes_total: u64, 201 | /// Same as `io_service_bytes`, but contains all descendant control groups. 202 | pub io_service_bytes_recursive: Vec, 203 | /// Total amount of bytes transferred between the tasks and block devices, including the 204 | /// descendant control groups' numbers. 205 | pub io_service_bytes_recursive_total: u64, 206 | /// The number of I/O operations (as seen by the CFQ I/O scheduler) between the devices and the 207 | /// control group's tasks. 208 | pub io_serviced: Vec, 209 | /// The total number of I/O operations performed on the devices as seen by the throttling 210 | /// policy. 211 | pub io_serviced_total: u64, 212 | /// Same as `io_serviced`, but contains all descendant control groups. 213 | pub io_serviced_recursive: Vec, 214 | /// Same as `io_serviced`, but contains all descendant control groups and contains only the 215 | /// total amount. 216 | pub io_serviced_recursive_total: u64, 217 | /// The total time spent between dispatch and request completion for I/O requests (as seen by 218 | /// the CFQ I/O scheduler) by the control group's tasks. 219 | pub io_service_time: Vec, 220 | /// Same as `io_service_time`, but contains all descendant control groups and contains only the 221 | /// total amount. 222 | pub io_service_time_total: u64, 223 | /// Same as `io_service_time`, but contains all descendant control groups. 224 | pub io_service_time_recursive: Vec, 225 | /// Same as `io_service_time_recursive`, but contains all descendant control groups and only 226 | /// the total amount. 227 | pub io_service_time_recursive_total: u64, 228 | /// Total amount of time spent waiting for a free slot in the CFQ I/O scheduler's queue. 229 | pub io_wait_time: Vec, 230 | /// Same as `io_wait_time`, but only reports the total amount. 231 | pub io_wait_time_total: u64, 232 | /// Same as `io_wait_time`, but contains all descendant control groups. 233 | pub io_wait_time_recursive: Vec, 234 | /// Same as `io_wait_time_recursive`, but only reports the total amount. 235 | pub io_wait_time_recursive_total: u64, 236 | /// How much weight do the control group's tasks have when competing against the descendant 237 | /// control group's tasks. 238 | pub leaf_weight: u64, 239 | /// Same as `leaf_weight`, but per-block-device. 240 | pub leaf_weight_device: Vec, 241 | /// Total number of sectors transferred between the block devices and the control group's 242 | /// tasks. 243 | pub sectors: Vec, 244 | /// Same as `sectors`, but contains all descendant control groups. 245 | pub sectors_recursive: Vec, 246 | /// Similar statistics, but as seen by the throttle policy. 247 | pub throttle: BlkIoThrottle, 248 | /// The time the control group had access to the I/O devices. 249 | pub time: Vec, 250 | /// Same as `time`, but contains all descendant control groups. 251 | pub time_recursive: Vec, 252 | /// The weight of this control group. 253 | pub weight: u64, 254 | /// Same as `weight`, but per-block-device. 255 | pub weight_device: Vec, 256 | } 257 | 258 | impl ControllerInternal for BlkIoController { 259 | fn control_type(&self) -> Controllers { 260 | Controllers::BlkIo 261 | } 262 | fn get_path(&self) -> &PathBuf { 263 | &self.path 264 | } 265 | fn get_path_mut(&mut self) -> &mut PathBuf { 266 | &mut self.path 267 | } 268 | fn get_base(&self) -> &PathBuf { 269 | &self.base 270 | } 271 | 272 | fn apply(&self, res: &Resources) -> Result<()> { 273 | // get the resources that apply to this controller 274 | let res: &BlkIoResources = &res.blkio; 275 | 276 | if res.update_values { 277 | let _ = self.set_weight(res.weight as u64); 278 | let _ = self.set_leaf_weight(res.leaf_weight as u64); 279 | 280 | for dev in &res.weight_device { 281 | let _ = self.set_weight_for_device(dev.major, dev.minor, dev.weight as u64); 282 | let _ = self.set_leaf_weight_for_device(dev.major, dev.minor, dev.leaf_weight as u64); 283 | } 284 | 285 | for dev in &res.throttle_read_bps_device { 286 | let _ = self.throttle_read_bps_for_device(dev.major, dev.minor, dev.rate); 287 | } 288 | 289 | for dev in &res.throttle_write_bps_device { 290 | let _ = self.throttle_write_bps_for_device(dev.major, dev.minor, dev.rate); 291 | } 292 | 293 | for dev in &res.throttle_read_iops_device { 294 | let _ = self.throttle_read_iops_for_device(dev.major, dev.minor, dev.rate); 295 | } 296 | 297 | for dev in &res.throttle_write_iops_device { 298 | let _ = self.throttle_write_iops_for_device(dev.major, dev.minor, dev.rate); 299 | } 300 | } 301 | 302 | Ok(()) 303 | } 304 | } 305 | 306 | impl ControllIdentifier for BlkIoController { 307 | fn controller_type() -> Controllers { 308 | Controllers::BlkIo 309 | } 310 | } 311 | 312 | impl<'a> From<&'a Subsystem> for &'a BlkIoController { 313 | fn from(sub: &'a Subsystem) -> &'a BlkIoController { 314 | unsafe { 315 | match sub { 316 | Subsystem::BlkIo(c) => c, 317 | _ => { 318 | assert_eq!(1, 0); 319 | ::std::mem::uninitialized() 320 | } 321 | } 322 | } 323 | } 324 | } 325 | 326 | fn read_string_from(mut file: File) -> Result { 327 | let mut string = String::new(); 328 | match file.read_to_string(&mut string) { 329 | Ok(_) => Ok(string.trim().to_string()), 330 | Err(e) => Err(Error::with_cause(ReadFailed, e)), 331 | } 332 | } 333 | 334 | fn read_u64_from(mut file: File) -> Result { 335 | let mut string = String::new(); 336 | match file.read_to_string(&mut string) { 337 | Ok(_) => string.trim().parse().map_err(|e| Error::with_cause(ParseError, e)), 338 | Err(e) => Err(Error::with_cause(ReadFailed, e)), 339 | } 340 | } 341 | 342 | impl BlkIoController { 343 | /// Constructs a new `BlkIoController` with `oroot` serving as the root of the control group. 344 | pub fn new(oroot: PathBuf) -> Self { 345 | let mut root = oroot; 346 | root.push(Self::controller_type().to_string()); 347 | Self { 348 | base: root.clone(), 349 | path: root, 350 | } 351 | } 352 | 353 | /// Gathers statistics about and reports the state of the block devices used by the control 354 | /// group's tasks. 355 | pub fn blkio(&self) -> BlkIo { 356 | BlkIo { 357 | io_merged: self 358 | .open_path("blkio.io_merged", false) 359 | .and_then(read_string_from) 360 | .and_then(parse_io_service) 361 | .unwrap_or(Vec::new()), 362 | io_merged_total: self 363 | .open_path("blkio.io_merged", false) 364 | .and_then(read_string_from) 365 | .and_then(parse_io_service_total) 366 | .unwrap_or(0), 367 | io_merged_recursive: self 368 | .open_path("blkio.io_merged_recursive", false) 369 | .and_then(read_string_from) 370 | .and_then(parse_io_service) 371 | .unwrap_or(Vec::new()), 372 | io_merged_recursive_total: self 373 | .open_path("blkio.io_merged_recursive", false) 374 | .and_then(read_string_from) 375 | .and_then(parse_io_service_total) 376 | .unwrap_or(0), 377 | io_queued: self 378 | .open_path("blkio.io_queued", false) 379 | .and_then(read_string_from) 380 | .and_then(parse_io_service) 381 | .unwrap_or(Vec::new()), 382 | io_queued_total: self 383 | .open_path("blkio.io_queued", false) 384 | .and_then(read_string_from) 385 | .and_then(parse_io_service_total) 386 | .unwrap_or(0), 387 | io_queued_recursive: self 388 | .open_path("blkio.io_queued_recursive", false) 389 | .and_then(read_string_from) 390 | .and_then(parse_io_service) 391 | .unwrap_or(Vec::new()), 392 | io_queued_recursive_total: self 393 | .open_path("blkio.io_queued_recursive", false) 394 | .and_then(read_string_from) 395 | .and_then(parse_io_service_total) 396 | .unwrap_or(0), 397 | io_service_bytes: self 398 | .open_path("blkio.io_service_bytes", false) 399 | .and_then(read_string_from) 400 | .and_then(parse_io_service) 401 | .unwrap_or(Vec::new()), 402 | io_service_bytes_total: self 403 | .open_path("blkio.io_service_bytes", false) 404 | .and_then(read_string_from) 405 | .and_then(parse_io_service_total) 406 | .unwrap_or(0), 407 | io_service_bytes_recursive: self 408 | .open_path("blkio.io_service_bytes_recursive", false) 409 | .and_then(read_string_from) 410 | .and_then(parse_io_service) 411 | .unwrap_or(Vec::new()), 412 | io_service_bytes_recursive_total: self 413 | .open_path("blkio.io_service_bytes_recursive", false) 414 | .and_then(read_string_from) 415 | .and_then(parse_io_service_total) 416 | .unwrap_or(0), 417 | io_serviced: self 418 | .open_path("blkio.io_serviced", false) 419 | .and_then(read_string_from) 420 | .and_then(parse_io_service) 421 | .unwrap_or(Vec::new()), 422 | io_serviced_total: self 423 | .open_path("blkio.io_serviced", false) 424 | .and_then(read_string_from) 425 | .and_then(parse_io_service_total) 426 | .unwrap_or(0), 427 | io_serviced_recursive: self 428 | .open_path("blkio.io_serviced_recursive", false) 429 | .and_then(read_string_from) 430 | .and_then(parse_io_service) 431 | .unwrap_or(Vec::new()), 432 | io_serviced_recursive_total: self 433 | .open_path("blkio.io_serviced_recursive", false) 434 | .and_then(read_string_from) 435 | .and_then(parse_io_service_total) 436 | .unwrap_or(0), 437 | io_service_time: self 438 | .open_path("blkio.io_service_time", false) 439 | .and_then(read_string_from) 440 | .and_then(parse_io_service) 441 | .unwrap_or(Vec::new()), 442 | io_service_time_total: self 443 | .open_path("blkio.io_service_time", false) 444 | .and_then(read_string_from) 445 | .and_then(parse_io_service_total) 446 | .unwrap_or(0), 447 | io_service_time_recursive: self 448 | .open_path("blkio.io_service_time_recursive", false) 449 | .and_then(read_string_from) 450 | .and_then(parse_io_service) 451 | .unwrap_or(Vec::new()), 452 | io_service_time_recursive_total: self 453 | .open_path("blkio.io_service_time_recursive", false) 454 | .and_then(read_string_from) 455 | .and_then(parse_io_service_total) 456 | .unwrap_or(0), 457 | io_wait_time: self 458 | .open_path("blkio.io_wait_time", false) 459 | .and_then(read_string_from) 460 | .and_then(parse_io_service) 461 | .unwrap_or(Vec::new()), 462 | io_wait_time_total: self 463 | .open_path("blkio.io_wait_time", false) 464 | .and_then(read_string_from) 465 | .and_then(parse_io_service_total) 466 | .unwrap_or(0), 467 | io_wait_time_recursive: self 468 | .open_path("blkio.io_wait_time_recursive", false) 469 | .and_then(read_string_from) 470 | .and_then(parse_io_service) 471 | .unwrap_or(Vec::new()), 472 | io_wait_time_recursive_total: self 473 | .open_path("blkio.io_wait_time_recursive", false) 474 | .and_then(read_string_from) 475 | .and_then(parse_io_service_total) 476 | .unwrap_or(0), 477 | leaf_weight: self 478 | .open_path("blkio.leaf_weight", false) 479 | .and_then(|file| read_u64_from(file)) 480 | .unwrap_or(0u64), 481 | leaf_weight_device: self 482 | .open_path("blkio.leaf_weight_device", false) 483 | .and_then(read_string_from) 484 | .and_then(parse_blkio_data) 485 | .unwrap_or(Vec::new()), 486 | sectors: self 487 | .open_path("blkio.sectors", false) 488 | .and_then(read_string_from) 489 | .and_then(parse_blkio_data) 490 | .unwrap_or(Vec::new()), 491 | sectors_recursive: self 492 | .open_path("blkio.sectors_recursive", false) 493 | .and_then(read_string_from) 494 | .and_then(parse_blkio_data) 495 | .unwrap_or(Vec::new()), 496 | throttle: BlkIoThrottle { 497 | io_service_bytes: self 498 | .open_path("blkio.throttle.io_service_bytes", false) 499 | .and_then(read_string_from) 500 | .and_then(parse_io_service) 501 | .unwrap_or(Vec::new()), 502 | io_service_bytes_total: self 503 | .open_path("blkio.throttle.io_service_bytes", false) 504 | .and_then(read_string_from) 505 | .and_then(parse_io_service_total) 506 | .unwrap_or(0), 507 | io_service_bytes_recursive: self 508 | .open_path("blkio.throttle.io_service_bytes_recursive", false) 509 | .and_then(read_string_from) 510 | .and_then(parse_io_service) 511 | .unwrap_or(Vec::new()), 512 | io_service_bytes_recursive_total: self 513 | .open_path("blkio.throttle.io_service_bytes_recursive", false) 514 | .and_then(read_string_from) 515 | .and_then(parse_io_service_total) 516 | .unwrap_or(0), 517 | io_serviced: self 518 | .open_path("blkio.throttle.io_serviced", false) 519 | .and_then(read_string_from) 520 | .and_then(parse_io_service) 521 | .unwrap_or(Vec::new()), 522 | io_serviced_total: self 523 | .open_path("blkio.throttle.io_serviced", false) 524 | .and_then(read_string_from) 525 | .and_then(parse_io_service_total) 526 | .unwrap_or(0), 527 | io_serviced_recursive: self 528 | .open_path("blkio.throttle.io_serviced_recursive", false) 529 | .and_then(read_string_from) 530 | .and_then(parse_io_service) 531 | .unwrap_or(Vec::new()), 532 | io_serviced_recursive_total: self 533 | .open_path("blkio.throttle.io_serviced_recursive", false) 534 | .and_then(read_string_from) 535 | .and_then(parse_io_service_total) 536 | .unwrap_or(0), 537 | read_bps_device: self 538 | .open_path("blkio.throttle.read_bps_device", false) 539 | .and_then(read_string_from) 540 | .and_then(parse_blkio_data) 541 | .unwrap_or(Vec::new()), 542 | read_iops_device: self 543 | .open_path("blkio.throttle.read_iops_device", false) 544 | .and_then(read_string_from) 545 | .and_then(parse_blkio_data) 546 | .unwrap_or(Vec::new()), 547 | write_bps_device: self 548 | .open_path("blkio.throttle.write_bps_device", false) 549 | .and_then(read_string_from) 550 | .and_then(parse_blkio_data) 551 | .unwrap_or(Vec::new()), 552 | write_iops_device: self 553 | .open_path("blkio.throttle.write_iops_device", false) 554 | .and_then(read_string_from) 555 | .and_then(parse_blkio_data) 556 | .unwrap_or(Vec::new()), 557 | }, 558 | time: self 559 | .open_path("blkio.time", false) 560 | .and_then(read_string_from) 561 | .and_then(parse_blkio_data) 562 | .unwrap_or(Vec::new()), 563 | time_recursive: self 564 | .open_path("blkio.time_recursive", false) 565 | .and_then(read_string_from) 566 | .and_then(parse_blkio_data) 567 | .unwrap_or(Vec::new()), 568 | weight: self 569 | .open_path("blkio.weight", false) 570 | .and_then(|file| read_u64_from(file)) 571 | .unwrap_or(0u64), 572 | weight_device: self 573 | .open_path("blkio.weight_device", false) 574 | .and_then(read_string_from) 575 | .and_then(parse_blkio_data) 576 | .unwrap_or(Vec::new()), 577 | } 578 | } 579 | 580 | /// Set the leaf weight on the control group's tasks, i.e., how are they weighted against the 581 | /// descendant control groups' tasks. 582 | pub fn set_leaf_weight(&self, w: u64) -> Result<()> { 583 | self.open_path("blkio.leaf_weight", true) 584 | .and_then(|mut file| { 585 | file.write_all(w.to_string().as_ref()) 586 | .map_err(|e| Error::with_cause(WriteFailed, e)) 587 | }) 588 | } 589 | 590 | /// Same as `set_leaf_weight()`, but settable per each block device. 591 | pub fn set_leaf_weight_for_device( 592 | &self, 593 | major: u64, 594 | minor: u64, 595 | weight: u64, 596 | ) -> Result<()> { 597 | self.open_path("blkio.leaf_weight_device", true) 598 | .and_then(|mut file| { 599 | file.write_all(format!("{}:{} {}", major, minor, weight).as_ref()) 600 | .map_err(|e| Error::with_cause(WriteFailed, e)) 601 | }) 602 | } 603 | 604 | /// Reset the statistics the kernel has gathered so far and start fresh. 605 | pub fn reset_stats(&self) -> Result<()> { 606 | self.open_path("blkio.reset_stats", true) 607 | .and_then(|mut file| { 608 | file.write_all("1".to_string().as_ref()) 609 | .map_err(|e| Error::with_cause(WriteFailed, e)) 610 | }) 611 | } 612 | 613 | /// Throttle the bytes per second rate of read operation affecting the block device 614 | /// `major:minor` to `bps`. 615 | pub fn throttle_read_bps_for_device( 616 | &self, 617 | major: u64, 618 | minor: u64, 619 | bps: u64, 620 | ) -> Result<()> { 621 | self.open_path("blkio.throttle.read_bps_device", true) 622 | .and_then(|mut file| { 623 | file.write_all(format!("{}:{} {}", major, minor, bps).to_string().as_ref()) 624 | .map_err(|e| Error::with_cause(WriteFailed, e)) 625 | }) 626 | } 627 | 628 | /// Throttle the I/O operations per second rate of read operation affecting the block device 629 | /// `major:minor` to `bps`. 630 | pub fn throttle_read_iops_for_device( 631 | &self, 632 | major: u64, 633 | minor: u64, 634 | iops: u64, 635 | ) -> Result<()> { 636 | self.open_path("blkio.throttle.read_iops_device", true) 637 | .and_then(|mut file| { 638 | file.write_all(format!("{}:{} {}", major, minor, iops).to_string().as_ref()) 639 | .map_err(|e| Error::with_cause(WriteFailed, e)) 640 | }) 641 | } 642 | /// Throttle the bytes per second rate of write operation affecting the block device 643 | /// `major:minor` to `bps`. 644 | pub fn throttle_write_bps_for_device( 645 | &self, 646 | major: u64, 647 | minor: u64, 648 | bps: u64, 649 | ) -> Result<()> { 650 | self.open_path("blkio.throttle.write_bps_device", true) 651 | .and_then(|mut file| { 652 | file.write_all(format!("{}:{} {}", major, minor, bps).to_string().as_ref()) 653 | .map_err(|e| Error::with_cause(WriteFailed, e)) 654 | }) 655 | } 656 | 657 | /// Throttle the I/O operations per second rate of write operation affecting the block device 658 | /// `major:minor` to `bps`. 659 | pub fn throttle_write_iops_for_device( 660 | &self, 661 | major: u64, 662 | minor: u64, 663 | iops: u64, 664 | ) -> Result<()> { 665 | self.open_path("blkio.throttle.write_iops_device", true) 666 | .and_then(|mut file| { 667 | file.write_all(format!("{}:{} {}", major, minor, iops).to_string().as_ref()) 668 | .map_err(|e| Error::with_cause(WriteFailed, e)) 669 | }) 670 | } 671 | 672 | /// Set the weight of the control group's tasks. 673 | pub fn set_weight(&self, w: u64) -> Result<()> { 674 | self.open_path("blkio.weight", true) 675 | .and_then(|mut file| { 676 | file.write_all(w.to_string().as_ref()) 677 | .map_err(|e| Error::with_cause(WriteFailed, e)) 678 | }) 679 | } 680 | 681 | /// Same as `set_weight()`, but settable per each block device. 682 | pub fn set_weight_for_device( 683 | &self, 684 | major: u64, 685 | minor: u64, 686 | weight: u64, 687 | ) -> Result<()> { 688 | self.open_path("blkio.weight_device", true) 689 | .and_then(|mut file| { 690 | file.write_all(format!("{}:{} {}", major, minor, weight).as_ref()) 691 | .map_err(|e| Error::with_cause(WriteFailed, e)) 692 | }) 693 | } 694 | } 695 | 696 | #[cfg(test)] 697 | mod test { 698 | use crate::blkio::{parse_blkio_data, BlkIoData}; 699 | use crate::blkio::{parse_io_service, parse_io_service_total, IoService}; 700 | use crate::error::*; 701 | 702 | static TEST_VALUE: &str = "\ 703 | 8:32 Read 4280320 704 | 8:32 Write 0 705 | 8:32 Sync 4280320 706 | 8:32 Async 0 707 | 8:32 Total 4280320 708 | 8:48 Read 5705479168 709 | 8:48 Write 56096055296 710 | 8:48 Sync 11213923328 711 | 8:48 Async 50587611136 712 | 8:48 Total 61801534464 713 | 8:16 Read 10059776 714 | 8:16 Write 0 715 | 8:16 Sync 10059776 716 | 8:16 Async 0 717 | 8:16 Total 10059776 718 | 8:0 Read 7192576 719 | 8:0 Write 0 720 | 8:0 Sync 7192576 721 | 8:0 Async 0 722 | 8:0 Total 7192576 723 | Total 61823067136 724 | "; 725 | 726 | static TEST_WRONG_VALUE: &str = "\ 727 | 8:32 Read 4280320 728 | 8:32 Write 0 729 | 8:32 Async 0 730 | 8:32 Total 4280320 8:48 Read 5705479168 731 | 8:48 Write 56096055296 732 | 8:48 Sync 11213923328 733 | 8:48 Async 50587611136 734 | 8:48 Total 61801534464 735 | 8:16 Read 10059776 736 | 8:16 Write 0 737 | 8:16 Sync 10059776 738 | 8:16 Async 0 739 | 8:16 Total 10059776 740 | 8:0 Read 7192576 741 | 8:0 Write 0 742 | 8:0 Sync 7192576 743 | 8:0 Async 0 744 | 8:0 Total 7192576 745 | Total 61823067136 746 | "; 747 | 748 | static TEST_BLKIO_DATA: &str = "\ 749 | 8:48 454480833999 750 | 8:32 228392923193 751 | 8:16 772456885 752 | 8:0 559583764 753 | "; 754 | 755 | #[test] 756 | fn test_parse_io_service_total() { 757 | let ok = parse_io_service_total(TEST_VALUE.to_string()).unwrap(); 758 | assert_eq!( 759 | ok, 760 | 61823067136 761 | ); 762 | } 763 | 764 | #[test] 765 | fn test_parse_io_service() { 766 | let ok = parse_io_service(TEST_VALUE.to_string()).unwrap(); 767 | assert_eq!( 768 | ok, 769 | vec![ 770 | IoService { 771 | major: 8, 772 | minor: 32, 773 | read: 4280320, 774 | write: 0, 775 | sync: 4280320, 776 | r#async: 0, 777 | total: 4280320, 778 | }, 779 | IoService { 780 | major: 8, 781 | minor: 48, 782 | read: 5705479168, 783 | write: 56096055296, 784 | sync: 11213923328, 785 | r#async: 50587611136, 786 | total: 61801534464, 787 | }, 788 | IoService { 789 | major: 8, 790 | minor: 16, 791 | read: 10059776, 792 | write: 0, 793 | sync: 10059776, 794 | r#async: 0, 795 | total: 10059776, 796 | }, 797 | IoService { 798 | major: 8, 799 | minor: 0, 800 | read: 7192576, 801 | write: 0, 802 | sync: 7192576, 803 | r#async: 0, 804 | total: 7192576, 805 | } 806 | ] 807 | ); 808 | let err = parse_io_service(TEST_WRONG_VALUE.to_string()).unwrap_err(); 809 | assert_eq!( 810 | err.kind(), 811 | &ErrorKind::ParseError, 812 | ); 813 | } 814 | 815 | #[test] 816 | fn test_parse_blkio_data() { 817 | assert_eq!( 818 | parse_blkio_data(TEST_BLKIO_DATA.to_string()).unwrap(), 819 | vec![ 820 | BlkIoData { 821 | major: 8, 822 | minor: 48, 823 | data: 454480833999, 824 | }, 825 | BlkIoData { 826 | major: 8, 827 | minor: 32, 828 | data: 228392923193, 829 | }, 830 | BlkIoData { 831 | major: 8, 832 | minor: 16, 833 | data: 772456885, 834 | }, 835 | BlkIoData { 836 | major: 8, 837 | minor: 0, 838 | data: 559583764, 839 | } 840 | ] 841 | ); 842 | } 843 | } 844 | -------------------------------------------------------------------------------- /src/cgroup.rs: -------------------------------------------------------------------------------- 1 | //! This module handles cgroup operations. Start here! 2 | 3 | use crate::error::*; 4 | 5 | use crate::{CgroupPid, ControllIdentifier, Controller, Hierarchy, Resources, Subsystem}; 6 | 7 | use std::convert::From; 8 | use std::path::Path; 9 | 10 | /// A control group is the central structure to this crate. 11 | /// 12 | /// 13 | /// # What are control groups? 14 | /// 15 | /// Lifting over from the Linux kernel sources: 16 | /// 17 | /// > Control Groups provide a mechanism for aggregating/partitioning sets of 18 | /// > tasks, and all their future children, into hierarchical groups with 19 | /// > specialized behaviour. 20 | /// 21 | /// This crate is an attempt at providing a Rust-native way of managing these cgroups. 22 | pub struct Cgroup<'b> { 23 | /// The list of subsystems that control this cgroup 24 | subsystems: Vec, 25 | 26 | /// The hierarchy. 27 | hier: &'b Hierarchy, 28 | } 29 | 30 | impl<'b> Cgroup<'b> { 31 | /// Create this control group. 32 | fn create(&self) { 33 | for subsystem in &self.subsystems { 34 | subsystem.to_controller().create(); 35 | } 36 | } 37 | 38 | /// Create a new control group in the hierarchy `hier`, with name `path`. 39 | /// 40 | /// Returns a handle to the control group that can be used to manipulate it. 41 | /// 42 | /// Note that if the handle goes out of scope and is dropped, the control group is _not_ 43 | /// destroyed. 44 | pub fn new>(hier: &Hierarchy, path: P) -> Cgroup { 45 | let cg = Cgroup::load(hier, path); 46 | cg.create(); 47 | cg 48 | } 49 | 50 | /// Create a handle for a control group in the hierarchy `hier`, with name `path`. 51 | /// 52 | /// Returns a handle to the control group (that possibly does not exist until `create()` has 53 | /// been called on the cgroup. 54 | /// 55 | /// Note that if the handle goes out of scope and is dropped, the control group is _not_ 56 | /// destroyed. 57 | pub fn load>(hier: &Hierarchy, path: P) -> Cgroup { 58 | let path = path.as_ref(); 59 | let mut subsystems = hier.subsystems(); 60 | if path.as_os_str() != "" { 61 | subsystems = subsystems 62 | .into_iter() 63 | .map(|x| x.enter(path)) 64 | .collect::>(); 65 | } 66 | 67 | let cg = Cgroup { 68 | subsystems: subsystems, 69 | hier: hier, 70 | }; 71 | 72 | cg 73 | } 74 | 75 | /// The list of subsystems that this control group supports. 76 | pub fn subsystems(&self) -> &Vec { 77 | &self.subsystems 78 | } 79 | 80 | /// Deletes the control group. 81 | /// 82 | /// Note that this function makes no effort in cleaning up the descendant and the underlying 83 | /// system call will fail if there are any descendants. Thus, one should check whether it was 84 | /// actually removed, and remove the descendants first if not. In the future, this behavior 85 | /// will change. 86 | pub fn delete(self) { 87 | self.subsystems.into_iter().for_each(|sub| match sub { 88 | Subsystem::Pid(pidc) => pidc.delete(), 89 | Subsystem::Mem(c) => c.delete(), 90 | Subsystem::CpuSet(c) => c.delete(), 91 | Subsystem::CpuAcct(c) => c.delete(), 92 | Subsystem::Cpu(c) => c.delete(), 93 | Subsystem::Devices(c) => c.delete(), 94 | Subsystem::Freezer(c) => c.delete(), 95 | Subsystem::NetCls(c) => c.delete(), 96 | Subsystem::BlkIo(c) => c.delete(), 97 | Subsystem::PerfEvent(c) => c.delete(), 98 | Subsystem::NetPrio(c) => c.delete(), 99 | Subsystem::HugeTlb(c) => c.delete(), 100 | Subsystem::Rdma(c) => c.delete(), 101 | }); 102 | } 103 | 104 | /// Apply a set of resource limits to the control group. 105 | pub fn apply(&self, res: &Resources) -> Result<()> { 106 | self.subsystems 107 | .iter() 108 | .try_fold((), |_, e| e.to_controller().apply(res)) 109 | } 110 | 111 | /// Retrieve a container based on type inference. 112 | /// 113 | /// ## Example: 114 | /// 115 | /// ```text 116 | /// let pids: &PidController = control_group.controller_of() 117 | /// .expect("No pids controller attached!"); 118 | /// let cpu: &CpuController = control_group.controller_of() 119 | /// .expect("No cpu controller attached!"); 120 | /// ``` 121 | pub fn controller_of<'a, T>(self: &'a Self) -> Option<&'a T> 122 | where 123 | &'a T: From<&'a Subsystem>, 124 | T: Controller + ControllIdentifier, 125 | { 126 | for i in &self.subsystems { 127 | if i.to_controller().control_type() == T::controller_type() { 128 | // N.B.: 129 | // https://play.rust-lang.org/?gist=978b2846bacebdaa00be62374f4f4334&version=stable&mode=debug&edition=2015 130 | return Some(i.into()); 131 | } 132 | } 133 | None 134 | } 135 | 136 | /// Removes a task from the control group. 137 | /// 138 | /// Note that this means that the task will be moved back to the root control group in the 139 | /// hierarchy and any rules applied to that control group will _still_ apply to the task. 140 | pub fn remove_task(&self, pid: CgroupPid) { 141 | let _ = self.hier.root_control_group().add_task(pid); 142 | } 143 | 144 | /// Attach a task to the control group. 145 | pub fn add_task(&self, pid: CgroupPid) -> Result<()> { 146 | self.subsystems() 147 | .iter() 148 | .try_for_each(|sub| sub.to_controller().add_task(&pid)) 149 | } 150 | 151 | /// Returns an Iterator that can be used to iterate over the tasks that are currently in the 152 | /// control group. 153 | pub fn tasks(&self) -> Vec { 154 | // Collect the tasks from all subsystems 155 | let mut v = self 156 | .subsystems() 157 | .iter() 158 | .map(|x| x.to_controller().tasks()) 159 | .fold(vec![], |mut acc, mut x| { 160 | acc.append(&mut x); 161 | acc 162 | }); 163 | v.sort(); 164 | v.dedup(); 165 | v 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/cgroup_builder.rs: -------------------------------------------------------------------------------- 1 | //! This module allows the user to create a control group using the Builder pattern. 2 | //! # Example 3 | //! 4 | //! The following example demonstrates how the control group builder looks like. The user 5 | //! specifies the name of the control group (here: "hello") and the hierarchy it belongs to (here: 6 | //! a V1 hierarchy). Next, the user selects a subsystem by calling functions like `memory()`, 7 | //! `cpu()` and `devices()`. The user can then add restrictions and details via subsystem-specific 8 | //! calls. To finalize a subsystem, the user may call `done()`. Finally, if the control group build 9 | //! is done and all requirements/restrictions have been specified, the control group can be created 10 | //! by a call to `build()`. 11 | //! 12 | //! ```rust,no_run 13 | //! # use cgroups::*; 14 | //! # use cgroups::devices::*; 15 | //! # use cgroups::cgroup_builder::*; 16 | //! let v1 = cgroups::hierarchies::V1::new(); 17 | //! let cgroup: Cgroup = CgroupBuilder::new("hello", &v1) 18 | //! .memory() 19 | //! .kernel_memory_limit(1024 * 1024) 20 | //! .memory_hard_limit(1024 * 1024) 21 | //! .done() 22 | //! .cpu() 23 | //! .shares(100) 24 | //! .done() 25 | //! .devices() 26 | //! .device(1000, 10, DeviceType::Block, true, 27 | //! vec![DevicePermissions::Read, 28 | //! DevicePermissions::Write, 29 | //! DevicePermissions::MkNod]) 30 | //! .device(6, 1, DeviceType::Char, false, vec![]) 31 | //! .done() 32 | //! .network() 33 | //! .class_id(1337) 34 | //! .priority("eth0".to_string(), 100) 35 | //! .priority("wl0".to_string(), 200) 36 | //! .done() 37 | //! .hugepages() 38 | //! .limit("2M".to_string(), 0) 39 | //! .limit("4M".to_string(), 4 * 1024 * 1024 * 100) 40 | //! .limit("2G".to_string(), 2 * 1024 * 1024 * 1024) 41 | //! .done() 42 | //! .blkio() 43 | //! .weight(123) 44 | //! .leaf_weight(99) 45 | //! .weight_device(6, 1, 100, 55) 46 | //! .weight_device(6, 1, 100, 55) 47 | //! .throttle_iops() 48 | //! .read(6, 1, 10) 49 | //! .write(11, 1, 100) 50 | //! .throttle_bps() 51 | //! .read(6, 1, 10) 52 | //! .write(11, 1, 100) 53 | //! .done() 54 | //! .build(); 55 | //! ``` 56 | use crate::error::*; 57 | 58 | use crate::{pid, BlkIoDeviceResource, BlkIoDeviceThrottleResource, Cgroup, DeviceResource, Hierarchy, HugePageResource, NetworkPriority, Resources}; 59 | 60 | macro_rules! gen_setter { 61 | ($res:ident, $cont:ident, $func:ident, $name:ident, $ty:ty) => { 62 | /// See the similarly named function in the respective controller. 63 | pub fn $name(mut self, $name: $ty) -> Self { 64 | self.cgroup.resources.$res.update_values = true; 65 | self.cgroup.resources.$res.$name = $name; 66 | self 67 | } 68 | } 69 | } 70 | 71 | /// A control group builder instance 72 | pub struct CgroupBuilder<'a> { 73 | name: String, 74 | hierarchy: &'a Hierarchy, 75 | /// Internal, unsupported field: use the associated builders instead. 76 | resources: Resources, 77 | } 78 | 79 | impl<'a> CgroupBuilder<'a> { 80 | /// Start building a control group with the supplied hierarchy and name pair. 81 | /// 82 | /// Note that this does not actually create the control group until `build()` is called. 83 | pub fn new(name: &'a str, hierarchy: &'a Hierarchy) -> CgroupBuilder<'a> { 84 | CgroupBuilder { 85 | name: name.to_owned(), 86 | hierarchy: hierarchy, 87 | resources: Resources::default(), 88 | } 89 | } 90 | 91 | /// Builds the memory resources of the control group. 92 | pub fn memory(self) -> MemoryResourceBuilder<'a> { 93 | MemoryResourceBuilder { 94 | cgroup: self, 95 | } 96 | } 97 | 98 | /// Builds the pid resources of the control group. 99 | pub fn pid(self) -> PidResourceBuilder<'a> { 100 | PidResourceBuilder { 101 | cgroup: self, 102 | } 103 | } 104 | 105 | /// Builds the cpu resources of the control group. 106 | pub fn cpu(self) -> CpuResourceBuilder<'a> { 107 | CpuResourceBuilder { 108 | cgroup: self, 109 | } 110 | } 111 | 112 | /// Builds the devices resources of the control group, disallowing or 113 | /// allowing access to certain devices in the system. 114 | pub fn devices(self) -> DeviceResourceBuilder<'a> { 115 | DeviceResourceBuilder { 116 | cgroup: self, 117 | } 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<'a> { 123 | NetworkResourceBuilder { 124 | cgroup: self, 125 | } 126 | } 127 | 128 | /// Builds the hugepage/hugetlb resources available to the control group. 129 | pub fn hugepages(self) -> HugepagesResourceBuilder<'a> { 130 | HugepagesResourceBuilder { 131 | cgroup: self, 132 | } 133 | } 134 | 135 | /// Builds the block I/O resources available for the control group. 136 | pub fn blkio(self) -> BlkIoResourcesBuilder<'a> { 137 | BlkIoResourcesBuilder { 138 | cgroup: self, 139 | throttling_iops: false, 140 | } 141 | } 142 | 143 | /// Finalize the control group, consuming the builder and creating the control group. 144 | pub fn build(self) -> Cgroup<'a> { 145 | let cg = Cgroup::new(self.hierarchy, self.name); 146 | cg.apply(&self.resources); 147 | cg 148 | } 149 | } 150 | 151 | /// A builder that configures the memory controller of a control group. 152 | pub struct MemoryResourceBuilder<'a> { 153 | cgroup: CgroupBuilder<'a>, 154 | } 155 | 156 | impl<'a> MemoryResourceBuilder<'a> { 157 | 158 | gen_setter!(memory, MemController, set_kmem_limit, kernel_memory_limit, u64); 159 | gen_setter!(memory, MemController, set_limit, memory_hard_limit, u64); 160 | gen_setter!(memory, MemController, set_soft_limit, memory_soft_limit, u64); 161 | gen_setter!(memory, MemController, set_tcp_limit, kernel_tcp_memory_limit, u64); 162 | gen_setter!(memory, MemController, set_memswap_limit, memory_swap_limit, u64); 163 | gen_setter!(memory, MemController, set_swappiness, swappiness, u64); 164 | 165 | /// Finish the construction of the memory resources of a control group. 166 | pub fn done(self) -> CgroupBuilder<'a> { 167 | self.cgroup 168 | } 169 | } 170 | 171 | /// A builder that configures the pid controller of a control group. 172 | pub struct PidResourceBuilder<'a> { 173 | cgroup: CgroupBuilder<'a>, 174 | } 175 | 176 | impl<'a> PidResourceBuilder<'a> { 177 | 178 | gen_setter!(pid, PidController, set_pid_max, maximum_number_of_processes, pid::PidMax); 179 | 180 | /// Finish the construction of the pid resources of a control group. 181 | pub fn done(self) -> CgroupBuilder<'a> { 182 | self.cgroup 183 | } 184 | } 185 | 186 | /// A builder that configures the cpuset & cpu controllers of a control group. 187 | pub struct CpuResourceBuilder<'a> { 188 | cgroup: CgroupBuilder<'a>, 189 | } 190 | 191 | impl<'a> CpuResourceBuilder<'a> { 192 | 193 | gen_setter!(cpu, CpuSetController, set_cpus, cpus, String); 194 | gen_setter!(cpu, CpuSetController, set_mems, mems, String); 195 | gen_setter!(cpu, CpuController, set_shares, shares, u64); 196 | gen_setter!(cpu, CpuController, set_cfs_quota, quota, i64); 197 | gen_setter!(cpu, CpuController, set_cfs_period, period, u64); 198 | gen_setter!(cpu, CpuController, set_rt_runtime, realtime_runtime, i64); 199 | gen_setter!(cpu, CpuController, set_rt_period, realtime_period, u64); 200 | 201 | /// Finish the construction of the cpu resources of a control group. 202 | pub fn done(self) -> CgroupBuilder<'a> { 203 | self.cgroup 204 | } 205 | } 206 | 207 | /// A builder that configures the devices controller of a control group. 208 | pub struct DeviceResourceBuilder<'a> { 209 | cgroup: CgroupBuilder<'a>, 210 | } 211 | 212 | impl<'a> DeviceResourceBuilder<'a> { 213 | 214 | /// Restrict (or allow) a device to the tasks inside the control group. 215 | pub fn device(mut self, 216 | major: i64, 217 | minor: i64, 218 | devtype: crate::devices::DeviceType, 219 | allow: bool, 220 | access: Vec) 221 | -> DeviceResourceBuilder<'a> { 222 | self.cgroup.resources.devices.update_values = true; 223 | self.cgroup.resources.devices.devices.push(DeviceResource { 224 | major, 225 | minor, 226 | devtype, 227 | allow, 228 | access 229 | }); 230 | self 231 | } 232 | 233 | /// Finish the construction of the devices resources of a control group. 234 | pub fn done(self) -> CgroupBuilder<'a> { 235 | self.cgroup 236 | } 237 | } 238 | 239 | /// A builder that configures the net_cls & net_prio controllers of a control group. 240 | pub struct NetworkResourceBuilder<'a> { 241 | cgroup: CgroupBuilder<'a>, 242 | } 243 | 244 | impl<'a> NetworkResourceBuilder<'a> { 245 | 246 | gen_setter!(network, NetclsController, set_class, class_id, u64); 247 | 248 | /// Set the priority of the tasks when operating on a networking device defined by `name` to be 249 | /// `priority`. 250 | pub fn priority(mut self, name: String, priority: u64) 251 | -> NetworkResourceBuilder<'a> { 252 | self.cgroup.resources.network.update_values = true; 253 | self.cgroup.resources.network.priorities.push(NetworkPriority { 254 | name, 255 | priority, 256 | }); 257 | self 258 | } 259 | 260 | /// Finish the construction of the network resources of a control group. 261 | pub fn done(self) -> CgroupBuilder<'a> { 262 | self.cgroup 263 | } 264 | } 265 | 266 | /// A builder that configures the hugepages controller of a control group. 267 | pub struct HugepagesResourceBuilder<'a> { 268 | cgroup: CgroupBuilder<'a>, 269 | } 270 | 271 | impl<'a> HugepagesResourceBuilder<'a> { 272 | 273 | /// Limit the usage of certain hugepages (determined by `size`) to be at most `limit` bytes. 274 | pub fn limit(mut self, size: String, limit: u64) 275 | -> HugepagesResourceBuilder<'a> { 276 | self.cgroup.resources.hugepages.update_values = true; 277 | self.cgroup.resources.hugepages.limits.push(HugePageResource { 278 | size, 279 | limit, 280 | }); 281 | self 282 | } 283 | 284 | /// Finish the construction of the network resources of a control group. 285 | pub fn done(self) -> CgroupBuilder<'a> { 286 | self.cgroup 287 | } 288 | } 289 | 290 | /// A builder that configures the blkio controller of a control group. 291 | pub struct BlkIoResourcesBuilder<'a> { 292 | cgroup: CgroupBuilder<'a>, 293 | throttling_iops: bool, 294 | } 295 | 296 | impl<'a> BlkIoResourcesBuilder<'a> { 297 | 298 | gen_setter!(blkio, BlkIoController, set_weight, weight, u16); 299 | gen_setter!(blkio, BlkIoController, set_leaf_weight, leaf_weight, u16); 300 | 301 | /// Set the weight of a certain device. 302 | pub fn weight_device(mut self, 303 | major: u64, 304 | minor: u64, 305 | weight: u16, 306 | leaf_weight: u16) 307 | -> BlkIoResourcesBuilder<'a> { 308 | self.cgroup.resources.blkio.update_values = true; 309 | self.cgroup.resources.blkio.weight_device.push(BlkIoDeviceResource { 310 | major, 311 | minor, 312 | weight, 313 | leaf_weight, 314 | }); 315 | self 316 | } 317 | 318 | /// Start configuring the I/O operations per second metric. 319 | pub fn throttle_iops(mut self) -> BlkIoResourcesBuilder<'a> { 320 | self.throttling_iops = true; 321 | self 322 | } 323 | 324 | /// Start configuring the bytes per second metric. 325 | pub fn throttle_bps(mut self) -> BlkIoResourcesBuilder<'a> { 326 | self.throttling_iops = false; 327 | self 328 | } 329 | 330 | /// Limit the read rate of the current metric for a certain device. 331 | pub fn read(mut self, major: u64, minor: u64, rate: u64) 332 | -> BlkIoResourcesBuilder<'a> { 333 | self.cgroup.resources.blkio.update_values = true; 334 | let throttle = BlkIoDeviceThrottleResource { 335 | major, 336 | minor, 337 | rate, 338 | }; 339 | if self.throttling_iops { 340 | self.cgroup.resources.blkio.throttle_read_iops_device.push(throttle); 341 | } else { 342 | self.cgroup.resources.blkio.throttle_read_bps_device.push(throttle); 343 | } 344 | self 345 | } 346 | 347 | /// Limit the write rate of the current metric for a certain device. 348 | pub fn write(mut self, major: u64, minor: u64, rate: u64) 349 | -> BlkIoResourcesBuilder<'a> { 350 | self.cgroup.resources.blkio.update_values = true; 351 | let throttle = BlkIoDeviceThrottleResource { 352 | major, 353 | minor, 354 | rate, 355 | }; 356 | if self.throttling_iops { 357 | self.cgroup.resources.blkio.throttle_write_iops_device.push(throttle); 358 | } else { 359 | self.cgroup.resources.blkio.throttle_write_bps_device.push(throttle); 360 | } 361 | self 362 | } 363 | 364 | /// Finish the construction of the blkio resources of a control group. 365 | pub fn done(self) -> CgroupBuilder<'a> { 366 | self.cgroup 367 | } 368 | } 369 | -------------------------------------------------------------------------------- /src/cpu.rs: -------------------------------------------------------------------------------- 1 | //! This module contains the implementation of the `cpu` cgroup subsystem. 2 | //! 3 | //! See the Kernel's documentation for more information about this subsystem, found at: 4 | //! [Documentation/scheduler/sched-design-CFS.txt](https://www.kernel.org/doc/Documentation/scheduler/sched-design-CFS.txt) 5 | //! paragraph 7 ("GROUP SCHEDULER EXTENSIONS TO CFS"). 6 | use std::fs::File; 7 | use std::io::{Read, Write}; 8 | use std::path::PathBuf; 9 | 10 | use crate::error::*; 11 | use crate::error::ErrorKind::*; 12 | 13 | use crate::{ 14 | ControllIdentifier, ControllerInternal, Controllers, CpuResources, Resources, Subsystem, 15 | }; 16 | 17 | /// A controller that allows controlling the `cpu` subsystem of a Cgroup. 18 | /// 19 | /// In essence, it allows gathering information about how much the tasks inside the control group 20 | /// are using the CPU and creating rules that limit their usage. Note that this crate does not yet 21 | /// support managing realtime tasks. 22 | #[derive(Debug, Clone)] 23 | pub struct CpuController { 24 | base: PathBuf, 25 | path: PathBuf, 26 | } 27 | 28 | /// The current state of the control group and its processes. 29 | #[derive(Debug)] 30 | pub struct Cpu { 31 | /// Reports CPU time statistics. 32 | /// 33 | /// Corresponds the `cpu.stat` file in `cpu` control group. 34 | pub stat: String, 35 | } 36 | 37 | impl ControllerInternal for CpuController { 38 | fn control_type(&self) -> Controllers { 39 | Controllers::Cpu 40 | } 41 | 42 | fn get_path(&self) -> &PathBuf { 43 | &self.path 44 | } 45 | 46 | fn get_path_mut(&mut self) -> &mut PathBuf { 47 | &mut self.path 48 | } 49 | 50 | fn get_base(&self) -> &PathBuf { 51 | &self.base 52 | } 53 | 54 | fn apply(&self, res: &Resources) -> Result<()> { 55 | // get the resources that apply to this controller 56 | let res: &CpuResources = &res.cpu; 57 | 58 | if res.update_values { 59 | // apply pid_max 60 | let _ = self.set_shares(res.shares); 61 | if self.shares()? != res.shares as u64 { 62 | return Err(Error::new(ErrorKind::Other)); 63 | } 64 | 65 | let _ = self.set_cfs_period(res.period); 66 | if self.cfs_period()? != res.period as u64 { 67 | return Err(Error::new(ErrorKind::Other)); 68 | } 69 | 70 | let _ = self.set_cfs_quota(res.quota as u64); 71 | if self.cfs_quota()? != res.quota as u64 { 72 | return Err(Error::new(ErrorKind::Other)); 73 | } 74 | 75 | // TODO: rt properties (CONFIG_RT_GROUP_SCHED) are not yet supported 76 | } 77 | 78 | Ok(()) 79 | } 80 | } 81 | 82 | impl ControllIdentifier for CpuController { 83 | fn controller_type() -> Controllers { 84 | Controllers::Cpu 85 | } 86 | } 87 | 88 | impl<'a> From<&'a Subsystem> for &'a CpuController { 89 | fn from(sub: &'a Subsystem) -> &'a CpuController { 90 | unsafe { 91 | match sub { 92 | Subsystem::Cpu(c) => c, 93 | _ => { 94 | assert_eq!(1, 0); 95 | ::std::mem::uninitialized() 96 | } 97 | } 98 | } 99 | } 100 | } 101 | 102 | fn read_u64_from(mut file: File) -> Result { 103 | let mut string = String::new(); 104 | match file.read_to_string(&mut string) { 105 | Ok(_) => string.trim().parse().map_err(|e| Error::with_cause(ParseError, e)), 106 | Err(e) => Err(Error::with_cause(ReadFailed, e)), 107 | } 108 | } 109 | 110 | impl CpuController { 111 | /// Contructs a new `CpuController` with `oroot` serving as the root of the control group. 112 | pub fn new(oroot: PathBuf) -> Self { 113 | let mut root = oroot; 114 | root.push(Self::controller_type().to_string()); 115 | Self { 116 | base: root.clone(), 117 | path: root, 118 | } 119 | } 120 | 121 | /// Returns CPU time statistics based on the processes in the control group. 122 | pub fn cpu(&self) -> Cpu { 123 | Cpu { 124 | stat: self 125 | .open_path("cpu.stat", false) 126 | .and_then(|mut file| { 127 | let mut s = String::new(); 128 | let res = file.read_to_string(&mut s); 129 | match res { 130 | Ok(_) => Ok(s), 131 | Err(e) => Err(Error::with_cause(ReadFailed, e)), 132 | } 133 | }).unwrap_or("".to_string()), 134 | } 135 | } 136 | 137 | /// Configures the CPU bandwidth (in relative relation to other control groups and this control 138 | /// group's parent). 139 | /// 140 | /// For example, setting control group `A`'s `shares` to `100`, and control group `B`'s 141 | /// `shares` to `200` ensures that control group `B` receives twice as much as CPU bandwidth. 142 | /// (Assuming both `A` and `B` are of the same parent) 143 | pub fn set_shares(&self, shares: u64) -> Result<()> { 144 | self.open_path("cpu.shares", true).and_then(|mut file| { 145 | file.write_all(shares.to_string().as_ref()) 146 | .map_err(|e| Error::with_cause(WriteFailed, e)) 147 | }) 148 | } 149 | 150 | /// Retrieve the CPU bandwidth that this control group (relative to other control groups and 151 | /// this control group's parent) can use. 152 | pub fn shares(&self) -> Result { 153 | self.open_path("cpu.shares", false).and_then(read_u64_from) 154 | } 155 | 156 | /// Specify a period (when using the CFS scheduler) of time in microseconds for how often this 157 | /// control group's access to the CPU should be reallocated. 158 | pub fn set_cfs_period(&self, us: u64) -> Result<()> { 159 | self.open_path("cpu.cfs_period_us", true) 160 | .and_then(|mut file| { 161 | file.write_all(us.to_string().as_ref()) 162 | .map_err(|e| Error::with_cause(WriteFailed, e)) 163 | }) 164 | } 165 | 166 | /// Retrieve the period of time of how often this cgroup's access to the CPU should be 167 | /// reallocated in microseconds. 168 | pub fn cfs_period(&self) -> Result { 169 | self.open_path("cpu.cfs_period_us", false) 170 | .and_then(read_u64_from) 171 | } 172 | 173 | /// Specify a quota (when using the CFS scheduler) of time in microseconds for which all tasks 174 | /// in this control group can run during one period (see: `set_cfs_period()`). 175 | pub fn set_cfs_quota(&self, us: u64) -> Result<()> { 176 | self.open_path("cpu.cfs_quota_us", true) 177 | .and_then(|mut file| { 178 | file.write_all(us.to_string().as_ref()) 179 | .map_err(|e| Error::with_cause(WriteFailed, e)) 180 | }) 181 | } 182 | 183 | /// Retrieve the quota of time for which all tasks in this cgroup can run during one period, in 184 | /// microseconds. 185 | pub fn cfs_quota(&self) -> Result { 186 | self.open_path("cpu.cfs_quota_us", false) 187 | .and_then(read_u64_from) 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /src/cpuacct.rs: -------------------------------------------------------------------------------- 1 | //! This module contains the implementation of the `cpuacct` cgroup subsystem. 2 | //! 3 | //! See the Kernel's documentation for more information about this subsystem, found at: 4 | //! [Documentation/cgroup-v1/cpuacct.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/cpuacct.txt) 5 | use std::fs::File; 6 | use std::io::{Read, Write}; 7 | use std::path::PathBuf; 8 | 9 | use crate::error::*; 10 | use crate::error::ErrorKind::*; 11 | 12 | use crate::{ControllIdentifier, ControllerInternal, Controllers, Resources, Subsystem}; 13 | 14 | /// A controller that allows controlling the `cpuacct` subsystem of a Cgroup. 15 | /// 16 | /// In essence, this control group provides accounting (hence the name `cpuacct`) for CPU usage of 17 | /// the tasks in the control group. 18 | #[derive(Debug, Clone)] 19 | pub struct CpuAcctController { 20 | base: PathBuf, 21 | path: PathBuf, 22 | } 23 | 24 | /// Represents the statistics retrieved from the control group. 25 | pub struct CpuAcct { 26 | /// Divides the time used by the tasks into `user` time and `system` time. 27 | pub stat: String, 28 | /// Total CPU time (in nanoseconds) spent by the tasks. 29 | pub usage: u64, 30 | /// Total CPU time (in nanoseconds) spent by the tasks, broken down by CPU and by whether the 31 | /// time spent is `user` time or `system` time. 32 | /// 33 | /// An example is as follows: 34 | /// ```text 35 | /// cpu user system 36 | /// 0 8348363768 0 37 | /// 1 8324369100 0 38 | /// 2 8598185449 0 39 | /// 3 8648262473 0 40 | /// ``` 41 | pub usage_all: String, 42 | /// CPU time (in nanoseconds) spent by the tasks, broken down by each CPU. 43 | /// Times spent in each CPU are separated by a space. 44 | pub usage_percpu: String, 45 | /// As for `usage_percpu`, but the `system` time spent. 46 | pub usage_percpu_sys: String, 47 | /// As for `usage_percpu`, but the `user` time spent. 48 | pub usage_percpu_user: String, 49 | /// CPU time (in nanoseconds) spent by the tasks that counted for `system` time. 50 | pub usage_sys: u64, 51 | /// CPU time (in nanoseconds) spent by the tasks that counted for `user` time. 52 | pub usage_user: u64, 53 | } 54 | 55 | impl ControllerInternal for CpuAcctController { 56 | fn control_type(&self) -> Controllers { 57 | Controllers::CpuAcct 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 | fn get_base(&self) -> &PathBuf { 66 | &self.base 67 | } 68 | 69 | fn apply(&self, _res: &Resources) -> Result<()> { 70 | Ok(()) 71 | } 72 | } 73 | 74 | impl ControllIdentifier for CpuAcctController { 75 | fn controller_type() -> Controllers { 76 | Controllers::CpuAcct 77 | } 78 | } 79 | 80 | impl<'a> From<&'a Subsystem> for &'a CpuAcctController { 81 | fn from(sub: &'a Subsystem) -> &'a CpuAcctController { 82 | unsafe { 83 | match sub { 84 | Subsystem::CpuAcct(c) => c, 85 | _ => { 86 | assert_eq!(1, 0); 87 | ::std::mem::uninitialized() 88 | } 89 | } 90 | } 91 | } 92 | } 93 | 94 | fn read_u64_from(mut file: File) -> Result { 95 | let mut string = String::new(); 96 | let res = file.read_to_string(&mut string); 97 | match res { 98 | Ok(_) => match string.trim().parse() { 99 | Ok(e) => Ok(e), 100 | Err(e) => Err(Error::with_cause(ParseError, e)), 101 | }, 102 | Err(e) => Err(Error::with_cause(ReadFailed, e)), 103 | } 104 | } 105 | 106 | fn read_string_from(mut file: File) -> Result { 107 | let mut string = String::new(); 108 | match file.read_to_string(&mut string) { 109 | Ok(_) => Ok(string.trim().to_string()), 110 | Err(e) => Err(Error::with_cause(ReadFailed, e)), 111 | } 112 | } 113 | 114 | impl CpuAcctController { 115 | /// Contructs a new `CpuAcctController` with `oroot` serving as the root of the control group. 116 | pub fn new(oroot: PathBuf) -> Self { 117 | let mut root = oroot; 118 | root.push(Self::controller_type().to_string()); 119 | Self { 120 | base: root.clone(), 121 | path: root, 122 | } 123 | } 124 | 125 | /// Gathers the statistics that are available in the control group into a `CpuAcct` structure. 126 | pub fn cpuacct(&self) -> CpuAcct { 127 | CpuAcct { 128 | stat: self 129 | .open_path("cpuacct.stat", false) 130 | .and_then(|file| read_string_from(file)) 131 | .unwrap_or("".to_string()), 132 | usage: self 133 | .open_path("cpuacct.usage", false) 134 | .and_then(|file| read_u64_from(file)) 135 | .unwrap_or(0), 136 | usage_all: self 137 | .open_path("cpuacct.usage_all", false) 138 | .and_then(|file| read_string_from(file)) 139 | .unwrap_or("".to_string()), 140 | usage_percpu: self 141 | .open_path("cpuacct.usage_percpu", false) 142 | .and_then(|file| read_string_from(file)) 143 | .unwrap_or("".to_string()), 144 | usage_percpu_sys: self 145 | .open_path("cpuacct.usage_percpu_sys", false) 146 | .and_then(|file| read_string_from(file)) 147 | .unwrap_or("".to_string()), 148 | usage_percpu_user: self 149 | .open_path("cpuacct.usage_percpu_user", false) 150 | .and_then(|file| read_string_from(file)) 151 | .unwrap_or("".to_string()), 152 | usage_sys: self 153 | .open_path("cpuacct.usage_sys", false) 154 | .and_then(|file| read_u64_from(file)) 155 | .unwrap_or(0), 156 | usage_user: self 157 | .open_path("cpuacct.usage_user", false) 158 | .and_then(|file| read_u64_from(file)) 159 | .unwrap_or(0), 160 | } 161 | } 162 | 163 | /// Reset the statistics the kernel has gathered about the control group. 164 | pub fn reset(&self) -> Result<()> { 165 | self.open_path("cpuacct.usage", true) 166 | .and_then(|mut file| file.write_all(b"0").map_err(|e| Error::with_cause(WriteFailed, e))) 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/cpuset.rs: -------------------------------------------------------------------------------- 1 | //! This module contains the implementation of the `cpuset` cgroup subsystem. 2 | //! 3 | //! See the Kernel's documentation for more information about this subsystem, found at: 4 | //! [Documentation/cgroup-v1/cpusets.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/cpusets.txt) 5 | use std::fs::File; 6 | use std::io::{Read, Write}; 7 | use std::path::PathBuf; 8 | 9 | use crate::error::*; 10 | use crate::error::ErrorKind::*; 11 | 12 | use crate::{ 13 | ControllIdentifier, ControllerInternal, Controllers, CpuResources, Resources, Subsystem, 14 | }; 15 | 16 | /// A controller that allows controlling the `cpuset` subsystem of a Cgroup. 17 | /// 18 | /// In essence, this controller is responsible for restricting the tasks in the control group to a 19 | /// set of CPUs and/or memory nodes. 20 | #[derive(Debug, Clone)] 21 | pub struct CpuSetController { 22 | base: PathBuf, 23 | path: PathBuf, 24 | } 25 | 26 | /// The current state of the `cpuset` controller for this control group. 27 | pub struct CpuSet { 28 | /// If true, no other control groups can share the CPUs listed in the `cpus` field. 29 | pub cpu_exclusive: bool, 30 | /// The list of CPUs the tasks of the control group can run on. 31 | /// 32 | /// This is a vector of `(start, end)` tuples, where each tuple is a range of CPUs where the 33 | /// control group is allowed to run on. Both sides of the range are inclusive. 34 | pub cpus: Vec<(u64, u64)>, 35 | /// The list of CPUs that the tasks can effectively run on. This removes the list of CPUs that 36 | /// the parent (and all of its parents) cannot run on from the `cpus` field of this control 37 | /// group. 38 | pub effective_cpus: Vec<(u64, u64)>, 39 | /// The list of memory nodes that the tasks can effectively use. This removes the list of nodes that 40 | /// the parent (and all of its parents) cannot use from the `mems` field of this control 41 | /// group. 42 | pub effective_mems: Vec<(u64, u64)>, 43 | /// If true, no other control groups can share the memory nodes listed in the `mems` field. 44 | pub mem_exclusive: bool, 45 | /// If true, the control group is 'hardwalled'. Kernel memory allocations (except for a few 46 | /// minor exceptions) are made from the memory nodes designated in the `mems` field. 47 | pub mem_hardwall: bool, 48 | /// If true, whenever `mems` is changed via `set_mems()`, the memory stored on the previous 49 | /// nodes are migrated to the new nodes selected by the new `mems`. 50 | pub memory_migrate: bool, 51 | /// Running average of the memory pressured faced by the tasks in the control group. 52 | pub memory_pressure: u64, 53 | /// This field is only at the root control group and controls whether the kernel will compute 54 | /// the memory pressure for control groups or not. 55 | pub memory_pressure_enabled: Option, 56 | /// If true, filesystem buffers are spread across evenly between the nodes specified in `mems`. 57 | pub memory_spread_page: bool, 58 | /// If true, kernel slab caches for file I/O are spread across evenly between the nodes 59 | /// specified in `mems`. 60 | pub memory_spread_slab: bool, 61 | /// The list of memory nodes the tasks of the control group can use. 62 | /// 63 | /// The format is the same as the `cpus`, `effective_cpus` and `effective_mems` fields. 64 | pub mems: Vec<(u64, u64)>, 65 | /// If true, the kernel will attempt to rebalance the load between the CPUs specified in the 66 | /// `cpus` field of this control group. 67 | pub sched_load_balance: bool, 68 | /// Represents how much work the kernel should do to rebalance this cpuset. 69 | /// 70 | /// | `sched_load_balance` | Effect | 71 | /// | -------------------- | ------ | 72 | /// | -1 | Use the system default value | 73 | /// | 0 | Only balance loads periodically | 74 | /// | 1 | Immediately balance the load across tasks on the same core | 75 | /// | 2 | Immediately balance the load across cores in the same CPU package | 76 | /// | 4 | Immediately balance the load across CPUs on the same node | 77 | /// | 5 | Immediately balance the load between CPUs even if the system is NUMA | 78 | /// | 6 | Immediately balance the load between all CPUs | 79 | pub sched_relax_domain_level: u64, 80 | } 81 | 82 | impl ControllerInternal for CpuSetController { 83 | fn control_type(&self) -> Controllers { 84 | Controllers::CpuSet 85 | } 86 | fn get_path(&self) -> &PathBuf { 87 | &self.path 88 | } 89 | fn get_path_mut(&mut self) -> &mut PathBuf { 90 | &mut self.path 91 | } 92 | fn get_base(&self) -> &PathBuf { 93 | &self.base 94 | } 95 | 96 | fn apply(&self, res: &Resources) -> Result<()> { 97 | // get the resources that apply to this controller 98 | let res: &CpuResources = &res.cpu; 99 | 100 | if res.update_values { 101 | let _ = self.set_cpus(&res.cpus); 102 | let _ = self.set_mems(&res.mems); 103 | } 104 | 105 | Ok(()) 106 | } 107 | } 108 | 109 | impl ControllIdentifier for CpuSetController { 110 | fn controller_type() -> Controllers { 111 | Controllers::CpuSet 112 | } 113 | } 114 | 115 | impl<'a> From<&'a Subsystem> for &'a CpuSetController { 116 | fn from(sub: &'a Subsystem) -> &'a CpuSetController { 117 | unsafe { 118 | match sub { 119 | Subsystem::CpuSet(c) => c, 120 | _ => { 121 | assert_eq!(1, 0); 122 | ::std::mem::uninitialized() 123 | } 124 | } 125 | } 126 | } 127 | } 128 | 129 | fn read_string_from(mut file: File) -> Result { 130 | let mut string = String::new(); 131 | match file.read_to_string(&mut string) { 132 | Ok(_) => Ok(string.trim().to_string()), 133 | Err(e) => Err(Error::with_cause(ReadFailed, e)), 134 | } 135 | } 136 | 137 | fn read_u64_from(mut file: File) -> Result { 138 | let mut string = String::new(); 139 | match file.read_to_string(&mut string) { 140 | Ok(_) => string.trim().parse().map_err(|e| Error::with_cause(ParseError, e)), 141 | Err(e) => Err(Error::with_cause(ReadFailed, e)), 142 | } 143 | } 144 | 145 | /// Parse a string like "1,2,4-5,8" into a list of (start, end) tuples. 146 | fn parse_range(s: String) -> Result> { 147 | let mut fin = Vec::new(); 148 | 149 | if s == "".to_string() { 150 | return Ok(fin); 151 | } 152 | 153 | // first split by commas 154 | let comma_split = s.split(","); 155 | 156 | for sp in comma_split { 157 | if sp.contains("-") { 158 | // this is a true range 159 | let dash_split = sp.split("-").collect::>(); 160 | if dash_split.len() != 2 { 161 | return Err(Error::new(ParseError)); 162 | } 163 | let first = dash_split[0].parse::(); 164 | let second = dash_split[1].parse::(); 165 | if first.is_err() || second.is_err() { 166 | return Err(Error::new(ParseError)); 167 | } 168 | fin.push((first.unwrap(), second.unwrap())); 169 | } else { 170 | // this is just a single number 171 | let num = sp.parse::(); 172 | if num.is_err() { 173 | return Err(Error::new(ParseError)); 174 | } 175 | fin.push((num.clone().unwrap(), num.clone().unwrap())); 176 | } 177 | } 178 | 179 | Ok(fin) 180 | } 181 | 182 | impl CpuSetController { 183 | /// Contructs a new `CpuSetController` with `oroot` serving as the root of the control group. 184 | pub fn new(oroot: PathBuf) -> Self { 185 | let mut root = oroot; 186 | root.push(Self::controller_type().to_string()); 187 | Self { 188 | base: root.clone(), 189 | path: root, 190 | } 191 | } 192 | 193 | /// Returns the statistics gathered by the kernel for this control group. See the struct for 194 | /// more information on what information this entails. 195 | pub fn cpuset(&self) -> CpuSet { 196 | CpuSet { 197 | cpu_exclusive: { 198 | self.open_path("cpuset.cpu_exclusive", false) 199 | .and_then(|file| read_u64_from(file)) 200 | .map(|x| x == 1) 201 | .unwrap_or(false) 202 | }, 203 | cpus: { 204 | self.open_path("cpuset.cpus", false) 205 | .and_then(read_string_from) 206 | .and_then(parse_range) 207 | .unwrap_or(Vec::new()) 208 | }, 209 | effective_cpus: { 210 | self.open_path("cpuset.effective_cpus", false) 211 | .and_then(read_string_from) 212 | .and_then(parse_range) 213 | .unwrap_or(Vec::new()) 214 | }, 215 | effective_mems: { 216 | self.open_path("cpuset.effective_mems", false) 217 | .and_then(read_string_from) 218 | .and_then(parse_range) 219 | .unwrap_or(Vec::new()) 220 | }, 221 | mem_exclusive: { 222 | self.open_path("cpuset.mem_exclusive", false) 223 | .and_then(read_u64_from) 224 | .map(|x| x == 1) 225 | .unwrap_or(false) 226 | }, 227 | mem_hardwall: { 228 | self.open_path("cpuset.mem_hardwall", false) 229 | .and_then(read_u64_from) 230 | .map(|x| x == 1) 231 | .unwrap_or(false) 232 | }, 233 | memory_migrate: { 234 | self.open_path("cpuset.memory_migrate", false) 235 | .and_then(read_u64_from) 236 | .map(|x| x == 1) 237 | .unwrap_or(false) 238 | }, 239 | memory_pressure: { 240 | self.open_path("cpuset.memory_pressure", false) 241 | .and_then(read_u64_from) 242 | .unwrap_or(0) 243 | }, 244 | memory_pressure_enabled: { 245 | self.open_path("cpuset.memory_pressure_enabled", false) 246 | .and_then(read_u64_from) 247 | .map(|x| x == 1) 248 | .ok() 249 | }, 250 | memory_spread_page: { 251 | self.open_path("cpuset.memory_spread_page", false) 252 | .and_then(read_u64_from) 253 | .map(|x| x == 1) 254 | .unwrap_or(false) 255 | }, 256 | memory_spread_slab: { 257 | self.open_path("cpuset.memory_spread_slab", false) 258 | .and_then(read_u64_from) 259 | .map(|x| x == 1) 260 | .unwrap_or(false) 261 | }, 262 | mems: { 263 | self.open_path("cpuset.mems", false) 264 | .and_then(read_string_from) 265 | .and_then(parse_range) 266 | .unwrap_or(Vec::new()) 267 | }, 268 | sched_load_balance: { 269 | self.open_path("cpuset.sched_load_balance", false) 270 | .and_then(read_u64_from) 271 | .map(|x| x == 1) 272 | .unwrap_or(false) 273 | }, 274 | sched_relax_domain_level: { 275 | self.open_path("cpuset.sched_relax_domain_level", false) 276 | .and_then(read_u64_from) 277 | .unwrap_or(0) 278 | }, 279 | } 280 | } 281 | 282 | /// Control whether the CPUs selected via `set_cpus()` should be exclusive to this control 283 | /// group or not. 284 | pub fn set_cpu_exclusive(&self, b: bool) -> Result<()> { 285 | self.open_path("cpuset.cpu_exclusive", true) 286 | .and_then(|mut file| { 287 | if b { 288 | file.write_all(b"1").map_err(|e| Error::with_cause(WriteFailed, e)) 289 | } else { 290 | file.write_all(b"0").map_err(|e| Error::with_cause(WriteFailed, e)) 291 | } 292 | }) 293 | } 294 | 295 | /// Control whether the memory nodes selected via `set_memss()` should be exclusive to this control 296 | /// group or not. 297 | pub fn set_mem_exclusive(&self, b: bool) -> Result<()> { 298 | self.open_path("cpuset.mem_exclusive", true) 299 | .and_then(|mut file| { 300 | if b { 301 | file.write_all(b"1").map_err(|e| Error::with_cause(WriteFailed, e)) 302 | } else { 303 | file.write_all(b"0").map_err(|e| Error::with_cause(WriteFailed, e)) 304 | } 305 | }) 306 | } 307 | 308 | /// Set the CPUs that the tasks in this control group can run on. 309 | /// 310 | /// Syntax is a comma separated list of CPUs, with an additional extension that ranges can 311 | /// be represented via dashes. 312 | pub fn set_cpus(&self, cpus: &str) -> Result<()> { 313 | self.open_path("cpuset.cpus", true).and_then(|mut file| { 314 | file.write_all(cpus.as_ref()) 315 | .map_err(|e| Error::with_cause(WriteFailed, e)) 316 | }) 317 | } 318 | 319 | /// Set the memory nodes that the tasks in this control group can use. 320 | /// 321 | /// Syntax is the same as with `set_cpus()`. 322 | pub fn set_mems(&self, mems: &str) -> Result<()> { 323 | self.open_path("cpuset.mems", true).and_then(|mut file| { 324 | file.write_all(mems.as_ref()) 325 | .map_err(|e| Error::with_cause(WriteFailed, e)) 326 | }) 327 | } 328 | 329 | /// Controls whether the control group should be "hardwalled", i.e., whether kernel allocations 330 | /// should exclusively use the memory nodes set via `set_mems()`. 331 | /// 332 | /// Note that some kernel allocations, most notably those that are made in interrupt handlers 333 | /// may disregard this. 334 | pub fn set_hardwall(&self, b: bool) -> Result<()> { 335 | self.open_path("cpuset.mem_hardwall", true) 336 | .and_then(|mut file| { 337 | if b { 338 | file.write_all(b"1").map_err(|e| Error::with_cause(WriteFailed, e)) 339 | } else { 340 | file.write_all(b"0").map_err(|e| Error::with_cause(WriteFailed, e)) 341 | } 342 | }) 343 | } 344 | 345 | /// Controls whether the kernel should attempt to rebalance the load between the CPUs specified in the 346 | /// `cpus` field of this control group. 347 | pub fn set_load_balancing(&self, b: bool) -> Result<()> { 348 | self.open_path("cpuset.sched_load_balance", true) 349 | .and_then(|mut file| { 350 | if b { 351 | file.write_all(b"1").map_err(|e| Error::with_cause(WriteFailed, e)) 352 | } else { 353 | file.write_all(b"0").map_err(|e| Error::with_cause(WriteFailed, e)) 354 | } 355 | }) 356 | } 357 | 358 | /// Contorl how much effort the kernel should invest in rebalacing the control group. 359 | /// 360 | /// See @CpuSet 's similar field for more information. 361 | pub fn set_rebalance_relax_domain_level(&self, i: i64) -> Result<()> { 362 | self.open_path("cpuset.sched_relax_domain_level", true) 363 | .and_then(|mut file| { 364 | file.write_all(i.to_string().as_ref()) 365 | .map_err(|e| Error::with_cause(WriteFailed, e)) 366 | }) 367 | } 368 | 369 | /// Control whether when using `set_mems()` the existing memory used by the tasks should be 370 | /// migrated over to the now-selected nodes. 371 | pub fn set_memory_migration(&self, b: bool) -> Result<()> { 372 | self.open_path("cpuset.memory_migrate", true) 373 | .and_then(|mut file| { 374 | if b { 375 | file.write_all(b"1").map_err(|e| Error::with_cause(WriteFailed, e)) 376 | } else { 377 | file.write_all(b"0").map_err(|e| Error::with_cause(WriteFailed, e)) 378 | } 379 | }) 380 | } 381 | 382 | /// Control whether filesystem buffers should be evenly split across the nodes selected via 383 | /// `set_mems()`. 384 | pub fn set_memory_spread_page(&self, b: bool) -> Result<()> { 385 | self.open_path("cpuset.memory_spread_page", true) 386 | .and_then(|mut file| { 387 | if b { 388 | file.write_all(b"1").map_err(|e| Error::with_cause(WriteFailed, e)) 389 | } else { 390 | file.write_all(b"0").map_err(|e| Error::with_cause(WriteFailed, e)) 391 | } 392 | }) 393 | } 394 | 395 | /// Control whether the kernel's slab cache for file I/O should be evenly split across the 396 | /// nodes selected via `set_mems()`. 397 | pub fn set_memory_spread_slab(&self, b: bool) -> Result<()> { 398 | self.open_path("cpuset.memory_spread_slab", true) 399 | .and_then(|mut file| { 400 | if b { 401 | file.write_all(b"1").map_err(|e| Error::with_cause(WriteFailed, e)) 402 | } else { 403 | file.write_all(b"0").map_err(|e| Error::with_cause(WriteFailed, e)) 404 | } 405 | }) 406 | } 407 | 408 | /// Control whether the kernel should collect information to calculate memory pressure for 409 | /// control groups. 410 | /// 411 | /// Note: This will fail with `InvalidOperation` if the current congrol group is not the root 412 | /// control group. 413 | pub fn set_enable_memory_pressure(&self, b: bool) -> Result<()> { 414 | if !self.path_exists("cpuset.memory_pressure_enabled") { 415 | return Err(Error::new(InvalidOperation)); 416 | } 417 | self.open_path("cpuset.memory_pressure_enabled", true) 418 | .and_then(|mut file| { 419 | if b { 420 | file.write_all(b"1").map_err(|e| Error::with_cause(WriteFailed, e)) 421 | } else { 422 | file.write_all(b"0").map_err(|e| Error::with_cause(WriteFailed, e)) 423 | } 424 | }) 425 | } 426 | } 427 | 428 | #[cfg(test)] 429 | mod tests { 430 | use crate::cpuset; 431 | #[test] 432 | fn test_parse_range() { 433 | let test_cases = vec![ 434 | "1,2,4-6,9".to_string(), 435 | "".to_string(), 436 | "1".to_string(), 437 | "1-111".to_string(), 438 | "1,2,3,4".to_string(), 439 | "1-5,6-7,8-9".to_string(), 440 | ]; 441 | let expecteds = vec![ 442 | vec![(1, 1), (2, 2), (4, 6), (9, 9)], 443 | vec![], 444 | vec![(1, 1)], 445 | vec![(1, 111)], 446 | vec![(1, 1), (2, 2), (3, 3), (4, 4)], 447 | vec![(1, 5), (6, 7), (8, 9)], 448 | ]; 449 | 450 | for (i, case) in test_cases.into_iter().enumerate() { 451 | let range = cpuset::parse_range(case.clone()); 452 | println!("{:?} => {:?}", case, range); 453 | assert!(range.is_ok()); 454 | assert_eq!(range.unwrap(), expecteds[i]); 455 | } 456 | } 457 | } 458 | -------------------------------------------------------------------------------- /src/devices.rs: -------------------------------------------------------------------------------- 1 | //! This module contains the implementation of the `devices` cgroup subsystem. 2 | //! 3 | //! See the Kernel's documentation for more information about this subsystem, found at: 4 | //! [Documentation/cgroup-v1/devices.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/devices.txt) 5 | use std::io::{Read, Write}; 6 | use std::path::PathBuf; 7 | 8 | use log::*; 9 | 10 | use crate::error::*; 11 | use crate::error::ErrorKind::*; 12 | 13 | use crate::{ 14 | ControllIdentifier, ControllerInternal, Controllers, DeviceResource, DeviceResources, 15 | Resources, Subsystem, 16 | }; 17 | 18 | /// A controller that allows controlling the `devices` subsystem of a Cgroup. 19 | /// 20 | /// In essence, using the devices controller, it is possible to allow or disallow sets of devices to 21 | /// be used by the control group's tasks. 22 | #[derive(Debug, Clone)] 23 | pub struct DevicesController { 24 | base: PathBuf, 25 | path: PathBuf, 26 | } 27 | 28 | /// An enum holding the different types of devices that can be manipulated using this controller. 29 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] 30 | pub enum DeviceType { 31 | /// The rule applies to all devices. 32 | All, 33 | /// The rule only applies to character devices. 34 | Char, 35 | /// The rule only applies to block devices. 36 | Block, 37 | } 38 | 39 | impl Default for DeviceType { 40 | fn default() -> Self { 41 | DeviceType::All 42 | } 43 | } 44 | 45 | impl DeviceType { 46 | /// Convert a DeviceType into the character that the kernel recognizes. 47 | pub fn to_char(&self) -> char { 48 | match self { 49 | DeviceType::All => 'a', 50 | DeviceType::Char => 'c', 51 | DeviceType::Block => 'b', 52 | } 53 | } 54 | 55 | /// Convert the kenrel's representation into the DeviceType type. 56 | pub fn from_char(c: Option) -> Option { 57 | match c { 58 | Some('a') => Some(DeviceType::All), 59 | Some('c') => Some(DeviceType::Char), 60 | Some('b') => Some(DeviceType::Block), 61 | _ => None, 62 | } 63 | } 64 | } 65 | 66 | /// An enum with the permissions that can be allowed/denied to the control group. 67 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] 68 | pub enum DevicePermissions { 69 | /// Permission to read from the device. 70 | Read, 71 | /// Permission to write to the device. 72 | Write, 73 | /// Permission to execute the `mknod(2)` system call with the device's major and minor numbers. 74 | /// That is, the permission to create a special file that refers to the device node. 75 | MkNod, 76 | } 77 | 78 | impl DevicePermissions { 79 | /// Convert a DevicePermissions into the character that the kernel recognizes. 80 | pub fn to_char(&self) -> char { 81 | match self { 82 | DevicePermissions::Read => 'r', 83 | DevicePermissions::Write => 'w', 84 | DevicePermissions::MkNod => 'm', 85 | } 86 | } 87 | 88 | /// Convert a char to a DevicePermission if there is such a mapping. 89 | pub fn from_char(c: char) -> Option { 90 | match c { 91 | 'r' => Some(DevicePermissions::Read), 92 | 'w' => Some(DevicePermissions::Write), 93 | 'm' => Some(DevicePermissions::MkNod), 94 | _ => None, 95 | } 96 | } 97 | 98 | /// Checks whether the string is a valid descriptor of DevicePermissions. 99 | pub fn is_valid(s: &str) -> bool { 100 | if s == "" { 101 | return false; 102 | } 103 | for i in s.chars() { 104 | if i != 'r' && i != 'w' && i != 'm' { 105 | return false; 106 | } 107 | } 108 | return true; 109 | } 110 | 111 | /// Returns a Vec will all the permissions that a device can have. 112 | pub fn all() -> Vec { 113 | vec![ 114 | DevicePermissions::Read, 115 | DevicePermissions::Write, 116 | DevicePermissions::MkNod, 117 | ] 118 | } 119 | 120 | /// Convert a string into DevicePermissions. 121 | pub fn from_str(s: &str) -> Result> { 122 | let mut v = Vec::new(); 123 | if s == "" { 124 | return Ok(v); 125 | } 126 | for e in s.chars() { 127 | let perm = DevicePermissions::from_char(e) 128 | .ok_or_else(|| Error::new(ParseError))?; 129 | v.push(perm); 130 | } 131 | 132 | Ok(v) 133 | } 134 | } 135 | 136 | impl ControllerInternal for DevicesController { 137 | fn control_type(&self) -> Controllers { 138 | Controllers::Devices 139 | } 140 | fn get_path(&self) -> &PathBuf { 141 | &self.path 142 | } 143 | fn get_path_mut(&mut self) -> &mut PathBuf { 144 | &mut self.path 145 | } 146 | fn get_base(&self) -> &PathBuf { 147 | &self.base 148 | } 149 | 150 | fn apply(&self, res: &Resources) -> Result<()> { 151 | // get the resources that apply to this controller 152 | let res: &DeviceResources = &res.devices; 153 | 154 | if res.update_values { 155 | for i in &res.devices { 156 | if i.allow { 157 | let _ = self.allow_device(i.devtype, i.major, i.minor, &i.access); 158 | } else { 159 | let _ = self.deny_device(i.devtype, i.major, i.minor, &i.access); 160 | } 161 | } 162 | } 163 | 164 | Ok(()) 165 | } 166 | } 167 | 168 | impl ControllIdentifier for DevicesController { 169 | fn controller_type() -> Controllers { 170 | Controllers::Devices 171 | } 172 | } 173 | 174 | impl<'a> From<&'a Subsystem> for &'a DevicesController { 175 | fn from(sub: &'a Subsystem) -> &'a DevicesController { 176 | unsafe { 177 | match sub { 178 | Subsystem::Devices(c) => c, 179 | _ => { 180 | assert_eq!(1, 0); 181 | ::std::mem::uninitialized() 182 | } 183 | } 184 | } 185 | } 186 | } 187 | 188 | impl DevicesController { 189 | /// Constructs a new `DevicesController` with `oroot` serving as the root of the control group. 190 | pub fn new(oroot: PathBuf) -> Self { 191 | let mut root = oroot; 192 | root.push(Self::controller_type().to_string()); 193 | Self { 194 | base: root.clone(), 195 | path: root, 196 | } 197 | } 198 | 199 | /// Allow a (possibly, set of) device(s) to be used by the tasks in the control group. 200 | /// 201 | /// When `-1` is passed as `major` or `minor`, the kernel interprets that value as "any", 202 | /// meaning that it will match any device. 203 | pub fn allow_device( 204 | &self, 205 | devtype: DeviceType, 206 | major: i64, 207 | minor: i64, 208 | perm: &Vec, 209 | ) -> Result<()> { 210 | let perms = perm 211 | .iter() 212 | .map(DevicePermissions::to_char) 213 | .collect::(); 214 | let minor = if minor == -1 { 215 | "*".to_string() 216 | } else { 217 | format!("{}", minor) 218 | }; 219 | let major = if major == -1 { 220 | "*".to_string() 221 | } else { 222 | format!("{}", major) 223 | }; 224 | let final_str = format!("{} {}:{} {}", devtype.to_char(), major, minor, perms); 225 | self.open_path("devices.allow", true).and_then(|mut file| { 226 | file.write_all(final_str.as_ref()) 227 | .map_err(|e| Error::with_cause(WriteFailed, e)) 228 | }) 229 | } 230 | 231 | /// Deny the control group's tasks access to the devices covered by `dev`. 232 | /// 233 | /// When `-1` is passed as `major` or `minor`, the kernel interprets that value as "any", 234 | /// meaning that it will match any device. 235 | pub fn deny_device( 236 | &self, 237 | devtype: DeviceType, 238 | major: i64, 239 | minor: i64, 240 | perm: &Vec, 241 | ) -> Result<()> { 242 | let perms = perm 243 | .iter() 244 | .map(DevicePermissions::to_char) 245 | .collect::(); 246 | let minor = if minor == -1 { 247 | "*".to_string() 248 | } else { 249 | format!("{}", minor) 250 | }; 251 | let major = if major == -1 { 252 | "*".to_string() 253 | } else { 254 | format!("{}", major) 255 | }; 256 | let final_str = format!("{} {}:{} {}", devtype.to_char(), major, minor, perms); 257 | self.open_path("devices.deny", true).and_then(|mut file| { 258 | file.write_all(final_str.as_ref()) 259 | .map_err(|e| Error::with_cause(WriteFailed, e)) 260 | }) 261 | } 262 | 263 | /// Get the current list of allowed devices. 264 | pub fn allowed_devices(&self) -> Result> { 265 | self.open_path("devices.list", false).and_then(|mut file| { 266 | let mut s = String::new(); 267 | let res = file.read_to_string(&mut s); 268 | match res { 269 | Ok(_) => { 270 | s.lines().fold(Ok(Vec::new()), |acc, line| { 271 | let ls = line.to_string().split(|c| c == ' ' || c == ':').map(|x| x.to_string()).collect::>(); 272 | if acc.is_err() || ls.len() != 4 { 273 | error!("allowed_devices: acc: {:?}, ls: {:?}", acc, ls); 274 | Err(Error::new(ParseError)) 275 | } else { 276 | let devtype = DeviceType::from_char(ls[0].chars().nth(0)); 277 | let mut major = ls[1].parse::(); 278 | let mut minor = ls[2].parse::(); 279 | if major.is_err() && ls[1] == "*".to_string() { 280 | major = Ok(-1); 281 | } 282 | if minor.is_err() && ls[2] == "*".to_string() { 283 | minor = Ok(-1); 284 | } 285 | if devtype.is_none() || major.is_err() || minor.is_err() || !DevicePermissions::is_valid(&ls[3]) { 286 | error!("allowed_devices: acc: {:?}, ls: {:?}, devtype: {:?}, major {:?} minor {:?} ls3 {:?}", 287 | acc, ls, devtype, major, minor, &ls[3]); 288 | Err(Error::new(ParseError)) 289 | } else { 290 | let access = DevicePermissions::from_str(&ls[3])?; 291 | let mut acc = acc.unwrap(); 292 | acc.push(DeviceResource { 293 | allow: true, 294 | devtype: devtype.unwrap(), 295 | major: major.unwrap(), 296 | minor: minor.unwrap(), 297 | access: access, 298 | }); 299 | Ok(acc) 300 | } 301 | } 302 | }) 303 | }, 304 | Err(e) => Err(Error::with_cause(ReadFailed, e)), 305 | } 306 | }) 307 | } 308 | } 309 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error as StdError; 2 | use std::fmt; 3 | 4 | /// The different types of errors that can occur while manipulating control groups. 5 | #[derive(Debug, Eq, PartialEq)] 6 | pub enum ErrorKind { 7 | /// An error occured while writing to a control group file. 8 | WriteFailed, 9 | 10 | /// An error occured while trying to read from a control group file. 11 | ReadFailed, 12 | 13 | /// An error occured while trying to parse a value from a control group file. 14 | /// 15 | /// In the future, there will be some information attached to this field. 16 | ParseError, 17 | 18 | /// You tried to do something invalid. 19 | /// 20 | /// This could be because you tried to set a value in a control group that is not a root 21 | /// control group. Or, when using unified hierarchy, you tried to add a task in a leaf node. 22 | InvalidOperation, 23 | 24 | /// The path of the control group was invalid. 25 | /// 26 | /// This could be caused by trying to escape the control group filesystem via a string of "..". 27 | /// This crate checks against this and operations will fail with this error. 28 | InvalidPath, 29 | 30 | /// An unknown error has occured. 31 | Other, 32 | } 33 | 34 | #[derive(Debug)] 35 | pub struct Error { 36 | kind: ErrorKind, 37 | cause: Option>, 38 | } 39 | 40 | impl fmt::Display for Error { 41 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 42 | let msg = match self.kind { 43 | ErrorKind::WriteFailed => "unable to write to a control group file", 44 | ErrorKind::ReadFailed => "unable to read a control group file", 45 | ErrorKind::ParseError => "unable to parse control group file", 46 | ErrorKind::InvalidOperation => "the requested operation is invalid", 47 | ErrorKind::InvalidPath => "the given path is invalid", 48 | ErrorKind::Other => "an unknown error", 49 | }; 50 | 51 | write!(f, "{}", msg) 52 | } 53 | } 54 | 55 | impl StdError for Error { 56 | fn cause(&self) -> Option<&StdError> { 57 | match self.cause { 58 | Some(ref x) => Some(&**x), 59 | None => None, 60 | } 61 | } 62 | } 63 | 64 | impl Error { 65 | pub(crate) fn new(kind: ErrorKind) -> Self { 66 | Self { 67 | kind, 68 | cause: None, 69 | } 70 | } 71 | 72 | pub(crate) fn with_cause(kind: ErrorKind, cause: E) -> Self 73 | where 74 | E: 'static + Send + StdError, 75 | { 76 | Self { 77 | kind, 78 | cause: Some(Box::new(cause)), 79 | } 80 | } 81 | 82 | pub fn kind(&self) -> &ErrorKind { 83 | &self.kind 84 | } 85 | } 86 | 87 | pub type Result = ::std::result::Result; 88 | -------------------------------------------------------------------------------- /src/freezer.rs: -------------------------------------------------------------------------------- 1 | //! This module contains the implementation of the `freezer` cgroup subsystem. 2 | //! 3 | //! See the Kernel's documentation for more information about this subsystem, found at: 4 | //! [Documentation/cgroup-v1/freezer-subsystem.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/freezer-subsystem.txt) 5 | use std::io::{Read, Write}; 6 | use std::path::PathBuf; 7 | 8 | use crate::error::*; 9 | use crate::error::ErrorKind::*; 10 | 11 | use crate::{ControllIdentifier, ControllerInternal, Controllers, Resources, Subsystem}; 12 | 13 | /// A controller that allows controlling the `freezer` subsystem of a Cgroup. 14 | /// 15 | /// In essence, this subsystem allows the user to freeze and thaw (== "un-freeze") the processes in 16 | /// the control group. This is done _transparently_ so that neither the parent, nor the children of 17 | /// the processes can observe the freeze. 18 | /// 19 | /// Note that if the control group is currently in the `Frozen` or `Freezing` state, then no 20 | /// processes can be added to it. 21 | #[derive(Debug, Clone)] 22 | pub struct FreezerController { 23 | base: PathBuf, 24 | path: PathBuf, 25 | } 26 | 27 | /// The current state of the control group 28 | pub enum FreezerState { 29 | /// The processes in the control group are _not_ frozen. 30 | Thawed, 31 | /// The processes in the control group are in the processes of being frozen. 32 | Freezing, 33 | /// The processes in the control group are frozen. 34 | Frozen, 35 | } 36 | 37 | impl ControllerInternal for FreezerController { 38 | fn control_type(&self) -> Controllers { 39 | Controllers::Freezer 40 | } 41 | fn get_path(&self) -> &PathBuf { 42 | &self.path 43 | } 44 | fn get_path_mut(&mut self) -> &mut PathBuf { 45 | &mut self.path 46 | } 47 | fn get_base(&self) -> &PathBuf { 48 | &self.base 49 | } 50 | 51 | fn apply(&self, _res: &Resources) -> Result<()> { 52 | Ok(()) 53 | } 54 | } 55 | 56 | impl ControllIdentifier for FreezerController { 57 | fn controller_type() -> Controllers { 58 | Controllers::Freezer 59 | } 60 | } 61 | 62 | impl<'a> From<&'a Subsystem> for &'a FreezerController { 63 | fn from(sub: &'a Subsystem) -> &'a FreezerController { 64 | unsafe { 65 | match sub { 66 | Subsystem::Freezer(c) => c, 67 | _ => { 68 | assert_eq!(1, 0); 69 | ::std::mem::uninitialized() 70 | } 71 | } 72 | } 73 | } 74 | } 75 | 76 | impl FreezerController { 77 | /// Contructs a new `FreezerController` with `oroot` serving as the root of the control group. 78 | pub fn new(oroot: PathBuf) -> Self { 79 | let mut root = oroot; 80 | root.push(Self::controller_type().to_string()); 81 | Self { 82 | base: root.clone(), 83 | path: root, 84 | } 85 | } 86 | 87 | /// Freezes the processes in the control group. 88 | pub fn freeze(&self) -> Result<()> { 89 | self.open_path("freezer.state", true).and_then(|mut file| { 90 | file.write_all("FROZEN".to_string().as_ref()) 91 | .map_err(|e| Error::with_cause(WriteFailed, e)) 92 | }) 93 | } 94 | 95 | /// Thaws, that is, unfreezes the processes in the control group. 96 | pub fn thaw(&self) -> Result<()> { 97 | self.open_path("freezer.state", true).and_then(|mut file| { 98 | file.write_all("THAWED".to_string().as_ref()) 99 | .map_err(|e| Error::with_cause(WriteFailed, e)) 100 | }) 101 | } 102 | 103 | /// Retrieve the state of processes in the control group. 104 | pub fn state(&self) -> Result { 105 | self.open_path("freezer.state", false).and_then(|mut file| { 106 | let mut s = String::new(); 107 | let res = file.read_to_string(&mut s); 108 | match res { 109 | Ok(_) => match s.as_ref() { 110 | "FROZEN" => Ok(FreezerState::Frozen), 111 | "THAWED" => Ok(FreezerState::Thawed), 112 | "FREEZING" => Ok(FreezerState::Freezing), 113 | _ => Err(Error::new(ParseError)), 114 | }, 115 | Err(e) => Err(Error::with_cause(ReadFailed, e)), 116 | } 117 | }) 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/hierarchies.rs: -------------------------------------------------------------------------------- 1 | //! This module represents the various control group hierarchies the Linux kernel supports. 2 | //! 3 | //! Currently, we only support the cgroupv1 hierarchy, but in the future we will add support for 4 | //! the Unified Hierarchy. 5 | 6 | use std::fs::File; 7 | use std::io::BufRead; 8 | use std::io::BufReader; 9 | use std::path::{Path, PathBuf}; 10 | 11 | use log::*; 12 | 13 | use crate::blkio::BlkIoController; 14 | use crate::cpu::CpuController; 15 | use crate::cpuacct::CpuAcctController; 16 | use crate::cpuset::CpuSetController; 17 | use crate::devices::DevicesController; 18 | use crate::freezer::FreezerController; 19 | use crate::hugetlb::HugeTlbController; 20 | use crate::memory::MemController; 21 | use crate::net_cls::NetClsController; 22 | use crate::net_prio::NetPrioController; 23 | use crate::perf_event::PerfEventController; 24 | use crate::pid::PidController; 25 | use crate::rdma::RdmaController; 26 | use crate::{Controllers, Hierarchy, Subsystem}; 27 | 28 | use crate::cgroup::Cgroup; 29 | 30 | /// The standard, original cgroup implementation. Often referred to as "cgroupv1". 31 | pub struct V1 { 32 | mount_point: String, 33 | } 34 | 35 | impl Hierarchy for V1 { 36 | fn subsystems(&self) -> Vec { 37 | let mut subs = vec![]; 38 | if self.check_support(Controllers::Pids) { 39 | subs.push(Subsystem::Pid(PidController::new(self.root()))); 40 | } 41 | if self.check_support(Controllers::Mem) { 42 | subs.push(Subsystem::Mem(MemController::new(self.root()))); 43 | } 44 | if self.check_support(Controllers::CpuSet) { 45 | subs.push(Subsystem::CpuSet(CpuSetController::new(self.root()))); 46 | } 47 | if self.check_support(Controllers::CpuAcct) { 48 | subs.push(Subsystem::CpuAcct(CpuAcctController::new(self.root()))); 49 | } 50 | if self.check_support(Controllers::Cpu) { 51 | subs.push(Subsystem::Cpu(CpuController::new(self.root()))); 52 | } 53 | if self.check_support(Controllers::Devices) { 54 | subs.push(Subsystem::Devices(DevicesController::new(self.root()))); 55 | } 56 | if self.check_support(Controllers::Freezer) { 57 | subs.push(Subsystem::Freezer(FreezerController::new(self.root()))); 58 | } 59 | if self.check_support(Controllers::NetCls) { 60 | subs.push(Subsystem::NetCls(NetClsController::new(self.root()))); 61 | } 62 | if self.check_support(Controllers::BlkIo) { 63 | subs.push(Subsystem::BlkIo(BlkIoController::new(self.root()))); 64 | } 65 | if self.check_support(Controllers::PerfEvent) { 66 | subs.push(Subsystem::PerfEvent(PerfEventController::new(self.root()))); 67 | } 68 | if self.check_support(Controllers::NetPrio) { 69 | subs.push(Subsystem::NetPrio(NetPrioController::new(self.root()))); 70 | } 71 | if self.check_support(Controllers::HugeTlb) { 72 | subs.push(Subsystem::HugeTlb(HugeTlbController::new(self.root()))); 73 | } 74 | if self.check_support(Controllers::Rdma) { 75 | subs.push(Subsystem::Rdma(RdmaController::new(self.root()))); 76 | } 77 | 78 | subs 79 | } 80 | 81 | fn root_control_group(&self) -> Cgroup { 82 | Cgroup::load(self, "".to_string()) 83 | } 84 | 85 | fn check_support(&self, sub: Controllers) -> bool { 86 | let root = self.root().read_dir().unwrap(); 87 | for entry in root { 88 | if let Ok(entry) = entry { 89 | if entry.file_name().into_string().unwrap() == sub.to_string() { 90 | return true; 91 | } 92 | } 93 | } 94 | return false; 95 | } 96 | 97 | fn root(&self) -> PathBuf { 98 | PathBuf::from(self.mount_point.clone()) 99 | } 100 | } 101 | 102 | impl V1 { 103 | /// Finds where control groups are mounted to and returns a hierarchy in which control groups 104 | /// can be created. 105 | pub fn new() -> Self { 106 | let mount_point = find_v1_mount().unwrap(); 107 | V1 { 108 | mount_point: mount_point, 109 | } 110 | } 111 | } 112 | 113 | fn find_v1_mount() -> Option { 114 | // Open mountinfo so we can get a parseable mount list 115 | let mountinfo_path = Path::new("/proc/self/mountinfo"); 116 | 117 | // If /proc isn't mounted, or something else happens, then bail out 118 | if mountinfo_path.exists() == false { 119 | return None; 120 | } 121 | 122 | let mountinfo_file = File::open(mountinfo_path).unwrap(); 123 | let mountinfo_reader = BufReader::new(&mountinfo_file); 124 | for _line in mountinfo_reader.lines() { 125 | let line = _line.unwrap(); 126 | let mut fields = line.split_whitespace(); 127 | let index = line.find(" - ").unwrap(); 128 | let mut more_fields = line[index + 3..].split_whitespace().collect::>(); 129 | let fstype = more_fields[0]; 130 | if fstype == "tmpfs" && more_fields[2].contains("ro") { 131 | let cgroups_mount = fields.nth(4).unwrap(); 132 | info!("found cgroups at {:?}", cgroups_mount); 133 | return Some(cgroups_mount.to_string()); 134 | } 135 | } 136 | 137 | None 138 | } 139 | -------------------------------------------------------------------------------- /src/hugetlb.rs: -------------------------------------------------------------------------------- 1 | //! This module contains the implementation of the `hugetlb` cgroup subsystem. 2 | //! 3 | //! See the Kernel's documentation for more information about this subsystem, found at: 4 | //! [Documentation/cgroup-v1/hugetlb.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/hugetlb.txt) 5 | use std::fs::File; 6 | use std::io::{Read, Write}; 7 | use std::path::PathBuf; 8 | 9 | use crate::error::*; 10 | use crate::error::ErrorKind::*; 11 | 12 | use crate::{ 13 | ControllIdentifier, ControllerInternal, Controllers, HugePageResources, Resources, 14 | Subsystem, 15 | }; 16 | 17 | /// A controller that allows controlling the `hugetlb` subsystem of a Cgroup. 18 | /// 19 | /// In essence, using this controller it is possible to limit the use of hugepages in the tasks of 20 | /// the control group. 21 | #[derive(Debug, Clone)] 22 | pub struct HugeTlbController { 23 | base: PathBuf, 24 | path: PathBuf, 25 | } 26 | 27 | impl ControllerInternal for HugeTlbController { 28 | fn control_type(&self) -> Controllers { 29 | Controllers::HugeTlb 30 | } 31 | fn get_path(&self) -> &PathBuf { 32 | &self.path 33 | } 34 | fn get_path_mut(&mut self) -> &mut PathBuf { 35 | &mut self.path 36 | } 37 | fn get_base(&self) -> &PathBuf { 38 | &self.base 39 | } 40 | 41 | fn apply(&self, res: &Resources) -> Result<()> { 42 | // get the resources that apply to this controller 43 | let res: &HugePageResources = &res.hugepages; 44 | 45 | if res.update_values { 46 | for i in &res.limits { 47 | let _ = self.set_limit_in_bytes(&i.size, i.limit); 48 | if self.limit_in_bytes(&i.size)? != i.limit { 49 | return Err(Error::new(Other)); 50 | } 51 | } 52 | } 53 | Ok(()) 54 | } 55 | } 56 | 57 | impl ControllIdentifier for HugeTlbController { 58 | fn controller_type() -> Controllers { 59 | Controllers::HugeTlb 60 | } 61 | } 62 | 63 | impl<'a> From<&'a Subsystem> for &'a HugeTlbController { 64 | fn from(sub: &'a Subsystem) -> &'a HugeTlbController { 65 | unsafe { 66 | match sub { 67 | Subsystem::HugeTlb(c) => c, 68 | _ => { 69 | assert_eq!(1, 0); 70 | ::std::mem::uninitialized() 71 | } 72 | } 73 | } 74 | } 75 | } 76 | 77 | fn read_u64_from(mut file: File) -> Result { 78 | let mut string = String::new(); 79 | match file.read_to_string(&mut string) { 80 | Ok(_) => string.trim().parse().map_err(|e| Error::with_cause(ParseError, e)), 81 | Err(e) => Err(Error::with_cause(ReadFailed, e)), 82 | } 83 | } 84 | 85 | impl HugeTlbController { 86 | /// Constructs a new `HugeTlbController` with `oroot` serving as the root of the control group. 87 | pub fn new(oroot: PathBuf) -> Self { 88 | let mut root = oroot; 89 | root.push(Self::controller_type().to_string()); 90 | Self { 91 | base: root.clone(), 92 | path: root, 93 | } 94 | } 95 | 96 | /// Whether the system supports `hugetlb_size` hugepages. 97 | pub fn size_supported(&self, _hugetlb_size: &str) -> bool { 98 | // TODO 99 | true 100 | } 101 | 102 | /// Check how many times has the limit of `hugetlb_size` hugepages been hit. 103 | pub fn failcnt(&self, hugetlb_size: &str) -> Result { 104 | self.open_path(&format!("hugetlb.{}.failcnt", hugetlb_size), false) 105 | .and_then(read_u64_from) 106 | } 107 | 108 | /// Get the limit (in bytes) of how much memory can be backed by hugepages of a certain size 109 | /// (`hugetlb_size`). 110 | pub fn limit_in_bytes(&self, hugetlb_size: &str) -> Result { 111 | self.open_path(&format!("hugetlb.{}.limit_in_bytes", hugetlb_size), false) 112 | .and_then(read_u64_from) 113 | } 114 | 115 | /// Get the current usage of memory that is backed by hugepages of a certain size 116 | /// (`hugetlb_size`). 117 | pub fn usage_in_bytes(&self, hugetlb_size: &str) -> Result { 118 | self.open_path(&format!("hugetlb.{}.usage_in_bytes", hugetlb_size), false) 119 | .and_then(read_u64_from) 120 | } 121 | 122 | /// Get the maximum observed usage of memory that is backed by hugepages of a certain size 123 | /// (`hugetlb_size`). 124 | pub fn max_usage_in_bytes(&self, hugetlb_size: &str) -> Result { 125 | self.open_path( 126 | &format!("hugetlb.{}.max_usage_in_bytes", hugetlb_size), 127 | false, 128 | ).and_then(read_u64_from) 129 | } 130 | 131 | /// Set the limit (in bytes) of how much memory can be backed by hugepages of a certain size 132 | /// (`hugetlb_size`). 133 | pub fn set_limit_in_bytes(&self, hugetlb_size: &str, limit: u64) -> Result<()> { 134 | self.open_path(&format!("hugetlb.{}.limit_in_bytes", hugetlb_size), true) 135 | .and_then(|mut file| { 136 | file.write_all(limit.to_string().as_ref()) 137 | .map_err(|e| Error::with_cause(WriteFailed, e)) 138 | }) 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use log::*; 2 | 3 | use std::fs::File; 4 | use std::io::{BufRead, BufReader, Write}; 5 | use std::path::{Path, PathBuf}; 6 | 7 | pub mod blkio; 8 | pub mod cgroup; 9 | pub mod cpu; 10 | pub mod cpuacct; 11 | pub mod cpuset; 12 | pub mod devices; 13 | pub mod error; 14 | pub mod freezer; 15 | pub mod hierarchies; 16 | pub mod hugetlb; 17 | pub mod memory; 18 | pub mod net_cls; 19 | pub mod net_prio; 20 | pub mod perf_event; 21 | pub mod pid; 22 | pub mod rdma; 23 | pub mod cgroup_builder; 24 | 25 | use crate::blkio::BlkIoController; 26 | use crate::cpu::CpuController; 27 | use crate::cpuacct::CpuAcctController; 28 | use crate::cpuset::CpuSetController; 29 | use crate::devices::DevicesController; 30 | use crate::error::*; 31 | use crate::freezer::FreezerController; 32 | use crate::hugetlb::HugeTlbController; 33 | use crate::memory::MemController; 34 | use crate::net_cls::NetClsController; 35 | use crate::net_prio::NetPrioController; 36 | use crate::perf_event::PerfEventController; 37 | use crate::pid::PidController; 38 | use crate::rdma::RdmaController; 39 | 40 | pub use crate::cgroup::Cgroup; 41 | 42 | /// Contains all the subsystems that are available in this crate. 43 | #[derive(Debug)] 44 | pub enum Subsystem { 45 | /// Controller for the `Pid` subsystem, see `PidController` for more information. 46 | Pid(PidController), 47 | /// Controller for the `Mem` subsystem, see `MemController` for more information. 48 | Mem(MemController), 49 | /// Controller for the `CpuSet subsystem, see `CpuSetController` for more information. 50 | CpuSet(CpuSetController), 51 | /// Controller for the `CpuAcct` subsystem, see `CpuAcctController` for more information. 52 | CpuAcct(CpuAcctController), 53 | /// Controller for the `Cpu` subsystem, see `CpuController` for more information. 54 | Cpu(CpuController), 55 | /// Controller for the `Devices` subsystem, see `DevicesController` for more information. 56 | Devices(DevicesController), 57 | /// Controller for the `Freezer` subsystem, see `FreezerController` for more information. 58 | Freezer(FreezerController), 59 | /// Controller for the `NetCls` subsystem, see `NetClsController` for more information. 60 | NetCls(NetClsController), 61 | /// Controller for the `BlkIo` subsystem, see `BlkIoController` for more information. 62 | BlkIo(BlkIoController), 63 | /// Controller for the `PerfEvent` subsystem, see `PerfEventController` for more information. 64 | PerfEvent(PerfEventController), 65 | /// Controller for the `NetPrio` subsystem, see `NetPrioController` for more information. 66 | NetPrio(NetPrioController), 67 | /// Controller for the `HugeTlb` subsystem, see `HugeTlbController` for more information. 68 | HugeTlb(HugeTlbController), 69 | /// Controller for the `Rdma` subsystem, see `RdmaController` for more information. 70 | Rdma(RdmaController), 71 | } 72 | 73 | #[doc(hidden)] 74 | #[derive(Eq, PartialEq, Debug)] 75 | pub enum Controllers { 76 | Pids, 77 | Mem, 78 | CpuSet, 79 | CpuAcct, 80 | Cpu, 81 | Devices, 82 | Freezer, 83 | NetCls, 84 | BlkIo, 85 | PerfEvent, 86 | NetPrio, 87 | HugeTlb, 88 | Rdma, 89 | } 90 | 91 | impl Controllers { 92 | pub fn to_string(&self) -> String { 93 | match self { 94 | Controllers::Pids => return "pids".to_string(), 95 | Controllers::Mem => return "memory".to_string(), 96 | Controllers::CpuSet => return "cpuset".to_string(), 97 | Controllers::CpuAcct => return "cpuacct".to_string(), 98 | Controllers::Cpu => return "cpu".to_string(), 99 | Controllers::Devices => return "devices".to_string(), 100 | Controllers::Freezer => return "freezer".to_string(), 101 | Controllers::NetCls => return "net_cls".to_string(), 102 | Controllers::BlkIo => return "blkio".to_string(), 103 | Controllers::PerfEvent => return "perf_event".to_string(), 104 | Controllers::NetPrio => return "net_prio".to_string(), 105 | Controllers::HugeTlb => return "hugetlb".to_string(), 106 | Controllers::Rdma => return "rdma".to_string(), 107 | } 108 | } 109 | } 110 | 111 | mod sealed { 112 | use super::*; 113 | 114 | pub trait ControllerInternal { 115 | fn apply(&self, res: &Resources) -> Result<()>; 116 | 117 | // meta stuff 118 | fn control_type(&self) -> Controllers; 119 | fn get_path(&self) -> &PathBuf; 120 | fn get_path_mut(&mut self) -> &mut PathBuf; 121 | fn get_base(&self) -> &PathBuf; 122 | 123 | fn verify_path(&self) -> Result<()> { 124 | if self.get_path().starts_with(self.get_base()) { 125 | Ok(()) 126 | } else { 127 | Err(Error::new(ErrorKind::InvalidPath)) 128 | } 129 | } 130 | 131 | fn open_path(&self, p: &str, w: bool) -> Result { 132 | let mut path = self.get_path().clone(); 133 | path.push(p); 134 | 135 | self.verify_path()?; 136 | 137 | if w { 138 | match File::create(&path) { 139 | Err(e) => return Err(Error::with_cause(ErrorKind::WriteFailed, e)), 140 | Ok(file) => return Ok(file), 141 | } 142 | } else { 143 | match File::open(&path) { 144 | Err(e) => return Err(Error::with_cause(ErrorKind::ReadFailed, e)), 145 | Ok(file) => return Ok(file), 146 | } 147 | } 148 | } 149 | 150 | #[doc(hidden)] 151 | fn path_exists(&self, p: &str) -> bool { 152 | if let Err(_) = self.verify_path() { 153 | return false; 154 | } 155 | 156 | std::path::Path::new(p).exists() 157 | } 158 | 159 | } 160 | } 161 | 162 | pub(crate) use crate::sealed::ControllerInternal; 163 | 164 | /// A Controller is a subsystem attached to the control group. 165 | /// 166 | /// Implementors are able to control certain aspects of a control group. 167 | pub trait Controller { 168 | #[doc(hidden)] 169 | fn control_type(&self) -> Controllers; 170 | 171 | /// The file system path to the controller. 172 | fn path(&self) -> &Path; 173 | 174 | /// Apply a set of resources to the Controller, invoking its internal functions to pass the 175 | /// kernel the information. 176 | fn apply(&self, res: &Resources) -> Result<()>; 177 | 178 | /// Create this controller 179 | fn create(&self); 180 | 181 | /// Does this controller already exist? 182 | fn exists(&self) -> bool; 183 | 184 | /// Delete the controller. 185 | fn delete(&self); 186 | 187 | /// Attach a task to this controller. 188 | fn add_task(&self, pid: &CgroupPid) -> Result<()>; 189 | 190 | /// Get the list of tasks that this controller has. 191 | fn tasks(&self) -> Vec; 192 | } 193 | 194 | impl Controller for T where T: ControllerInternal { 195 | fn control_type(&self) -> Controllers { 196 | ControllerInternal::control_type(self) 197 | } 198 | 199 | fn path(&self) -> &Path { 200 | self.get_path() 201 | } 202 | 203 | /// Apply a set of resources to the Controller, invoking its internal functions to pass the 204 | /// kernel the information. 205 | fn apply(&self, res: &Resources) -> Result<()> { 206 | ControllerInternal::apply(self, res) 207 | } 208 | 209 | /// Create this controller 210 | fn create(&self) { 211 | self.verify_path().expect("path should be valid"); 212 | 213 | match ::std::fs::create_dir(self.get_path()) { 214 | Ok(_) => (), 215 | Err(e) => warn!("error create_dir {:?}", e), 216 | } 217 | } 218 | 219 | /// Does this controller already exist? 220 | fn exists(&self) -> bool { 221 | self.get_path().exists() 222 | } 223 | 224 | /// Delete the controller. 225 | fn delete(&self) { 226 | if self.get_path().exists() { 227 | let _ = ::std::fs::remove_dir(self.get_path()); 228 | } 229 | } 230 | 231 | /// Attach a task to this controller. 232 | fn add_task(&self, pid: &CgroupPid) -> Result<()> { 233 | self.open_path("tasks", true).and_then(|mut file| { 234 | file.write_all(pid.pid.to_string().as_ref()) 235 | .map_err(|e| Error::with_cause(ErrorKind::WriteFailed, e)) 236 | }) 237 | } 238 | 239 | /// Get the list of tasks that this controller has. 240 | fn tasks(&self) -> Vec { 241 | self.open_path("tasks", false) 242 | .and_then(|file| { 243 | let bf = BufReader::new(file); 244 | let mut v = Vec::new(); 245 | for line in bf.lines() { 246 | if let Ok(line) = line { 247 | let n = line.trim().parse().unwrap_or(0u64); 248 | v.push(n); 249 | } 250 | } 251 | Ok(v.into_iter().map(CgroupPid::from).collect()) 252 | }).unwrap_or(vec![]) 253 | } 254 | } 255 | 256 | #[doc(hidden)] 257 | pub trait ControllIdentifier { 258 | fn controller_type() -> Controllers; 259 | } 260 | 261 | /// Control group hierarchy (right now, only V1 is supported, but in the future Unified will be 262 | /// implemented as well). 263 | pub trait Hierarchy { 264 | /// Returns what subsystems are supported by the hierarchy. 265 | fn subsystems(&self) -> Vec; 266 | 267 | /// Returns the root directory of the hierarchy. 268 | fn root(&self) -> PathBuf; 269 | 270 | /// Return a handle to the root control group in the hierarchy. 271 | fn root_control_group(&self) -> Cgroup; 272 | 273 | /// Checks whether a certain subsystem is supported in the hierarchy. 274 | /// 275 | /// This is an internal function and should not be used. 276 | #[doc(hidden)] 277 | fn check_support(&self, sub: Controllers) -> bool; 278 | } 279 | 280 | /// Resource limits for the memory subsystem. 281 | #[derive(Debug, Clone, Eq, PartialEq, Default)] 282 | pub struct MemoryResources { 283 | /// Whether values should be applied to the controller. 284 | pub update_values: bool, 285 | /// How much memory (in bytes) can the kernel consume. 286 | pub kernel_memory_limit: u64, 287 | /// Upper limit of memory usage of the control group's tasks. 288 | pub memory_hard_limit: u64, 289 | /// How much memory the tasks in the control group can use when the system is under memory 290 | /// pressure. 291 | pub memory_soft_limit: u64, 292 | /// How much of the kernel's memory (in bytes) can be used for TCP-related buffers. 293 | pub kernel_tcp_memory_limit: u64, 294 | /// How much memory and swap together can the tasks in the control group use. 295 | pub memory_swap_limit: u64, 296 | /// Controls the tendency of the kernel to swap out parts of the address space of the tasks to 297 | /// disk. Lower value implies less likely. 298 | /// 299 | /// Note, however, that a value of zero does not mean the process is never swapped out. Use the 300 | /// traditional `mlock(2)` system call for that purpose. 301 | pub swappiness: u64, 302 | } 303 | 304 | /// Resources limits on the number of processes. 305 | #[derive(Debug, Clone, Eq, PartialEq, Default)] 306 | pub struct PidResources { 307 | /// Whether values should be applied to the controller. 308 | pub update_values: bool, 309 | /// The maximum number of processes that can exist in the control group. 310 | /// 311 | /// Note that attaching processes to the control group will still succeed _even_ if the limit 312 | /// would be violated, however forks/clones inside the control group will have with `EAGAIN` if 313 | /// they would violate the limit set here. 314 | pub maximum_number_of_processes: pid::PidMax, 315 | } 316 | 317 | /// Resources limits about how the tasks can use the CPU. 318 | #[derive(Debug, Clone, Eq, PartialEq, Default)] 319 | pub struct CpuResources { 320 | /// Whether values should be applied to the controller. 321 | pub update_values: bool, 322 | // cpuset 323 | /// A comma-separated list of CPU IDs where the task in the control group can run. Dashes 324 | /// between numbers indicate ranges. 325 | pub cpus: String, 326 | /// Same syntax as the `cpus` field of this structure, but applies to memory nodes instead of 327 | /// processors. 328 | pub mems: String, 329 | // cpu 330 | /// Weight of how much of the total CPU time should this control group get. Note that this is 331 | /// hierarchical, so this is weighted against the siblings of this control group. 332 | pub shares: u64, 333 | /// In one `period`, how much can the tasks run in nanoseconds. 334 | pub quota: i64, 335 | /// Period of time in nanoseconds. 336 | pub period: u64, 337 | /// This is currently a no-operation. 338 | pub realtime_runtime: i64, 339 | /// This is currently a no-operation. 340 | pub realtime_period: u64, 341 | } 342 | 343 | /// A device resource that can be allowed or denied access to. 344 | #[derive(Debug, Clone, Eq, PartialEq, Default)] 345 | pub struct DeviceResource { 346 | /// If true, access to the device is allowed, otherwise it's denied. 347 | pub allow: bool, 348 | /// `'c'` for character device, `'b'` for block device; or `'a'` for all devices. 349 | pub devtype: crate::devices::DeviceType, 350 | /// The major number of the device. 351 | pub major: i64, 352 | /// The minor number of the device. 353 | pub minor: i64, 354 | /// Sequence of `'r'`, `'w'` or `'m'`, each denoting read, write or mknod permissions. 355 | pub access: Vec, 356 | } 357 | 358 | /// Limit the usage of devices for the control group's tasks. 359 | #[derive(Debug, Clone, Eq, PartialEq, Default)] 360 | pub struct DeviceResources { 361 | /// Whether values should be applied to the controller. 362 | pub update_values: bool, 363 | /// For each device in the list, the limits in the structure are applied. 364 | pub devices: Vec, 365 | } 366 | 367 | /// Assigned priority for a network device. 368 | #[derive(Debug, Clone, Eq, PartialEq, Default)] 369 | pub struct NetworkPriority { 370 | /// The name (as visible in `ifconfig`) of the interface. 371 | pub name: String, 372 | /// Assigned priority. 373 | pub priority: u64, 374 | } 375 | 376 | /// Collections of limits and tags that can be imposed on packets emitted by the tasks in the 377 | /// control group. 378 | #[derive(Debug, Clone, Eq, PartialEq, Default)] 379 | pub struct NetworkResources { 380 | /// Whether values should be applied to the controller. 381 | pub update_values: bool, 382 | /// The networking class identifier to attach to the packets. 383 | /// 384 | /// This can then later be used in iptables and such to have special rules. 385 | pub class_id: u64, 386 | /// Priority of the egress traffic for each interface. 387 | pub priorities: Vec, 388 | } 389 | 390 | /// A hugepage type and its consumption limit for the control group. 391 | #[derive(Debug, Clone, Eq, PartialEq, Default)] 392 | pub struct HugePageResource { 393 | /// The size of the hugepage, i.e. `2MB`, `1GB`, etc. 394 | pub size: String, 395 | /// The amount of bytes (of memory consumed by the tasks) that are allowed to be backed by 396 | /// hugepages. 397 | pub limit: u64, 398 | } 399 | 400 | /// Provides the ability to set consumption limit on each type of hugepages. 401 | #[derive(Debug, Clone, Eq, PartialEq, Default)] 402 | pub struct HugePageResources { 403 | /// Whether values should be applied to the controller. 404 | pub update_values: bool, 405 | /// Set a limit of consumption for each hugepages type. 406 | pub limits: Vec, 407 | } 408 | 409 | /// Weight for a particular block device. 410 | #[derive(Debug, Clone, Eq, PartialEq, Default)] 411 | pub struct BlkIoDeviceResource { 412 | /// The major number of the device. 413 | pub major: u64, 414 | /// The minor number of the device. 415 | pub minor: u64, 416 | /// The weight of the device against the descendant nodes. 417 | pub weight: u16, 418 | /// The weight of the device against the sibling nodes. 419 | pub leaf_weight: u16, 420 | } 421 | 422 | /// Provides the ability to throttle a device (both byte/sec, and IO op/s) 423 | #[derive(Debug, Clone, Eq, PartialEq, Default)] 424 | pub struct BlkIoDeviceThrottleResource { 425 | /// The major number of the device. 426 | pub major: u64, 427 | /// The minor number of the device. 428 | pub minor: u64, 429 | /// The rate. 430 | pub rate: u64, 431 | } 432 | 433 | /// General block I/O resource limits. 434 | #[derive(Debug, Clone, Eq, PartialEq, Default)] 435 | pub struct BlkIoResources { 436 | /// Whether values should be applied to the controller. 437 | pub update_values: bool, 438 | /// The weight of the control group against descendant nodes. 439 | pub weight: u16, 440 | /// The weight of the control group against sibling nodes. 441 | pub leaf_weight: u16, 442 | /// For each device, a separate weight (both normal and leaf) can be provided. 443 | pub weight_device: Vec, 444 | /// Throttled read bytes/second can be provided for each device. 445 | pub throttle_read_bps_device: Vec, 446 | /// Throttled read IO operations per second can be provided for each device. 447 | pub throttle_read_iops_device: Vec, 448 | /// Throttled written bytes/second can be provided for each device. 449 | pub throttle_write_bps_device: Vec, 450 | /// Throttled write IO operations per second can be provided for each device. 451 | pub throttle_write_iops_device: Vec, 452 | } 453 | 454 | /// The resource limits and constraints that will be set on the control group. 455 | #[derive(Debug, Clone, Eq, PartialEq, Default)] 456 | pub struct Resources { 457 | /// Memory usage related limits. 458 | pub memory: MemoryResources, 459 | /// Process identifier related limits. 460 | pub pid: PidResources, 461 | /// CPU related limits. 462 | pub cpu: CpuResources, 463 | /// Device related limits. 464 | pub devices: DeviceResources, 465 | /// Network related tags and limits. 466 | pub network: NetworkResources, 467 | /// Hugepages consumption related limits. 468 | pub hugepages: HugePageResources, 469 | /// Block device I/O related limits. 470 | pub blkio: BlkIoResources, 471 | } 472 | 473 | /// A structure representing a `pid`. Currently implementations exist for `u64` and 474 | /// `std::process::Child`. 475 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] 476 | pub struct CgroupPid { 477 | /// The process identifier 478 | pub pid: u64, 479 | } 480 | 481 | impl From for CgroupPid { 482 | fn from(u: u64) -> CgroupPid { 483 | CgroupPid { pid: u } 484 | } 485 | } 486 | 487 | impl<'a> From<&'a std::process::Child> for CgroupPid { 488 | fn from(u: &std::process::Child) -> CgroupPid { 489 | CgroupPid { pid: u.id() as u64 } 490 | } 491 | } 492 | 493 | impl Subsystem { 494 | fn enter(self, path: &Path) -> Self { 495 | match self { 496 | Subsystem::Pid(cont) => Subsystem::Pid({ 497 | let mut c = cont.clone(); 498 | c.get_path_mut().push(path); 499 | c 500 | }), 501 | Subsystem::Mem(cont) => Subsystem::Mem({ 502 | let mut c = cont.clone(); 503 | c.get_path_mut().push(path); 504 | c 505 | }), 506 | Subsystem::CpuSet(cont) => Subsystem::CpuSet({ 507 | let mut c = cont.clone(); 508 | c.get_path_mut().push(path); 509 | c 510 | }), 511 | Subsystem::CpuAcct(cont) => Subsystem::CpuAcct({ 512 | let mut c = cont.clone(); 513 | c.get_path_mut().push(path); 514 | c 515 | }), 516 | Subsystem::Cpu(cont) => Subsystem::Cpu({ 517 | let mut c = cont.clone(); 518 | c.get_path_mut().push(path); 519 | c 520 | }), 521 | Subsystem::Devices(cont) => Subsystem::Devices({ 522 | let mut c = cont.clone(); 523 | c.get_path_mut().push(path); 524 | c 525 | }), 526 | Subsystem::Freezer(cont) => Subsystem::Freezer({ 527 | let mut c = cont.clone(); 528 | c.get_path_mut().push(path); 529 | c 530 | }), 531 | Subsystem::NetCls(cont) => Subsystem::NetCls({ 532 | let mut c = cont.clone(); 533 | c.get_path_mut().push(path); 534 | c 535 | }), 536 | Subsystem::BlkIo(cont) => Subsystem::BlkIo({ 537 | let mut c = cont.clone(); 538 | c.get_path_mut().push(path); 539 | c 540 | }), 541 | Subsystem::PerfEvent(cont) => Subsystem::PerfEvent({ 542 | let mut c = cont.clone(); 543 | c.get_path_mut().push(path); 544 | c 545 | }), 546 | Subsystem::NetPrio(cont) => Subsystem::NetPrio({ 547 | let mut c = cont.clone(); 548 | c.get_path_mut().push(path); 549 | c 550 | }), 551 | Subsystem::HugeTlb(cont) => Subsystem::HugeTlb({ 552 | let mut c = cont.clone(); 553 | c.get_path_mut().push(path); 554 | c 555 | }), 556 | Subsystem::Rdma(cont) => Subsystem::Rdma({ 557 | let mut c = cont.clone(); 558 | c.get_path_mut().push(path); 559 | c 560 | }), 561 | } 562 | } 563 | 564 | fn to_controller(&self) -> &dyn Controller { 565 | match self { 566 | Subsystem::Pid(cont) => cont, 567 | Subsystem::Mem(cont) => cont, 568 | Subsystem::CpuSet(cont) => cont, 569 | Subsystem::CpuAcct(cont) => cont, 570 | Subsystem::Cpu(cont) => cont, 571 | Subsystem::Devices(cont) => cont, 572 | Subsystem::Freezer(cont) => cont, 573 | Subsystem::NetCls(cont) => cont, 574 | Subsystem::BlkIo(cont) => cont, 575 | Subsystem::PerfEvent(cont) => cont, 576 | Subsystem::NetPrio(cont) => cont, 577 | Subsystem::HugeTlb(cont) => cont, 578 | Subsystem::Rdma(cont) => cont, 579 | } 580 | } 581 | } 582 | -------------------------------------------------------------------------------- /src/memory.rs: -------------------------------------------------------------------------------- 1 | //! This module contains the implementation of the `memory` cgroup subsystem. 2 | //! 3 | //! See the Kernel's documentation for more information about this subsystem, found at: 4 | //! [Documentation/cgroup-v1/memory.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/memory.txt) 5 | use std::fs::File; 6 | use std::io::{Read, Write}; 7 | use std::path::PathBuf; 8 | 9 | use crate::error::*; 10 | use crate::error::ErrorKind::*; 11 | 12 | use crate::{ 13 | ControllIdentifier, ControllerInternal, Controllers, MemoryResources, Resources, Subsystem, 14 | }; 15 | 16 | /// A controller that allows controlling the `memory` subsystem of a Cgroup. 17 | /// 18 | /// In essence, using the memory controller, the user can gather statistics about the memory usage 19 | /// of the tasks in the control group. Additonally, one can also set powerful limits on their 20 | /// memory usage. 21 | #[derive(Debug, Clone)] 22 | pub struct MemController { 23 | base: PathBuf, 24 | path: PathBuf, 25 | } 26 | 27 | /// Controls statistics and controls about the OOM killer operating in this control group. 28 | #[derive(Default, Debug, PartialEq, Eq)] 29 | pub struct OomControl { 30 | /// If true, the OOM killer has been disabled for the tasks in this control group. 31 | pub oom_kill_disable: bool, 32 | /// Is the OOM killer currently running for the tasks in the control group? 33 | pub under_oom: bool, 34 | /// How many tasks were killed by the OOM killer so far. 35 | pub oom_kill: u64, 36 | } 37 | 38 | fn parse_oom_control(s: String) -> Result { 39 | let spl = s.split_whitespace().collect::>(); 40 | 41 | Ok(OomControl { 42 | oom_kill_disable: spl[1].parse::().unwrap() == 1, 43 | under_oom: spl[3].parse::().unwrap() == 1, 44 | oom_kill: spl[5].parse::().unwrap(), 45 | }) 46 | } 47 | 48 | /// Contains statistics about the NUMA locality of the control group's tasks. 49 | #[derive(Default, Debug, PartialEq, Eq)] 50 | pub struct NumaStat { 51 | /// Total amount of pages used by the control group. 52 | pub total_pages: u64, 53 | /// Total amount of pages used by the control group, broken down by NUMA node. 54 | pub total_pages_per_node: Vec, 55 | /// Total amount of file pages used by the control group. 56 | pub file_pages: u64, 57 | /// Total amount of file pages used by the control group, broken down by NUMA node. 58 | pub file_pages_per_node: Vec, 59 | /// Total amount of anonymous pages used by the control group. 60 | pub anon_pages: u64, 61 | /// Total amount of anonymous pages used by the control group, broken down by NUMA node. 62 | pub anon_pages_per_node: Vec, 63 | /// Total amount of unevictable pages used by the control group. 64 | pub unevictable_pages: u64, 65 | /// Total amount of unevictable pages used by the control group, broken down by NUMA node. 66 | pub unevictable_pages_per_node: Vec, 67 | 68 | /// Same as `total_pages`, but includes the descedant control groups' number as well. 69 | pub hierarchical_total_pages: u64, 70 | /// Same as `total_pages_per_node`, but includes the descedant control groups' number as well. 71 | pub hierarchical_total_pages_per_node: Vec, 72 | /// Same as `file_pages`, but includes the descedant control groups' number as well. 73 | pub hierarchical_file_pages: u64, 74 | /// Same as `file_pages_per_node`, but includes the descedant control groups' number as well. 75 | pub hierarchical_file_pages_per_node: Vec, 76 | /// Same as `anon_pages`, but includes the descedant control groups' number as well. 77 | pub hierarchical_anon_pages: u64, 78 | /// Same as `anon_pages_per_node`, but includes the descedant control groups' number as well. 79 | pub hierarchical_anon_pages_per_node: Vec, 80 | /// Same as `unevictable`, but includes the descedant control groups' number as well. 81 | pub hierarchical_unevictable_pages: u64, 82 | /// Same as `unevictable_per_node`, but includes the descedant control groups' number as well. 83 | pub hierarchical_unevictable_pages_per_node: Vec, 84 | } 85 | 86 | fn parse_numa_stat(s: String) -> Result { 87 | // Parse the number of nodes 88 | let _nodes = (s.split_whitespace().collect::>().len() - 8) / 8; 89 | let mut ls = s.lines(); 90 | let total_line = ls.next().unwrap(); 91 | let file_line = ls.next().unwrap(); 92 | let anon_line = ls.next().unwrap(); 93 | let unevict_line = ls.next().unwrap(); 94 | let hier_total_line = ls.next().unwrap(); 95 | let hier_file_line = ls.next().unwrap(); 96 | let hier_anon_line = ls.next().unwrap(); 97 | let hier_unevict_line = ls.next().unwrap(); 98 | 99 | Ok(NumaStat { 100 | total_pages: total_line 101 | .split(|x| x == ' ' || x == '=') 102 | .collect::>()[1] 103 | .parse::() 104 | .unwrap_or(0), 105 | total_pages_per_node: { 106 | let spl = &total_line.split(" ").collect::>()[1..]; 107 | spl.iter() 108 | .map(|x| { 109 | x.split("=").collect::>()[1] 110 | .parse::() 111 | .unwrap_or(0) 112 | }).collect() 113 | }, 114 | file_pages: file_line 115 | .split(|x| x == ' ' || x == '=') 116 | .collect::>()[1] 117 | .parse::() 118 | .unwrap_or(0), 119 | file_pages_per_node: { 120 | let spl = &file_line.split(" ").collect::>()[1..]; 121 | spl.iter() 122 | .map(|x| { 123 | x.split("=").collect::>()[1] 124 | .parse::() 125 | .unwrap_or(0) 126 | }).collect() 127 | }, 128 | anon_pages: anon_line 129 | .split(|x| x == ' ' || x == '=') 130 | .collect::>()[1] 131 | .parse::() 132 | .unwrap_or(0), 133 | anon_pages_per_node: { 134 | let spl = &anon_line.split(" ").collect::>()[1..]; 135 | spl.iter() 136 | .map(|x| { 137 | x.split("=").collect::>()[1] 138 | .parse::() 139 | .unwrap_or(0) 140 | }).collect() 141 | }, 142 | unevictable_pages: unevict_line 143 | .split(|x| x == ' ' || x == '=') 144 | .collect::>()[1] 145 | .parse::() 146 | .unwrap_or(0), 147 | unevictable_pages_per_node: { 148 | let spl = &unevict_line.split(" ").collect::>()[1..]; 149 | spl.iter() 150 | .map(|x| { 151 | x.split("=").collect::>()[1] 152 | .parse::() 153 | .unwrap_or(0) 154 | }).collect() 155 | }, 156 | hierarchical_total_pages: hier_total_line 157 | .split(|x| x == ' ' || x == '=') 158 | .collect::>()[1] 159 | .parse::() 160 | .unwrap_or(0), 161 | hierarchical_total_pages_per_node: { 162 | let spl = &hier_total_line.split(" ").collect::>()[1..]; 163 | spl.iter() 164 | .map(|x| { 165 | x.split("=").collect::>()[1] 166 | .parse::() 167 | .unwrap_or(0) 168 | }).collect() 169 | }, 170 | hierarchical_file_pages: hier_file_line 171 | .split(|x| x == ' ' || x == '=') 172 | .collect::>()[1] 173 | .parse::() 174 | .unwrap_or(0), 175 | hierarchical_file_pages_per_node: { 176 | let spl = &hier_file_line.split(" ").collect::>()[1..]; 177 | spl.iter() 178 | .map(|x| { 179 | x.split("=").collect::>()[1] 180 | .parse::() 181 | .unwrap_or(0) 182 | }).collect() 183 | }, 184 | hierarchical_anon_pages: hier_anon_line 185 | .split(|x| x == ' ' || x == '=') 186 | .collect::>()[1] 187 | .parse::() 188 | .unwrap_or(0), 189 | hierarchical_anon_pages_per_node: { 190 | let spl = &hier_anon_line.split(" ").collect::>()[1..]; 191 | spl.iter() 192 | .map(|x| { 193 | x.split("=").collect::>()[1] 194 | .parse::() 195 | .unwrap_or(0) 196 | }).collect() 197 | }, 198 | hierarchical_unevictable_pages: hier_unevict_line 199 | .split(|x| x == ' ' || x == '=') 200 | .collect::>()[1] 201 | .parse::() 202 | .unwrap_or(0), 203 | hierarchical_unevictable_pages_per_node: { 204 | let spl = &hier_unevict_line.split(" ").collect::>()[1..]; 205 | spl.iter() 206 | .map(|x| { 207 | x.split("=").collect::>()[1] 208 | .parse::() 209 | .unwrap_or(0) 210 | }).collect() 211 | }, 212 | }) 213 | } 214 | 215 | #[derive(Default, Debug, PartialEq, Eq)] 216 | pub struct MemoryStat { 217 | pub cache: u64, 218 | pub rss: u64, 219 | pub rss_huge: u64, 220 | pub shmem: u64, 221 | pub mapped_file: u64, 222 | pub dirty: u64, 223 | pub writeback: u64, 224 | pub swap: u64, 225 | pub pgpgin: u64, 226 | pub pgpgout: u64, 227 | pub pgfault: u64, 228 | pub pgmajfault: u64, 229 | pub inactive_anon: u64, 230 | pub active_anon: u64, 231 | pub inactive_file: u64, 232 | pub active_file: u64, 233 | pub unevictable: u64, 234 | pub hierarchical_memory_limit: u64, 235 | pub hierarchical_memsw_limit: u64, 236 | pub total_cache: u64, 237 | pub total_rss: u64, 238 | pub total_rss_huge: u64, 239 | pub total_shmem: u64, 240 | pub total_mapped_file: u64, 241 | pub total_dirty: u64, 242 | pub total_writeback: u64, 243 | pub total_swap: u64, 244 | pub total_pgpgin: u64, 245 | pub total_pgpgout: u64, 246 | pub total_pgfault: u64, 247 | pub total_pgmajfault: u64, 248 | pub total_inactive_anon: u64, 249 | pub total_active_anon: u64, 250 | pub total_inactive_file: u64, 251 | pub total_active_file: u64, 252 | pub total_unevictable: u64, 253 | } 254 | 255 | fn parse_memory_stat(s: String) -> Result { 256 | let sp: Vec<&str> = s 257 | .split_whitespace() 258 | .filter(|x| x.parse::().is_ok()) 259 | .collect(); 260 | 261 | let mut spl = sp.iter(); 262 | Ok(MemoryStat { 263 | cache: spl.next().unwrap().parse::().unwrap(), 264 | rss: spl.next().unwrap().parse::().unwrap(), 265 | rss_huge: spl.next().unwrap().parse::().unwrap(), 266 | shmem: spl.next().unwrap().parse::().unwrap(), 267 | mapped_file: spl.next().unwrap().parse::().unwrap(), 268 | dirty: spl.next().unwrap().parse::().unwrap(), 269 | writeback: spl.next().unwrap().parse::().unwrap(), 270 | swap: spl.next().unwrap().parse::().unwrap(), 271 | pgpgin: spl.next().unwrap().parse::().unwrap(), 272 | pgpgout: spl.next().unwrap().parse::().unwrap(), 273 | pgfault: spl.next().unwrap().parse::().unwrap(), 274 | pgmajfault: spl.next().unwrap().parse::().unwrap(), 275 | inactive_anon: spl.next().unwrap().parse::().unwrap(), 276 | active_anon: spl.next().unwrap().parse::().unwrap(), 277 | inactive_file: spl.next().unwrap().parse::().unwrap(), 278 | active_file: spl.next().unwrap().parse::().unwrap(), 279 | unevictable: spl.next().unwrap().parse::().unwrap(), 280 | hierarchical_memory_limit: spl.next().unwrap().parse::().unwrap(), 281 | hierarchical_memsw_limit: spl.next().unwrap().parse::().unwrap(), 282 | total_cache: spl.next().unwrap().parse::().unwrap(), 283 | total_rss: spl.next().unwrap().parse::().unwrap(), 284 | total_rss_huge: spl.next().unwrap().parse::().unwrap(), 285 | total_shmem: spl.next().unwrap().parse::().unwrap(), 286 | total_mapped_file: spl.next().unwrap().parse::().unwrap(), 287 | total_dirty: spl.next().unwrap().parse::().unwrap(), 288 | total_writeback: spl.next().unwrap().parse::().unwrap(), 289 | total_swap: spl.next().unwrap().parse::().unwrap(), 290 | total_pgpgin: spl.next().unwrap().parse::().unwrap(), 291 | total_pgpgout: spl.next().unwrap().parse::().unwrap(), 292 | total_pgfault: spl.next().unwrap().parse::().unwrap(), 293 | total_pgmajfault: spl.next().unwrap().parse::().unwrap(), 294 | total_inactive_anon: spl.next().unwrap().parse::().unwrap(), 295 | total_active_anon: spl.next().unwrap().parse::().unwrap(), 296 | total_inactive_file: spl.next().unwrap().parse::().unwrap(), 297 | total_active_file: spl.next().unwrap().parse::().unwrap(), 298 | total_unevictable: spl.next().unwrap().parse::().unwrap(), 299 | }) 300 | } 301 | 302 | /// Contains statistics about the current usage of memory and swap (together, not seperately) by 303 | /// the control group's tasks. 304 | #[derive(Debug)] 305 | pub struct MemSwap { 306 | /// How many times the limit has been hit. 307 | pub fail_cnt: u64, 308 | /// Memory and swap usage limit in bytes. 309 | pub limit_in_bytes: u64, 310 | /// Current usage of memory and swap in bytes. 311 | pub usage_in_bytes: u64, 312 | /// The maximum observed usage of memory and swap in bytes. 313 | pub max_usage_in_bytes: u64, 314 | } 315 | 316 | /// State of and statistics gathered by the kernel about the memory usage of the control group's 317 | /// tasks. 318 | #[derive(Debug)] 319 | pub struct Memory { 320 | /// How many times the limit has been hit. 321 | pub fail_cnt: u64, 322 | /// The limit in bytes of the memory usage of the control group's tasks. 323 | pub limit_in_bytes: u64, 324 | /// The current usage of memory by the control group's tasks. 325 | pub usage_in_bytes: u64, 326 | /// The maximum observed usage of memory by the control group's tasks. 327 | pub max_usage_in_bytes: u64, 328 | /// Whether moving charges at immigrate is allowed. 329 | pub move_charge_at_immigrate: u64, 330 | /// Contains various statistics about the NUMA locality of the control group's tasks. 331 | /// 332 | /// The format of this field (as lifted from the kernel sources): 333 | /// ```text 334 | /// total= N0= N1= ... 335 | /// file= N0= N1= ... 336 | /// anon= N0= N1= ... 337 | /// unevictable= N0= N1= ... 338 | /// hierarchical_= N0= N1= ... 339 | /// ``` 340 | pub numa_stat: NumaStat, 341 | /// Various statistics and control information about the Out Of Memory killer. 342 | pub oom_control: OomControl, 343 | /// Allows setting a limit to memory usage which is enforced when the system (note, _not_ the 344 | /// control group) detects memory pressure. 345 | pub soft_limit_in_bytes: u64, 346 | /// Contains a wide array of statistics about the memory usage of the tasks in the control 347 | /// group. 348 | pub stat: MemoryStat, 349 | /// Set the tendency of the kernel to swap out parts of the address space consumed by the 350 | /// control group's tasks. 351 | /// 352 | /// Note that setting this to zero does *not* prevent swapping, use `mlock(2)` for that 353 | /// purpose. 354 | pub swappiness: u64, 355 | /// If set, then under OOM conditions, the kernel will try to reclaim memory from the children 356 | /// of the offending process too. By default, this is not allowed. 357 | pub use_hierarchy: u64, 358 | } 359 | 360 | /// The current state of and gathered statistics about the kernel's memory usage for TCP-related 361 | /// data structures. 362 | #[derive(Debug)] 363 | pub struct Tcp { 364 | /// How many times the limit has been hit. 365 | pub fail_cnt: u64, 366 | /// The limit in bytes of the memory usage of the kernel's TCP buffers by control group's 367 | /// tasks. 368 | pub limit_in_bytes: u64, 369 | /// The current memory used by the kernel's TCP buffers related to these tasks. 370 | pub usage_in_bytes: u64, 371 | /// The observed maximum usage of memory by the kernel's TCP buffers (that originated from 372 | /// these tasks). 373 | pub max_usage_in_bytes: u64, 374 | } 375 | 376 | /// Gathered statistics and the current state of limitation of the kernel's memory usage. Note that 377 | /// this is per-cgroup, so the kernel can of course use more memory, but it will fail operations by 378 | /// these tasks if it would think that the limits here would be violated. It's important to note 379 | /// that interrupts in particular might not be able to enforce these limits. 380 | #[derive(Debug)] 381 | pub struct Kmem { 382 | /// How many times the limit has been hit. 383 | pub fail_cnt: u64, 384 | /// The limit in bytes of the kernel memory used by the control group's tasks. 385 | pub limit_in_bytes: u64, 386 | /// The current usage of kernel memory used by the control group's tasks, in bytes. 387 | pub usage_in_bytes: u64, 388 | /// The maximum observed usage of kernel memory used by the control group's tasks, in bytes. 389 | pub max_usage_in_bytes: u64, 390 | /// Contains information about the memory usage of the kernel's caches, per control group. 391 | pub slabinfo: String, 392 | } 393 | 394 | impl ControllerInternal for MemController { 395 | fn control_type(&self) -> Controllers { 396 | Controllers::Mem 397 | } 398 | fn get_path(&self) -> &PathBuf { 399 | &self.path 400 | } 401 | fn get_path_mut(&mut self) -> &mut PathBuf { 402 | &mut self.path 403 | } 404 | fn get_base(&self) -> &PathBuf { 405 | &self.base 406 | } 407 | 408 | fn apply(&self, res: &Resources) -> Result<()> { 409 | // get the resources that apply to this controller 410 | let memres: &MemoryResources = &res.memory; 411 | 412 | if memres.update_values { 413 | let _ = self.set_limit(memres.memory_hard_limit); 414 | let _ = self.set_soft_limit(memres.memory_soft_limit); 415 | let _ = self.set_kmem_limit(memres.kernel_memory_limit); 416 | let _ = self.set_memswap_limit(memres.memory_swap_limit); 417 | let _ = self.set_tcp_limit(memres.kernel_tcp_memory_limit); 418 | let _ = self.set_swappiness(memres.swappiness); 419 | } 420 | 421 | Ok(()) 422 | } 423 | } 424 | 425 | impl MemController { 426 | /// Contructs a new `MemController` with `oroot` serving as the root of the control group. 427 | pub fn new(oroot: PathBuf) -> Self { 428 | let mut root = oroot; 429 | root.push(Self::controller_type().to_string()); 430 | Self { 431 | base: root.clone(), 432 | path: root, 433 | } 434 | } 435 | 436 | /// Gathers overall statistics (and the current state of) about the memory usage of the control 437 | /// group's tasks. 438 | /// 439 | /// See the individual fields for more explanation, and as always, remember to consult the 440 | /// kernel Documentation and/or sources. 441 | pub fn memory_stat(&self) -> Memory { 442 | Memory { 443 | fail_cnt: self 444 | .open_path("memory.failcnt", false) 445 | .and_then(read_u64_from) 446 | .unwrap_or(0), 447 | limit_in_bytes: self 448 | .open_path("memory.limit_in_bytes", false) 449 | .and_then(read_u64_from) 450 | .unwrap_or(0), 451 | usage_in_bytes: self 452 | .open_path("memory.usage_in_bytes", false) 453 | .and_then(read_u64_from) 454 | .unwrap_or(0), 455 | max_usage_in_bytes: self 456 | .open_path("memory.max_usage_in_bytes", false) 457 | .and_then(read_u64_from) 458 | .unwrap_or(0), 459 | move_charge_at_immigrate: self 460 | .open_path("memory.move_charge_at_immigrate", false) 461 | .and_then(read_u64_from) 462 | .unwrap_or(0), 463 | numa_stat: self 464 | .open_path("memory.numa_stat", false) 465 | .and_then(read_string_from) 466 | .and_then(parse_numa_stat) 467 | .unwrap_or(NumaStat::default()), 468 | oom_control: self 469 | .open_path("memory.oom_control", false) 470 | .and_then(read_string_from) 471 | .and_then(parse_oom_control) 472 | .unwrap_or(OomControl::default()), 473 | soft_limit_in_bytes: self 474 | .open_path("memory.soft_limit_in_bytes", false) 475 | .and_then(read_u64_from) 476 | .unwrap_or(0), 477 | stat: self 478 | .open_path("memory.stat", false) 479 | .and_then(read_string_from) 480 | .and_then(parse_memory_stat) 481 | .unwrap_or(MemoryStat::default()), 482 | swappiness: self 483 | .open_path("memory.swappiness", false) 484 | .and_then(read_u64_from) 485 | .unwrap_or(0), 486 | use_hierarchy: self 487 | .open_path("memory.use_hierarchy", false) 488 | .and_then(read_u64_from) 489 | .unwrap_or(0), 490 | } 491 | } 492 | 493 | /// Gathers information about the kernel memory usage of the control group's tasks. 494 | pub fn kmem_stat(&self) -> Kmem { 495 | Kmem { 496 | fail_cnt: self 497 | .open_path("memory.kmem.failcnt", false) 498 | .and_then(read_u64_from) 499 | .unwrap_or(0), 500 | limit_in_bytes: self 501 | .open_path("memory.kmem.limit_in_bytes", false) 502 | .and_then(read_u64_from) 503 | .unwrap_or(0), 504 | usage_in_bytes: self 505 | .open_path("memory.kmem.usage_in_bytes", false) 506 | .and_then(read_u64_from) 507 | .unwrap_or(0), 508 | max_usage_in_bytes: self 509 | .open_path("memory.kmem.max_usage_in_bytes", false) 510 | .and_then(read_u64_from) 511 | .unwrap_or(0), 512 | slabinfo: self 513 | .open_path("memory.kmem.slabinfo", false) 514 | .and_then(read_string_from) 515 | .unwrap_or("".to_string()), 516 | } 517 | } 518 | 519 | /// Gathers information about the control group's kernel memory usage where said memory is 520 | /// TCP-related. 521 | pub fn kmem_tcp_stat(&self) -> Tcp { 522 | Tcp { 523 | fail_cnt: self 524 | .open_path("memory.kmem.tcp.failcnt", false) 525 | .and_then(read_u64_from) 526 | .unwrap_or(0), 527 | limit_in_bytes: self 528 | .open_path("memory.kmem.tcp.limit_in_bytes", false) 529 | .and_then(read_u64_from) 530 | .unwrap_or(0), 531 | usage_in_bytes: self 532 | .open_path("memory.kmem.tcp.usage_in_bytes", false) 533 | .and_then(read_u64_from) 534 | .unwrap_or(0), 535 | max_usage_in_bytes: self 536 | .open_path("memory.kmem.tcp.max_usage_in_bytes", false) 537 | .and_then(read_u64_from) 538 | .unwrap_or(0), 539 | } 540 | } 541 | 542 | /// Gathers information about the memory usage of the control group including the swap usage 543 | /// (if any). 544 | pub fn memswap(&self) -> MemSwap { 545 | MemSwap { 546 | fail_cnt: self 547 | .open_path("memory.memsw.failcnt", false) 548 | .and_then(read_u64_from) 549 | .unwrap_or(0), 550 | limit_in_bytes: self 551 | .open_path("memory.memsw.limit_in_bytes", false) 552 | .and_then(read_u64_from) 553 | .unwrap_or(0), 554 | usage_in_bytes: self 555 | .open_path("memory.memsw.usage_in_bytes", false) 556 | .and_then(read_u64_from) 557 | .unwrap_or(0), 558 | max_usage_in_bytes: self 559 | .open_path("memory.memsw.max_usage_in_bytes", false) 560 | .and_then(read_u64_from) 561 | .unwrap_or(0), 562 | } 563 | } 564 | 565 | /// Reset the fail counter 566 | pub fn reset_fail_count(&self) -> Result<()> { 567 | self.open_path("memory.failcnt", true) 568 | .and_then(|mut file| { 569 | file.write_all("0".to_string().as_ref()) 570 | .map_err(|e| Error::with_cause(WriteFailed, e)) 571 | }) 572 | } 573 | 574 | /// Reset the kernel memory fail counter 575 | pub fn reset_kmem_fail_count(&self) -> Result<()> { 576 | self.open_path("memory.kmem.failcnt", true) 577 | .and_then(|mut file| { 578 | file.write_all("0".to_string().as_ref()) 579 | .map_err(|e| Error::with_cause(WriteFailed, e)) 580 | }) 581 | } 582 | 583 | /// Reset the TCP related fail counter 584 | pub fn reset_tcp_fail_count(&self) -> Result<()> { 585 | self.open_path("memory.kmem.tcp.failcnt", true) 586 | .and_then(|mut file| { 587 | file.write_all("0".to_string().as_ref()) 588 | .map_err(|e| Error::with_cause(WriteFailed, e)) 589 | }) 590 | } 591 | 592 | /// Reset the memory+swap fail counter 593 | pub fn reset_memswap_fail_count(&self) -> Result<()> { 594 | self.open_path("memory.memsw.failcnt", true) 595 | .and_then(|mut file| { 596 | file.write_all("0".to_string().as_ref()) 597 | .map_err(|e| Error::with_cause(WriteFailed, e)) 598 | }) 599 | } 600 | 601 | /// Set the memory usage limit of the control group, in bytes. 602 | pub fn set_limit(&self, limit: u64) -> Result<()> { 603 | self.open_path("memory.limit_in_bytes", true) 604 | .and_then(|mut file| { 605 | file.write_all(limit.to_string().as_ref()) 606 | .map_err(|e| Error::with_cause(WriteFailed, e)) 607 | }) 608 | } 609 | 610 | /// Set the kernel memory limit of the control group, in bytes. 611 | pub fn set_kmem_limit(&self, limit: u64) -> Result<()> { 612 | self.open_path("memory.kmem.limit_in_bytes", true) 613 | .and_then(|mut file| { 614 | file.write_all(limit.to_string().as_ref()) 615 | .map_err(|e| Error::with_cause(WriteFailed, e)) 616 | }) 617 | } 618 | 619 | /// Set the memory+swap limit of the control group, in bytes. 620 | pub fn set_memswap_limit(&self, limit: u64) -> Result<()> { 621 | self.open_path("memory.memsw.limit_in_bytes", true) 622 | .and_then(|mut file| { 623 | file.write_all(limit.to_string().as_ref()) 624 | .map_err(|e| Error::with_cause(WriteFailed, e)) 625 | }) 626 | } 627 | 628 | /// Set how much kernel memory can be used for TCP-related buffers by the control group. 629 | pub fn set_tcp_limit(&self, limit: u64) -> Result<()> { 630 | self.open_path("memory.kmem.tcp.limit_in_bytes", true) 631 | .and_then(|mut file| { 632 | file.write_all(limit.to_string().as_ref()) 633 | .map_err(|e| Error::with_cause(WriteFailed, e)) 634 | }) 635 | } 636 | 637 | /// Set the soft limit of the control group, in bytes. 638 | /// 639 | /// This limit is enforced when the system is nearing OOM conditions. Contrast this with the 640 | /// hard limit, which is _always_ enforced. 641 | pub fn set_soft_limit(&self, limit: u64) -> Result<()> { 642 | self.open_path("memory.soft_limit_in_bytes", true) 643 | .and_then(|mut file| { 644 | file.write_all(limit.to_string().as_ref()) 645 | .map_err(|e| Error::with_cause(WriteFailed, e)) 646 | }) 647 | } 648 | 649 | /// Set how likely the kernel is to swap out parts of the address space used by the control 650 | /// group. 651 | /// 652 | /// Note that a value of zero does not imply that the process will not be swapped out. 653 | pub fn set_swappiness(&self, swp: u64) -> Result<()> { 654 | self.open_path("memory.swappiness", true) 655 | .and_then(|mut file| { 656 | file.write_all(swp.to_string().as_ref()) 657 | .map_err(|e| Error::with_cause(WriteFailed, e)) 658 | }) 659 | } 660 | } 661 | 662 | impl ControllIdentifier for MemController { 663 | fn controller_type() -> Controllers { 664 | Controllers::Mem 665 | } 666 | } 667 | 668 | impl<'a> From<&'a Subsystem> for &'a MemController { 669 | fn from(sub: &'a Subsystem) -> &'a MemController { 670 | unsafe { 671 | match sub { 672 | Subsystem::Mem(c) => c, 673 | _ => { 674 | assert_eq!(1, 0); 675 | ::std::mem::uninitialized() 676 | } 677 | } 678 | } 679 | } 680 | } 681 | 682 | fn read_u64_from(mut file: File) -> Result { 683 | let mut string = String::new(); 684 | match file.read_to_string(&mut string) { 685 | Ok(_) => string.trim().parse().map_err(|e| Error::with_cause(ParseError, e)), 686 | Err(e) => Err(Error::with_cause(ReadFailed, e)), 687 | } 688 | } 689 | 690 | fn read_string_from(mut file: File) -> Result { 691 | let mut string = String::new(); 692 | match file.read_to_string(&mut string) { 693 | Ok(_) => Ok(string.trim().to_string()), 694 | Err(e) => Err(Error::with_cause(ReadFailed, e)), 695 | } 696 | } 697 | 698 | #[cfg(test)] 699 | mod tests { 700 | use crate::memory::{ 701 | parse_memory_stat, parse_numa_stat, parse_oom_control, MemoryStat, NumaStat, OomControl, 702 | }; 703 | 704 | static GOOD_VALUE: &str = "\ 705 | total=51189 N0=51189 N1=123 706 | file=50175 N0=50175 N1=123 707 | anon=1014 N0=1014 N1=123 708 | unevictable=0 N0=0 N1=123 709 | hierarchical_total=1628573 N0=1628573 N1=123 710 | hierarchical_file=858151 N0=858151 N1=123 711 | hierarchical_anon=770402 N0=770402 N1=123 712 | hierarchical_unevictable=20 N0=20 N1=123 713 | "; 714 | 715 | static GOOD_OOMCONTROL_VAL: &str = "\ 716 | oom_kill_disable 0 717 | under_oom 1 718 | oom_kill 1337 719 | "; 720 | 721 | static GOOD_MEMORYSTAT_VAL: &str = "\ 722 | cache 178880512 723 | rss 4206592 724 | rss_huge 0 725 | shmem 106496 726 | mapped_file 7491584 727 | dirty 114688 728 | writeback 49152 729 | swap 0 730 | pgpgin 213928 731 | pgpgout 169220 732 | pgfault 87064 733 | pgmajfault 202 734 | inactive_anon 0 735 | active_anon 4153344 736 | inactive_file 84779008 737 | active_file 94273536 738 | unevictable 0 739 | hierarchical_memory_limit 9223372036854771712 740 | hierarchical_memsw_limit 9223372036854771712 741 | total_cache 4200333312 742 | total_rss 2927677440 743 | total_rss_huge 0 744 | total_shmem 590061568 745 | total_mapped_file 1086164992 746 | total_dirty 1769472 747 | total_writeback 602112 748 | total_swap 0 749 | total_pgpgin 5267326291 750 | total_pgpgout 5265586647 751 | total_pgfault 9947902469 752 | total_pgmajfault 25132 753 | total_inactive_anon 585981952 754 | total_active_anon 2928996352 755 | total_inactive_file 1272135680 756 | total_active_file 2338816000 757 | total_unevictable 81920 758 | "; 759 | 760 | #[test] 761 | fn test_parse_numa_stat() { 762 | let ok = parse_numa_stat(GOOD_VALUE.to_string()).unwrap(); 763 | assert_eq!( 764 | ok, 765 | NumaStat { 766 | total_pages: 51189, 767 | total_pages_per_node: vec![51189, 123], 768 | file_pages: 50175, 769 | file_pages_per_node: vec![50175, 123], 770 | anon_pages: 1014, 771 | anon_pages_per_node: vec![1014, 123], 772 | unevictable_pages: 0, 773 | unevictable_pages_per_node: vec![0, 123], 774 | 775 | hierarchical_total_pages: 1628573, 776 | hierarchical_total_pages_per_node: vec![1628573, 123], 777 | hierarchical_file_pages: 858151, 778 | hierarchical_file_pages_per_node: vec![858151, 123], 779 | hierarchical_anon_pages: 770402, 780 | hierarchical_anon_pages_per_node: vec![770402, 123], 781 | hierarchical_unevictable_pages: 20, 782 | hierarchical_unevictable_pages_per_node: vec![20, 123], 783 | } 784 | ); 785 | } 786 | 787 | #[test] 788 | fn test_parse_oom_control() { 789 | let ok = parse_oom_control(GOOD_OOMCONTROL_VAL.to_string()).unwrap(); 790 | assert_eq!( 791 | ok, 792 | OomControl { 793 | oom_kill_disable: false, 794 | under_oom: true, 795 | oom_kill: 1337, 796 | } 797 | ); 798 | } 799 | 800 | #[test] 801 | fn test_parse_memory_stat() { 802 | let ok = parse_memory_stat(GOOD_MEMORYSTAT_VAL.to_string()).unwrap(); 803 | assert_eq!( 804 | ok, 805 | MemoryStat { 806 | cache: 178880512, 807 | rss: 4206592, 808 | rss_huge: 0, 809 | shmem: 106496, 810 | mapped_file: 7491584, 811 | dirty: 114688, 812 | writeback: 49152, 813 | swap: 0, 814 | pgpgin: 213928, 815 | pgpgout: 169220, 816 | pgfault: 87064, 817 | pgmajfault: 202, 818 | inactive_anon: 0, 819 | active_anon: 4153344, 820 | inactive_file: 84779008, 821 | active_file: 94273536, 822 | unevictable: 0, 823 | hierarchical_memory_limit: 9223372036854771712, 824 | hierarchical_memsw_limit: 9223372036854771712, 825 | total_cache: 4200333312, 826 | total_rss: 2927677440, 827 | total_rss_huge: 0, 828 | total_shmem: 590061568, 829 | total_mapped_file: 1086164992, 830 | total_dirty: 1769472, 831 | total_writeback: 602112, 832 | total_swap: 0, 833 | total_pgpgin: 5267326291, 834 | total_pgpgout: 5265586647, 835 | total_pgfault: 9947902469, 836 | total_pgmajfault: 25132, 837 | total_inactive_anon: 585981952, 838 | total_active_anon: 2928996352, 839 | total_inactive_file: 1272135680, 840 | total_active_file: 2338816000, 841 | total_unevictable: 81920, 842 | } 843 | ); 844 | } 845 | } 846 | -------------------------------------------------------------------------------- /src/net_cls.rs: -------------------------------------------------------------------------------- 1 | //! This module contains the implementation of the `net_cls` cgroup subsystem. 2 | //! 3 | //! See the Kernel's documentation for more information about this subsystem, found at: 4 | //! [Documentation/cgroup-v1/net_cls.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/net_cls.txt) 5 | use std::fs::File; 6 | use std::io::{Read, Write}; 7 | use std::path::PathBuf; 8 | 9 | use crate::error::*; 10 | use crate::error::ErrorKind::*; 11 | 12 | use crate::{ 13 | ControllIdentifier, ControllerInternal, Controllers, NetworkResources, Resources, 14 | Subsystem, 15 | }; 16 | 17 | /// A controller that allows controlling the `net_cls` subsystem of a Cgroup. 18 | /// 19 | /// In esssence, using the `net_cls` controller, one can attach a custom class to the network 20 | /// packets emitted by the control group's tasks. This can then later be used in iptables to have 21 | /// custom firewall rules, QoS, etc. 22 | #[derive(Debug, Clone)] 23 | pub struct NetClsController { 24 | base: PathBuf, 25 | path: PathBuf, 26 | } 27 | 28 | impl ControllerInternal for NetClsController { 29 | fn control_type(&self) -> Controllers { 30 | Controllers::NetCls 31 | } 32 | fn get_path(&self) -> &PathBuf { 33 | &self.path 34 | } 35 | fn get_path_mut(&mut self) -> &mut PathBuf { 36 | &mut self.path 37 | } 38 | fn get_base(&self) -> &PathBuf { 39 | &self.base 40 | } 41 | 42 | fn apply(&self, res: &Resources) -> Result<()> { 43 | // get the resources that apply to this controller 44 | let res: &NetworkResources = &res.network; 45 | 46 | if res.update_values { 47 | let _ = self.set_class(res.class_id); 48 | if self.get_class()? != res.class_id { 49 | return Err(Error::new(Other)); 50 | } 51 | } 52 | return 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 | ::std::mem::uninitialized() 70 | } 71 | } 72 | } 73 | } 74 | } 75 | 76 | fn read_u64_from(mut file: File) -> Result { 77 | let mut string = String::new(); 78 | match file.read_to_string(&mut string) { 79 | Ok(_) => string.trim().parse().map_err(|e| Error::with_cause(ParseError, e)), 80 | Err(e) => Err(Error::with_cause(ReadFailed, e)), 81 | } 82 | } 83 | 84 | impl NetClsController { 85 | /// Constructs a new `NetClsController` with `oroot` serving as the root of the control group. 86 | pub fn new(oroot: PathBuf) -> Self { 87 | let mut root = oroot; 88 | root.push(Self::controller_type().to_string()); 89 | Self { 90 | base: root.clone(), 91 | path: root, 92 | } 93 | } 94 | 95 | /// Set the network class id of the outgoing packets of the control group's tasks. 96 | pub fn set_class(&self, class: u64) -> Result<()> { 97 | self.open_path("net_cls.classid", true) 98 | .and_then(|mut file| { 99 | let s = format!("{:#08X}", class); 100 | file.write_all(s.as_ref()).map_err(|e| Error::with_cause(WriteFailed, e)) 101 | }) 102 | } 103 | 104 | /// Get the network class id of the outgoing packets of the control group's tasks. 105 | pub fn get_class(&self) -> Result { 106 | self.open_path("net_cls.classid", false) 107 | .and_then(|file| read_u64_from(file)) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/net_prio.rs: -------------------------------------------------------------------------------- 1 | //! This module contains the implementation of the `net_prio` cgroup subsystem. 2 | //! 3 | //! See the Kernel's documentation for more information about this subsystem, found at: 4 | //! [Documentation/cgroup-v1/net_prio.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/net_prio.txt) 5 | use std::collections::HashMap; 6 | use std::fs::File; 7 | use std::io::{BufRead, BufReader, Read, Write}; 8 | use std::path::PathBuf; 9 | 10 | use crate::error::*; 11 | use crate::error::ErrorKind::*; 12 | 13 | use crate::{ 14 | ControllIdentifier, ControllerInternal, Controllers, NetworkResources, Resources, 15 | Subsystem, 16 | }; 17 | 18 | /// A controller that allows controlling the `net_prio` subsystem of a Cgroup. 19 | /// 20 | /// In essence, using `net_prio` one can set the priority of the packets emitted from the control 21 | /// group's tasks. This can then be used to have QoS restrictions on certain control groups and 22 | /// thus, prioritizing certain tasks. 23 | #[derive(Debug, Clone)] 24 | pub struct NetPrioController { 25 | base: PathBuf, 26 | path: PathBuf, 27 | } 28 | 29 | impl ControllerInternal for NetPrioController { 30 | fn control_type(&self) -> Controllers { 31 | Controllers::NetPrio 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 | // get the resources that apply to this controller 45 | let res: &NetworkResources = &res.network; 46 | 47 | if res.update_values { 48 | for i in &res.priorities { 49 | let _ = self.set_if_prio(&i.name, i.priority); 50 | } 51 | } 52 | 53 | Ok(()) 54 | } 55 | } 56 | 57 | impl ControllIdentifier for NetPrioController { 58 | fn controller_type() -> Controllers { 59 | Controllers::NetPrio 60 | } 61 | } 62 | 63 | impl<'a> From<&'a Subsystem> for &'a NetPrioController { 64 | fn from(sub: &'a Subsystem) -> &'a NetPrioController { 65 | unsafe { 66 | match sub { 67 | Subsystem::NetPrio(c) => c, 68 | _ => { 69 | assert_eq!(1, 0); 70 | ::std::mem::uninitialized() 71 | } 72 | } 73 | } 74 | } 75 | } 76 | 77 | fn read_u64_from(mut file: File) -> Result { 78 | let mut string = String::new(); 79 | match file.read_to_string(&mut string) { 80 | Ok(_) => string.trim().parse().map_err(|e| Error::with_cause(ParseError, e)), 81 | Err(e) => Err(Error::with_cause(ReadFailed, e)), 82 | } 83 | } 84 | 85 | impl NetPrioController { 86 | /// Constructs a new `NetPrioController` with `oroot` serving as the root of the control group. 87 | pub fn new(oroot: PathBuf) -> Self { 88 | let mut root = oroot; 89 | root.push(Self::controller_type().to_string()); 90 | Self { 91 | base: root.clone(), 92 | path: root, 93 | } 94 | } 95 | 96 | /// Retrieves the current priority of the emitted packets. 97 | pub fn prio_idx(&self) -> u64 { 98 | self.open_path("net_prio.prioidx", false) 99 | .and_then(read_u64_from) 100 | .unwrap_or(0) 101 | } 102 | 103 | /// A map of priorities for each network interface. 104 | pub fn ifpriomap(&self) -> Result> { 105 | self.open_path("net_prio.ifpriomap", false) 106 | .and_then(|file| { 107 | let bf = BufReader::new(file); 108 | bf.lines().fold(Ok(HashMap::new()), |acc, line| { 109 | if acc.is_err() { 110 | acc 111 | } else { 112 | let mut acc = acc.unwrap(); 113 | let l = line.unwrap(); 114 | let mut sp = l.split_whitespace(); 115 | let ifname = sp.nth(0); 116 | let ifprio = sp.nth(1); 117 | if ifname.is_none() || ifprio.is_none() { 118 | Err(Error::new(ParseError)) 119 | } else { 120 | let ifname = ifname.unwrap(); 121 | let ifprio = ifprio.unwrap().trim().parse(); 122 | match ifprio { 123 | Err(e) => Err(Error::with_cause(ParseError, e)), 124 | Ok(_) => { 125 | acc.insert(ifname.to_string(), ifprio.unwrap()); 126 | Ok(acc) 127 | } 128 | } 129 | } 130 | } 131 | }) 132 | }) 133 | } 134 | 135 | /// Set the priority of the network traffic on `eif` to be `prio`. 136 | pub fn set_if_prio(&self, eif: &str, prio: u64) -> Result<()> { 137 | self.open_path("net_prio.ifpriomap", true) 138 | .and_then(|mut file| { 139 | file.write_all(format!("{} {}", eif, prio).as_ref()) 140 | .map_err(|e| Error::with_cause(WriteFailed, e)) 141 | }) 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/perf_event.rs: -------------------------------------------------------------------------------- 1 | //! This module contains the implementation of the `perf_event` cgroup subsystem. 2 | //! 3 | //! See the Kernel's documentation for more information about this subsystem, found at: 4 | //! [tools/perf/Documentation/perf-record.txt](https://raw.githubusercontent.com/torvalds/linux/master/tools/perf/Documentation/perf-record.txt) 5 | use std::path::PathBuf; 6 | 7 | use crate::error::*; 8 | 9 | use crate::{ControllIdentifier, ControllerInternal, Controllers, Resources, Subsystem}; 10 | 11 | /// A controller that allows controlling the `perf_event` subsystem of a Cgroup. 12 | /// 13 | /// In essence, when processes belong to the same `perf_event` controller, they can be monitored 14 | /// together using the `perf` performance monitoring and reporting tool. 15 | #[derive(Debug, Clone)] 16 | pub struct PerfEventController { 17 | base: PathBuf, 18 | path: PathBuf, 19 | } 20 | 21 | impl ControllerInternal for PerfEventController { 22 | fn control_type(&self) -> Controllers { 23 | Controllers::PerfEvent 24 | } 25 | fn get_path(&self) -> &PathBuf { 26 | &self.path 27 | } 28 | fn get_path_mut(&mut self) -> &mut PathBuf { 29 | &mut self.path 30 | } 31 | fn get_base(&self) -> &PathBuf { 32 | &self.base 33 | } 34 | 35 | fn apply(&self, _res: &Resources) -> Result<()> { 36 | Ok(()) 37 | } 38 | } 39 | 40 | impl ControllIdentifier for PerfEventController { 41 | fn controller_type() -> Controllers { 42 | Controllers::PerfEvent 43 | } 44 | } 45 | 46 | impl<'a> From<&'a Subsystem> for &'a PerfEventController { 47 | fn from(sub: &'a Subsystem) -> &'a PerfEventController { 48 | unsafe { 49 | match sub { 50 | Subsystem::PerfEvent(c) => c, 51 | _ => { 52 | assert_eq!(1, 0); 53 | ::std::mem::uninitialized() 54 | } 55 | } 56 | } 57 | } 58 | } 59 | 60 | impl PerfEventController { 61 | /// Constructs a new `PerfEventController` with `oroot` serving as the root of the control group. 62 | pub fn new(oroot: PathBuf) -> Self { 63 | let mut root = oroot; 64 | root.push(Self::controller_type().to_string()); 65 | Self { 66 | base: root.clone(), 67 | path: root, 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/pid.rs: -------------------------------------------------------------------------------- 1 | //! This module contains the implementation of the `pids` cgroup subsystem. 2 | //! 3 | //! See the Kernel's documentation for more information about this subsystem, found at: 4 | //! [Documentation/cgroups-v1/pids.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/pids.txt) 5 | use std::fs::File; 6 | use std::io::{Read, Write}; 7 | use std::path::PathBuf; 8 | 9 | use crate::error::*; 10 | use crate::error::ErrorKind::*; 11 | 12 | use crate::{ 13 | ControllIdentifier, ControllerInternal, Controllers, PidResources, Resources, Subsystem, 14 | }; 15 | 16 | /// A controller that allows controlling the `pids` subsystem of a Cgroup. 17 | #[derive(Debug, Clone)] 18 | pub struct PidController { 19 | base: PathBuf, 20 | path: PathBuf, 21 | } 22 | 23 | /// The values found in the `pids.max` file in a Cgroup's `pids` subsystem. 24 | #[derive(Eq, PartialEq, Copy, Clone, Debug)] 25 | pub enum PidMax { 26 | /// This value is returned when the text found `pids.max` is `"max"`. 27 | Max, 28 | /// When the value in `pids.max` is a numerical value, they are returned via this enum field. 29 | Value(i64), 30 | } 31 | 32 | impl Default for PidMax { 33 | /// By default, (as per the kernel) `pids.max` should contain `"max"`. 34 | fn default() -> Self { 35 | PidMax::Max 36 | } 37 | } 38 | 39 | impl ControllerInternal for PidController { 40 | fn control_type(&self) -> Controllers { 41 | Controllers::Pids 42 | } 43 | fn get_path(&self) -> &PathBuf { 44 | &self.path 45 | } 46 | fn get_path_mut(&mut self) -> &mut PathBuf { 47 | &mut self.path 48 | } 49 | fn get_base(&self) -> &PathBuf { 50 | &self.base 51 | } 52 | 53 | fn apply(&self, res: &Resources) -> Result<()> { 54 | // get the resources that apply to this controller 55 | let pidres: &PidResources = &res.pid; 56 | 57 | if pidres.update_values { 58 | // apply pid_max 59 | let _ = self.set_pid_max(pidres.maximum_number_of_processes); 60 | 61 | // now, verify 62 | if self.get_pid_max()? == pidres.maximum_number_of_processes { 63 | return Ok(()); 64 | } else { 65 | return Err(Error::new(Other)); 66 | } 67 | } 68 | 69 | Ok(()) 70 | } 71 | } 72 | 73 | // impl<'a> ControllIdentifier for &'a PidController { 74 | // fn controller_type() -> Controllers { 75 | // Controllers::Pids 76 | // } 77 | // } 78 | 79 | impl ControllIdentifier for PidController { 80 | fn controller_type() -> Controllers { 81 | Controllers::Pids 82 | } 83 | } 84 | 85 | impl<'a> From<&'a Subsystem> for &'a PidController { 86 | fn from(sub: &'a Subsystem) -> &'a PidController { 87 | unsafe { 88 | match sub { 89 | Subsystem::Pid(c) => c, 90 | _ => { 91 | assert_eq!(1, 0); 92 | ::std::mem::uninitialized() 93 | } 94 | } 95 | } 96 | } 97 | } 98 | 99 | fn read_u64_from(mut file: File) -> Result { 100 | let mut string = String::new(); 101 | match file.read_to_string(&mut string) { 102 | Ok(_) => string.trim().parse().map_err(|e| Error::with_cause(ParseError, e)), 103 | Err(e) => Err(Error::with_cause(ReadFailed, e)), 104 | } 105 | } 106 | 107 | impl PidController { 108 | /// Constructors a new `PidController` instance, with `oroot` serving as the controller's root 109 | /// directory. 110 | pub fn new(oroot: PathBuf) -> Self { 111 | let mut root = oroot; 112 | root.push(Self::controller_type().to_string()); 113 | Self { 114 | base: root.clone(), 115 | path: root, 116 | } 117 | } 118 | 119 | /// The number of times `fork` failed because the limit was hit. 120 | pub fn get_pid_events(&self) -> Result { 121 | self.open_path("pids.events", false).and_then(|mut file| { 122 | let mut string = String::new(); 123 | match file.read_to_string(&mut string) { 124 | Ok(_) => match string.split_whitespace().nth(1) { 125 | Some(elem) => match elem.parse() { 126 | Ok(val) => Ok(val), 127 | Err(e) => Err(Error::with_cause(ParseError, e)), 128 | }, 129 | None => Err(Error::new(ParseError)), 130 | }, 131 | Err(e) => Err(Error::with_cause(ReadFailed, e)), 132 | } 133 | }) 134 | } 135 | 136 | /// The number of processes currently. 137 | pub fn get_pid_current(&self) -> Result { 138 | self.open_path("pids.current", false) 139 | .and_then(read_u64_from) 140 | } 141 | 142 | /// The maximum number of processes that can exist at one time in the control group. 143 | pub fn get_pid_max(&self) -> Result { 144 | self.open_path("pids.max", false).and_then(|mut file| { 145 | let mut string = String::new(); 146 | let res = file.read_to_string(&mut string); 147 | match res { 148 | Ok(_) => if string.trim() == "max" { 149 | Ok(PidMax::Max) 150 | } else { 151 | match string.trim().parse() { 152 | Ok(val) => Ok(PidMax::Value(val)), 153 | Err(e) => Err(Error::with_cause(ParseError, e)), 154 | } 155 | }, 156 | Err(e) => Err(Error::with_cause(ReadFailed, e)), 157 | } 158 | }) 159 | } 160 | 161 | /// Set the maximum number of processes that can exist in this control group. 162 | /// 163 | /// Note that if `get_pid_current()` returns a higher number than what you 164 | /// are about to set (`max_pid`), then no processess will be killed. Additonally, attaching 165 | /// extra processes to a control group disregards the limit. 166 | pub fn set_pid_max(&self, max_pid: PidMax) -> Result<()> { 167 | self.open_path("pids.max", true).and_then(|mut file| { 168 | let string_to_write = match max_pid { 169 | PidMax::Max => "max".to_string(), 170 | PidMax::Value(num) => num.to_string(), 171 | }; 172 | match file.write_all(string_to_write.as_ref()) { 173 | Ok(_) => Ok(()), 174 | Err(e) => Err(Error::with_cause(WriteFailed, e)), 175 | } 176 | }) 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /src/rdma.rs: -------------------------------------------------------------------------------- 1 | //! This module contains the implementation of the `rdma` cgroup subsystem. 2 | //! 3 | //! See the Kernel's documentation for more information about this subsystem, found at: 4 | //! [Documentation/cgroup-v1/rdma.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/rdma.txt) 5 | use std::fs::File; 6 | use std::io::{Read, Write}; 7 | use std::path::PathBuf; 8 | 9 | use crate::error::*; 10 | use crate::error::ErrorKind::*; 11 | 12 | use crate::{ControllIdentifier, ControllerInternal, Controllers, Resources, Subsystem}; 13 | 14 | /// A controller that allows controlling the `rdma` subsystem of a Cgroup. 15 | /// 16 | /// In essence, using this controller one can limit the RDMA/IB specific resources that the tasks 17 | /// in the control group can use. 18 | #[derive(Debug, Clone)] 19 | pub struct RdmaController { 20 | base: PathBuf, 21 | path: PathBuf, 22 | } 23 | 24 | impl ControllerInternal for RdmaController { 25 | fn control_type(&self) -> Controllers { 26 | Controllers::Rdma 27 | } 28 | fn get_path(&self) -> &PathBuf { 29 | &self.path 30 | } 31 | fn get_path_mut(&mut self) -> &mut PathBuf { 32 | &mut self.path 33 | } 34 | fn get_base(&self) -> &PathBuf { 35 | &self.base 36 | } 37 | 38 | fn apply(&self, _res: &Resources) -> Result<()> { 39 | Ok(()) 40 | } 41 | } 42 | 43 | impl ControllIdentifier for RdmaController { 44 | fn controller_type() -> Controllers { 45 | Controllers::Rdma 46 | } 47 | } 48 | 49 | impl<'a> From<&'a Subsystem> for &'a RdmaController { 50 | fn from(sub: &'a Subsystem) -> &'a RdmaController { 51 | unsafe { 52 | match sub { 53 | Subsystem::Rdma(c) => c, 54 | _ => { 55 | assert_eq!(1, 0); 56 | ::std::mem::uninitialized() 57 | } 58 | } 59 | } 60 | } 61 | } 62 | 63 | fn read_string_from(mut file: File) -> Result { 64 | let mut string = String::new(); 65 | match file.read_to_string(&mut string) { 66 | Ok(_) => Ok(string.trim().to_string()), 67 | Err(e) => Err(Error::with_cause(ReadFailed, e)), 68 | } 69 | } 70 | 71 | impl RdmaController { 72 | /// Constructs a new `RdmaController` with `oroot` serving as the root of the control group. 73 | pub fn new(oroot: PathBuf) -> Self { 74 | let mut root = oroot; 75 | root.push(Self::controller_type().to_string()); 76 | Self { 77 | base: root.clone(), 78 | path: root, 79 | } 80 | } 81 | 82 | /// Returns the current usage of RDMA/IB specific resources. 83 | pub fn current(&self) -> Result { 84 | self.open_path("rdma.current", false) 85 | .and_then(read_string_from) 86 | } 87 | 88 | /// Set a maximum usage for each RDMA/IB resource. 89 | pub fn set_max(&self, max: &str) -> Result<()> { 90 | self.open_path("rdma.max", true).and_then(|mut file| { 91 | file.write_all(max.as_ref()) 92 | .map_err(|e| Error::with_cause(WriteFailed, e)) 93 | }) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /tests/builder.rs: -------------------------------------------------------------------------------- 1 | //! Some simple tests covering the builder pattern for control groups. 2 | use cgroups::*; 3 | use cgroups::cpu::*; 4 | use cgroups::devices::*; 5 | use cgroups::pid::*; 6 | use cgroups::memory::*; 7 | use cgroups::net_cls::*; 8 | use cgroups::hugetlb::*; 9 | use cgroups::blkio::*; 10 | use cgroups::cgroup_builder::*; 11 | 12 | #[test] 13 | pub fn test_cpu_res_build() { 14 | let v1 = crate::hierarchies::V1::new(); 15 | let cg: Cgroup = CgroupBuilder::new("test_cpu_res_build", &v1) 16 | .cpu() 17 | .shares(85) 18 | .done() 19 | .build(); 20 | 21 | { 22 | let cpu: &CpuController = cg.controller_of().unwrap(); 23 | assert!(cpu.shares().is_ok()); 24 | assert_eq!(cpu.shares().unwrap(), 85); 25 | } 26 | 27 | cg.delete(); 28 | } 29 | 30 | #[test] 31 | pub fn test_memory_res_build() { 32 | let v1 = crate::hierarchies::V1::new(); 33 | let cg: Cgroup = CgroupBuilder::new("test_memory_res_build", &v1) 34 | .memory() 35 | .kernel_memory_limit(128 * 1024 * 1024) 36 | .swappiness(70) 37 | .memory_hard_limit(1024 * 1024 * 1024) 38 | .done() 39 | .build(); 40 | 41 | { 42 | let c: &MemController = cg.controller_of().unwrap(); 43 | assert_eq!(c.kmem_stat().limit_in_bytes, 128 * 1024 * 1024); 44 | assert_eq!(c.memory_stat().swappiness, 70); 45 | assert_eq!(c.memory_stat().limit_in_bytes, 1024 * 1024 * 1024); 46 | } 47 | 48 | cg.delete(); 49 | } 50 | 51 | #[test] 52 | pub fn test_pid_res_build() { 53 | let v1 = crate::hierarchies::V1::new(); 54 | let cg: Cgroup = CgroupBuilder::new("test_pid_res_build", &v1) 55 | .pid() 56 | .maximum_number_of_processes(PidMax::Value(123)) 57 | .done() 58 | .build(); 59 | 60 | { 61 | let c: &PidController = cg.controller_of().unwrap(); 62 | assert!(c.get_pid_max().is_ok()); 63 | assert_eq!(c.get_pid_max().unwrap(), PidMax::Value(123)); 64 | } 65 | 66 | cg.delete(); 67 | } 68 | 69 | #[test] 70 | #[ignore] // ignore this test for now, not sure why my kernel doesn't like it 71 | pub fn test_devices_res_build() { 72 | let v1 = crate::hierarchies::V1::new(); 73 | let cg: Cgroup = CgroupBuilder::new("test_devices_res_build", &v1) 74 | .devices() 75 | .device(1, 6, DeviceType::Char, true, 76 | vec![DevicePermissions::Read]) 77 | .done() 78 | .build(); 79 | 80 | { 81 | let c: &DevicesController = cg.controller_of().unwrap(); 82 | assert!(c.allowed_devices().is_ok()); 83 | assert_eq!(c.allowed_devices().unwrap(), vec![ 84 | DeviceResource { 85 | allow: true, 86 | devtype: DeviceType::Char, 87 | major: 1, 88 | minor: 6, 89 | access: vec![DevicePermissions::Read], 90 | } 91 | ]); 92 | } 93 | cg.delete(); 94 | } 95 | 96 | #[test] 97 | pub fn test_network_res_build() { 98 | let v1 = crate::hierarchies::V1::new(); 99 | let cg: Cgroup = CgroupBuilder::new("test_network_res_build", &v1) 100 | .network() 101 | .class_id(1337) 102 | .done() 103 | .build(); 104 | 105 | { 106 | let c: &NetClsController = cg.controller_of().unwrap(); 107 | assert!(c.get_class().is_ok()); 108 | assert_eq!(c.get_class().unwrap(), 1337); 109 | } 110 | cg.delete(); 111 | } 112 | 113 | #[test] 114 | pub fn test_hugepages_res_build() { 115 | let v1 = crate::hierarchies::V1::new(); 116 | let cg: Cgroup = CgroupBuilder::new("test_hugepages_res_build", &v1) 117 | .hugepages() 118 | .limit("2MB".to_string(), 4 * 2 * 1024 * 1024) 119 | .done() 120 | .build(); 121 | 122 | { 123 | let c: &HugeTlbController = cg.controller_of().unwrap(); 124 | assert!(c.limit_in_bytes(&"2MB".to_string()).is_ok()); 125 | assert_eq!(c.limit_in_bytes(&"2MB".to_string()).unwrap(), 4 * 2 * 1024 * 1024); 126 | } 127 | cg.delete(); 128 | } 129 | 130 | #[test] 131 | pub fn test_blkio_res_build() { 132 | let v1 = crate::hierarchies::V1::new(); 133 | let cg: Cgroup = CgroupBuilder::new("test_blkio_res_build", &v1) 134 | .blkio() 135 | .weight(100) 136 | .done() 137 | .build(); 138 | 139 | { 140 | let c: &BlkIoController = cg.controller_of().unwrap(); 141 | assert_eq!(c.blkio().weight, 100); 142 | } 143 | cg.delete(); 144 | } 145 | -------------------------------------------------------------------------------- /tests/cgroup.rs: -------------------------------------------------------------------------------- 1 | //! Simple unit tests about the control groups system. 2 | use cgroups::{Cgroup, CgroupPid}; 3 | 4 | #[test] 5 | fn test_tasks_iterator() { 6 | let hier = cgroups::hierarchies::V1::new(); 7 | let pid = libc::pid_t::from(nix::unistd::getpid()) as u64; 8 | let cg = Cgroup::new(&hier, String::from("test_tasks_iterator")); 9 | { 10 | // Add a task to the control group. 11 | cg.add_task(CgroupPid::from(pid)); 12 | let mut tasks = cg.tasks().into_iter(); 13 | // Verify that the task is indeed in the control group 14 | assert_eq!(tasks.next(), Some(CgroupPid::from(pid))); 15 | assert_eq!(tasks.next(), None); 16 | 17 | // Now, try removing it. 18 | cg.remove_task(CgroupPid::from(pid)); 19 | tasks = cg.tasks().into_iter(); 20 | 21 | // Verify that it was indeed removed. 22 | assert_eq!(tasks.next(), None); 23 | } 24 | cg.delete(); 25 | } 26 | -------------------------------------------------------------------------------- /tests/cpuset.rs: -------------------------------------------------------------------------------- 1 | use cgroups::cpuset::CpuSetController; 2 | use cgroups::error::ErrorKind; 3 | use cgroups::Cgroup; 4 | 5 | #[test] 6 | fn test_cpuset_memory_pressure_root_cg() { 7 | let hier = cgroups::hierarchies::V1::new(); 8 | let cg = Cgroup::new(&hier, String::from("test_cpuset_memory_pressure_root_cg")); 9 | { 10 | let cpuset: &CpuSetController = cg.controller_of().unwrap(); 11 | 12 | // This is not a root control group, so it should fail via InvalidOperation. 13 | let res = cpuset.set_enable_memory_pressure(true); 14 | assert_eq!(res.unwrap_err().kind(), &ErrorKind::InvalidOperation); 15 | } 16 | cg.delete(); 17 | } 18 | -------------------------------------------------------------------------------- /tests/devices.rs: -------------------------------------------------------------------------------- 1 | //! Integration tests about the devices subsystem 2 | 3 | use cgroups::devices::{DevicePermissions, DeviceType, DevicesController}; 4 | use cgroups::{Cgroup, DeviceResource}; 5 | 6 | #[test] 7 | fn test_devices_parsing() { 8 | let hier = cgroups::hierarchies::V1::new(); 9 | let cg = Cgroup::new(&hier, String::from("test_devices_parsing")); 10 | { 11 | let devices: &DevicesController = cg.controller_of().unwrap(); 12 | 13 | // Deny access to all devices first 14 | devices.deny_device( 15 | DeviceType::All, 16 | -1, 17 | -1, 18 | &vec![ 19 | DevicePermissions::Read, 20 | DevicePermissions::Write, 21 | DevicePermissions::MkNod, 22 | ], 23 | ); 24 | // Acquire the list of allowed devices after we denied all 25 | let allowed_devices = devices.allowed_devices(); 26 | // Verify that there are no devices that we can access. 27 | assert!(allowed_devices.is_ok()); 28 | assert_eq!(allowed_devices.unwrap(), Vec::new()); 29 | 30 | // Now add mknod access to /dev/null device 31 | devices.allow_device(DeviceType::Char, 1, 3, &vec![DevicePermissions::MkNod]); 32 | let allowed_devices = devices.allowed_devices(); 33 | assert!(allowed_devices.is_ok()); 34 | let allowed_devices = allowed_devices.unwrap(); 35 | assert_eq!(allowed_devices.len(), 1); 36 | assert_eq!( 37 | allowed_devices[0], 38 | DeviceResource { 39 | allow: true, 40 | devtype: DeviceType::Char, 41 | major: 1, 42 | minor: 3, 43 | access: vec![DevicePermissions::MkNod], 44 | } 45 | ); 46 | 47 | // Now deny, this device explicitly. 48 | devices.deny_device(DeviceType::Char, 1, 3, &DevicePermissions::all()); 49 | // Finally, check that. 50 | let allowed_devices = devices.allowed_devices(); 51 | // Verify that there are no devices that we can access. 52 | assert!(allowed_devices.is_ok()); 53 | assert_eq!(allowed_devices.unwrap(), Vec::new()); 54 | } 55 | cg.delete(); 56 | } 57 | -------------------------------------------------------------------------------- /tests/pids.rs: -------------------------------------------------------------------------------- 1 | //! Integration tests about the pids subsystem 2 | use cgroups::pid::{PidController, PidMax}; 3 | use cgroups::Controller; 4 | use cgroups::{Cgroup, CgroupPid, PidResources, Resources}; 5 | 6 | use nix::sys::wait::{waitpid, WaitStatus}; 7 | use nix::unistd::{fork, ForkResult, Pid}; 8 | 9 | use libc::pid_t; 10 | 11 | use std::thread; 12 | 13 | #[test] 14 | fn create_and_delete_cgroup() { 15 | let hier = cgroups::hierarchies::V1::new(); 16 | let cg = Cgroup::new(&hier, String::from("create_and_delete_cgroup")); 17 | { 18 | let pidcontroller: &PidController = cg.controller_of().unwrap(); 19 | pidcontroller.set_pid_max(PidMax::Value(1337)); 20 | let max = pidcontroller.get_pid_max(); 21 | assert!(max.is_ok()); 22 | assert_eq!(max.unwrap(), PidMax::Value(1337)); 23 | } 24 | cg.delete(); 25 | } 26 | 27 | #[test] 28 | fn test_pids_current_is_zero() { 29 | let hier = cgroups::hierarchies::V1::new(); 30 | let cg = Cgroup::new(&hier, String::from("test_pids_current_is_zero")); 31 | { 32 | let pidcontroller: &PidController = cg.controller_of().unwrap(); 33 | let current = pidcontroller.get_pid_current(); 34 | assert_eq!(current.unwrap(), 0); 35 | } 36 | cg.delete(); 37 | } 38 | 39 | #[test] 40 | fn test_pids_events_is_zero() { 41 | let hier = cgroups::hierarchies::V1::new(); 42 | let cg = Cgroup::new(&hier, String::from("test_pids_events_is_zero")); 43 | { 44 | let pidcontroller: &PidController = cg.controller_of().unwrap(); 45 | let events = pidcontroller.get_pid_events(); 46 | assert!(events.is_ok()); 47 | assert_eq!(events.unwrap(), 0); 48 | } 49 | cg.delete(); 50 | } 51 | 52 | #[test] 53 | fn test_pid_events_is_not_zero() { 54 | let hier = cgroups::hierarchies::V1::new(); 55 | let cg = Cgroup::new(&hier, String::from("test_pid_events_is_not_zero")); 56 | { 57 | let pids: &PidController = cg.controller_of().unwrap(); 58 | let before = pids.get_pid_events(); 59 | let before = before.unwrap(); 60 | 61 | match fork() { 62 | Ok(ForkResult::Parent { child, .. }) => { 63 | // move the process into the control group 64 | pids.add_task(&(pid_t::from(child) as u64).into()); 65 | 66 | println!("added task to cg: {:?}", child); 67 | 68 | // Set limit to one 69 | pids.set_pid_max(PidMax::Value(1)); 70 | println!("err = {:?}", pids.get_pid_max()); 71 | 72 | // wait on the child 73 | let res = waitpid(child, None); 74 | if let Ok(WaitStatus::Exited(_, e)) = res { 75 | assert_eq!(e, 0i32); 76 | } else { 77 | panic!("found result: {:?}", res); 78 | } 79 | 80 | // Check pids.events 81 | let events = pids.get_pid_events(); 82 | assert!(events.is_ok()); 83 | assert_eq!(events.unwrap(), before + 1); 84 | } 85 | Ok(ForkResult::Child) => loop { 86 | let pids_max = pids.get_pid_max(); 87 | if pids_max.is_ok() && pids_max.unwrap() == PidMax::Value(1) { 88 | if let Err(_) = fork() { 89 | unsafe { libc::exit(0) }; 90 | } else { 91 | unsafe { libc::exit(1) }; 92 | } 93 | } 94 | }, 95 | Err(_) => panic!("failed to fork"), 96 | } 97 | } 98 | cg.delete(); 99 | } 100 | -------------------------------------------------------------------------------- /tests/resources.rs: -------------------------------------------------------------------------------- 1 | //! Integration test about setting resources using `apply()` 2 | use cgroups::pid::{PidController, PidMax}; 3 | use cgroups::{Cgroup, PidResources, Resources}; 4 | 5 | #[test] 6 | fn pid_resources() { 7 | let hier = cgroups::hierarchies::V1::new(); 8 | let cg = Cgroup::new(&hier, String::from("pid_resources")); 9 | { 10 | let res = Resources { 11 | pid: PidResources { 12 | update_values: true, 13 | maximum_number_of_processes: PidMax::Value(512), 14 | }, 15 | ..Default::default() 16 | }; 17 | cg.apply(&res); 18 | 19 | // verify 20 | let pidcontroller: &PidController = cg.controller_of().unwrap(); 21 | let pid_max = pidcontroller.get_pid_max(); 22 | assert_eq!(pid_max.is_ok(), true); 23 | assert_eq!(pid_max.unwrap(), PidMax::Value(512)); 24 | } 25 | cg.delete(); 26 | } 27 | -------------------------------------------------------------------------------- /tools/create_cgroup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | CONTROL_GROUPS=`cargo test -- --list 2>/dev/null | egrep 'test$' | egrep -v '^src' | cut -d':' -f1` 4 | 5 | echo This script will create a control group in every subsystem of the V1 hierarchy. 6 | 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. 7 | sudo -v 8 | 9 | for i in ${CONTROL_GROUPS} 10 | do sudo mkdir -p /sys/fs/cgroup/{blkio,cpu,cpuacct,cpuset,devices,freezer,hugetlb,memory,net_cls,net_prio,perf_event,pids}/$i/ 11 | 12 | done 13 | echo 14 | echo We will now set up permissions... 15 | echo 16 | 17 | for i in ${CONTROL_GROUPS} 18 | do sudo chown -R ${USER} /sys/fs/cgroup/{blkio,cpu,cpuacct,cpuset,devices,freezer,hugetlb,memory,net_cls,net_prio,perf_event,pids}/$i/ 19 | done 20 | -------------------------------------------------------------------------------- /tools/delete_cgroup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | CONTROL_GROUPS=`cargo test -- --list 2>/dev/null | egrep 'test$' | egrep -v '^src' | cut -d':' -f1` 4 | 5 | echo This script will delete the control groups created by the create_cgroup.sh shell script. 6 | echo 7 | echo It may spit out some errors, but that is fine. 8 | echo 9 | 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. 10 | sudo -v 11 | 12 | for i in ${CONTROL_GROUPS} 13 | do sudo rmdir /sys/fs/cgroup/{blkio,cpu,cpuacct,cpuset,devices,freezer,hugetlb,memory,net_cls,net_prio,perf_event,pids}/$i/ 14 | done 15 | --------------------------------------------------------------------------------