Compare commits

...

71 Commits

Author SHA1 Message Date
Peng Tao
3852d7c180 Merge pull request #8 from liubin/fix/7-add-sync-marker-to-error-cause
errors: add sync marker to error cause
2020-09-14 16:15:12 +08:00
Peng Tao
0aaf7dba0b Merge pull request #6 from liubin/fix/cfs_quota-to-i64
cpu: change cfs_quota from u64 to i64
2020-09-14 16:14:22 +08:00
bin liu
42685bbfe9 errors: add sync marker to error cause
Errors cause should be shared to let anyhow to wrap it.

Fixes: #7

Signed-off-by: bin liu <bin@hyper.sh>
2020-09-14 15:22:55 +08:00
bin liu
f8d653e987 cpu: change cfs_quota from u64 to i64
cfs_quota can be set to -1 to indicate no limit

Fixes: #5

Signed-off-by: bin liu <bin@hyper.sh>
2020-09-14 13:00:43 +08:00
Peng Tao
d20e6a5383 Merge pull request #4 from liubin/master
add cgroup v2 support
2020-09-09 20:44:28 +08:00
bin liu
be617190c7 hugetlb: update test to avoid depending on test env
Signed-off-by: bin liu <bin@hyper.sh>
2020-09-09 17:09:12 +08:00
bin liu
250ada183a cargo fmt
Signed-off-by: bin liu <bin@hyper.sh>
2020-09-09 16:42:55 +08:00
bin liu
ff6a0ea82a add SPDX-License-Identifier for modified files
Signed-off-by: bin liu <bin@hyper.sh>
2020-09-09 13:35:46 +08:00
bin liu
bcbf438823 Merge remote-tracking branch 'upstream/master' 2020-09-09 13:27:12 +08:00
bin liu
c3912223d0 use let Some() instead of is_some() to make codes clear
Signed-off-by: bin liu <bin@hyper.sh>
2020-09-09 13:26:18 +08:00
bin liu
5d51e50bec get relative paths at new/load func default.
Signed-off-by: bin liu <bin@hyper.sh>
2020-09-08 10:30:29 +08:00
Peng Tao
a1bc5868d6 Merge pull request #3 from liubin/fix/2
add SPDX-License-Identifier for all source
2020-09-08 09:16:47 +08:00
bin liu
86b245076c add SPDX-License-Identifier for all source
Add SPDX-License-Identifier for all existing codes.

Fixes: #2

Signed-off-by: bin liu <bin@hyper.sh>
2020-09-07 20:29:40 +08:00
bin liu
cd2c748a74 add systemd controller; add more tests; update blkio stat for v2
Signed-off-by: bin liu <bin@hyper.sh>
2020-09-07 14:18:41 +08:00
bin liu
c623dc3fba add basic v2 cpu/memory functions
Signed-off-by: bin liu <bin@hyper.sh>
2020-09-02 22:03:11 +08:00
bin liu
704db324ae fix: create all dirs
Signed-off-by: bin liu <bin@hyper.sh>
2020-09-01 00:01:28 +08:00
bin liu
9fe6cb58e4 change *limit* in memory from u64 to i64
Signed-off-by: bin liu <bin@hyper.sh>
2020-08-27 23:06:44 +08:00
bin liu
c702852fd7 add disable oom_control function
Signed-off-by: bin liu <bin@hyper.sh>
2020-08-25 11:35:01 +08:00
bin liu
9d70467327 add hugetlb functions
Signed-off-by: bin liu <bin@hyper.sh>
2020-08-25 10:58:56 +08:00
bin liu
6b1b26b1fe copy cpuset after created
Signed-off-by: bin liu <bin@hyper.sh>
2020-08-25 10:51:29 +08:00
bin liu
9ce206f6bc probe cgroup v1 mount path
Signed-off-by: bin liu <bin@hyper.sh>
2020-08-25 10:50:52 +08:00
Levente Kurusa
ed1e8162d5 rust2018: remove extern crate
Signed-off-by: Levente Kurusa <lkurusa@acm.org>
2018-12-23 16:57:41 +01:00
Levente Kurusa
932a6e770f Cargo: bump Rust version to 2018
Signed-off-by: Levente Kurusa <lkurusa@acm.org>
2018-12-23 16:32:00 +01:00
Levente Kurusa
01a3c22829 Transition to Rust 2018
Signed-off-by: Levente Kurusa <lkurusa@acm.org>
2018-12-23 16:31:05 +01:00
Levente Kurusa
b5e0706d6a (cargo-release) start next development iteration 0.1.1-alpha.0 2018-12-23 16:24:51 +01:00
Levente Kurusa
8a2fa92c3a Update README
Signed-off-by: Levente Kurusa <lkurusa@acm.org>
2018-11-20 15:11:22 +02:00
Sam Wilson
dd621eae4e Replace &String with &str 2018-10-30 21:52:31 +01:00
Sam Wilson
751dbc7244 Replace some &String with &Path 2018-10-30 21:51:50 +01:00
Sam Wilson
3413b7d847 Expose the path of controllers for chown 2018-10-30 21:51:50 +01:00
Levente Kurusa
3b9d4a8c2a builder: document blkio
Signed-off-by: Levente Kurusa <lkurusa@acm.org>
2018-10-30 21:47:21 +01:00
Levente Kurusa
e51d781baf builder: add some documentation
Signed-off-by: Levente Kurusa <lkurusa@acm.org>
2018-10-30 21:47:21 +01:00
Levente Kurusa
b4cc91f977 Builder pattern for control groups
Signed-off-by: Levente Kurusa <lkurusa@acm.org>
2018-10-30 21:47:18 +01:00
Levente Kurusa
f9ffbe2ba4 memory: allow the fail counters to be reset
Signed-off-by: Levente Kurusa <lkurusa@acm.org>
2018-10-11 11:34:27 +02:00
Levente Kurusa
b3738c2c9b memory: remove outdated TODOs
Signed-off-by: Levente Kurusa <lkurusa@acm.org>
2018-10-09 09:00:16 -04:00
Levente Kurusa
90ab756be8 blkio: parse {read,write}_{bps,iops}_device
Signed-off-by: Levente Kurusa <lkurusa@acm.org>
2018-10-08 17:11:01 -04:00
Levente Kurusa
e85754d943 blkio: parse sectors{,_recursive}
Signed-off-by: Levente Kurusa <lkurusa@acm.org>
2018-10-05 14:21:55 -04:00
Levente Kurusa
c08e4d3e5d blkio: parse leaf_weight_device 2018-10-04 12:51:21 -04:00
Sam Wilson
c63dad411b Hide most of the previously undocumented methods in a sealed trait 2018-10-12 09:49:52 +02:00
Sam Wilson
7826b798bd Implement ::std::error::Error 2018-10-12 09:49:52 +02:00
Sam Wilson
84ae587360 Replace println! calls with logging 2018-10-12 09:49:52 +02:00
Hidehito Yabuuchi
adc3323be4 Replace '/*' style comments with '//' style, which is recommended officially 2018-10-06 18:05:59 +02:00
Hidehito Yabuuchi
ffd4cd70e2 Capitalize static values in tests 2018-10-06 18:05:59 +02:00
Hidehito Yabuuchi
ae56cb02b9 Use mem::discriminant in impl PartialEq for CgroupError 2018-10-06 18:05:59 +02:00
Hidehito Yabuuchi
2149e1c0c4 Run rustfmt 2018-10-06 18:05:59 +02:00
Hidehito Yabuuchi
5660656e3a Use self short-hand 2018-10-01 11:56:12 +02:00
Levente Kurusa
af6ed48e39 blkio: use BlkIoData for weight_device, too
Signed-off-by: Levente Kurusa <lkurusa@acm.org>
2018-09-29 09:41:36 -04:00
Levente Kurusa
4b67af3eef blkio: rename BlkIoTime to BlkIoData
Signed-off-by: Levente Kurusa <lkurusa@acm.org>
2018-09-28 11:11:19 +01:00
Levente Kurusa
afe1519ed2 blkio: parse time/time_recursive
Signed-off-by: Levente Kurusa <lkurusa@acm.org>
2018-09-27 10:19:19 +01:00
Levente Kurusa
7fa4527c2a memory: parse memory.stat
Signed-off-by: Levente Kurusa <lkurusa@acm.org>
2018-09-16 17:53:53 +02:00
Levente Kurusa
d9bc157388 memory: parse oom_control field
Signed-off-by: Levente Kurusa <lkurusa@acm.org>
2018-09-16 16:49:04 +02:00
Levente Kurusa
9da8998cd4 memory: parse numa_stat field
Signed-off-by: Levente Kurusa <lkurusa@acm.org>
2018-09-16 16:05:29 +02:00
Levente Kurusa
07421b2aff blkio: parse a bunch of fields
Signed-off-by: Levente Kurusa <lkurusa@acm.org>
2018-09-16 14:43:09 +02:00
Levente Kurusa
196d3e4d45 cargo: set default test runner to be sudo
Signed-off-by: Levente Kurusa <lkurusa@acm.org>
2018-09-14 15:59:56 +02:00
Levente Kurusa
0e350463a1 controllers: verify that resource application went ok or not
Signed-off-by: Levente Kurusa <lkurusa@acm.org>
2018-09-14 15:59:29 +02:00
Levente Kurusa
af12452fca tests: cgroup: re-enable the test for moving the task
Signed-off-by: Levente Kurusa <lkurusa@acm.org>
2018-09-09 16:46:15 +02:00
Levente Kurusa
dbbcb86884 devices: correct some documentation
Signed-off-by: Levente Kurusa <lkurusa@acm.org>
2018-09-05 10:48:12 +02:00
Levente Kurusa
1c213caea5 cpuset: parse ranges like cpus & mems
Signed-off-by: Levente Kurusa <lkurusa@acm.org>
2018-09-05 10:47:53 +02:00
Levente Kurusa
be5db6ba50 cpuset: fail if trying to enable memory pressure on a non-root cg
Signed-off-by: Levente Kurusa <lkurusa@acm.org>
2018-09-04 10:45:32 +02:00
Levente Kurusa
19e2847e15 tests: fix broken tests
Fixes:  c82a94b
Signed-off-by: Levente Kurusa <lkurusa@acm.org>
2018-09-04 10:03:56 +02:00
Levente Kurusa
ede7201b73 devices: parse files in the devices subsystem
Also, a test!

Signed-off-by: Levente Kurusa <lkurusa@acm.org>
2018-09-04 10:02:54 +02:00
Levente Kurusa
363fa7bdbf Cargo: Up version to v0.0.2
Signed-off-by: Levente Kurusa <lkurusa@acm.org>
2018-09-03 11:09:37 +02:00
Levente Kurusa
c82a94b58e tree: add some simple error reporting
There's still plenty to wish for.

Signed-off-by: Levente Kurusa <lkurusa@acm.org>
2018-09-03 00:11:33 +02:00
Levente Kurusa
84bcf24183 README: Add Travis CI logo
Signed-off-by: Levente Kurusa <lkurusa@acm.org>
2018-09-02 15:43:48 +02:00
Levente Kurusa
69fa1816bb ci: add travis support
For now, do not run tests as they require sudo privileges. In the near
future, a plan will be devised to automate testing.

Signed-off-by: Levente Kurusa <lkurusa@acm.org>
2018-09-02 15:40:56 +02:00
Levente Kurusa
7c83b7b236 test: cgroup: test moving to the root cg
Signed-off-by: Levente Kurusa <lkurusa@acm.org>
2018-09-02 15:33:57 +02:00
Levente Kurusa
acb0c448b1 cgroup: make it possible to move a control group to root
Signed-off-by: Levente Kurusa <lkurusa@acm.org>
2018-09-02 15:33:38 +02:00
Levente Kurusa
17b6c2787a tests: pids: add a proper test
Signed-off-by: Levente Kurusa <lkurusa@acm.org>
2018-09-01 17:07:27 +02:00
Levente Kurusa
367ea556ca tests: move tests to tests/ directory and give the user cgroup perms
Signed-off-by: Levente Kurusa <lkurusa@acm.org>
2018-08-30 13:40:15 +02:00
Levente Kurusa
c8b029b154 tests: fix the first few tests
Signed-off-by: Levente Kurusa <lkurusa@acm.org>
2018-08-30 11:41:54 +02:00
Levente Kurusa
be58b53fc9 README: clarify license and add an example
Signed-off-by: Levente Kurusa <lkurusa@acm.org>
2018-08-30 09:30:14 +02:00
Levente Kurusa
2e60a1f634 Relicense under MIT OR Apache-2.0
Signed-off-by: Levente Kurusa <lkurusa@acm.org>
2018-08-30 00:04:53 +02:00
38 changed files with 5624 additions and 1103 deletions

2
.cargo/config Normal file
View File

@@ -0,0 +1,2 @@
[target.x86_64-unknown-linux-gnu]
runner = 'sudo -E'

11
.travis.yml Normal file
View File

@@ -0,0 +1,11 @@
language: rust
rust:
- stable
- beta
- nightly
matrix:
allow_failures:
- rust: nightly
fast_finish: true
script:
- cargo build --verbose --all

View File

@@ -4,8 +4,16 @@ description = "Native Rust crate for managing control groups on Linux"
repository = "https://github.com/levex/cgroups-rs"
keywords = ["linux", "cgroup", "containers", "isolation"]
categories = ["os", "api-bindings", "os::unix-apis"]
license = "BSD-2-Clause OR Apache-2.0"
version = "0.0.1"
authors = ["Levente Kurusa <lkurusa@acm.org>"]
license = "MIT OR Apache-2.0"
version = "0.1.1-alpha.0"
authors = ["Levente Kurusa <lkurusa@acm.org>", "Sam Wilson <tecywiz121@hotmail.com>"]
edition = "2018"
[dependencies]
log = "0.4"
regex = "1.1"
nix = "0.18.0"
libc = "0.2"
[dev-dependencies]
libc = "0.2.76"

23
LICENSE
View File

@@ -1,21 +1,6 @@
MIT License
This crate is licensed under either of
Copyright (c) 2018 Levente Kurusa
- "Apache License, Version 2.0, (See LICENSE-Apache-2.0 file); or
- "MIT license" (See LICENSE-MIT file),
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
at your option.

13
LICENSE-Apache-2.0 Normal file
View File

@@ -0,0 +1,13 @@
Copyright 2018 Levente Kurusa <lkurusa@acm.org>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

21
LICENSE-MIT Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Levente Kurusa
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,2 +1,56 @@
# cgroups-rs
# cgroups-rs ![Build](https://travis-ci.org/levex/cgroups-rs.svg?branch=master)
Native Rust library for managing control groups under Linux
Right now the crate only support the original, V1 hierarchy, however support
is planned for the Unified hierarchy.
# Examples
## Create a control group using the builder pattern
``` rust
// Acquire a handle for the V1 cgroup hierarchy.
let hier = ::hierarchies::V1::new();
// Use the builder pattern (see the documentation to create the control group)
//
// This creates a control group named "example" in the V1 hierarchy.
let cg: Cgroup = CgroupBuilder::new("example", &v1)
.cpu()
.shares(85)
.done()
.build();
// Now `cg` is a control group that gets 85% of the CPU time in relative to
// other control groups.
// Get a handle to the CPU controller.
let cpus: &CpuController = cg.controller_of().unwrap();
cpus.add_task(1234u64);
// [...]
// Finally, clean up and delete the control group.
cg.delete();
// Note that `Cgroup` does not implement `Drop` and therefore when the
// structure is dropped, the Cgroup will stay around. This is because, later
// you can then re-create the `Cgroup` using `load()`. We aren't too set on
// this behavior, so it might change in the feature. Rest assured, it will be a
// major version change.
```
# Disclaimer
This crate is licensed under:
- MIT License (see LICENSE-MIT); or
- Apache 2.0 License (see LICENSE-Apache-2.0),
at your option.
Please note that this crate is under heavy development, we will use sematic
versioning, but during the `0.0.*` phase, no guarantees are made about
backwards compatibility.
Regardless, check back often and thanks for taking a look!

File diff suppressed because it is too large Load Diff

View File

@@ -1,57 +1,144 @@
// Copyright (c) 2018 Levente Kurusa
// Copyright (c) 2020 Ant Group
//
// SPDX-License-Identifier: Apache-2.0 or MIT
//
//! This module handles cgroup operations. Start here!
use {CgroupPid, Resources, ControllIdentifier, Controller, Hierarchy, Subsystem};
use crate::error::ErrorKind::*;
use crate::error::*;
use crate::libc_rmdir;
use crate::{CgroupPid, ControllIdentifier, Controller, Hierarchy, Resources, Subsystem};
use std::collections::HashMap;
use std::convert::From;
use std::fs;
use std::path::{Path, PathBuf};
/// A control group is the central structure to this crate.
///
///
/// # What are control groups?
///
/// Lifting over from the Linux kernel sources:
/// Lifting over from the Linux kernel sources:
///
/// > Control Groups provide a mechanism for aggregating/partitioning sets of
/// > tasks, and all their future children, into hierarchical groups with
/// > specialized behaviour.
///
/// This crate is an attempt at providing a Rust-native way of managing these cgroups.
pub struct Cgroup {
pub struct Cgroup<'b> {
/// The list of subsystems that control this cgroup
subsystems: Vec<Subsystem>,
/// The hierarchy.
hier: Box<&'b dyn Hierarchy>,
path: String,
}
impl Cgroup {
impl<'b> Cgroup<'b> {
/// Create this control group.
fn create(self: &Self) {
for subsystem in &self.subsystems {
subsystem.to_controller().create();
fn create(&self) {
if self.hier.v2() {
create_v2_cgroup(self.hier.root().clone(), &self.path);
} else {
for subsystem in &self.subsystems {
subsystem.to_controller().create();
}
}
}
pub fn v2(&self) -> bool {
self.hier.v2()
}
/// Create a new control group in the hierarchy `hier`, with name `path`.
///
/// Returns a handle to the control group that can be used to manipulate it.
///
/// Note that if the handle goes out of scope and is dropped, the control group is _not_
/// destroyed.
pub fn new(hier: &Hierarchy, path: String) -> Cgroup {
let mut subsystems = hier.subsystems();
subsystems = subsystems.into_iter().map(|x| x.enter(&path)).collect::<Vec<_>>();
pub fn new<P: AsRef<Path>>(hier: Box<&'b dyn Hierarchy>, path: P) -> Cgroup<'b> {
let relative_paths = get_cgroups_relative_paths().unwrap();
Cgroup::new_with_relative_paths(hier, path, relative_paths)
}
let cg = Cgroup {
//name: path,
subsystems: subsystems,
};
/// Create a handle for a control group in the hierarchy `hier`, with name `path`.
///
/// Returns a handle to the control group (that possibly does not exist until `create()` has
/// been called on the cgroup.
///
/// Note that if the handle goes out of scope and is dropped, the control group is _not_
/// destroyed.
pub fn load<P: AsRef<Path>>(hier: Box<&'b dyn Hierarchy>, path: P) -> Cgroup<'b> {
let relative_paths = get_cgroups_relative_paths().unwrap();
Cgroup::load_with_relative_paths(hier, path, relative_paths)
}
/// Create a new control group in the hierarchy `hier`, with name `path`.
/// and relative paths from `/proc/self/cgroup`
///
/// Returns a handle to the control group that can be used to manipulate it.
///
/// Note that if the handle goes out of scope and is dropped, the control group is _not_
/// destroyed.
pub fn new_with_relative_paths<P: AsRef<Path>>(
hier: Box<&'b dyn Hierarchy>,
path: P,
relative_paths: HashMap<String, String>,
) -> Cgroup<'b> {
let cg = Cgroup::load_with_relative_paths(hier, path, relative_paths);
cg.create();
cg
}
/// Create a handle for a control group in the hierarchy `hier`, with name `path`,
/// and relative paths from `/proc/self/cgroup`
///
/// Returns a handle to the control group (that possibly does not exist until `create()` has
/// been called on the cgroup.
///
/// Note that if the handle goes out of scope and is dropped, the control group is _not_
/// destroyed.
pub fn load_with_relative_paths<P: AsRef<Path>>(
hier: Box<&'b dyn Hierarchy>,
path: P,
relative_paths: HashMap<String, String>,
) -> Cgroup<'b> {
let path = path.as_ref();
let mut subsystems = hier.subsystems();
if path.as_os_str() != "" {
subsystems = subsystems
.into_iter()
.map(|x| {
let cn = x.controller_name();
if relative_paths.contains_key(&cn) {
let rp = relative_paths.get(&cn).unwrap();
let valid_path = rp.trim_start_matches("/").to_string();
let mut p = PathBuf::from(valid_path);
p.push(path);
x.enter(p.as_ref())
} else {
x.enter(path)
}
})
.collect::<Vec<_>>();
}
let cg = Cgroup {
subsystems: subsystems,
hier: hier,
path: path.to_str().unwrap().to_string(),
};
cg
}
/// The list of subsystems that this control group supports.
pub fn subsystems(self: &Self) -> &Vec<Subsystem> {
pub fn subsystems(&self) -> &Vec<Subsystem> {
&self.subsystems
}
@@ -61,61 +148,190 @@ impl Cgroup {
/// system call will fail if there are any descendants. Thus, one should check whether it was
/// actually removed, and remove the descendants first if not. In the future, this behavior
/// will change.
pub fn delete(self: Self) {
self.subsystems.into_iter().for_each(|sub| {
match sub {
Subsystem::Pid(pidc) => pidc.delete(),
Subsystem::Mem(c) => c.delete(),
Subsystem::CpuSet(c) => c.delete(),
Subsystem::CpuAcct(c) => c.delete(),
Subsystem::Cpu(c) => c.delete(),
Subsystem::Devices(c) => c.delete(),
Subsystem::Freezer(c) => c.delete(),
Subsystem::NetCls(c) => c.delete(),
Subsystem::BlkIo(c) => c.delete(),
Subsystem::PerfEvent(c) => c.delete(),
Subsystem::NetPrio(c) => c.delete(),
Subsystem::HugeTlb(c) => c.delete(),
Subsystem::Rdma(c) => c.delete(),
pub fn delete(self) {
if self.v2() {
if self.path != "" {
let mut p = self.hier.root().clone();
p.push(self.path);
libc_rmdir(p.to_str().unwrap());
}
return;
}
self.subsystems.into_iter().for_each(|sub| match sub {
Subsystem::Pid(pidc) => pidc.delete(),
Subsystem::Mem(c) => c.delete(),
Subsystem::CpuSet(c) => c.delete(),
Subsystem::CpuAcct(c) => c.delete(),
Subsystem::Cpu(c) => c.delete(),
Subsystem::Devices(c) => c.delete(),
Subsystem::Freezer(c) => c.delete(),
Subsystem::NetCls(c) => c.delete(),
Subsystem::BlkIo(c) => c.delete(),
Subsystem::PerfEvent(c) => c.delete(),
Subsystem::NetPrio(c) => c.delete(),
Subsystem::HugeTlb(c) => c.delete(),
Subsystem::Rdma(c) => c.delete(),
Subsystem::Systemd(c) => c.delete(),
});
}
/// Apply a set of resource limits to the control group.
pub fn apply(self: &Self, res: &Resources) {
for subsystem in &self.subsystems {
subsystem.to_controller().apply(res);
}
pub fn apply(&self, res: &Resources) -> Result<()> {
self.subsystems
.iter()
.try_fold((), |_, e| e.to_controller().apply(res))
}
/// Retrieve a container based on type inference.
///
/// ## Example:
///
/// ```
/// ```text
/// let pids: &PidController = control_group.controller_of()
/// .expect("No pids controller attached!");
/// let cpu: &CpuController = control_group.controller_of()
/// .expect("No cpu controller attached!");
/// ```
pub fn controller_of<'a, T>(self: &'a Self) -> Option<&'a T>
where &'a T: From<&'a Subsystem>,
T: Controller + ControllIdentifier,
where
&'a T: From<&'a Subsystem>,
T: Controller + ControllIdentifier,
{
for i in &self.subsystems {
if i.to_controller().control_type() == T::controller_type() {
/*
* N.B.:
* https://play.rust-lang.org/?gist=978b2846bacebdaa00be62374f4f4334&version=stable&mode=debug&edition=2015
*/
// N.B.:
// https://play.rust-lang.org/?gist=978b2846bacebdaa00be62374f4f4334&version=stable&mode=debug&edition=2015
return Some(i.into());
}
}
None
}
/// Removes a task from the control group.
///
/// Note that this means that the task will be moved back to the root control group in the
/// hierarchy and any rules applied to that control group will _still_ apply to the task.
pub fn remove_task(&self, pid: CgroupPid) {
let _ = self.hier.root_control_group().add_task(pid);
}
/// Attach a task to the control group.
pub fn add_task(self: &Self, pid: CgroupPid) {
self.subsystems().iter().for_each(|sub| sub.to_controller().add_task(&pid));
pub fn add_task(&self, pid: CgroupPid) -> Result<()> {
if self.v2() {
let subsystems = self.subsystems();
if subsystems.len() > 0 {
let c = subsystems[0].to_controller();
c.add_task(&pid)
} else {
Ok(())
}
} else {
self.subsystems()
.iter()
.try_for_each(|sub| sub.to_controller().add_task(&pid))
}
}
/// Returns an Iterator that can be used to iterate over the tasks that are currently in the
/// control group.
pub fn tasks(&self) -> Vec<CgroupPid> {
// Collect the tasks from all subsystems
let mut v = if self.v2() {
let subsystems = self.subsystems();
if subsystems.len() > 0 {
let c = subsystems[0].to_controller();
c.tasks()
} else {
vec![]
}
} else {
self.subsystems()
.iter()
.map(|x| x.to_controller().tasks())
.fold(vec![], |mut acc, mut x| {
acc.append(&mut x);
acc
})
};
v.sort();
v.dedup();
v
}
}
pub const UNIFIED_MOUNTPOINT: &'static str = "/sys/fs/cgroup";
fn enable_controllers(controllers: &Vec<String>, path: &PathBuf) {
let mut f = path.clone();
f.push("cgroup.subtree_control");
for c in controllers {
let body = format!("+{}", c);
let _rest = fs::write(f.as_path(), body.as_bytes());
}
}
fn supported_controllers(p: &PathBuf) -> Vec<String> {
let p = format!("{}/{}", UNIFIED_MOUNTPOINT, "cgroup.controllers");
let ret = fs::read_to_string(p.as_str());
ret.unwrap_or(String::new())
.split(" ")
.map(|x| x.to_string())
.collect::<Vec<String>>()
}
fn create_v2_cgroup(root: PathBuf, path: &str) -> Result<()> {
// controler list ["memory", "cpu"]
let controllers = supported_controllers(&root);
let mut fp = root;
// enable for root
enable_controllers(&controllers, &fp);
// path: "a/b/c"
let elements = path.split("/").collect::<Vec<&str>>();
let last_index = elements.len() - 1;
for (i, ele) in elements.iter().enumerate() {
// ROOT/a
fp.push(ele);
// create dir, need not check if is a file or directory
if !fp.exists() {
match ::std::fs::create_dir(fp.clone()) {
Err(e) => return Err(Error::with_cause(ErrorKind::FsError, e)),
Ok(_) => {}
}
}
if i < last_index {
// enable controllers for substree
enable_controllers(&controllers, &fp);
}
}
Ok(())
}
pub fn get_cgroups_relative_paths() -> Result<HashMap<String, String>> {
let mut m = HashMap::new();
let content =
fs::read_to_string("/proc/self/cgroup").map_err(|e| Error::with_cause(ReadFailed, e))?;
for l in content.lines() {
let fl: Vec<&str> = l.split(':').collect();
if fl.len() != 3 {
continue;
}
let keys: Vec<&str> = fl[1].split(',').collect();
for key in &keys {
// this is a workaround, cgroup file are using `name=systemd`,
// but if file system the name is `systemd`
if *key == "name=systemd" {
m.insert("systemd".to_string(), fl[2].to_string());
} else {
m.insert(key.to_string(), fl[2].to_string());
}
}
}
Ok(m)
}

408
src/cgroup_builder.rs Normal file
View File

@@ -0,0 +1,408 @@
// Copyright (c) 2018 Levente Kurusa
// Copyright (c) 2020 Ant Group
//
// SPDX-License-Identifier: Apache-2.0 or MIT
//
//! This module allows the user to create a control group using the Builder pattern.
//! # Example
//!
//! The following example demonstrates how the control group builder looks like. The user
//! specifies the name of the control group (here: "hello") and the hierarchy it belongs to (here:
//! a V1 hierarchy). Next, the user selects a subsystem by calling functions like `memory()`,
//! `cpu()` and `devices()`. The user can then add restrictions and details via subsystem-specific
//! calls. To finalize a subsystem, the user may call `done()`. Finally, if the control group build
//! is done and all requirements/restrictions have been specified, the control group can be created
//! by a call to `build()`.
//!
//! ```rust,no_run
//! # use cgroups::*;
//! # use cgroups::devices::*;
//! # use cgroups::cgroup_builder::*;
//! let h = cgroups::hierarchies::auto();
//! let h = Box::new(&*h);
//! let cgroup: Cgroup = CgroupBuilder::new("hello", h)
//! .memory()
//! .kernel_memory_limit(1024 * 1024)
//! .memory_hard_limit(1024 * 1024)
//! .done()
//! .cpu()
//! .shares(100)
//! .done()
//! .devices()
//! .device(1000, 10, DeviceType::Block, true,
//! vec![DevicePermissions::Read,
//! DevicePermissions::Write,
//! DevicePermissions::MkNod])
//! .device(6, 1, DeviceType::Char, false, vec![])
//! .done()
//! .network()
//! .class_id(1337)
//! .priority("eth0".to_string(), 100)
//! .priority("wl0".to_string(), 200)
//! .done()
//! .hugepages()
//! .limit("2M".to_string(), 0)
//! .limit("4M".to_string(), 4 * 1024 * 1024 * 100)
//! .limit("2G".to_string(), 2 * 1024 * 1024 * 1024)
//! .done()
//! .blkio()
//! .weight(Some(123))
//! .leaf_weight(Some(99))
//! .weight_device(6, 1, Some(100), Some(55))
//! .weight_device(6, 1, Some(100), Some(55))
//! .throttle_iops()
//! .read(6, 1, 10)
//! .write(11, 1, 100)
//! .throttle_bps()
//! .read(6, 1, 10)
//! .write(11, 1, 100)
//! .done()
//! .build();
//! ```
use crate::error::*;
use crate::{
pid, BlkIoDeviceResource, BlkIoDeviceThrottleResource, Cgroup, DeviceResource, Hierarchy,
HugePageResource, MaxValue, NetworkPriority, Resources,
};
macro_rules! gen_setter {
($res:ident, $cont:ident, $func:ident, $name:ident, $ty:ty) => {
/// See the similarly named function in the respective controller.
pub fn $name(mut self, $name: $ty) -> Self {
self.cgroup.resources.$res.update_values = true;
self.cgroup.resources.$res.$name = $name;
self
}
};
}
/// A control group builder instance
pub struct CgroupBuilder<'a> {
name: String,
hierarchy: Box<&'a dyn Hierarchy>,
/// Internal, unsupported field: use the associated builders instead.
resources: Resources,
}
impl<'a> CgroupBuilder<'a> {
/// Start building a control group with the supplied hierarchy and name pair.
///
/// Note that this does not actually create the control group until `build()` is called.
pub fn new(name: &'a str, hierarchy: Box<&'a dyn Hierarchy>) -> CgroupBuilder<'a> {
CgroupBuilder {
name: name.to_owned(),
hierarchy: hierarchy,
resources: Resources::default(),
}
}
/// Builds the memory resources of the control group.
pub fn memory(self) -> MemoryResourceBuilder<'a> {
MemoryResourceBuilder { cgroup: self }
}
/// Builds the pid resources of the control group.
pub fn pid(self) -> PidResourceBuilder<'a> {
PidResourceBuilder { cgroup: self }
}
/// Builds the cpu resources of the control group.
pub fn cpu(self) -> CpuResourceBuilder<'a> {
CpuResourceBuilder { cgroup: self }
}
/// Builds the devices resources of the control group, disallowing or
/// allowing access to certain devices in the system.
pub fn devices(self) -> DeviceResourceBuilder<'a> {
DeviceResourceBuilder { cgroup: self }
}
/// Builds the network resources of the control group, setting class id, or
/// various priorities on networking interfaces.
pub fn network(self) -> NetworkResourceBuilder<'a> {
NetworkResourceBuilder { cgroup: self }
}
/// Builds the hugepage/hugetlb resources available to the control group.
pub fn hugepages(self) -> HugepagesResourceBuilder<'a> {
HugepagesResourceBuilder { cgroup: self }
}
/// Builds the block I/O resources available for the control group.
pub fn blkio(self) -> BlkIoResourcesBuilder<'a> {
BlkIoResourcesBuilder {
cgroup: self,
throttling_iops: false,
}
}
/// Finalize the control group, consuming the builder and creating the control group.
pub fn build(self) -> Cgroup<'a> {
let cg = Cgroup::new(self.hierarchy, self.name);
cg.apply(&self.resources);
cg
}
}
/// A builder that configures the memory controller of a control group.
pub struct MemoryResourceBuilder<'a> {
cgroup: CgroupBuilder<'a>,
}
impl<'a> MemoryResourceBuilder<'a> {
gen_setter!(
memory,
MemController,
set_kmem_limit,
kernel_memory_limit,
i64
);
gen_setter!(memory, MemController, set_limit, memory_hard_limit, i64);
gen_setter!(
memory,
MemController,
set_soft_limit,
memory_soft_limit,
i64
);
gen_setter!(
memory,
MemController,
set_tcp_limit,
kernel_tcp_memory_limit,
i64
);
gen_setter!(
memory,
MemController,
set_memswap_limit,
memory_swap_limit,
i64
);
gen_setter!(memory, MemController, set_swappiness, swappiness, u64);
/// Finish the construction of the memory resources of a control group.
pub fn done(self) -> CgroupBuilder<'a> {
self.cgroup
}
}
/// A builder that configures the pid controller of a control group.
pub struct PidResourceBuilder<'a> {
cgroup: CgroupBuilder<'a>,
}
impl<'a> PidResourceBuilder<'a> {
gen_setter!(
pid,
PidController,
set_pid_max,
maximum_number_of_processes,
MaxValue
);
/// Finish the construction of the pid resources of a control group.
pub fn done(self) -> CgroupBuilder<'a> {
self.cgroup
}
}
/// A builder that configures the cpuset & cpu controllers of a control group.
pub struct CpuResourceBuilder<'a> {
cgroup: CgroupBuilder<'a>,
}
impl<'a> CpuResourceBuilder<'a> {
// FIXME this should all changed to options.
gen_setter!(cpu, CpuSetController, set_cpus, cpus, Option<String>);
gen_setter!(cpu, CpuSetController, set_mems, mems, String);
gen_setter!(cpu, CpuController, set_shares, shares, u64);
gen_setter!(cpu, CpuController, set_cfs_quota, quota, i64);
gen_setter!(cpu, CpuController, set_cfs_period, period, u64);
gen_setter!(cpu, CpuController, set_rt_runtime, realtime_runtime, i64);
gen_setter!(cpu, CpuController, set_rt_period, realtime_period, u64);
/// Finish the construction of the cpu resources of a control group.
pub fn done(self) -> CgroupBuilder<'a> {
self.cgroup
}
}
/// A builder that configures the devices controller of a control group.
pub struct DeviceResourceBuilder<'a> {
cgroup: CgroupBuilder<'a>,
}
impl<'a> DeviceResourceBuilder<'a> {
/// Restrict (or allow) a device to the tasks inside the control group.
pub fn device(
mut self,
major: i64,
minor: i64,
devtype: crate::devices::DeviceType,
allow: bool,
access: Vec<crate::devices::DevicePermissions>,
) -> DeviceResourceBuilder<'a> {
self.cgroup.resources.devices.update_values = true;
self.cgroup.resources.devices.devices.push(DeviceResource {
major,
minor,
devtype,
allow,
access,
});
self
}
/// Finish the construction of the devices resources of a control group.
pub fn done(self) -> CgroupBuilder<'a> {
self.cgroup
}
}
/// A builder that configures the net_cls & net_prio controllers of a control group.
pub struct NetworkResourceBuilder<'a> {
cgroup: CgroupBuilder<'a>,
}
impl<'a> NetworkResourceBuilder<'a> {
gen_setter!(network, NetclsController, set_class, class_id, u64);
/// Set the priority of the tasks when operating on a networking device defined by `name` to be
/// `priority`.
pub fn priority(mut self, name: String, priority: u64) -> NetworkResourceBuilder<'a> {
self.cgroup.resources.network.update_values = true;
self.cgroup
.resources
.network
.priorities
.push(NetworkPriority { name, priority });
self
}
/// Finish the construction of the network resources of a control group.
pub fn done(self) -> CgroupBuilder<'a> {
self.cgroup
}
}
/// A builder that configures the hugepages controller of a control group.
pub struct HugepagesResourceBuilder<'a> {
cgroup: CgroupBuilder<'a>,
}
impl<'a> HugepagesResourceBuilder<'a> {
/// Limit the usage of certain hugepages (determined by `size`) to be at most `limit` bytes.
pub fn limit(mut self, size: String, limit: u64) -> HugepagesResourceBuilder<'a> {
self.cgroup.resources.hugepages.update_values = true;
self.cgroup
.resources
.hugepages
.limits
.push(HugePageResource { size, limit });
self
}
/// Finish the construction of the network resources of a control group.
pub fn done(self) -> CgroupBuilder<'a> {
self.cgroup
}
}
/// A builder that configures the blkio controller of a control group.
pub struct BlkIoResourcesBuilder<'a> {
cgroup: CgroupBuilder<'a>,
throttling_iops: bool,
}
impl<'a> BlkIoResourcesBuilder<'a> {
gen_setter!(blkio, BlkIoController, set_weight, weight, Option<u16>);
gen_setter!(
blkio,
BlkIoController,
set_leaf_weight,
leaf_weight,
Option<u16>
);
/// Set the weight of a certain device.
pub fn weight_device(
mut self,
major: u64,
minor: u64,
weight: Option<u16>,
leaf_weight: Option<u16>,
) -> BlkIoResourcesBuilder<'a> {
self.cgroup.resources.blkio.update_values = true;
self.cgroup
.resources
.blkio
.weight_device
.push(BlkIoDeviceResource {
major,
minor,
weight,
leaf_weight,
});
self
}
/// Start configuring the I/O operations per second metric.
pub fn throttle_iops(mut self) -> BlkIoResourcesBuilder<'a> {
self.throttling_iops = true;
self
}
/// Start configuring the bytes per second metric.
pub fn throttle_bps(mut self) -> BlkIoResourcesBuilder<'a> {
self.throttling_iops = false;
self
}
/// Limit the read rate of the current metric for a certain device.
pub fn read(mut self, major: u64, minor: u64, rate: u64) -> BlkIoResourcesBuilder<'a> {
self.cgroup.resources.blkio.update_values = true;
let throttle = BlkIoDeviceThrottleResource { major, minor, rate };
if self.throttling_iops {
self.cgroup
.resources
.blkio
.throttle_read_iops_device
.push(throttle);
} else {
self.cgroup
.resources
.blkio
.throttle_read_bps_device
.push(throttle);
}
self
}
/// Limit the write rate of the current metric for a certain device.
pub fn write(mut self, major: u64, minor: u64, rate: u64) -> BlkIoResourcesBuilder<'a> {
self.cgroup.resources.blkio.update_values = true;
let throttle = BlkIoDeviceThrottleResource { major, minor, rate };
if self.throttling_iops {
self.cgroup
.resources
.blkio
.throttle_write_iops_device
.push(throttle);
} else {
self.cgroup
.resources
.blkio
.throttle_write_bps_device
.push(throttle);
}
self
}
/// Finish the construction of the blkio resources of a control group.
pub fn done(self) -> CgroupBuilder<'a> {
self.cgroup
}
}

View File

@@ -1,22 +1,37 @@
// Copyright (c) 2018 Levente Kurusa
// Copyright (c) 2020 Ant Group
//
// SPDX-License-Identifier: Apache-2.0 or MIT
//
//! This module contains the implementation of the `cpu` cgroup subsystem.
//!
//!
//! See the Kernel's documentation for more information about this subsystem, found at:
//! [Documentation/scheduler/sched-design-CFS.txt](https://www.kernel.org/doc/Documentation/scheduler/sched-design-CFS.txt)
//! paragraph 7 ("GROUP SCHEDULER EXTENSIONS TO CFS").
use std::path::PathBuf;
use std::fs::File;
use std::io::{Read, Write};
use std::path::PathBuf;
use {CpuResources, Controllers, Controller, Resources, ControllIdentifier, Subsystem};
use crate::error::ErrorKind::*;
use crate::error::*;
use crate::{parse_max_value, read_i64_from};
use crate::{
ControllIdentifier, ControllerInternal, Controllers, CpuResources, MaxValue, Resources,
Subsystem,
};
/// A controller that allows controlling the `cpu` subsystem of a Cgroup.
///
///
/// In essence, it allows gathering information about how much the tasks inside the control group
/// are using the CPU and creating rules that limit their usage. Note that this crate does not yet
/// support managing realtime tasks.
#[derive(Debug, Clone)]
pub struct CpuController{
pub struct CpuController {
base: PathBuf,
path: PathBuf,
v2: bool,
}
/// The current state of the control group and its processes.
@@ -28,23 +43,58 @@ pub struct Cpu {
pub stat: String,
}
impl Controller for CpuController {
fn control_type(self: &Self) -> Controllers { Controllers::Cpu}
fn get_path<'a>(self: &'a Self) -> &'a PathBuf { &self.path }
fn get_path_mut<'a>(self: &'a mut Self) -> &'a mut PathBuf { &mut self.path }
fn get_base<'a>(self: &'a Self) -> &'a PathBuf { &self.base }
/// The current state of the control group and its processes.
#[derive(Debug)]
struct CFSQuotaAndPeriod {
quota: MaxValue,
period: u64,
}
fn apply(self: &Self, res: &Resources) {
/* get the resources that apply to this controller */
impl ControllerInternal for CpuController {
fn control_type(&self) -> Controllers {
Controllers::Cpu
}
fn get_path(&self) -> &PathBuf {
&self.path
}
fn get_path_mut(&mut self) -> &mut PathBuf {
&mut self.path
}
fn get_base(&self) -> &PathBuf {
&self.base
}
fn is_v2(&self) -> bool {
self.v2
}
fn apply(&self, res: &Resources) -> Result<()> {
// get the resources that apply to this controller
let res: &CpuResources = &res.cpu;
if res.update_values {
/* apply pid_max */
self.set_shares(res.shares);
self.set_cfs_period(res.period);
self.set_cfs_quota(res.quota as u64);
/* TODO: rt properties (CONFIG_RT_GROUP_SCHED) are not yet supported */
let _ = self.set_shares(res.shares);
if self.shares()? != res.shares as u64 {
return Err(Error::new(ErrorKind::Other));
}
let _ = self.set_cfs_period(res.period);
if self.cfs_period()? != res.period as u64 {
return Err(Error::new(ErrorKind::Other));
}
let _ = self.set_cfs_quota(res.quota);
if self.cfs_quota()? != res.quota {
return Err(Error::new(ErrorKind::Other));
}
// TODO: rt properties (CONFIG_RT_GROUP_SCHED) are not yet supported
}
Ok(())
}
}
@@ -62,59 +112,216 @@ impl<'a> From<&'a Subsystem> for &'a CpuController {
_ => {
assert_eq!(1, 0);
::std::mem::uninitialized()
},
}
}
}
}
}
fn read_u64_from(mut file: File) -> Result<u64> {
let mut string = String::new();
match file.read_to_string(&mut string) {
Ok(_) => string
.trim()
.parse()
.map_err(|e| Error::with_cause(ParseError, e)),
Err(e) => Err(Error::with_cause(ReadFailed, e)),
}
}
impl CpuController {
/// Contructs a new `CpuController` with `oroot` serving as the root of the control group.
pub fn new(oroot: PathBuf) -> Self {
pub fn new(oroot: PathBuf, v2: bool) -> Self {
let mut root = oroot;
root.push(Self::controller_type().to_string());
if !v2 {
root.push(Self::controller_type().to_string());
}
Self {
base: root.clone(),
path: root,
v2: v2,
}
}
/// Returns CPU time statistics based on the processes in the control group.
pub fn cpu(self: &Self) -> Cpu {
pub fn cpu(&self) -> Cpu {
Cpu {
stat: self.open_path("cpu.stat", false).and_then(|mut file| {
let mut s = String::new();
let _ = file.read_to_string(&mut s);
Some(s)
}).unwrap_or("".to_string()),
stat: self
.open_path("cpu.stat", false)
.and_then(|mut file| {
let mut s = String::new();
let res = file.read_to_string(&mut s);
match res {
Ok(_) => Ok(s),
Err(e) => Err(Error::with_cause(ReadFailed, e)),
}
})
.unwrap_or("".to_string()),
}
}
/// Configures the CPU bandwidth (in relative relation to other control groups and this control
/// group's parent).
///
///
/// For example, setting control group `A`'s `shares` to `100`, and control group `B`'s
/// `shares` to `200` ensures that control group `B` receives twice as much as CPU bandwidth.
/// (Assuming both `A` and `B` are of the same parent)
pub fn set_shares(self: &Self, shares: u64) {
self.open_path("cpu.shares", true).and_then(|mut file| {
file.write_all(shares.to_string().as_ref()).ok()
});
pub fn set_shares(&self, shares: u64) -> Result<()> {
let mut file = "cpu.shares";
if self.v2 {
file = "cpu.weight";
}
// NOTE: .CpuShares is not used here. Conversion is the caller's responsibility.
self.open_path(file, true).and_then(|mut file| {
file.write_all(shares.to_string().as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
})
}
/// Retrieve the CPU bandwidth that this control group (relative to other control groups and
/// this control group's parent) can use.
pub fn shares(&self) -> Result<u64> {
let mut file = "cpu.shares";
if self.v2 {
file = "cpu.weight";
}
self.open_path(file, false).and_then(read_u64_from)
}
/// Specify a period (when using the CFS scheduler) of time in microseconds for how often this
/// control group's access to the CPU should be reallocated.
pub fn set_cfs_period(self: &Self, us: u64) {
self.open_path("cpu.cfs_period_us", true).and_then(|mut file| {
file.write_all(us.to_string().as_ref()).ok()
});
pub fn set_cfs_period(&self, us: u64) -> Result<()> {
if self.v2 {
return self.set_cfs_quota_and_period(None, Some(us));
}
self.open_path("cpu.cfs_period_us", true)
.and_then(|mut file| {
file.write_all(us.to_string().as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
})
}
/// Retrieve the period of time of how often this cgroup's access to the CPU should be
/// reallocated in microseconds.
pub fn cfs_period(&self) -> Result<u64> {
if self.v2 {
let current_value = self
.open_path("cpu.max", false)
.and_then(parse_cfs_quota_and_period)?;
return Ok(current_value.period);
}
self.open_path("cpu.cfs_period_us", false)
.and_then(read_u64_from)
}
/// Specify a quota (when using the CFS scheduler) of time in microseconds for which all tasks
/// in this control group can run during one period (see: `set_cfs_period()`).
pub fn set_cfs_quota(self: &Self, us: u64) {
self.open_path("cpu.cfs_quota_us", true).and_then(|mut file| {
file.write_all(us.to_string().as_ref()).ok()
});
pub fn set_cfs_quota(&self, us: i64) -> Result<()> {
if self.v2 {
return self.set_cfs_quota_and_period(Some(us), None);
}
self.open_path("cpu.cfs_quota_us", true)
.and_then(|mut file| {
file.write_all(us.to_string().as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
})
}
/// Retrieve the quota of time for which all tasks in this cgroup can run during one period, in
/// microseconds.
pub fn cfs_quota(&self) -> Result<i64> {
if self.v2 {
let current_value = self
.open_path("cpu.max", false)
.and_then(parse_cfs_quota_and_period)?;
return Ok(current_value.quota.to_i64());
}
self.open_path("cpu.cfs_quota_us", false)
.and_then(read_i64_from)
}
pub fn set_cfs_quota_and_period(&self, quota: Option<i64>, period: Option<u64>) -> Result<()> {
if !self.v2 {
if let Some(q) = quota {
self.set_cfs_quota(q)?;
}
if let Some(p) = period {
self.set_cfs_period(p)?;
}
return Ok(());
}
// https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html
// cpu.max
// A read-write two value file which exists on non-root cgroups. The default is “max 100000”.
// The maximum bandwidth limit. Its in the following format:
// $MAX $PERIOD
// which indicates that the group may consume upto $MAX in each $PERIOD duration.
// “max” for $MAX indicates no limit. If only one number is written, $MAX is updated.
let current_value = self
.open_path("cpu.max", false)
.and_then(parse_cfs_quota_and_period)?;
let new_quota = if let Some(q) = quota {
if q > 0 {
q.to_string()
} else {
"max".to_string()
}
} else {
current_value.quota.to_string()
};
let new_period = if let Some(p) = period {
p.to_string()
} else {
current_value.period.to_string()
};
let line = format!("{} {}", new_quota, new_period);
self.open_path("cpu.max", true).and_then(|mut file| {
file.write_all(line.as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
})
}
pub fn set_rt_runtime(&self, us: i64) -> Result<()> {
self.open_path("cpu.rt_runtime_us", true)
.and_then(|mut file| {
file.write_all(us.to_string().as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
})
}
pub fn set_rt_period_us(&self, us: u64) -> Result<()> {
self.open_path("cpu.rt_period_us", true)
.and_then(|mut file| {
file.write_all(us.to_string().as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
})
}
}
fn parse_cfs_quota_and_period(mut file: File) -> Result<CFSQuotaAndPeriod> {
let mut content = String::new();
file.read_to_string(&mut content)
.map_err(|e| Error::with_cause(ReadFailed, e))?;
let fields = content.trim().split(' ').collect::<Vec<&str>>();
if fields.len() != 2 {
return Err(Error::from_string(format!("invaild format: {}", content)));
}
let quota = parse_max_value(&fields[0].to_string())?;
let period = fields[1]
.parse::<u64>()
.map_err(|e| Error::with_cause(ParseError, e))?;
Ok(CFSQuotaAndPeriod {
quota: quota,
period: period,
})
}

View File

@@ -1,12 +1,20 @@
// Copyright (c) 2018 Levente Kurusa
//
// SPDX-License-Identifier: Apache-2.0 or MIT
//
//! This module contains the implementation of the `cpuacct` cgroup subsystem.
//!
//!
//! See the Kernel's documentation for more information about this subsystem, found at:
//! [Documentation/cgroup-v1/cpuacct.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/cpuacct.txt)
use std::path::PathBuf;
use std::io::{Read, Write};
use std::fs::File;
use std::io::{Read, Write};
use std::path::PathBuf;
use {Controllers, Resources, Subsystem, ControllIdentifier, Controller};
use crate::error::ErrorKind::*;
use crate::error::*;
use crate::{ControllIdentifier, ControllerInternal, Controllers, Resources, Subsystem};
/// A controller that allows controlling the `cpuacct` subsystem of a Cgroup.
///
@@ -28,7 +36,7 @@ pub struct CpuAcct {
/// time spent is `user` time or `system` time.
///
/// An example is as follows:
/// ```
/// ```text
/// cpu user system
/// 0 8348363768 0
/// 1 8324369100 0
@@ -49,13 +57,22 @@ pub struct CpuAcct {
pub usage_user: u64,
}
impl Controller for CpuAcctController {
fn control_type(self: &Self) -> Controllers { Controllers::CpuAcct }
fn get_path<'a>(self: &'a Self) -> &'a PathBuf { &self.path }
fn get_path_mut<'a>(self: &'a mut Self) -> &'a mut PathBuf { &mut self.path }
fn get_base<'a>(self: &'a Self) -> &'a PathBuf { &self.base }
impl ControllerInternal for CpuAcctController {
fn control_type(&self) -> Controllers {
Controllers::CpuAcct
}
fn get_path(&self) -> &PathBuf {
&self.path
}
fn get_path_mut(&mut self) -> &mut PathBuf {
&mut self.path
}
fn get_base(&self) -> &PathBuf {
&self.base
}
fn apply(self: &Self, _res: &Resources) {
fn apply(&self, _res: &Resources) -> Result<()> {
Ok(())
}
}
@@ -73,20 +90,33 @@ impl<'a> From<&'a Subsystem> for &'a CpuAcctController {
_ => {
assert_eq!(1, 0);
::std::mem::uninitialized()
},
}
}
}
}
}
fn read_u64_from(mut file: File) -> Option<u64> {
fn read_u64_from(mut file: File) -> Result<u64> {
let mut string = String::new();
let _ = file.read_to_string(&mut string);
string.trim().parse().ok()
let res = file.read_to_string(&mut string);
match res {
Ok(_) => match string.trim().parse() {
Ok(e) => Ok(e),
Err(e) => Err(Error::with_cause(ParseError, e)),
},
Err(e) => Err(Error::with_cause(ReadFailed, e)),
}
}
fn read_string_from(mut file: File) -> Result<String> {
let mut string = String::new();
match file.read_to_string(&mut string) {
Ok(_) => Ok(string.trim().to_string()),
Err(e) => Err(Error::with_cause(ReadFailed, e)),
}
}
impl CpuAcctController {
/// Contructs a new `CpuAcctController` with `oroot` serving as the root of the control group.
pub fn new(oroot: PathBuf) -> Self {
let mut root = oroot;
@@ -98,54 +128,48 @@ impl CpuAcctController {
}
/// Gathers the statistics that are available in the control group into a `CpuAcct` structure.
pub fn cpuacct(self: &Self) -> CpuAcct {
pub fn cpuacct(&self) -> CpuAcct {
CpuAcct {
stat: self.open_path("cpuacct.stat", false)
.and_then(|mut file| {
let mut string = String::new();
let _ = file.read_to_string(&mut string);
Some(string.trim().to_string())
}).unwrap_or("".to_string()),
usage: self.open_path("cpuacct.usage", false)
.and_then(|file| read_u64_from(file))
.unwrap_or(0),
usage_all: self.open_path("cpuacct.usage_all", false)
.and_then(|mut file| {
let mut string = String::new();
let _ = file.read_to_string(&mut string);
Some(string.trim().to_string())
}).unwrap_or("".to_string()),
usage_percpu: self.open_path("cpuacct.usage_percpu", false)
.and_then(|mut file| {
let mut string = String::new();
let _ = file.read_to_string(&mut string);
Some(string.trim().to_string())
}).unwrap_or("".to_string()),
usage_percpu_sys: self.open_path("cpuacct.usage_percpu_sys", false)
.and_then(|mut file| {
let mut string = String::new();
let _ = file.read_to_string(&mut string);
Some(string.trim().to_string())
}).unwrap_or("".to_string()),
usage_percpu_user: self.open_path("cpuacct.usage_percpu_user", false)
.and_then(|mut file| {
let mut string = String::new();
let _ = file.read_to_string(&mut string);
Some(string.trim().to_string())
}).unwrap_or("".to_string()),
usage_sys: self.open_path("cpuacct.usage_sys", false)
.and_then(|file| read_u64_from(file))
.unwrap_or(0),
usage_user: self.open_path("cpuacct.usage_user", false)
.and_then(|file| read_u64_from(file))
.unwrap_or(0),
stat: self
.open_path("cpuacct.stat", false)
.and_then(|file| read_string_from(file))
.unwrap_or("".to_string()),
usage: self
.open_path("cpuacct.usage", false)
.and_then(|file| read_u64_from(file))
.unwrap_or(0),
usage_all: self
.open_path("cpuacct.usage_all", false)
.and_then(|file| read_string_from(file))
.unwrap_or("".to_string()),
usage_percpu: self
.open_path("cpuacct.usage_percpu", false)
.and_then(|file| read_string_from(file))
.unwrap_or("".to_string()),
usage_percpu_sys: self
.open_path("cpuacct.usage_percpu_sys", false)
.and_then(|file| read_string_from(file))
.unwrap_or("".to_string()),
usage_percpu_user: self
.open_path("cpuacct.usage_percpu_user", false)
.and_then(|file| read_string_from(file))
.unwrap_or("".to_string()),
usage_sys: self
.open_path("cpuacct.usage_sys", false)
.and_then(|file| read_u64_from(file))
.unwrap_or(0),
usage_user: self
.open_path("cpuacct.usage_user", false)
.and_then(|file| read_u64_from(file))
.unwrap_or(0),
}
}
/// Reset the statistics the kernel has gathered about the control group.
pub fn reset(self: &Self) {
pub fn reset(&self) -> Result<()> {
self.open_path("cpuacct.usage", true).and_then(|mut file| {
file.write_all(b"0").ok()
});
file.write_all(b"0")
.map_err(|e| Error::with_cause(WriteFailed, e))
})
}
}

View File

@@ -1,38 +1,54 @@
// Copyright (c) 2018 Levente Kurusa
// Copyright (c) 2020 Ant Group
//
// SPDX-License-Identifier: Apache-2.0 or MIT
//
//! This module contains the implementation of the `cpuset` cgroup subsystem.
//!
//!
//! See the Kernel's documentation for more information about this subsystem, found at:
//! [Documentation/cgroup-v1/cpusets.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/cpusets.txt)
use std::path::PathBuf;
use std::io::{Read, Write};
use std::fs::File;
use {CpuResources, Resources, Controller, ControllIdentifier, Subsystem, Controllers};
use log::*;
use std::fs::File;
use std::io::{Read, Write};
use std::path::PathBuf;
use crate::error::ErrorKind::*;
use crate::error::*;
use crate::{
ControllIdentifier, ControllerInternal, Controllers, CpuResources, Resources, Subsystem,
};
/// A controller that allows controlling the `cpuset` subsystem of a Cgroup.
///
///
/// In essence, this controller is responsible for restricting the tasks in the control group to a
/// set of CPUs and/or memory nodes.
#[derive(Debug, Clone)]
pub struct CpuSetController {
base: PathBuf,
path: PathBuf,
v2: bool,
}
/// The current state of the `cpuset` controller for this control group.
pub struct CpuSet {
/// If true, no other control groups can share the CPUs listed in the `cpus` field.
pub cpu_exclusive: bool,
/// The list of CPUs the tasks of the control group can run on. This is a comma-separated list
/// with dashes between numbers representing ranges.
pub cpus: String,
/// The list of CPUs the tasks of the control group can run on.
///
/// This is a vector of `(start, end)` tuples, where each tuple is a range of CPUs where the
/// control group is allowed to run on. Both sides of the range are inclusive.
pub cpus: Vec<(u64, u64)>,
/// The list of CPUs that the tasks can effectively run on. This removes the list of CPUs that
/// the parent (and all of its parents) cannot run on from the `cpus` field of this control
/// group.
pub effective_cpus: String,
pub effective_cpus: Vec<(u64, u64)>,
/// The list of memory nodes that the tasks can effectively use. This removes the list of nodes that
/// the parent (and all of its parents) cannot use from the `mems` field of this control
/// group.
pub effective_mems: String,
pub effective_mems: Vec<(u64, u64)>,
/// If true, no other control groups can share the memory nodes listed in the `mems` field.
pub mem_exclusive: bool,
/// If true, the control group is 'hardwalled'. Kernel memory allocations (except for a few
@@ -47,13 +63,14 @@ pub struct CpuSet {
/// the memory pressure for control groups or not.
pub memory_pressure_enabled: Option<bool>,
/// If true, filesystem buffers are spread across evenly between the nodes specified in `mems`.
pub memory_spread_page: bool,
pub memory_spread_page: bool,
/// If true, kernel slab caches for file I/O are spread across evenly between the nodes
/// specified in `mems`.
pub memory_spread_slab: bool,
/// The list of memory nodes the tasks of the control group can use. This is a comma-separated list
/// with dashes between numbers representing ranges.
pub mems: String,
pub memory_spread_slab: bool,
/// The list of memory nodes the tasks of the control group can use.
///
/// The format is the same as the `cpus`, `effective_cpus` and `effective_mems` fields.
pub mems: Vec<(u64, u64)>,
/// If true, the kernel will attempt to rebalance the load between the CPUs specified in the
/// `cpus` field of this control group.
pub sched_load_balance: bool,
@@ -69,27 +86,111 @@ pub struct CpuSet {
/// | 5 | Immediately balance the load between CPUs even if the system is NUMA |
/// | 6 | Immediately balance the load between all CPUs |
pub sched_relax_domain_level: u64,
}
impl Controller for CpuSetController {
fn control_type(self: &Self) -> Controllers { Controllers::CpuSet }
fn get_path<'a>(self: &'a Self) -> &'a PathBuf { &self.path }
fn get_path_mut<'a>(self: &'a mut Self) -> &'a mut PathBuf { &mut self.path }
fn get_base<'a>(self: &'a Self) -> &'a PathBuf { &self.base }
impl ControllerInternal for CpuSetController {
fn control_type(&self) -> Controllers {
Controllers::CpuSet
}
fn get_path(&self) -> &PathBuf {
&self.path
}
fn get_path_mut(&mut self) -> &mut PathBuf {
&mut self.path
}
fn get_base(&self) -> &PathBuf {
&self.base
}
fn apply(self: &Self, res: &Resources) {
/* get the resources that apply to this controller */
fn is_v2(&self) -> bool {
self.v2
}
fn apply(&self, res: &Resources) -> Result<()> {
// get the resources that apply to this controller
let res: &CpuResources = &res.cpu;
if res.update_values {
/* apply pid_max */
self.set_cpus(&res.cpus);
self.set_mems(&res.mems);
if res.cpus.is_some() {
let _ = self.set_cpus(res.cpus.as_ref().unwrap().as_str());
}
let _ = self.set_mems(&res.mems);
}
Ok(())
}
fn post_create(&self) {
if self.is_v2() {
return;
}
let current = self.get_path();
let parent = match current.parent() {
Some(p) => p,
None => return,
};
if current != self.get_base() {
match copy_from_parent(current.to_str().unwrap(), "cpuset.cpus") {
Ok(_) => (),
Err(err) => error!("error create_dir for cpuset.cpus {:?}", err),
}
match copy_from_parent(current.to_str().unwrap(), "cpuset.mems") {
Ok(_) => (),
Err(err) => error!("error create_dir for cpuset.mems {:?}", err),
}
}
}
}
fn find_no_empty_parent(from: &str, file: &str) -> Result<(String, Vec<PathBuf>)> {
let mut current_path = ::std::path::Path::new(from).to_path_buf();
let mut v = vec![];
loop {
let current_value =
match ::std::fs::read_to_string(current_path.clone().join(file).to_str().unwrap()) {
Ok(cpus) => String::from(cpus.trim()),
Err(e) => return Err(Error::with_cause(ReadFailed, e)),
};
if current_value != "" {
return Ok((current_value, v));
}
v.push(current_path.clone());
let parent = match current_path.parent() {
Some(p) => p,
None => return Ok(("".to_string(), v)),
};
// next loop, find parent
current_path = parent.to_path_buf();
}
}
/// copy_from_parent copy the cpuset.cpus and cpuset.mems from the parent
/// directory to the current directory if the file's contents are 0
fn copy_from_parent(current: &str, file: &str) -> Result<()> {
// find not empty cpus/memes from current directory.
let (value, parents) = find_no_empty_parent(current, file)?;
if value == "" || parents.len() == 0 {
return Ok(());
}
for p in parents.iter().rev() {
let mut pb = p.clone();
pb.push(file);
match ::std::fs::write(pb.to_str().unwrap(), value.as_bytes()) {
Ok(_) => (),
Err(e) => return Err(Error::with_cause(WriteFailed, e)),
}
}
Ok(())
}
impl ControllIdentifier for CpuSetController {
fn controller_type() -> Controllers {
Controllers::CpuSet
@@ -104,155 +205,220 @@ impl<'a> From<&'a Subsystem> for &'a CpuSetController {
_ => {
assert_eq!(1, 0);
::std::mem::uninitialized()
},
}
}
}
}
}
fn read_u64_from(mut file: File) -> Option<u64> {
fn read_string_from(mut file: File) -> Result<String> {
let mut string = String::new();
let _ = file.read_to_string(&mut string);
string.trim().parse().ok()
match file.read_to_string(&mut string) {
Ok(_) => Ok(string.trim().to_string()),
Err(e) => Err(Error::with_cause(ReadFailed, e)),
}
}
fn read_u64_from(mut file: File) -> Result<u64> {
let mut string = String::new();
match file.read_to_string(&mut string) {
Ok(_) => string
.trim()
.parse()
.map_err(|e| Error::with_cause(ParseError, e)),
Err(e) => Err(Error::with_cause(ReadFailed, e)),
}
}
/// Parse a string like "1,2,4-5,8" into a list of (start, end) tuples.
fn parse_range(s: String) -> Result<Vec<(u64, u64)>> {
let mut fin = Vec::new();
if s == "".to_string() {
return Ok(fin);
}
// first split by commas
let comma_split = s.split(",");
for sp in comma_split {
if sp.contains("-") {
// this is a true range
let dash_split = sp.split("-").collect::<Vec<_>>();
if dash_split.len() != 2 {
return Err(Error::new(ParseError));
}
let first = dash_split[0].parse::<u64>();
let second = dash_split[1].parse::<u64>();
if first.is_err() || second.is_err() {
return Err(Error::new(ParseError));
}
fin.push((first.unwrap(), second.unwrap()));
} else {
// this is just a single number
let num = sp.parse::<u64>();
if num.is_err() {
return Err(Error::new(ParseError));
}
fin.push((num.clone().unwrap(), num.clone().unwrap()));
}
}
Ok(fin)
}
impl CpuSetController {
/// Contructs a new `CpuSetController` with `oroot` serving as the root of the control group.
pub fn new(oroot: PathBuf) -> Self {
pub fn new(oroot: PathBuf, v2: bool) -> Self {
let mut root = oroot;
root.push(Self::controller_type().to_string());
if !v2 {
root.push(Self::controller_type().to_string());
}
Self {
base: root.clone(),
path: root,
v2: v2,
}
}
/// Returns the statistics gathered by the kernel for this control group. See the struct for
/// more information on what information this entails.
pub fn cpuset(self: &Self) -> CpuSet {
pub fn cpuset(&self) -> CpuSet {
CpuSet {
cpu_exclusive: {
self.open_path("cpuset.cpu_exclusive", false).and_then(|file| {
read_u64_from(file)
}).map(|x| x == 1).unwrap_or(false)
self.open_path("cpuset.cpu_exclusive", false)
.and_then(|file| read_u64_from(file))
.map(|x| x == 1)
.unwrap_or(false)
},
cpus: {
self.open_path("cpuset.cpus", false).and_then(|mut file| {
let mut string = String::new();
let _ = file.read_to_string(&mut string);
Some(string.trim().to_string())
}).unwrap()
self.open_path("cpuset.cpus", false)
.and_then(read_string_from)
.and_then(parse_range)
.unwrap_or(Vec::new())
},
effective_cpus: {
self.open_path("cpuset.effective_cpus", false).and_then(|mut file| {
let mut string = String::new();
let _ = file.read_to_string(&mut string);
Some(string.trim().to_string())
}).unwrap()
self.open_path("cpuset.effective_cpus", false)
.and_then(read_string_from)
.and_then(parse_range)
.unwrap_or(Vec::new())
},
effective_mems: {
self.open_path("cpuset.effective_mems", false).and_then(|mut file| {
let mut string = String::new();
let _ = file.read_to_string(&mut string);
Some(string.trim().to_string())
}).unwrap()
self.open_path("cpuset.effective_mems", false)
.and_then(read_string_from)
.and_then(parse_range)
.unwrap_or(Vec::new())
},
mem_exclusive: {
self.open_path("cpuset.mem_exclusive", false).and_then(|file| {
read_u64_from(file)
}).map(|x| x == 1).unwrap_or(false)
self.open_path("cpuset.mem_exclusive", false)
.and_then(read_u64_from)
.map(|x| x == 1)
.unwrap_or(false)
},
mem_hardwall: {
self.open_path("cpuset.mem_hardwall", false).and_then(|file| {
read_u64_from(file)
}).map(|x| x == 1).unwrap_or(false)
self.open_path("cpuset.mem_hardwall", false)
.and_then(read_u64_from)
.map(|x| x == 1)
.unwrap_or(false)
},
memory_migrate: {
self.open_path("cpuset.memory_migrate", false).and_then(|file| {
read_u64_from(file)
}).map(|x| x == 1).unwrap_or(false)
self.open_path("cpuset.memory_migrate", false)
.and_then(read_u64_from)
.map(|x| x == 1)
.unwrap_or(false)
},
memory_pressure: {
self.open_path("cpuset.memory_pressure", false).and_then(|file| {
read_u64_from(file)
}).unwrap_or(0)
self.open_path("cpuset.memory_pressure", false)
.and_then(read_u64_from)
.unwrap_or(0)
},
memory_pressure_enabled: {
self.open_path("cpuset.memory_pressure_enabled", false).and_then(|file| {
read_u64_from(file)
}).map(|x| x == 1)
self.open_path("cpuset.memory_pressure_enabled", false)
.and_then(read_u64_from)
.map(|x| x == 1)
.ok()
},
memory_spread_page: {
self.open_path("cpuset.memory_spread_page", false).and_then(|file| {
read_u64_from(file)
}).map(|x| x == 1).unwrap_or(false)
self.open_path("cpuset.memory_spread_page", false)
.and_then(read_u64_from)
.map(|x| x == 1)
.unwrap_or(false)
},
memory_spread_slab: {
self.open_path("cpuset.memory_spread_slab", false).and_then(|file| {
read_u64_from(file)
}).map(|x| x == 1).unwrap_or(false)
self.open_path("cpuset.memory_spread_slab", false)
.and_then(read_u64_from)
.map(|x| x == 1)
.unwrap_or(false)
},
mems: {
self.open_path("cpuset.mems", false).and_then(|mut file| {
let mut string = String::new();
let _ = file.read_to_string(&mut string);
Some(string.trim().to_string())
}).unwrap()
self.open_path("cpuset.mems", false)
.and_then(read_string_from)
.and_then(parse_range)
.unwrap_or(Vec::new())
},
sched_load_balance: {
self.open_path("cpuset.sched_load_balance", false).and_then(|file| {
read_u64_from(file)
}).map(|x| x == 1).unwrap_or(false)
self.open_path("cpuset.sched_load_balance", false)
.and_then(read_u64_from)
.map(|x| x == 1)
.unwrap_or(false)
},
sched_relax_domain_level: {
self.open_path("cpuset.sched_relax_domain_level", false).and_then(|file| {
read_u64_from(file)
}).unwrap_or(0)
self.open_path("cpuset.sched_relax_domain_level", false)
.and_then(read_u64_from)
.unwrap_or(0)
},
}
}
/// Control whether the CPUs selected via `set_cpus()` should be exclusive to this control
/// group or not.
pub fn set_cpu_exclusive(self: &Self, b: bool) {
self.open_path("cpuset.cpu_exclusive", true).and_then(|mut file| {
if b {
file.write_all(b"1").ok()
} else {
file.write_all(b"0").ok()
}
});
pub fn set_cpu_exclusive(&self, b: bool) -> Result<()> {
self.open_path("cpuset.cpu_exclusive", true)
.and_then(|mut file| {
if b {
file.write_all(b"1")
.map_err(|e| Error::with_cause(WriteFailed, e))
} else {
file.write_all(b"0")
.map_err(|e| Error::with_cause(WriteFailed, e))
}
})
}
/// Control whether the memory nodes selected via `set_memss()` should be exclusive to this control
/// group or not.
pub fn set_mem_exclusive(self: &Self, b: bool) {
self.open_path("cpuset.mem_exclusive", true).and_then(|mut file| {
if b {
file.write_all(b"1").ok()
} else {
file.write_all(b"0").ok()
}
});
pub fn set_mem_exclusive(&self, b: bool) -> Result<()> {
self.open_path("cpuset.mem_exclusive", true)
.and_then(|mut file| {
if b {
file.write_all(b"1")
.map_err(|e| Error::with_cause(WriteFailed, e))
} else {
file.write_all(b"0")
.map_err(|e| Error::with_cause(WriteFailed, e))
}
})
}
/// Set the CPUs that the tasks in this control group can run on.
///
/// Syntax is a comma separated list of CPUs, with an additional extension that ranges can
/// be represented via dashes.
pub fn set_cpus(self: &Self, cpus: &String) {
pub fn set_cpus(&self, cpus: &str) -> Result<()> {
self.open_path("cpuset.cpus", true).and_then(|mut file| {
file.write_all(cpus.as_ref()).ok()
});
file.write_all(cpus.as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
})
}
/// Set the memory nodes that the tasks in this control group can use.
///
/// Syntax is the same as with `set_cpus()`.
pub fn set_mems(self: &Self, mems: &String) {
pub fn set_mems(&self, mems: &str) -> Result<()> {
self.open_path("cpuset.mems", true).and_then(|mut file| {
file.write_all(mems.as_ref()).ok()
});
file.write_all(mems.as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
})
}
/// Controls whether the control group should be "hardwalled", i.e., whether kernel allocations
@@ -260,86 +426,139 @@ impl CpuSetController {
///
/// Note that some kernel allocations, most notably those that are made in interrupt handlers
/// may disregard this.
pub fn set_hardwall(self: &Self, b: bool) {
self.open_path("cpuset.mem_hardwall", true).and_then(|mut file| {
if b {
file.write_all(b"1").ok()
} else {
file.write_all(b"0").ok()
}
});
pub fn set_hardwall(&self, b: bool) -> Result<()> {
self.open_path("cpuset.mem_hardwall", true)
.and_then(|mut file| {
if b {
file.write_all(b"1")
.map_err(|e| Error::with_cause(WriteFailed, e))
} else {
file.write_all(b"0")
.map_err(|e| Error::with_cause(WriteFailed, e))
}
})
}
/// Controls whether the kernel should attempt to rebalance the load between the CPUs specified in the
/// `cpus` field of this control group.
pub fn set_load_balancing(self: &Self, b: bool) {
self.open_path("cpuset.sched_load_balance", true).and_then(|mut file| {
if b {
file.write_all(b"1").ok()
} else {
file.write_all(b"0").ok()
}
});
pub fn set_load_balancing(&self, b: bool) -> Result<()> {
self.open_path("cpuset.sched_load_balance", true)
.and_then(|mut file| {
if b {
file.write_all(b"1")
.map_err(|e| Error::with_cause(WriteFailed, e))
} else {
file.write_all(b"0")
.map_err(|e| Error::with_cause(WriteFailed, e))
}
})
}
/// Contorl how much effort the kernel should invest in rebalacing the control group.
///
/// See @CpuSet 's similar field for more information.
pub fn set_rebalance_relax_domain_level(self: &Self, i: i64) {
self.open_path("cpuset.sched_relax_domain_level", true).and_then(|mut file| {
file.write_all(i.to_string().as_ref()).ok()
});
pub fn set_rebalance_relax_domain_level(&self, i: i64) -> Result<()> {
self.open_path("cpuset.sched_relax_domain_level", true)
.and_then(|mut file| {
file.write_all(i.to_string().as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
})
}
/// Control whether when using `set_mems()` the existing memory used by the tasks should be
/// migrated over to the now-selected nodes.
pub fn set_memory_migration(self: &Self, b: bool) {
self.open_path("cpuset.memory_migrate", true).and_then(|mut file| {
if b {
file.write_all(b"1").ok()
} else {
file.write_all(b"0").ok()
}
});
pub fn set_memory_migration(&self, b: bool) -> Result<()> {
self.open_path("cpuset.memory_migrate", true)
.and_then(|mut file| {
if b {
file.write_all(b"1")
.map_err(|e| Error::with_cause(WriteFailed, e))
} else {
file.write_all(b"0")
.map_err(|e| Error::with_cause(WriteFailed, e))
}
})
}
/// Control whether filesystem buffers should be evenly split across the nodes selected via
/// `set_mems()`.
pub fn set_memory_spread_page(self: &Self, b: bool) {
self.open_path("cpuset.memory_spread_page", true).and_then(|mut file| {
if b {
file.write_all(b"1").ok()
} else {
file.write_all(b"0").ok()
}
});
pub fn set_memory_spread_page(&self, b: bool) -> Result<()> {
self.open_path("cpuset.memory_spread_page", true)
.and_then(|mut file| {
if b {
file.write_all(b"1")
.map_err(|e| Error::with_cause(WriteFailed, e))
} else {
file.write_all(b"0")
.map_err(|e| Error::with_cause(WriteFailed, e))
}
})
}
/// Control whether the kernel's slab cache for file I/O should be evenly split across the
/// nodes selected via `set_mems()`.
pub fn set_memory_spread_slab(self: &Self, b: bool) {
self.open_path("cpuset.memory_spread_slab", true).and_then(|mut file| {
if b {
file.write_all(b"1").ok()
} else {
file.write_all(b"0").ok()
}
});
pub fn set_memory_spread_slab(&self, b: bool) -> Result<()> {
self.open_path("cpuset.memory_spread_slab", true)
.and_then(|mut file| {
if b {
file.write_all(b"1")
.map_err(|e| Error::with_cause(WriteFailed, e))
} else {
file.write_all(b"0")
.map_err(|e| Error::with_cause(WriteFailed, e))
}
})
}
/// Control whether the kernel should collect information to calculate memory pressure for
/// control groups.
///
/// Note: This is a no-operation if the control group referred by `self` is not the root
/// Note: This will fail with `InvalidOperation` if the current congrol group is not the root
/// control group.
pub fn set_enable_memory_pressure(self: &Self, b: bool) {
/* XXX: this file should only be present in the root cpuset cg */
self.open_path("cpuset.memory_pressure_enabled", true).and_then(|mut file| {
if b {
file.write_all(b"1").ok()
} else {
file.write_all(b"0").ok()
}
});
pub fn set_enable_memory_pressure(&self, b: bool) -> Result<()> {
if !self.path_exists("cpuset.memory_pressure_enabled") {
return Err(Error::new(InvalidOperation));
}
self.open_path("cpuset.memory_pressure_enabled", true)
.and_then(|mut file| {
if b {
file.write_all(b"1")
.map_err(|e| Error::with_cause(WriteFailed, e))
} else {
file.write_all(b"0")
.map_err(|e| Error::with_cause(WriteFailed, e))
}
})
}
}
#[cfg(test)]
mod tests {
use crate::cpuset;
#[test]
fn test_parse_range() {
let test_cases = vec![
"1,2,4-6,9".to_string(),
"".to_string(),
"1".to_string(),
"1-111".to_string(),
"1,2,3,4".to_string(),
"1-5,6-7,8-9".to_string(),
];
let expecteds = vec![
vec![(1, 1), (2, 2), (4, 6), (9, 9)],
vec![],
vec![(1, 1)],
vec![(1, 111)],
vec![(1, 1), (2, 2), (3, 3), (4, 4)],
vec![(1, 5), (6, 7), (8, 9)],
];
for (i, case) in test_cases.into_iter().enumerate() {
let range = cpuset::parse_range(case.clone());
println!("{:?} => {:?}", case, range);
assert!(range.is_ok());
assert_eq!(range.unwrap(), expecteds[i]);
}
}
}

View File

@@ -1,43 +1,171 @@
// Copyright (c) 2018 Levente Kurusa
//
// SPDX-License-Identifier: Apache-2.0 or MIT
//
//! This module contains the implementation of the `devices` cgroup subsystem.
//!
//!
//! See the Kernel's documentation for more information about this subsystem, found at:
//! [Documentation/cgroup-v1/devices.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/devices.txt)
use std::path::PathBuf;
use std::io::{Read, Write};
use std::path::PathBuf;
use {DeviceResources, Controllers, Controller, Resources, ControllIdentifier, Subsystem};
use log::*;
use crate::error::ErrorKind::*;
use crate::error::*;
use crate::{
ControllIdentifier, ControllerInternal, Controllers, DeviceResource, DeviceResources,
Resources, Subsystem,
};
/// A controller that allows controlling the `devices` subsystem of a Cgroup.
///
/// In essence, using the devices controller, it is possible to allow or disallow sets of devices to
/// be used by the control group's tasks.
#[derive(Debug, Clone)]
pub struct DevicesController{
pub struct DevicesController {
base: PathBuf,
path: PathBuf,
}
impl Controller for DevicesController {
fn control_type(self: &Self) -> Controllers { Controllers::Devices }
fn get_path<'a>(self: &'a Self) -> &'a PathBuf { &self.path }
fn get_path_mut<'a>(self: &'a mut Self) -> &'a mut PathBuf { &mut self.path }
fn get_base<'a>(self: &'a Self) -> &'a PathBuf { &self.base }
/// An enum holding the different types of devices that can be manipulated using this controller.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum DeviceType {
/// The rule applies to all devices.
All,
/// The rule only applies to character devices.
Char,
/// The rule only applies to block devices.
Block,
}
fn apply(self: &Self, res: &Resources) {
/* get the resources that apply to this controller */
impl Default for DeviceType {
fn default() -> Self {
DeviceType::All
}
}
impl DeviceType {
/// Convert a DeviceType into the character that the kernel recognizes.
pub fn to_char(&self) -> char {
match self {
DeviceType::All => 'a',
DeviceType::Char => 'c',
DeviceType::Block => 'b',
}
}
/// Convert the kenrel's representation into the DeviceType type.
pub fn from_char(c: Option<char>) -> Option<DeviceType> {
match c {
Some('a') => Some(DeviceType::All),
Some('c') => Some(DeviceType::Char),
Some('b') => Some(DeviceType::Block),
_ => None,
}
}
}
/// An enum with the permissions that can be allowed/denied to the control group.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum DevicePermissions {
/// Permission to read from the device.
Read,
/// Permission to write to the device.
Write,
/// Permission to execute the `mknod(2)` system call with the device's major and minor numbers.
/// That is, the permission to create a special file that refers to the device node.
MkNod,
}
impl DevicePermissions {
/// Convert a DevicePermissions into the character that the kernel recognizes.
pub fn to_char(&self) -> char {
match self {
DevicePermissions::Read => 'r',
DevicePermissions::Write => 'w',
DevicePermissions::MkNod => 'm',
}
}
/// Convert a char to a DevicePermission if there is such a mapping.
pub fn from_char(c: char) -> Option<DevicePermissions> {
match c {
'r' => Some(DevicePermissions::Read),
'w' => Some(DevicePermissions::Write),
'm' => Some(DevicePermissions::MkNod),
_ => None,
}
}
/// Checks whether the string is a valid descriptor of DevicePermissions.
pub fn is_valid(s: &str) -> bool {
if s == "" {
return false;
}
for i in s.chars() {
if i != 'r' && i != 'w' && i != 'm' {
return false;
}
}
return true;
}
/// Returns a Vec will all the permissions that a device can have.
pub fn all() -> Vec<DevicePermissions> {
vec![
DevicePermissions::Read,
DevicePermissions::Write,
DevicePermissions::MkNod,
]
}
/// Convert a string into DevicePermissions.
pub fn from_str(s: &str) -> Result<Vec<DevicePermissions>> {
let mut v = Vec::new();
if s == "" {
return Ok(v);
}
for e in s.chars() {
let perm = DevicePermissions::from_char(e).ok_or_else(|| Error::new(ParseError))?;
v.push(perm);
}
Ok(v)
}
}
impl ControllerInternal for DevicesController {
fn control_type(&self) -> Controllers {
Controllers::Devices
}
fn get_path(&self) -> &PathBuf {
&self.path
}
fn get_path_mut(&mut self) -> &mut PathBuf {
&mut self.path
}
fn get_base(&self) -> &PathBuf {
&self.base
}
fn apply(&self, res: &Resources) -> Result<()> {
// get the resources that apply to this controller
let res: &DeviceResources = &res.devices;
if res.update_values {
for i in &res.devices {
let wstr = format!("{} {}:{} {}",
i.devtype, i.major, i.minor, i.access);
if i.allow {
self.allow_device(&wstr);
let _ = self.allow_device(i.devtype, i.major, i.minor, &i.access);
} else {
self.deny_device(&wstr);
let _ = self.deny_device(i.devtype, i.major, i.minor, &i.access);
}
}
}
Ok(())
}
}
@@ -55,7 +183,7 @@ impl<'a> From<&'a Subsystem> for &'a DevicesController {
_ => {
assert_eq!(1, 0);
::std::mem::uninitialized()
},
}
}
}
}
@@ -74,40 +202,111 @@ impl DevicesController {
/// Allow a (possibly, set of) device(s) to be used by the tasks in the control group.
///
/// The format of `dev` is rather simple:
/// `$type $major:$minor $rwm`
/// where `$rwm` is a combination of the characters `r`, `w`, `m`, each standing for read,
/// write, mknod permissions.
///
/// Note that `dev` can be "regex"-like: both `$major` and `$minor` can be `*` which implies
/// that their value does not matter.
pub fn allow_device(self: &Self, dev: &String) {
/// When `-1` is passed as `major` or `minor`, the kernel interprets that value as "any",
/// meaning that it will match any device.
pub fn allow_device(
&self,
devtype: DeviceType,
major: i64,
minor: i64,
perm: &Vec<DevicePermissions>,
) -> Result<()> {
let perms = perm
.iter()
.map(DevicePermissions::to_char)
.collect::<String>();
let minor = if minor == -1 {
"*".to_string()
} else {
format!("{}", minor)
};
let major = if major == -1 {
"*".to_string()
} else {
format!("{}", major)
};
let final_str = format!("{} {}:{} {}", devtype.to_char(), major, minor, perms);
self.open_path("devices.allow", true).and_then(|mut file| {
file.write_all(dev.as_ref()).ok()
});
file.write_all(final_str.as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
})
}
/// Deny the control group's tasks access to the devices covered by `dev`.
///
/// The format of `dev` is rather simple:
/// `$type $major:$minor $rwm`
/// where `$rwm` is a combination of the characters `r`, `w`, `m`, each standing for read,
/// write, mknod permissions.
///
/// Note that `dev` can be "regex"-like: both `$major` and `$minor` can be `*` which implies
/// that their value does not matter.
pub fn deny_device(self: &Self, dev: &String) {
/// When `-1` is passed as `major` or `minor`, the kernel interprets that value as "any",
/// meaning that it will match any device.
pub fn deny_device(
&self,
devtype: DeviceType,
major: i64,
minor: i64,
perm: &Vec<DevicePermissions>,
) -> Result<()> {
let perms = perm
.iter()
.map(DevicePermissions::to_char)
.collect::<String>();
let minor = if minor == -1 {
"*".to_string()
} else {
format!("{}", minor)
};
let major = if major == -1 {
"*".to_string()
} else {
format!("{}", major)
};
let final_str = format!("{} {}:{} {}", devtype.to_char(), major, minor, perms);
self.open_path("devices.deny", true).and_then(|mut file| {
file.write_all(dev.as_ref()).ok()
});
file.write_all(final_str.as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
})
}
/// Get the current list of allowed devices.
pub fn allowed_devices(self: &Self) -> String {
pub fn allowed_devices(&self) -> Result<Vec<DeviceResource>> {
self.open_path("devices.list", false).and_then(|mut file| {
let mut s = String::new();
let _ = file.read_to_string(&mut s);
Some(s)
}).unwrap_or("".to_string())
let res = file.read_to_string(&mut s);
match res {
Ok(_) => {
s.lines().fold(Ok(Vec::new()), |acc, line| {
let ls = line.to_string().split(|c| c == ' ' || c == ':').map(|x| x.to_string()).collect::<Vec<String>>();
if acc.is_err() || ls.len() != 4 {
error!("allowed_devices: acc: {:?}, ls: {:?}", acc, ls);
Err(Error::new(ParseError))
} else {
let devtype = DeviceType::from_char(ls[0].chars().nth(0));
let mut major = ls[1].parse::<i64>();
let mut minor = ls[2].parse::<i64>();
if major.is_err() && ls[1] == "*".to_string() {
major = Ok(-1);
}
if minor.is_err() && ls[2] == "*".to_string() {
minor = Ok(-1);
}
if devtype.is_none() || major.is_err() || minor.is_err() || !DevicePermissions::is_valid(&ls[3]) {
error!("allowed_devices: acc: {:?}, ls: {:?}, devtype: {:?}, major {:?} minor {:?} ls3 {:?}",
acc, ls, devtype, major, minor, &ls[3]);
Err(Error::new(ParseError))
} else {
let access = DevicePermissions::from_str(&ls[3])?;
let mut acc = acc.unwrap();
acc.push(DeviceResource {
allow: true,
devtype: devtype.unwrap(),
major: major.unwrap(),
minor: minor.unwrap(),
access: access,
});
Ok(acc)
}
}
})
},
Err(e) => Err(Error::with_cause(ReadFailed, e)),
}
})
}
}

104
src/error.rs Normal file
View File

@@ -0,0 +1,104 @@
// Copyright (c) 2018 Levente Kurusa
// Copyright (c) 2020 Ant Group
//
// SPDX-License-Identifier: Apache-2.0 or MIT
//
use std::error::Error as StdError;
use std::fmt;
/// The different types of errors that can occur while manipulating control groups.
#[derive(Debug, Eq, PartialEq)]
pub enum ErrorKind {
FsError,
Common(String),
/// An error occured while writing to a control group file.
WriteFailed,
/// An error occured while trying to read from a control group file.
ReadFailed,
/// An error occured while trying to parse a value from a control group file.
///
/// In the future, there will be some information attached to this field.
ParseError,
/// You tried to do something invalid.
///
/// This could be because you tried to set a value in a control group that is not a root
/// control group. Or, when using unified hierarchy, you tried to add a task in a leaf node.
InvalidOperation,
/// The path of the control group was invalid.
///
/// This could be caused by trying to escape the control group filesystem via a string of "..".
/// This crate checks against this and operations will fail with this error.
InvalidPath,
InvalidBytesSize,
/// An unknown error has occured.
Other,
}
#[derive(Debug)]
pub struct Error {
kind: ErrorKind,
cause: Option<Box<StdError + Send + Sync>>,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let msg = match &self.kind {
ErrorKind::FsError => "fs error".to_string(),
ErrorKind::Common(s) => s.clone(),
ErrorKind::WriteFailed => "unable to write to a control group file".to_string(),
ErrorKind::ReadFailed => "unable to read a control group file".to_string(),
ErrorKind::ParseError => "unable to parse control group file".to_string(),
ErrorKind::InvalidOperation => "the requested operation is invalid".to_string(),
ErrorKind::InvalidPath => "the given path is invalid".to_string(),
ErrorKind::InvalidBytesSize => "invalid bytes size".to_string(),
ErrorKind::Other => "an unknown error".to_string(),
};
write!(f, "{}", msg)
}
}
impl StdError for Error {
fn cause(&self) -> Option<&StdError> {
match self.cause {
Some(ref x) => Some(&**x),
None => None,
}
}
}
impl Error {
pub(crate) fn from_string(s: String) -> Self {
Self {
kind: ErrorKind::Common(s),
cause: None,
}
}
pub(crate) fn new(kind: ErrorKind) -> Self {
Self { kind, cause: None }
}
pub(crate) fn with_cause<E>(kind: ErrorKind, cause: E) -> Self
where
E: 'static + Send + Sync + StdError,
{
Self {
kind,
cause: Some(Box::new(cause)),
}
}
pub fn kind(&self) -> &ErrorKind {
&self.kind
}
}
pub type Result<T> = ::std::result::Result<T, Error>;

90
src/events.rs Normal file
View File

@@ -0,0 +1,90 @@
// Copyright (c) 2020 Ant Group
//
// SPDX-License-Identifier: Apache-2.0 or MIT
//
use eventfd::{eventfd, EfdFlags};
use nix::sys::eventfd;
use std::fs::{self, File};
use std::io::Read;
use std::os::unix::io::{AsRawFd, FromRawFd};
use std::path::{Path, PathBuf};
use std::sync::mpsc::{self, Receiver};
use std::thread;
use crate::error::ErrorKind::*;
use crate::error::*;
// notify_on_oom returns channel on which you can expect event about OOM,
// if process died without OOM this channel will be closed.
pub fn notify_on_oom_v2(key: &str, dir: &PathBuf) -> Result<Receiver<String>> {
register_memory_event(key, dir, "memory.oom_control", "")
}
// notify_on_oom returns channel on which you can expect event about OOM,
// if process died without OOM this channel will be closed.
pub fn notify_on_oom_v1(key: &str, dir: &PathBuf) -> Result<Receiver<String>> {
register_memory_event(key, dir, "memory.oom_control", "")
}
// level is one of "low", "medium", or "critical"
pub fn notify_memory_pressure(key: &str, dir: &PathBuf, level: &str) -> Result<Receiver<String>> {
if level != "low" && level != "medium" && level != "critical" {
return Err(Error::from_string(format!(
"invalid pressure level {}",
level
)));
}
register_memory_event(key, dir, "memory.pressure_level", level)
}
fn register_memory_event(
key: &str,
cg_dir: &PathBuf,
event_name: &str,
arg: &str,
) -> Result<Receiver<String>> {
let path = cg_dir.join(event_name);
let event_file = File::open(path).map_err(|e| Error::with_cause(ReadFailed, e))?;
let eventfd =
eventfd(0, EfdFlags::EFD_CLOEXEC).map_err(|e| Error::with_cause(ReadFailed, e))?;
let event_control_path = cg_dir.join("cgroup.event_control");
let data;
if arg == "" {
data = format!("{} {}", eventfd, event_file.as_raw_fd());
} else {
data = format!("{} {} {}", eventfd, event_file.as_raw_fd(), arg);
}
// write to file and set mode to 0700(FIXME)
fs::write(&event_control_path, data).map_err(|e| Error::with_cause(WriteFailed, e));
let mut eventfd_file = unsafe { File::from_raw_fd(eventfd) };
let (sender, receiver) = mpsc::channel();
let key = key.to_string();
thread::spawn(move || {
loop {
let mut buf = [0; 8];
match eventfd_file.read(&mut buf) {
Err(err) => {
return;
}
Ok(_) => {}
}
// When a cgroup is destroyed, an event is sent to eventfd.
// So if the control path is gone, return instead of notifying.
if !Path::new(&event_control_path).exists() {
return;
}
sender.send(key.clone()).unwrap();
}
});
Ok(receiver)
}

View File

@@ -1,11 +1,20 @@
// Copyright (c) 2018 Levente Kurusa
// Copyright (c) 2020 Ant Group
//
// SPDX-License-Identifier: Apache-2.0 or MIT
//
//! This module contains the implementation of the `freezer` cgroup subsystem.
//!
//!
//! See the Kernel's documentation for more information about this subsystem, found at:
//! [Documentation/cgroup-v1/freezer-subsystem.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/freezer-subsystem.txt)
use std::path::PathBuf;
use std::io::{Read, Write};
use std::path::PathBuf;
use {Controllers, Controller, Resources, ControllIdentifier, Subsystem};
use crate::error::ErrorKind::*;
use crate::error::*;
use crate::{ControllIdentifier, ControllerInternal, Controllers, Resources, Subsystem};
/// A controller that allows controlling the `freezer` subsystem of a Cgroup.
///
@@ -16,9 +25,10 @@ use {Controllers, Controller, Resources, ControllIdentifier, Subsystem};
/// Note that if the control group is currently in the `Frozen` or `Freezing` state, then no
/// processes can be added to it.
#[derive(Debug, Clone)]
pub struct FreezerController{
pub struct FreezerController {
base: PathBuf,
path: PathBuf,
v2: bool,
}
/// The current state of the control group
@@ -31,13 +41,22 @@ pub enum FreezerState {
Frozen,
}
impl Controller for FreezerController {
fn control_type(self: &Self) -> Controllers { Controllers::Freezer }
fn get_path<'a>(self: &'a Self) -> &'a PathBuf { &self.path }
fn get_path_mut<'a>(self: &'a mut Self) -> &'a mut PathBuf { &mut self.path }
fn get_base<'a>(self: &'a Self) -> &'a PathBuf { &self.base }
impl ControllerInternal for FreezerController {
fn control_type(&self) -> Controllers {
Controllers::Freezer
}
fn get_path(&self) -> &PathBuf {
&self.path
}
fn get_path_mut(&mut self) -> &mut PathBuf {
&mut self.path
}
fn get_base(&self) -> &PathBuf {
&self.base
}
fn apply(self: &Self, _res: &Resources) {
fn apply(&self, _res: &Resources) -> Result<()> {
Ok(())
}
}
@@ -55,7 +74,7 @@ impl<'a> From<&'a Subsystem> for &'a FreezerController {
_ => {
assert_eq!(1, 0);
::std::mem::uninitialized()
},
}
}
}
}
@@ -63,40 +82,67 @@ impl<'a> From<&'a Subsystem> for &'a FreezerController {
impl FreezerController {
/// Contructs a new `FreezerController` with `oroot` serving as the root of the control group.
pub fn new(oroot: PathBuf) -> Self {
pub fn new(oroot: PathBuf, v2: bool) -> Self {
let mut root = oroot;
root.push(Self::controller_type().to_string());
if !v2 {
root.push(Self::controller_type().to_string());
}
Self {
base: root.clone(),
path: root,
v2: v2,
}
}
/// Freezes the processes in the control group.
pub fn freeze(self: &Self) {
self.open_path("freezer.state", true).and_then(|mut file| {
file.write_all("FROZEN".to_string().as_ref()).ok()
});
pub fn freeze(&self) -> Result<()> {
let mut file = "freezer.state";
let mut content = "FROZEN".to_string();
if self.v2 {
file = "cgroup.freeze";
content = "1".to_string();
}
self.open_path(file, true).and_then(|mut file| {
file.write_all(content.as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
})
}
/// Thaws, that is, unfreezes the processes in the control group.
pub fn thaw(self: &Self) {
self.open_path("freezer.state", true).and_then(|mut file| {
file.write_all("THAWED".to_string().as_ref()).ok()
});
pub fn thaw(&self) -> Result<()> {
let mut file = "freezer.state";
let mut content = "THAWED".to_string();
if self.v2 {
file = "cgroup.freeze";
content = "0".to_string();
}
self.open_path(file, true).and_then(|mut file| {
file.write_all(content.as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
})
}
/// Retrieve the state of processes in the control group.
pub fn state(self: &Self) -> FreezerState {
self.open_path("freezer.state", false).and_then(|mut file| {
pub fn state(&self) -> Result<FreezerState> {
let mut file = "freezer.state";
if self.v2 {
file = "cgroup.freeze";
}
self.open_path(file, false).and_then(|mut file| {
let mut s = String::new();
let _ = file.read_to_string(&mut s);
match s.as_ref() {
"FROZEN" => Some(FreezerState::Frozen),
"THAWED" => Some(FreezerState::Thawed),
"FREEZING" => Some(FreezerState::Freezing),
_ => None,
let res = file.read_to_string(&mut s);
match res {
Ok(_) => match s.as_ref() {
"FROZEN" => Ok(FreezerState::Frozen),
"THAWED" => Ok(FreezerState::Thawed),
"1" => Ok(FreezerState::Frozen),
"0" => Ok(FreezerState::Thawed),
"FREEZING" => Ok(FreezerState::Freezing),
_ => Err(Error::new(ParseError)),
},
Err(e) => Err(Error::with_cause(ReadFailed, e)),
}
}).unwrap_or(FreezerState::Thawed)
})
}
}

View File

@@ -1,63 +1,85 @@
// Copyright (c) 2018 Levente Kurusa
// Copyright (c) 2020 Ant Group
//
// SPDX-License-Identifier: Apache-2.0 or MIT
//
//! This module represents the various control group hierarchies the Linux kernel supports.
//!
//! Currently, we only support the cgroupv1 hierarchy, but in the future we will add support for
//! the Unified Hierarchy.
use nix::sys::statfs;
use std::fs::{self, File};
use std::io::BufRead;
use std::io::BufReader;
use std::fs::File;
use std::path::{Path, PathBuf};
use {Controllers, Hierarchy, Subsystem};
use ::pid::PidController;
use ::memory::MemController;
use ::cpuset::CpuSetController;
use ::cpuacct::CpuAcctController;
use ::cpu::CpuController;
use ::freezer::FreezerController;
use ::devices::DevicesController;
use ::net_cls::NetClsController;
use ::blkio::BlkIoController;
use ::perf_event::PerfEventController;
use ::net_prio::NetPrioController;
use ::hugetlb::HugeTlbController;
use ::rdma::RdmaController;
use log::*;
use crate::blkio::BlkIoController;
use crate::cpu::CpuController;
use crate::cpuacct::CpuAcctController;
use crate::cpuset::CpuSetController;
use crate::devices::DevicesController;
use crate::freezer::FreezerController;
use crate::hugetlb::HugeTlbController;
use crate::memory::MemController;
use crate::net_cls::NetClsController;
use crate::net_prio::NetPrioController;
use crate::perf_event::PerfEventController;
use crate::pid::PidController;
use crate::rdma::RdmaController;
use crate::systemd::SystemdController;
use crate::{Controllers, Hierarchy, Subsystem};
use crate::cgroup::Cgroup;
/// The standard, original cgroup implementation. Often referred to as "cgroupv1".
pub struct V1 {
mount_point: String,
}
pub struct V2 {
root: String,
}
impl Hierarchy for V1 {
fn subsystems(self: &Self) -> Vec<Subsystem> {
fn v2(&self) -> bool {
false
}
fn subsystems(&self) -> Vec<Subsystem> {
let mut subs = vec![];
if self.check_support(Controllers::Pids) {
subs.push(Subsystem::Pid(PidController::new(self.root())));
subs.push(Subsystem::Pid(PidController::new(self.root(), false)));
}
if self.check_support(Controllers::Mem) {
subs.push(Subsystem::Mem(MemController::new(self.root())));
subs.push(Subsystem::Mem(MemController::new(self.root(), false)));
}
if self.check_support(Controllers::CpuSet) {
subs.push(Subsystem::CpuSet(CpuSetController::new(self.root())));
subs.push(Subsystem::CpuSet(CpuSetController::new(self.root(), false)));
}
if self.check_support(Controllers::CpuAcct) {
subs.push(Subsystem::CpuAcct(CpuAcctController::new(self.root())));
}
if self.check_support(Controllers::Cpu) {
subs.push(Subsystem::Cpu(CpuController::new(self.root())));
subs.push(Subsystem::Cpu(CpuController::new(self.root(), false)));
}
if self.check_support(Controllers::Devices) {
subs.push(Subsystem::Devices(DevicesController::new(self.root())));
}
if self.check_support(Controllers::Freezer) {
subs.push(Subsystem::Freezer(FreezerController::new(self.root())));
subs.push(Subsystem::Freezer(FreezerController::new(
self.root(),
false,
)));
}
if self.check_support(Controllers::NetCls) {
subs.push(Subsystem::NetCls(NetClsController::new(self.root())));
}
if self.check_support(Controllers::BlkIo) {
subs.push(Subsystem::BlkIo(BlkIoController::new(self.root())));
subs.push(Subsystem::BlkIo(BlkIoController::new(self.root(), false)));
}
if self.check_support(Controllers::PerfEvent) {
subs.push(Subsystem::PerfEvent(PerfEventController::new(self.root())));
@@ -66,16 +88,30 @@ impl Hierarchy for V1 {
subs.push(Subsystem::NetPrio(NetPrioController::new(self.root())));
}
if self.check_support(Controllers::HugeTlb) {
subs.push(Subsystem::HugeTlb(HugeTlbController::new(self.root())));
subs.push(Subsystem::HugeTlb(HugeTlbController::new(
self.root(),
false,
)));
}
if self.check_support(Controllers::Rdma) {
subs.push(Subsystem::Rdma(RdmaController::new(self.root())));
}
if self.check_support(Controllers::Systemd) {
subs.push(Subsystem::Systemd(SystemdController::new(
self.root(),
false,
)));
}
subs
}
fn check_support(self: &Self, sub: Controllers) -> bool {
fn root_control_group(&self) -> Cgroup {
let b: &Hierarchy = self as &Hierarchy;
Cgroup::load(Box::new(&*b), "".to_string())
}
fn check_support(&self, sub: Controllers) -> bool {
let root = self.root().read_dir().unwrap();
for entry in root {
if let Ok(entry) = entry {
@@ -87,15 +123,82 @@ impl Hierarchy for V1 {
return false;
}
fn root(self: &Self) -> PathBuf {
fn root(&self) -> PathBuf {
PathBuf::from(self.mount_point.clone())
}
}
impl Hierarchy for V2 {
fn v2(&self) -> bool {
true
}
fn subsystems(&self) -> Vec<Subsystem> {
let p = format!("{}/{}", UNIFIED_MOUNTPOINT, "cgroup.controllers");
let ret = fs::read_to_string(p.as_str());
if ret.is_err() {
return vec![];
}
let mut subs = vec![];
let controllers = ret.unwrap().trim().to_string();
let controller_list: Vec<&str> = controllers.split(' ').collect();
for s in controller_list {
match s {
"cpu" => {
subs.push(Subsystem::Cpu(CpuController::new(self.root(), true)));
}
"io" => {
subs.push(Subsystem::BlkIo(BlkIoController::new(self.root(), true)));
}
"cpuset" => {
subs.push(Subsystem::CpuSet(CpuSetController::new(self.root(), true)));
}
"memory" => {
subs.push(Subsystem::Mem(MemController::new(self.root(), true)));
}
"pids" => {
subs.push(Subsystem::Pid(PidController::new(self.root(), true)));
}
"freezer" => {
subs.push(Subsystem::Freezer(FreezerController::new(
self.root(),
true,
)));
}
"hugetlb" => {
subs.push(Subsystem::HugeTlb(HugeTlbController::new(
self.root(),
true,
)));
}
_ => {}
}
}
subs
}
fn root_control_group(&self) -> Cgroup {
let b: &Hierarchy = self as &Hierarchy;
Cgroup::load(Box::new(&*b), "".to_string())
}
fn check_support(&self, _sub: Controllers) -> bool {
return false;
}
fn root(&self) -> PathBuf {
PathBuf::from(self.root.clone())
}
}
impl V1 {
/// Finds where control groups are mounted to and returns a hierarchy in which control groups
/// can be created.
pub fn new() -> Self {
pub fn new() -> V1 {
let mount_point = find_v1_mount().unwrap();
V1 {
mount_point: mount_point,
@@ -103,11 +206,65 @@ impl V1 {
}
}
impl V2 {
/// Finds where control groups are mounted to and returns a hierarchy in which control groups
/// can be created.
pub fn new() -> V2 {
V2 {
root: String::from(UNIFIED_MOUNTPOINT),
}
}
}
pub const UNIFIED_MOUNTPOINT: &'static str = "/sys/fs/cgroup";
#[cfg(all(target_os = "linux", not(target_env = "musl")))]
pub fn is_cgroup2_unified_mode() -> bool {
let path = Path::new(UNIFIED_MOUNTPOINT);
let fs_stat = statfs::statfs(path);
if fs_stat.is_err() {
return false;
}
// FIXME notwork, nix will not compile CGROUP2_SUPER_MAGIC because not(target_env = "musl")
fs_stat.unwrap().filesystem_type() == statfs::CGROUP2_SUPER_MAGIC
}
pub const INIT_CGROUP_PATHS: &'static str = "/proc/1/cgroup";
#[cfg(all(target_os = "linux", target_env = "musl"))]
pub fn is_cgroup2_unified_mode() -> bool {
let lines = fs::read_to_string(INIT_CGROUP_PATHS);
if lines.is_err() {
return false;
}
for line in lines.unwrap().lines() {
let fields: Vec<&str> = line.split(':').collect();
if fields.len() != 3 {
continue;
}
if fields[0] != "0" {
return false;
}
}
true
}
pub fn auto() -> Box<dyn Hierarchy> {
if is_cgroup2_unified_mode() {
Box::new(V2::new())
} else {
Box::new(V1::new())
}
}
fn find_v1_mount() -> Option<String> {
/* Open mountinfo so we can get a parseable mount list */
// Open mountinfo so we can get a parseable mount list
let mountinfo_path = Path::new("/proc/self/mountinfo");
/* If /proc isn't mounted, or something else happens, then bail out */
// If /proc isn't mounted, or something else happens, then bail out
if mountinfo_path.exists() == false {
return None;
}
@@ -118,12 +275,22 @@ fn find_v1_mount() -> Option<String> {
let line = _line.unwrap();
let mut fields = line.split_whitespace();
let index = line.find(" - ").unwrap();
let mut more_fields = line[index + 3..].split_whitespace().collect::<Vec<_>>();
let fstype = more_fields[0];
if fstype == "tmpfs" && more_fields[2].contains("ro") {
let more_fields = line[index + 3..].split_whitespace().collect::<Vec<_>>();
if more_fields.len() == 0 {
continue;
}
if more_fields[0] == "cgroup" {
if more_fields.len() < 3 {
continue;
}
let cgroups_mount = fields.nth(4).unwrap();
println!("found cgroups at {:?}", cgroups_mount);
return Some(cgroups_mount.to_string());
if let Some(parent) = std::path::Path::new(cgroups_mount).parent() {
if let Some(path) = parent.as_os_str().to_str() {
debug!("found cgroups {:?} from {:?}", path, cgroups_mount);
return Some(path.to_string());
}
}
continue;
}
}

View File

@@ -1,13 +1,24 @@
// Copyright (c) 2018 Levente Kurusa
// Copyright (c) 2020 Ant Group
//
// SPDX-License-Identifier: Apache-2.0 or MIT
//
//! This module contains the implementation of the `hugetlb` cgroup subsystem.
//!
//!
//! See the Kernel's documentation for more information about this subsystem, found at:
//! [Documentation/cgroup-v1/hugetlb.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/hugetlb.txt)
use std::path::PathBuf;
use std::fs::File;
use std::io::{Write, Read};
use std::io::{Read, Write};
use std::path::PathBuf;
use {HugePageResources, Controllers, Controller, Resources, ControllIdentifier, Subsystem};
use crate::error::ErrorKind::*;
use crate::error::*;
use crate::flat_keyed_to_vec;
use crate::{
ControllIdentifier, ControllerInternal, Controllers, HugePageResources, Resources, Subsystem,
};
/// A controller that allows controlling the `hugetlb` subsystem of a Cgroup.
///
@@ -17,23 +28,41 @@ use {HugePageResources, Controllers, Controller, Resources, ControllIdentifier,
pub struct HugeTlbController {
base: PathBuf,
path: PathBuf,
sizes: Vec<String>,
v2: bool,
}
impl Controller for HugeTlbController {
fn control_type(self: &Self) -> Controllers { Controllers::HugeTlb }
fn get_path<'a>(self: &'a Self) -> &'a PathBuf { &self.path }
fn get_path_mut<'a>(self: &'a mut Self) -> &'a mut PathBuf { &mut self.path }
fn get_base<'a>(self: &'a Self) -> &'a PathBuf { &self.base }
impl ControllerInternal for HugeTlbController {
fn control_type(&self) -> Controllers {
Controllers::HugeTlb
}
fn get_path(&self) -> &PathBuf {
&self.path
}
fn get_path_mut(&mut self) -> &mut PathBuf {
&mut self.path
}
fn get_base(&self) -> &PathBuf {
&self.base
}
fn apply(self: &Self, res: &Resources) {
/* get the resources that apply to this controller */
fn is_v2(&self) -> bool {
self.v2
}
fn apply(&self, res: &Resources) -> Result<()> {
// get the resources that apply to this controller
let res: &HugePageResources = &res.hugepages;
if res.update_values {
for i in &res.limits {
self.set_limit_in_bytes(&i.size, i.limit);
let _ = self.set_limit_in_bytes(&i.size, i.limit);
if self.limit_in_bytes(&i.size)? != i.limit {
return Err(Error::new(Other));
}
}
}
Ok(())
}
}
@@ -51,68 +80,232 @@ impl<'a> From<&'a Subsystem> for &'a HugeTlbController {
_ => {
assert_eq!(1, 0);
::std::mem::uninitialized()
},
}
}
}
}
}
fn read_u64_from(mut file: File) -> Option<u64> {
fn read_u64_from(mut file: File) -> Result<u64> {
let mut string = String::new();
let _ = file.read_to_string(&mut string);
string.trim().parse().ok()
match file.read_to_string(&mut string) {
Ok(_) => string
.trim()
.parse()
.map_err(|e| Error::with_cause(ParseError, e)),
Err(e) => Err(Error::with_cause(ReadFailed, e)),
}
}
impl HugeTlbController {
/// Constructs a new `HugeTlbController` with `oroot` serving as the root of the control group.
pub fn new(oroot: PathBuf) -> Self {
pub fn new(oroot: PathBuf, v2: bool) -> Self {
let mut root = oroot;
root.push(Self::controller_type().to_string());
if !v2 {
root.push(Self::controller_type().to_string());
}
let sizes = get_hugepage_sizes().unwrap();
Self {
base: root.clone(),
path: root,
sizes: sizes,
v2: v2,
}
}
/// Whether the system supports `hugetlb_size` hugepages.
pub fn size_supported(self: &Self, _hugetlb_size: String) -> bool {
/* TODO */
true
pub fn size_supported(&self, hugetlb_size: &str) -> bool {
for s in &self.sizes {
if s == hugetlb_size {
return true;
}
}
false
}
pub fn get_sizes(&self) -> Vec<String> {
self.sizes.clone()
}
fn failcnt_v2(&self, hugetlb_size: &str) -> Result<u64> {
self.open_path(&format!("hugetlb.{}.events", hugetlb_size), false)
.and_then(flat_keyed_to_vec)
.and_then(|x| {
if x.len() == 0 {
return Err(Error::from_string(format!(
"get empty from hugetlb.{}.events",
hugetlb_size
)));
}
Ok(x[0].1 as u64)
})
}
/// Check how many times has the limit of `hugetlb_size` hugepages been hit.
pub fn failcnt(self: &Self, hugetlb_size: &String) -> Option<u64> {
pub fn failcnt(&self, hugetlb_size: &str) -> Result<u64> {
if self.v2 {
return self.failcnt_v2(hugetlb_size);
}
self.open_path(&format!("hugetlb.{}.failcnt", hugetlb_size), false)
.and_then(read_u64_from)
}
/// Get the limit (in bytes) of how much memory can be backed by hugepages of a certain size
/// (`hugetlb_size`).
pub fn limit_in_bytes(self: &Self, hugetlb_size: &String) -> Option<u64> {
pub fn limit_in_bytes(&self, hugetlb_size: &str) -> Result<u64> {
self.open_path(&format!("hugetlb.{}.limit_in_bytes", hugetlb_size), false)
.and_then(read_u64_from)
}
/// Get the current usage of memory that is backed by hugepages of a certain size
/// (`hugetlb_size`).
pub fn usage_in_bytes(self: &Self, hugetlb_size: &String) -> Option<u64> {
self.open_path(&format!("hugetlb.{}.usage_in_bytes", hugetlb_size), false)
.and_then(read_u64_from)
pub fn usage_in_bytes(&self, hugetlb_size: &str) -> Result<u64> {
let mut file = format!("hugetlb.{}.usage_in_bytes", hugetlb_size);
if self.v2 {
file = format!("hugetlb.{}.current", hugetlb_size);
}
self.open_path(&file, false).and_then(read_u64_from)
}
/// Get the maximum observed usage of memory that is backed by hugepages of a certain size
/// (`hugetlb_size`).
pub fn max_usage_in_bytes(self: &Self, hugetlb_size: &String) -> Option<u64> {
self.open_path(&format!("hugetlb.{}.max_usage_in_bytes", hugetlb_size), false)
.and_then(read_u64_from)
pub fn max_usage_in_bytes(&self, hugetlb_size: &str) -> Result<u64> {
self.open_path(
&format!("hugetlb.{}.max_usage_in_bytes", hugetlb_size),
false,
)
.and_then(read_u64_from)
}
/// Set the limit (in bytes) of how much memory can be backed by hugepages of a certain size
/// (`hugetlb_size`).
pub fn set_limit_in_bytes(self: &Self, hugetlb_size: &String, limit: u64) {
self.open_path(&format!("hugetlb.{}.limit_in_bytes", hugetlb_size), false)
.and_then(|mut file| {
file.write_all(limit.to_string().as_ref()).ok()
});
pub fn set_limit_in_bytes(&self, hugetlb_size: &str, limit: u64) -> Result<()> {
let mut file = format!("hugetlb.{}.limit_in_bytes", hugetlb_size);
if self.v2 {
file = format!("hugetlb.{}.max", hugetlb_size);
}
self.open_path(&file, true).and_then(|mut file| {
file.write_all(limit.to_string().as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
})
}
}
pub const HUGEPAGESIZE_DIR: &'static str = "/sys/kernel/mm/hugepages";
use regex::Regex;
use std::collections::HashMap;
use std::fs;
fn get_hugepage_sizes() -> Result<Vec<String>> {
let mut m = Vec::new();
let dirs = fs::read_dir(HUGEPAGESIZE_DIR);
if dirs.is_err() {
return Ok(m);
}
for e in dirs.unwrap() {
let entry = e.unwrap();
let name = entry.file_name().into_string().unwrap();
let parts: Vec<&str> = name.split('-').collect();
if parts.len() != 2 {
continue;
}
let bmap = get_binary_size_map();
let size = parse_size(parts[1], &bmap)?;
let dabbrs = get_decimal_abbrs();
m.push(custom_size(size as f64, 1024.0, &dabbrs));
}
Ok(m)
}
pub const KB: u128 = 1000;
pub const MB: u128 = 1000 * KB;
pub const GB: u128 = 1000 * MB;
pub const TB: u128 = 1000 * GB;
pub const PB: u128 = 1000 * TB;
pub const KiB: u128 = 1024;
pub const MiB: u128 = 1024 * KiB;
pub const GiB: u128 = 1024 * MiB;
pub const TiB: u128 = 1024 * GiB;
pub const PiB: u128 = 1024 * TiB;
pub fn get_binary_size_map() -> HashMap<String, u128> {
let mut m = HashMap::new();
m.insert("k".to_string(), KiB);
m.insert("m".to_string(), MiB);
m.insert("g".to_string(), GiB);
m.insert("t".to_string(), TiB);
m.insert("p".to_string(), PiB);
m
}
pub fn get_decimal_size_map() -> HashMap<String, u128> {
let mut m = HashMap::new();
m.insert("k".to_string(), KB);
m.insert("m".to_string(), MB);
m.insert("g".to_string(), GB);
m.insert("t".to_string(), TB);
m.insert("p".to_string(), PB);
m
}
pub fn get_decimal_abbrs() -> Vec<String> {
let m = vec![
"B".to_string(),
"KB".to_string(),
"MB".to_string(),
"GB".to_string(),
"TB".to_string(),
"PB".to_string(),
"EB".to_string(),
"ZB".to_string(),
"YB".to_string(),
];
m
}
fn parse_size(s: &str, m: &HashMap<String, u128>) -> Result<u128> {
let re = Regex::new(r"(?P<num>\d+)(?P<mul>[kKmMgGtTpP]?)[bB]?$");
if re.is_err() {
return Err(Error::new(InvalidBytesSize));
}
let caps = re.unwrap().captures(s).unwrap();
let num = caps.name("num");
let size: u128 = if num.is_some() {
let n = num.unwrap().as_str().trim().parse::<u128>();
if n.is_err() {
return Err(Error::new(InvalidBytesSize));
}
n.unwrap()
} else {
return Err(Error::new(InvalidBytesSize));
};
let q = caps.name("mul");
let mul: u128 = if q.is_some() {
let t = m.get(q.unwrap().as_str());
if t.is_some() {
*t.unwrap()
} else {
return Err(Error::new(InvalidBytesSize));
}
} else {
return Err(Error::new(InvalidBytesSize));
};
Ok(size * mul)
}
fn custom_size(mut size: f64, base: f64, m: &Vec<String>) -> String {
let mut i = 0;
while size >= base && i < m.len() - 1 {
size /= base;
i += 1;
}
format!("{}{}", size, m[i].as_str())
}

View File

@@ -1,36 +1,54 @@
use std::path::PathBuf;
// Copyright (c) 2018 Levente Kurusa
// Copyright (c) 2020 Ant Group
//
// SPDX-License-Identifier: Apache-2.0 or MIT
//
use log::*;
use std::collections::HashMap;
use std::fs::File;
use std::io::Write;
use std::io::{BufRead, BufReader, Read, Write};
use std::path::{Path, PathBuf};
pub mod hierarchies;
pub mod pid;
pub mod memory;
pub mod cpuset;
pub mod cpuacct;
pub mod cpu;
pub mod devices;
pub mod cgroup;
pub mod freezer;
pub mod net_cls;
pub mod blkio;
pub mod perf_event;
pub mod net_prio;
pub mod cgroup;
pub mod cgroup_builder;
pub mod cpu;
pub mod cpuacct;
pub mod cpuset;
pub mod devices;
pub mod error;
pub mod events;
pub mod freezer;
pub mod hierarchies;
pub mod hugetlb;
pub mod memory;
pub mod net_cls;
pub mod net_prio;
pub mod perf_event;
pub mod pid;
pub mod rdma;
pub mod systemd;
use pid::PidController;
use memory::MemController;
use cpuset::CpuSetController;
use cpuacct::CpuAcctController;
use cpu::CpuController;
use freezer::FreezerController;
use devices::DevicesController;
use net_cls::NetClsController;
use blkio::BlkIoController;
use perf_event::PerfEventController;
use net_prio::NetPrioController;
use hugetlb::HugeTlbController;
use rdma::RdmaController;
use crate::blkio::BlkIoController;
use crate::cpu::CpuController;
use crate::cpuacct::CpuAcctController;
use crate::cpuset::CpuSetController;
use crate::devices::DevicesController;
use crate::error::ErrorKind::*;
use crate::error::*;
use crate::freezer::FreezerController;
use crate::hugetlb::HugeTlbController;
use crate::memory::MemController;
use crate::net_cls::NetClsController;
use crate::net_prio::NetPrioController;
use crate::perf_event::PerfEventController;
use crate::pid::PidController;
use crate::rdma::RdmaController;
use crate::systemd::SystemdController;
pub use crate::cgroup::Cgroup;
/// Contains all the subsystems that are available in this crate.
#[derive(Debug)]
@@ -61,6 +79,8 @@ pub enum Subsystem {
HugeTlb(HugeTlbController),
/// Controller for the `Rdma` subsystem, see `RdmaController` for more information.
Rdma(RdmaController),
/// Controller for the `Systemd` subsystem, see `SystemdController` for more information.
Systemd(SystemdController),
}
#[doc(hidden)]
@@ -79,10 +99,11 @@ pub enum Controllers {
NetPrio,
HugeTlb,
Rdma,
Systemd,
}
impl Controllers {
pub fn to_string(self: &Self) -> String {
pub fn to_string(&self) -> String {
match self {
Controllers::Pids => return "pids".to_string(),
Controllers::Mem => return "memory".to_string(),
@@ -97,82 +118,189 @@ impl Controllers {
Controllers::NetPrio => return "net_prio".to_string(),
Controllers::HugeTlb => return "hugetlb".to_string(),
Controllers::Rdma => return "rdma".to_string(),
Controllers::Systemd => return "systemd".to_string(),
}
}
}
mod sealed {
use super::*;
pub trait ControllerInternal {
fn apply(&self, res: &Resources) -> Result<()>;
// meta stuff
fn control_type(&self) -> Controllers;
fn get_path(&self) -> &PathBuf;
fn get_path_mut(&mut self) -> &mut PathBuf;
fn get_base(&self) -> &PathBuf;
/// Hooks running after controller crated, if have
fn post_create(&self) {}
fn is_v2(&self) -> bool {
false
}
fn verify_path(&self) -> Result<()> {
if self.get_path().starts_with(self.get_base()) {
Ok(())
} else {
Err(Error::new(ErrorKind::InvalidPath))
}
}
fn open_path(&self, p: &str, w: bool) -> Result<File> {
let mut path = self.get_path().clone();
path.push(p);
self.verify_path()?;
if w {
match File::create(&path) {
Err(e) => return Err(Error::with_cause(ErrorKind::WriteFailed, e)),
Ok(file) => return Ok(file),
}
} else {
match File::open(&path) {
Err(e) => return Err(Error::with_cause(ErrorKind::ReadFailed, e)),
Ok(file) => return Ok(file),
}
}
}
fn get_max_value(&self, f: &str) -> Result<MaxValue> {
self.open_path(f, false).and_then(|mut file| {
let mut string = String::new();
let res = file.read_to_string(&mut string);
match res {
Ok(_) => parse_max_value(&string),
Err(e) => Err(Error::with_cause(ReadFailed, e)),
}
})
}
#[doc(hidden)]
fn path_exists(&self, p: &str) -> bool {
if let Err(_) = self.verify_path() {
return false;
}
std::path::Path::new(p).exists()
}
}
}
pub(crate) use crate::sealed::ControllerInternal;
/// A Controller is a subsystem attached to the control group.
///
/// Implementors are able to control certain aspects of a control group.
pub trait Controller {
#[doc(hidden)]
fn control_type(&self) -> Controllers;
/// The file system path to the controller.
fn path(&self) -> &Path;
/// Apply a set of resources to the Controller, invoking its internal functions to pass the
/// kernel the information.
fn apply(self: &Self, res: &Resources);
fn apply(&self, res: &Resources) -> Result<()>;
/* meta stuff */
#[doc(hidden)]
fn control_type(self: &Self) -> Controllers;
#[doc(hidden)]
fn get_path<'a>(self: &'a Self) -> &'a PathBuf;
#[doc(hidden)]
fn get_path_mut<'a>(self: &'a mut Self) -> &'a mut PathBuf;
#[doc(hidden)]
fn get_base<'a>(self: &'a Self) -> &'a PathBuf;
/// Create this controller
fn create(&self);
#[doc(hidden)]
fn verify_path(self: &Self) -> bool {
self.get_path().starts_with(self.get_base())
/// Does this controller already exist?
fn exists(&self) -> bool;
/// Delete the controller.
fn delete(&self);
/// Attach a task to this controller.
fn add_task(&self, pid: &CgroupPid) -> Result<()>;
/// Get the list of tasks that this controller has.
fn tasks(&self) -> Vec<CgroupPid>;
fn v2(&self) -> bool;
}
impl<T> Controller for T
where
T: ControllerInternal,
{
fn control_type(&self) -> Controllers {
ControllerInternal::control_type(self)
}
fn path(&self) -> &Path {
self.get_path()
}
/// Apply a set of resources to the Controller, invoking its internal functions to pass the
/// kernel the information.
fn apply(&self, res: &Resources) -> Result<()> {
ControllerInternal::apply(self, res)
}
/// Create this controller
fn create(self: &Self) {
if self.verify_path() {
match ::std::fs::create_dir(self.get_path()) {
Ok(_) => (),
Err(e) => println!("error create_dir {:?}", e),
}
fn create(&self) {
self.verify_path()
.expect(format!("path should be valid: {:?}", self.path()).as_str());
match ::std::fs::create_dir_all(self.get_path()) {
Ok(_) => self.post_create(),
Err(e) => warn!("error create_dir {:?}", e),
}
}
/// Does this controller already exist?
fn exists(self: &Self) -> bool {
fn exists(&self) -> bool {
self.get_path().exists()
}
/// Delete the controller.
fn delete(self: &Self) {
fn delete(&self) {
if self.get_path().exists() {
let _ = ::std::fs::remove_dir(self.get_path());
}
}
#[doc(hidden)]
fn open_path(self: &Self, p: &str, w: bool) -> Option<File> {
let mut path = self.get_path().clone();
path.push(p);
if !self.verify_path() {
return None;
}
if w {
match File::create(&path) {
Err(_) => return None,
Ok(file) => return Some(file),
}
} else {
match File::open(&path) {
Err(_) => return None,
Ok(file) => return Some(file),
}
libc_rmdir(self.get_path().to_str().unwrap());
}
}
/// Attach a task to this controller.
fn add_task(self: &Self, pid: &CgroupPid) {
self.open_path("tasks", true).and_then(|mut file| {
file.write_all(pid.pid.to_string().as_ref()).ok()
});
fn add_task(&self, pid: &CgroupPid) -> Result<()> {
let mut file = "tasks";
if self.is_v2() {
file = "cgroup.procs";
}
self.open_path(file, true).and_then(|mut file| {
file.write_all(pid.pid.to_string().as_ref())
.map_err(|e| Error::with_cause(ErrorKind::WriteFailed, e))
})
}
/// Get the list of tasks that this controller has.
fn tasks(&self) -> Vec<CgroupPid> {
let mut file = "tasks";
if self.is_v2() {
file = "cgroup.procs";
}
self.open_path(file, false)
.and_then(|file| {
let bf = BufReader::new(file);
let mut v = Vec::new();
for line in bf.lines() {
if let Ok(line) = line {
let n = line.trim().parse().unwrap_or(0u64);
v.push(n);
}
}
Ok(v.into_iter().map(CgroupPid::from).collect())
})
.unwrap_or(vec![])
}
fn v2(&self) -> bool {
self.is_v2()
}
}
@@ -185,13 +313,21 @@ pub trait ControllIdentifier {
/// implemented as well).
pub trait Hierarchy {
/// Returns what subsystems are supported by the hierarchy.
fn subsystems(self: &Self) -> Vec<Subsystem>;
fn subsystems(&self) -> Vec<Subsystem>;
/// Returns the root directory of the hierarchy.
fn root(self: &Self) -> PathBuf;
fn root(&self) -> PathBuf;
/// Return a handle to the root control group in the hierarchy.
fn root_control_group(&self) -> Cgroup;
fn v2(&self) -> bool;
/// Checks whether a certain subsystem is supported in the hierarchy.
///
/// This is an internal function and should not be used.
fn check_support(self: &Self, sub: Controllers) -> bool;
#[doc(hidden)]
fn check_support(&self, sub: Controllers) -> bool;
}
/// Resource limits for the memory subsystem.
@@ -200,16 +336,16 @@ pub struct MemoryResources {
/// Whether values should be applied to the controller.
pub update_values: bool,
/// How much memory (in bytes) can the kernel consume.
pub kernel_memory_limit: u64,
pub kernel_memory_limit: i64,
/// Upper limit of memory usage of the control group's tasks.
pub memory_hard_limit: u64,
pub memory_hard_limit: i64,
/// How much memory the tasks in the control group can use when the system is under memory
/// pressure.
pub memory_soft_limit: u64,
pub memory_soft_limit: i64,
/// How much of the kernel's memory (in bytes) can be used for TCP-related buffers.
pub kernel_tcp_memory_limit: u64,
pub kernel_tcp_memory_limit: i64,
/// How much memory and swap together can the tasks in the control group use.
pub memory_swap_limit: u64,
pub memory_swap_limit: i64,
/// Controls the tendency of the kernel to swap out parts of the address space of the tasks to
/// disk. Lower value implies less likely.
///
@@ -228,7 +364,7 @@ pub struct PidResources {
/// Note that attaching processes to the control group will still succeed _even_ if the limit
/// would be violated, however forks/clones inside the control group will have with `EAGAIN` if
/// they would violate the limit set here.
pub maximum_number_of_processes: pid::PidMax,
pub maximum_number_of_processes: MaxValue,
}
/// Resources limits about how the tasks can use the CPU.
@@ -236,14 +372,14 @@ pub struct PidResources {
pub struct CpuResources {
/// Whether values should be applied to the controller.
pub update_values: bool,
/* cpuset */
// cpuset
/// A comma-separated list of CPU IDs where the task in the control group can run. Dashes
/// between numbers indicate ranges.
pub cpus: String,
pub cpus: Option<String>,
/// Same syntax as the `cpus` field of this structure, but applies to memory nodes instead of
/// processors.
pub mems: String,
/* cpu */
// cpu
/// Weight of how much of the total CPU time should this control group get. Note that this is
/// hierarchical, so this is weighted against the siblings of this control group.
pub shares: u64,
@@ -263,13 +399,13 @@ pub struct DeviceResource {
/// If true, access to the device is allowed, otherwise it's denied.
pub allow: bool,
/// `'c'` for character device, `'b'` for block device; or `'a'` for all devices.
pub devtype: String,
pub devtype: crate::devices::DeviceType,
/// The major number of the device.
pub major: u64,
pub major: i64,
/// The minor number of the device.
pub minor: u64,
pub minor: i64,
/// Sequence of `'r'`, `'w'` or `'m'`, each denoting read, write or mknod permissions.
pub access: String,
pub access: Vec<crate::devices::DevicePermissions>,
}
/// Limit the usage of devices for the control group's tasks.
@@ -331,9 +467,9 @@ pub struct BlkIoDeviceResource {
/// The minor number of the device.
pub minor: u64,
/// The weight of the device against the descendant nodes.
pub weight: u16,
pub weight: Option<u16>,
/// The weight of the device against the sibling nodes.
pub leaf_weight: u16,
pub leaf_weight: Option<u16>,
}
/// Provides the ability to throttle a device (both byte/sec, and IO op/s)
@@ -353,9 +489,9 @@ pub struct BlkIoResources {
/// Whether values should be applied to the controller.
pub update_values: bool,
/// The weight of the control group against descendant nodes.
pub weight: u16,
pub weight: Option<u16>,
/// The weight of the control group against sibling nodes.
pub leaf_weight: u16,
pub leaf_weight: Option<u16>,
/// For each device, a separate weight (both normal and leaf) can be provided.
pub weight_device: Vec<BlkIoDeviceResource>,
/// Throttled read bytes/second can be provided for each device.
@@ -389,6 +525,7 @@ pub struct Resources {
/// A structure representing a `pid`. Currently implementations exist for `u64` and
/// `std::process::Child`.
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct CgroupPid {
/// The process identifier
pub pid: u64,
@@ -396,23 +533,18 @@ pub struct CgroupPid {
impl From<u64> for CgroupPid {
fn from(u: u64) -> CgroupPid {
CgroupPid {
pid: u,
}
CgroupPid { pid: u }
}
}
impl<'a> From<&'a std::process::Child> for CgroupPid {
fn from(u: &std::process::Child) -> CgroupPid {
CgroupPid {
pid: u.id() as u64,
}
CgroupPid { pid: u.id() as u64 }
}
}
impl Subsystem {
fn enter(self: Self, path: &String) -> Self {
fn enter(self, path: &Path) -> Self {
match self {
Subsystem::Pid(cont) => Subsystem::Pid({
let mut c = cont.clone();
@@ -479,10 +611,15 @@ impl Subsystem {
c.get_path_mut().push(path);
c
}),
Subsystem::Systemd(cont) => Subsystem::Systemd({
let mut c = cont.clone();
c.get_path_mut().push(path);
c
}),
}
}
fn to_controller(self: &Self) -> &dyn Controller {
fn to_controller(&self) -> &dyn Controller {
match self {
Subsystem::Pid(cont) => cont,
Subsystem::Mem(cont) => cont,
@@ -497,70 +634,149 @@ impl Subsystem {
Subsystem::NetPrio(cont) => cont,
Subsystem::HugeTlb(cont) => cont,
Subsystem::Rdma(cont) => cont,
Subsystem::Systemd(cont) => cont,
}
}
fn controller_name(&self) -> String {
self.to_controller().control_type().to_string()
}
}
/// The values for `memory.hight` or `pids.max`
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
pub enum MaxValue {
/// This value is returned when the text is `"max"`.
Max,
/// When the value is a numerical value, they are returned via this enum field.
Value(i64),
}
impl Default for MaxValue {
fn default() -> Self {
MaxValue::Max
}
}
impl MaxValue {
fn to_i64(&self) -> i64 {
match self {
MaxValue::Max => -1,
MaxValue::Value(num) => *num,
}
}
fn to_string(&self) -> String {
match self {
MaxValue::Max => "max".to_string(),
MaxValue::Value(num) => num.to_string(),
}
}
}
#[cfg(test)]
mod tests {
use {Resources, PidResources, Hierarchy, Controller, Controllers, Subsystem};
use pid::{PidMax, PidController};
use cgroup::Cgroup;
#[test]
fn create_and_delete_cgroup() {
let hier = ::hierarchies::V1::new();
let cg = Cgroup::new(&hier, String::from("ltest2"), 0);
{
let pidcontroller: &PidController = cg.controller_of().unwrap();
pidcontroller.set_pid_max(PidMax::Value(1337));
assert_eq!(pidcontroller.get_pid_max(), Some(PidMax::Value(1337)));
}
cg.delete();
pub fn parse_max_value(s: &String) -> Result<MaxValue> {
if s.trim() == "max" {
return Ok(MaxValue::Max);
}
#[test]
fn test_pid_pids_current_is_zero() {
let hier = ::hierarchies::V1::new();
let cg = Cgroup::new(&hier, String::from("ltest3"), 0);
{
let pidcontroller: &PidController = cg.controller_of().unwrap();
assert_eq!(pidcontroller.get_pid_current(), 0);
}
cg.delete();
match s.trim().parse() {
Ok(val) => Ok(MaxValue::Value(val)),
Err(e) => Err(Error::with_cause(ParseError, e)),
}
}
// Flat keyed
// KEY0 VAL0\n
// KEY1 VAL1\n
pub fn flat_keyed_to_vec(mut file: File) -> Result<Vec<(String, i64)>> {
let mut content = String::new();
file.read_to_string(&mut content)
.map_err(|e| Error::with_cause(ReadFailed, e))?;
let mut v = Vec::new();
for line in content.lines() {
let parts: Vec<&str> = line.split(' ').collect();
if parts.len() == 2 {
match parts[1].parse::<i64>() {
Ok(i) => {
v.push((parts[0].to_string(), i));
}
Err(_) => {}
}
}
}
Ok(v)
}
// Flat keyed
// KEY0 VAL0\n
// KEY1 VAL1\n
pub fn flat_keyed_to_hashmap(mut file: File) -> Result<HashMap<String, i64>> {
let mut content = String::new();
file.read_to_string(&mut content)
.map_err(|e| Error::with_cause(ReadFailed, e))?;
let mut h = HashMap::new();
for line in content.lines() {
let parts: Vec<&str> = line.split(' ').collect();
if parts.len() == 2 {
match parts[1].parse::<i64>() {
Ok(i) => {
h.insert(parts[0].to_string(), i);
}
Err(_) => {}
}
}
}
Ok(h)
}
// Nested keyed
// KEY0 SUB_KEY0=VAL00 SUB_KEY1=VAL01...
// KEY1 SUB_KEY0=VAL10 SUB_KEY1=VAL11...
pub fn nested_keyed_to_hashmap(mut file: File) -> Result<HashMap<String, HashMap<String, i64>>> {
let mut content = String::new();
file.read_to_string(&mut content)
.map_err(|e| Error::with_cause(ReadFailed, e))?;
let mut h = HashMap::new();
for line in content.lines() {
let parts: Vec<&str> = line.split(' ').collect();
if parts.len() == 0 {
continue;
}
let mut th = HashMap::new();
for item in parts[1..].into_iter() {
let fields: Vec<&str> = item.split('=').collect();
if fields.len() == 2 {
match fields[1].parse::<i64>() {
Ok(i) => {
th.insert(fields[0].to_string(), i);
}
Err(_) => {}
}
}
}
h.insert(parts[0].to_string(), th);
}
Ok(h)
}
/// fs::remove_dir_all or fs::remove_dir can't work with cgroup directory sometimes.
/// with error: `Os { code: 1, kind: PermissionDenied, message: "Operation not permitted" }`
pub fn libc_rmdir(p: &str) {
// with int return value
let _ = unsafe { libc::rmdir(p.as_ptr() as *const i8) };
}
/// read and parse an i64 data
pub fn read_i64_from(mut file: File) -> Result<i64> {
let mut string = String::new();
match file.read_to_string(&mut string) {
Ok(_) => string
.trim()
.parse()
.map_err(|e| Error::with_cause(ParseError, e)),
Err(e) => Err(Error::with_cause(ReadFailed, e)),
}
#[test]
fn test_pid_pids_events_is_zero() {
let hier = ::hierarchies::V1::new();
let cg = Cgroup::new(&hier, String::from("ltest4"), 0);
{
let pidcontroller: &PidController = cg.controller_of().unwrap();
assert_eq!(pidcontroller.get_pid_events(), 0);
}
cg.delete();
}
#[test]
fn test_setting_resources() {
let hier = ::hierarchies::V1::new();
let cg = Cgroup::new(&hier, String::from("ltest5"), 0);
{
let res = Resources {
pid: PidResources {
update_values: true,
maximum_number_of_processes: PidMax::Value(512),
},
..Default::default()
};
cg.apply(&res);
/* verify */
let pidcontroller: &PidController = cg.controller_of().unwrap();
assert_eq!(pidcontroller.get_pid_max(), Some(PidMax::Value(512)));
}
cg.delete();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,12 +1,22 @@
// Copyright (c) 2018 Levente Kurusa
//
// SPDX-License-Identifier: Apache-2.0 or MIT
//
//! This module contains the implementation of the `net_cls` cgroup subsystem.
//!
//!
//! See the Kernel's documentation for more information about this subsystem, found at:
//! [Documentation/cgroup-v1/net_cls.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/net_cls.txt)
use std::path::PathBuf;
use std::io::{Read, Write};
use std::fs::File;
use std::io::{Read, Write};
use std::path::PathBuf;
use {NetworkResources, Controllers, Controller, Resources, ControllIdentifier, Subsystem};
use crate::error::ErrorKind::*;
use crate::error::*;
use crate::{
ControllIdentifier, ControllerInternal, Controllers, NetworkResources, Resources, Subsystem,
};
/// A controller that allows controlling the `net_cls` subsystem of a Cgroup.
///
@@ -19,19 +29,31 @@ pub struct NetClsController {
path: PathBuf,
}
impl Controller for NetClsController {
fn control_type(self: &Self) -> Controllers { Controllers::NetCls }
fn get_path<'a>(self: &'a Self) -> &'a PathBuf { &self.path }
fn get_path_mut<'a>(self: &'a mut Self) -> &'a mut PathBuf { &mut self.path }
fn get_base<'a>(self: &'a Self) -> &'a PathBuf { &self.base }
impl ControllerInternal for NetClsController {
fn control_type(&self) -> Controllers {
Controllers::NetCls
}
fn get_path(&self) -> &PathBuf {
&self.path
}
fn get_path_mut(&mut self) -> &mut PathBuf {
&mut self.path
}
fn get_base(&self) -> &PathBuf {
&self.base
}
fn apply(self: &Self, res: &Resources) {
/* get the resources that apply to this controller */
fn apply(&self, res: &Resources) -> Result<()> {
// get the resources that apply to this controller
let res: &NetworkResources = &res.network;
if res.update_values {
self.set_class(res.class_id);
let _ = self.set_class(res.class_id);
if self.get_class()? != res.class_id {
return Err(Error::new(Other));
}
}
return Ok(());
}
}
@@ -49,16 +71,21 @@ impl<'a> From<&'a Subsystem> for &'a NetClsController {
_ => {
assert_eq!(1, 0);
::std::mem::uninitialized()
},
}
}
}
}
}
fn read_u64_from(mut file: File) -> Option<u64> {
fn read_u64_from(mut file: File) -> Result<u64> {
let mut string = String::new();
let _ = file.read_to_string(&mut string);
string.trim().parse().ok()
match file.read_to_string(&mut string) {
Ok(_) => string
.trim()
.parse()
.map_err(|e| Error::with_cause(ParseError, e)),
Err(e) => Err(Error::with_cause(ReadFailed, e)),
}
}
impl NetClsController {
@@ -71,19 +98,20 @@ impl NetClsController {
path: root,
}
}
/// Set the network class id of the outgoing packets of the control group's tasks.
pub fn set_class(self: &Self, class: u64) {
self.open_path("net_cls.classid", true).and_then(|mut file| {
let s = format!("{:#08X}", class);
file.write_all(s.as_ref()).ok()
});
pub fn set_class(&self, class: u64) -> Result<()> {
self.open_path("net_cls.classid", true)
.and_then(|mut file| {
let s = format!("{:#08X}", class);
file.write_all(s.as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
})
}
/// Get the network class id of the outgoing packets of the control group's tasks.
pub fn get_class(self: &Self) -> u64 {
self.open_path("net_cls.classid", false).and_then(|file| {
read_u64_from(file)
}).unwrap_or(0u64)
pub fn get_class(&self) -> Result<u64> {
self.open_path("net_cls.classid", false)
.and_then(|file| read_u64_from(file))
}
}

View File

@@ -1,13 +1,23 @@
// Copyright (c) 2018 Levente Kurusa
//
// SPDX-License-Identifier: Apache-2.0 or MIT
//
//! This module contains the implementation of the `net_prio` cgroup subsystem.
//!
//!
//! See the Kernel's documentation for more information about this subsystem, found at:
//! [Documentation/cgroup-v1/net_prio.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/net_prio.txt)
use std::path::PathBuf;
use std::io::{BufReader, BufRead, Write, Read};
use std::fs::File;
use std::collections::HashMap;
use std::fs::File;
use std::io::{BufRead, BufReader, Read, Write};
use std::path::PathBuf;
use {NetworkResources, Controllers, Controller, Resources, ControllIdentifier, Subsystem};
use crate::error::ErrorKind::*;
use crate::error::*;
use crate::{
ControllIdentifier, ControllerInternal, Controllers, NetworkResources, Resources, Subsystem,
};
/// A controller that allows controlling the `net_prio` subsystem of a Cgroup.
///
@@ -20,21 +30,31 @@ pub struct NetPrioController {
path: PathBuf,
}
impl Controller for NetPrioController {
fn control_type(self: &Self) -> Controllers { Controllers::NetPrio }
fn get_path<'a>(self: &'a Self) -> &'a PathBuf { &self.path }
fn get_path_mut<'a>(self: &'a mut Self) -> &'a mut PathBuf { &mut self.path }
fn get_base<'a>(self: &'a Self) -> &'a PathBuf { &self.base }
impl ControllerInternal for NetPrioController {
fn control_type(&self) -> Controllers {
Controllers::NetPrio
}
fn get_path(&self) -> &PathBuf {
&self.path
}
fn get_path_mut(&mut self) -> &mut PathBuf {
&mut self.path
}
fn get_base(&self) -> &PathBuf {
&self.base
}
fn apply(self: &Self, res: &Resources) {
/* get the resources that apply to this controller */
fn apply(&self, res: &Resources) -> Result<()> {
// get the resources that apply to this controller
let res: &NetworkResources = &res.network;
if res.update_values {
for i in &res.priorities {
self.set_if_prio(&i.name, i.priority);
let _ = self.set_if_prio(&i.name, i.priority);
}
}
Ok(())
}
}
@@ -52,16 +72,21 @@ impl<'a> From<&'a Subsystem> for &'a NetPrioController {
_ => {
assert_eq!(1, 0);
::std::mem::uninitialized()
},
}
}
}
}
}
fn read_u64_from(mut file: File) -> Option<u64> {
fn read_u64_from(mut file: File) -> Result<u64> {
let mut string = String::new();
let _ = file.read_to_string(&mut string);
string.trim().parse().ok()
match file.read_to_string(&mut string) {
Ok(_) => string
.trim()
.parse()
.map_err(|e| Error::with_cause(ParseError, e)),
Err(e) => Err(Error::with_cause(ReadFailed, e)),
}
}
impl NetPrioController {
@@ -76,31 +101,50 @@ impl NetPrioController {
}
/// Retrieves the current priority of the emitted packets.
pub fn prio_idx(self: &Self) -> u64 {
pub fn prio_idx(&self) -> u64 {
self.open_path("net_prio.prioidx", false)
.and_then(read_u64_from)
.unwrap_or(0)
}
/// A map of priorities for each network interface.
pub fn ifpriomap(self: &Self) -> HashMap<String, u64> {
pub fn ifpriomap(&self) -> Result<HashMap<String, u64>> {
self.open_path("net_prio.ifpriomap", false)
.and_then(|file| {
let bf = BufReader::new(file);
Some(bf.lines().map(|line| {
let l = line.unwrap();
let mut sp = l.split_whitespace();
(sp.nth(0).unwrap().to_string(),
sp.nth(1).unwrap().trim().parse().unwrap())
}).collect())
}).unwrap_or(HashMap::new())
bf.lines().fold(Ok(HashMap::new()), |acc, line| {
if acc.is_err() {
acc
} else {
let mut acc = acc.unwrap();
let l = line.unwrap();
let mut sp = l.split_whitespace();
let ifname = sp.nth(0);
let ifprio = sp.nth(1);
if ifname.is_none() || ifprio.is_none() {
Err(Error::new(ParseError))
} else {
let ifname = ifname.unwrap();
let ifprio = ifprio.unwrap().trim().parse();
match ifprio {
Err(e) => Err(Error::with_cause(ParseError, e)),
Ok(_) => {
acc.insert(ifname.to_string(), ifprio.unwrap());
Ok(acc)
}
}
}
}
})
})
}
/// Set the priority of the network traffic on `eif` to be `prio`.
pub fn set_if_prio(self: &Self, eif: &String, prio: u64) {
pub fn set_if_prio(&self, eif: &str, prio: u64) -> Result<()> {
self.open_path("net_prio.ifpriomap", true)
.and_then(|mut file| {
Some(file.write_all(format!("{} {}", eif, prio).as_ref()))
});
file.write_all(format!("{} {}", eif, prio).as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
})
}
}

View File

@@ -1,10 +1,17 @@
// Copyright (c) 2018 Levente Kurusa
//
// SPDX-License-Identifier: Apache-2.0 or MIT
//
//! This module contains the implementation of the `perf_event` cgroup subsystem.
//!
//!
//! See the Kernel's documentation for more information about this subsystem, found at:
//! [tools/perf/Documentation/perf-record.txt](https://raw.githubusercontent.com/torvalds/linux/master/tools/perf/Documentation/perf-record.txt)
use std::path::PathBuf;
use {Controllers, Controller, Resources, ControllIdentifier, Subsystem};
use crate::error::*;
use crate::{ControllIdentifier, ControllerInternal, Controllers, Resources, Subsystem};
/// A controller that allows controlling the `perf_event` subsystem of a Cgroup.
///
@@ -16,13 +23,22 @@ pub struct PerfEventController {
path: PathBuf,
}
impl Controller for PerfEventController {
fn control_type(self: &Self) -> Controllers { Controllers::PerfEvent }
fn get_path<'a>(self: &'a Self) -> &'a PathBuf { &self.path }
fn get_path_mut<'a>(self: &'a mut Self) -> &'a mut PathBuf { &mut self.path }
fn get_base<'a>(self: &'a Self) -> &'a PathBuf { &self.base }
impl ControllerInternal for PerfEventController {
fn control_type(&self) -> Controllers {
Controllers::PerfEvent
}
fn get_path(&self) -> &PathBuf {
&self.path
}
fn get_path_mut(&mut self) -> &mut PathBuf {
&mut self.path
}
fn get_base(&self) -> &PathBuf {
&self.base
}
fn apply(self: &Self, _res: &Resources) {
fn apply(&self, _res: &Resources) -> Result<()> {
Ok(())
}
}
@@ -40,7 +56,7 @@ impl<'a> From<&'a Subsystem> for &'a PerfEventController {
_ => {
assert_eq!(1, 0);
::std::mem::uninitialized()
},
}
}
}
}

View File

@@ -1,57 +1,76 @@
// Copyright (c) 2018 Levente Kurusa
// Copyright (c) 2020 Ant Group
//
// SPDX-License-Identifier: Apache-2.0 or MIT
//
//! This module contains the implementation of the `pids` cgroup subsystem.
//!
//!
//! See the Kernel's documentation for more information about this subsystem, found at:
//! [Documentation/cgroups-v1/pids.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/pids.txt)
use std::fs::File;
use std::io::{Read, Write};
use std::path::PathBuf;
use std::io::{Write, Read};
use {Resources, PidResources, Controller, ControllIdentifier, Subsystem, Controllers};
use crate::error::ErrorKind::*;
use crate::error::*;
use crate::{
parse_max_value, ControllIdentifier, ControllerInternal, Controllers, MaxValue, PidResources,
Resources, Subsystem,
};
/// A controller that allows controlling the `pids` subsystem of a Cgroup.
#[derive(Debug, Clone)]
pub struct PidController {
base: PathBuf,
path: PathBuf,
v2: bool,
}
/// The values found in the `pids.max` file in a Cgroup's `pids` subsystem.
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
pub enum PidMax {
/// This value is returned when the text found `pids.max` is `"max"`.
Max,
/// When the value in `pids.max` is a numerical value, they are returned via this enum field.
Value(i64),
}
impl Default for PidMax {
/// By default, (as per the kernel) `pids.max` should contain `"max"`.
fn default() -> Self {
PidMax::Max
impl ControllerInternal for PidController {
fn control_type(&self) -> Controllers {
Controllers::Pids
}
fn get_path(&self) -> &PathBuf {
&self.path
}
fn get_path_mut(&mut self) -> &mut PathBuf {
&mut self.path
}
fn get_base(&self) -> &PathBuf {
&self.base
}
}
impl Controller for PidController {
fn control_type(self: &Self) -> Controllers { Controllers::Pids }
fn get_path<'a>(self: &'a Self) -> &'a PathBuf { &self.path }
fn get_path_mut<'a>(self: &'a mut Self) -> &'a mut PathBuf { &mut self.path }
fn get_base<'a>(self: &'a Self) -> &'a PathBuf { &self.base }
fn is_v2(&self) -> bool {
self.v2
}
fn apply(self: &Self, res: &Resources) {
/* get the resources that apply to this controller */
fn apply(&self, res: &Resources) -> Result<()> {
// get the resources that apply to this controller
let pidres: &PidResources = &res.pid;
if pidres.update_values {
/* apply pid_max */
self.set_pid_max(pidres.maximum_number_of_processes);
// apply pid_max
let _ = self.set_pid_max(pidres.maximum_number_of_processes);
// now, verify
if self.get_pid_max()? == pidres.maximum_number_of_processes {
return Ok(());
} else {
return Err(Error::new(Other));
}
}
Ok(())
}
}
/*impl<'a> ControllIdentifier for &'a PidController {
fn controller_type() -> Controllers {
Controllers::Pids
}
}*/
// impl<'a> ControllIdentifier for &'a PidController {
// fn controller_type() -> Controllers {
// Controllers::Pids
// }
// }
impl ControllIdentifier for PidController {
fn controller_type() -> Controllers {
@@ -67,51 +86,69 @@ impl<'a> From<&'a Subsystem> for &'a PidController {
_ => {
assert_eq!(1, 0);
::std::mem::uninitialized()
},
}
}
}
}
}
fn read_u64_from(mut file: File) -> Result<u64> {
let mut string = String::new();
match file.read_to_string(&mut string) {
Ok(_) => string
.trim()
.parse()
.map_err(|e| Error::with_cause(ParseError, e)),
Err(e) => Err(Error::with_cause(ReadFailed, e)),
}
}
impl PidController {
/// Constructors a new `PidController` instance, with `oroot` serving as the controller's root
/// directory.
pub fn new(oroot: PathBuf) -> Self {
pub fn new(oroot: PathBuf, v2: bool) -> Self {
let mut root = oroot;
root.push(Self::controller_type().to_string());
if !v2 {
root.push(Self::controller_type().to_string());
}
Self {
base: root.clone(),
path: root,
v2: v2,
}
}
/// The number of times `fork` failed because the limit was hit.
pub fn get_pid_events(self: &Self) -> i64 {
pub fn get_pid_events(&self) -> Result<u64> {
self.open_path("pids.events", false).and_then(|mut file| {
let mut string = String::new();
let _ = file.read_to_string(&mut string);
Some(string.split_whitespace().nth(1).unwrap().parse().unwrap_or(0))
}).unwrap()
match file.read_to_string(&mut string) {
Ok(_) => match string.split_whitespace().nth(1) {
Some(elem) => match elem.parse() {
Ok(val) => Ok(val),
Err(e) => Err(Error::with_cause(ParseError, e)),
},
None => Err(Error::new(ParseError)),
},
Err(e) => Err(Error::with_cause(ReadFailed, e)),
}
})
}
/// The number of processes currently.
pub fn get_pid_current(self: &Self) -> i64 {
self.open_path("pids.current", false).and_then(|mut file| {
let mut string = String::new();
let _ = file.read_to_string(&mut string);
Some(string.trim().parse().unwrap_or(0))
}).unwrap()
pub fn get_pid_current(&self) -> Result<u64> {
self.open_path("pids.current", false)
.and_then(read_u64_from)
}
/// The maximum number of processes that can exist at one time in the control group.
pub fn get_pid_max(self: &Self) -> Option<PidMax> {
pub fn get_pid_max(&self) -> Result<MaxValue> {
self.open_path("pids.max", false).and_then(|mut file| {
let mut string = String::new();
let _ = file.read_to_string(&mut string);
if string.trim() == "max" {
Some(PidMax::Max)
} else {
Some(PidMax::Value(string.trim().parse().unwrap_or(0)))
let res = file.read_to_string(&mut string);
match res {
Ok(_) => parse_max_value(&string),
Err(e) => Err(Error::with_cause(ReadFailed, e)),
}
})
}
@@ -121,17 +158,13 @@ impl PidController {
/// Note that if `get_pid_current()` returns a higher number than what you
/// are about to set (`max_pid`), then no processess will be killed. Additonally, attaching
/// extra processes to a control group disregards the limit.
pub fn set_pid_max(self: &Self, max_pid: PidMax) {
pub fn set_pid_max(&self, max_pid: MaxValue) -> Result<()> {
self.open_path("pids.max", true).and_then(|mut file| {
let string_to_write = match max_pid {
PidMax::Max => "max".to_string(),
PidMax::Value(num) => num.to_string(),
};
let string_to_write = max_pid.to_string();
match file.write_all(string_to_write.as_ref()) {
Ok(_) => (),
Err(e) => println!("error {:?}", e),
Ok(_) => Ok(()),
Err(e) => Err(Error::with_cause(WriteFailed, e)),
}
Some(0i64)
});
})
}
}

View File

@@ -1,12 +1,20 @@
// Copyright (c) 2018 Levente Kurusa
//
// SPDX-License-Identifier: Apache-2.0 or MIT
//
//! This module contains the implementation of the `rdma` cgroup subsystem.
//!
//!
//! See the Kernel's documentation for more information about this subsystem, found at:
//! [Documentation/cgroup-v1/rdma.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/rdma.txt)
use std::path::PathBuf;
use std::io::{Write, Read};
use std::fs::File;
use std::io::{Read, Write};
use std::path::PathBuf;
use {Controllers, Controller, Resources, ControllIdentifier, Subsystem};
use crate::error::ErrorKind::*;
use crate::error::*;
use crate::{ControllIdentifier, ControllerInternal, Controllers, Resources, Subsystem};
/// A controller that allows controlling the `rdma` subsystem of a Cgroup.
///
@@ -18,13 +26,22 @@ pub struct RdmaController {
path: PathBuf,
}
impl Controller for RdmaController {
fn control_type(self: &Self) -> Controllers { Controllers::Rdma }
fn get_path<'a>(self: &'a Self) -> &'a PathBuf { &self.path }
fn get_path_mut<'a>(self: &'a mut Self) -> &'a mut PathBuf { &mut self.path }
fn get_base<'a>(self: &'a Self) -> &'a PathBuf { &self.base }
impl ControllerInternal for RdmaController {
fn control_type(&self) -> Controllers {
Controllers::Rdma
}
fn get_path(&self) -> &PathBuf {
&self.path
}
fn get_path_mut(&mut self) -> &mut PathBuf {
&mut self.path
}
fn get_base(&self) -> &PathBuf {
&self.base
}
fn apply(self: &Self, _res: &Resources) {
fn apply(&self, _res: &Resources) -> Result<()> {
Ok(())
}
}
@@ -42,16 +59,18 @@ impl<'a> From<&'a Subsystem> for &'a RdmaController {
_ => {
assert_eq!(1, 0);
::std::mem::uninitialized()
},
}
}
}
}
}
fn read_string_from(mut file: File) -> Option<String> {
fn read_string_from(mut file: File) -> Result<String> {
let mut string = String::new();
let _ = file.read_to_string(&mut string);
Some(string.trim().to_string())
match file.read_to_string(&mut string) {
Ok(_) => Ok(string.trim().to_string()),
Err(e) => Err(Error::with_cause(ReadFailed, e)),
}
}
impl RdmaController {
@@ -66,17 +85,16 @@ impl RdmaController {
}
/// Returns the current usage of RDMA/IB specific resources.
pub fn current(self: &Self) -> String {
pub fn current(&self) -> Result<String> {
self.open_path("rdma.current", false)
.and_then(read_string_from)
.unwrap_or("".to_string())
}
/// Set a maximum usage for each RDMA/IB resource.
pub fn set_max(self: &Self, max: &String) {
self.open_path("rdma.max", true)
.and_then(|mut file| {
file.write_all(max.as_ref()).ok()
});
pub fn set_max(&self, max: &str) -> Result<()> {
self.open_path("rdma.max", true).and_then(|mut file| {
file.write_all(max.as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
})
}
}

76
src/systemd.rs Normal file
View File

@@ -0,0 +1,76 @@
// Copyright (c) 2020 Ant Group
//
// SPDX-License-Identifier: Apache-2.0 or MIT
//
//! This module contains the implementation of the `systemd` cgroup subsystem.
//!
use std::path::PathBuf;
use crate::error::ErrorKind::*;
use crate::error::*;
use crate::{ControllIdentifier, ControllerInternal, Controllers, Resources, Subsystem};
/// A controller that allows controlling the `systemd` subsystem of a Cgroup.
///
#[derive(Debug, Clone)]
pub struct SystemdController {
base: PathBuf,
path: PathBuf,
v2: bool,
}
impl ControllerInternal for SystemdController {
fn control_type(&self) -> Controllers {
Controllers::Systemd
}
fn get_path(&self) -> &PathBuf {
&self.path
}
fn get_path_mut(&mut self) -> &mut PathBuf {
&mut self.path
}
fn get_base(&self) -> &PathBuf {
&self.base
}
fn apply(&self, _res: &Resources) -> Result<()> {
Ok(())
}
}
impl ControllIdentifier for SystemdController {
fn controller_type() -> Controllers {
Controllers::Systemd
}
}
impl<'a> From<&'a Subsystem> for &'a SystemdController {
fn from(sub: &'a Subsystem) -> &'a SystemdController {
unsafe {
match sub {
Subsystem::Systemd(c) => c,
_ => {
assert_eq!(1, 0);
::std::mem::uninitialized()
}
}
}
}
}
impl SystemdController {
/// Constructs a new `SystemdController` with `oroot` serving as the root of the control group.
pub fn new(oroot: PathBuf, v2: bool) -> Self {
let mut root = oroot;
if !v2 {
root.push(Self::controller_type().to_string());
}
Self {
base: root.clone(),
path: root,
v2: v2,
}
}
}

171
tests/builder.rs Normal file
View File

@@ -0,0 +1,171 @@
// Copyright (c) 2018 Levente Kurusa
// Copyright (c) 2020 And Group
//
// SPDX-License-Identifier: Apache-2.0 or MIT
//
//! Some simple tests covering the builder pattern for control groups.
use cgroups::blkio::*;
use cgroups::cgroup_builder::*;
use cgroups::cpu::*;
use cgroups::devices::*;
use cgroups::hugetlb::*;
use cgroups::memory::*;
use cgroups::net_cls::*;
use cgroups::pid::*;
use cgroups::*;
#[test]
pub fn test_cpu_res_build() {
let h = cgroups::hierarchies::auto();
let h = Box::new(&*h);
let cg: Cgroup = CgroupBuilder::new("test_cpu_res_build", h)
.cpu()
.shares(85)
.done()
.build();
{
let cpu: &CpuController = cg.controller_of().unwrap();
assert!(cpu.shares().is_ok());
assert_eq!(cpu.shares().unwrap(), 85);
}
cg.delete();
}
#[test]
pub fn test_memory_res_build() {
let h = cgroups::hierarchies::auto();
let h = Box::new(&*h);
let cg: Cgroup = CgroupBuilder::new("test_memory_res_build", h)
.memory()
.kernel_memory_limit(128 * 1024 * 1024)
.swappiness(70)
.memory_hard_limit(1024 * 1024 * 1024)
.done()
.build();
{
let c: &MemController = cg.controller_of().unwrap();
if !c.v2() {
assert_eq!(c.kmem_stat().limit_in_bytes, 128 * 1024 * 1024);
assert_eq!(c.memory_stat().swappiness, 70);
}
assert_eq!(c.memory_stat().limit_in_bytes, 1024 * 1024 * 1024);
}
cg.delete();
}
#[test]
pub fn test_pid_res_build() {
let h = cgroups::hierarchies::auto();
let h = Box::new(&*h);
let cg: Cgroup = CgroupBuilder::new("test_pid_res_build", h)
.pid()
.maximum_number_of_processes(MaxValue::Value(123))
.done()
.build();
{
let c: &PidController = cg.controller_of().unwrap();
assert!(c.get_pid_max().is_ok());
assert_eq!(c.get_pid_max().unwrap(), MaxValue::Value(123));
}
cg.delete();
}
#[test]
#[ignore] // ignore this test for now, not sure why my kernel doesn't like it
pub fn test_devices_res_build() {
let h = cgroups::hierarchies::auto();
let h = Box::new(&*h);
let cg: Cgroup = CgroupBuilder::new("test_devices_res_build", h)
.devices()
.device(1, 6, DeviceType::Char, true, vec![DevicePermissions::Read])
.done()
.build();
{
let c: &DevicesController = cg.controller_of().unwrap();
assert!(c.allowed_devices().is_ok());
assert_eq!(
c.allowed_devices().unwrap(),
vec![DeviceResource {
allow: true,
devtype: DeviceType::Char,
major: 1,
minor: 6,
access: vec![DevicePermissions::Read],
}]
);
}
cg.delete();
}
#[test]
pub fn test_network_res_build() {
let h = cgroups::hierarchies::auto();
if h.v2() {
// FIXME add cases for v2
return;
}
let h = Box::new(&*h);
let cg: Cgroup = CgroupBuilder::new("test_network_res_build", h)
.network()
.class_id(1337)
.done()
.build();
{
let c: &NetClsController = cg.controller_of().unwrap();
assert!(c.get_class().is_ok());
assert_eq!(c.get_class().unwrap(), 1337);
}
cg.delete();
}
#[test]
pub fn test_hugepages_res_build() {
let h = cgroups::hierarchies::auto();
if h.v2() {
// FIXME add cases for v2
return;
}
let h = Box::new(&*h);
let cg: Cgroup = CgroupBuilder::new("test_hugepages_res_build", h)
.hugepages()
.limit("2MB".to_string(), 4 * 2 * 1024 * 1024)
.done()
.build();
{
let c: &HugeTlbController = cg.controller_of().unwrap();
assert!(c.limit_in_bytes(&"2MB".to_string()).is_ok());
assert_eq!(
c.limit_in_bytes(&"2MB".to_string()).unwrap(),
4 * 2 * 1024 * 1024
);
}
cg.delete();
}
#[test]
#[ignore] // high version kernel not support `blkio.weight`
pub fn test_blkio_res_build() {
let h = cgroups::hierarchies::auto();
let h = Box::new(&*h);
let cg: Cgroup = CgroupBuilder::new("test_blkio_res_build", h)
.blkio()
.weight(Some(100))
.done()
.build();
{
let c: &BlkIoController = cg.controller_of().unwrap();
assert_eq!(c.blkio().weight, 100);
}
cg.delete();
}

117
tests/cgroup.rs Normal file
View File

@@ -0,0 +1,117 @@
// Copyright (c) 2018 Levente Kurusa
// Copyright (c) 2020 And Group
//
// SPDX-License-Identifier: Apache-2.0 or MIT
//
//! Simple unit tests about the control groups system.
use cgroups::memory::{MemController, SetMemory};
use cgroups::Controller;
use cgroups::{Cgroup, CgroupPid, Hierarchy, Subsystem};
use std::collections::HashMap;
#[test]
fn test_tasks_iterator() {
let h = cgroups::hierarchies::auto();
let h = Box::new(&*h);
let pid = libc::pid_t::from(nix::unistd::getpid()) as u64;
let cg = Cgroup::new(h, String::from("test_tasks_iterator"));
{
// Add a task to the control group.
cg.add_task(CgroupPid::from(pid)).unwrap();
use std::{thread, time};
thread::sleep(time::Duration::from_millis(100));
let mut tasks = cg.tasks().into_iter();
// Verify that the task is indeed in the control group
assert_eq!(tasks.next(), Some(CgroupPid::from(pid)));
assert_eq!(tasks.next(), None);
// Now, try removing it.
cg.remove_task(CgroupPid::from(pid));
tasks = cg.tasks().into_iter();
// Verify that it was indeed removed.
assert_eq!(tasks.next(), None);
}
cg.delete();
}
#[test]
fn test_cgroup_with_relative_paths() {
if cgroups::hierarchies::is_cgroup2_unified_mode() {
return;
}
let h = cgroups::hierarchies::auto();
let cgroup_root = h.root();
let h = Box::new(&*h);
let mut relative_paths = HashMap::new();
let mem_relative_path = "/mmm/abc/def";
relative_paths.insert("memory".to_string(), mem_relative_path.to_string());
let cgroup_name = "test_cgroup_with_relative_paths";
let cg = Cgroup::new_with_relative_paths(h, String::from(cgroup_name), relative_paths);
{
let subsystems = cg.subsystems();
subsystems.into_iter().for_each(|sub| match sub {
Subsystem::Pid(c) => {
let cgroup_path = c.path().to_str().unwrap();
let relative_path = "/pids/";
// cgroup_path = cgroup_root + relative_path + cgroup_name
assert_eq!(
cgroup_path,
format!(
"{}{}{}",
cgroup_root.to_str().unwrap(),
relative_path,
cgroup_name
)
);
}
Subsystem::Mem(c) => {
let cgroup_path = c.path().to_str().unwrap();
// cgroup_path = cgroup_root + relative_path + cgroup_name
assert_eq!(
cgroup_path,
format!(
"{}/memory{}/{}",
cgroup_root.to_str().unwrap(),
mem_relative_path,
cgroup_name
)
);
}
_ => {}
});
}
cg.delete();
}
#[test]
fn test_cgroup_v2() {
if !cgroups::hierarchies::is_cgroup2_unified_mode() {
return;
}
let h = cgroups::hierarchies::auto();
let h = Box::new(&*h);
let cg = Cgroup::new_with_relative_paths(h, String::from("test_v2"), HashMap::new());
let mem_controller: &MemController = cg.controller_of().unwrap();
let (mem, swp, rev) = (4 * 1024 * 1000, 2 * 1024 * 1000, 1024 * 1000);
let _ = mem_controller.set_limit(mem);
let _ = mem_controller.set_memswap_limit(swp);
let _ = mem_controller.set_soft_limit(rev);
let memory_stat = mem_controller.memory_stat();
println!("memory_stat {:?}", memory_stat);
assert_eq!(mem, memory_stat.limit_in_bytes);
assert_eq!(rev, memory_stat.soft_limit_in_bytes);
let memswap = mem_controller.memswap();
println!("memswap {:?}", memswap);
assert_eq!(swp, memswap.limit_in_bytes);
cg.delete();
}

61
tests/cpu.rs Normal file
View File

@@ -0,0 +1,61 @@
// Copyright (c) 2020 And Group
//
// SPDX-License-Identifier: Apache-2.0 or MIT
//
//! Simple unit tests about the CPU control groups system.
use cgroups::cpu::CpuController;
use cgroups::error::ErrorKind;
use cgroups::{Cgroup, CgroupPid, CpuResources, Hierarchy, Resources};
use std::fs;
#[test]
fn test_cfs_quota_and_periods() {
let h = cgroups::hierarchies::auto();
let h = Box::new(&*h);
let cg = Cgroup::new(h, String::from("test_cfs_quota_and_periods"));
let cpu_controller: &CpuController = cg.controller_of().unwrap();
let current_quota = cpu_controller.cfs_quota().unwrap();
let current_peroid = cpu_controller.cfs_period().unwrap();
// verify default value
// The default is “max 100000”.
assert_eq!(-1, current_quota);
assert_eq!(100000, current_peroid);
// case 1 set quota
let r = cpu_controller.set_cfs_quota(2000);
let current_quota = cpu_controller.cfs_quota().unwrap();
let current_peroid = cpu_controller.cfs_period().unwrap();
assert_eq!(2000, current_quota);
assert_eq!(100000, current_peroid);
// case 2 set period
cpu_controller.set_cfs_period(1000000);
let current_quota = cpu_controller.cfs_quota().unwrap();
let current_peroid = cpu_controller.cfs_period().unwrap();
assert_eq!(2000, current_quota);
assert_eq!(1000000, current_peroid);
// case 3 set both quota and period
cpu_controller.set_cfs_quota_and_period(Some(5000), Some(100000));
let current_quota = cpu_controller.cfs_quota().unwrap();
let current_peroid = cpu_controller.cfs_period().unwrap();
assert_eq!(5000, current_quota);
assert_eq!(100000, current_peroid);
// case 4 set both quota and period, set quota to -1
cpu_controller.set_cfs_quota_and_period(Some(-1), None);
let current_quota = cpu_controller.cfs_quota().unwrap();
let current_peroid = cpu_controller.cfs_period().unwrap();
assert_eq!(-1, current_quota);
assert_eq!(100000, current_peroid);
cg.delete();
}

96
tests/cpuset.rs Normal file
View File

@@ -0,0 +1,96 @@
// Copyright (c) 2018 Levente Kurusa
// Copyright (c) 2020 And Group
//
// SPDX-License-Identifier: Apache-2.0 or MIT
//
use cgroups::cpuset::CpuSetController;
use cgroups::error::ErrorKind;
use cgroups::{Cgroup, CgroupPid, CpuResources, Hierarchy, Resources};
use std::fs;
#[test]
fn test_cpuset_memory_pressure_root_cg() {
let h = cgroups::hierarchies::auto();
let h = Box::new(&*h);
let cg = Cgroup::new(h, String::from("test_cpuset_memory_pressure_root_cg"));
{
let cpuset: &CpuSetController = cg.controller_of().unwrap();
// This is not a root control group, so it should fail via InvalidOperation.
let res = cpuset.set_enable_memory_pressure(true);
assert_eq!(res.unwrap_err().kind(), &ErrorKind::InvalidOperation);
}
cg.delete();
}
#[test]
fn test_cpuset_set_cpus() {
let h = cgroups::hierarchies::auto();
let h = Box::new(&*h);
let cg = Cgroup::new(h, String::from("test_cpuset_set_cpus"));
{
let cpuset: &CpuSetController = cg.controller_of().unwrap();
let set = cpuset.cpuset();
if cg.v2() {
assert_eq!(0, set.cpus.len());
} else {
// for cgroup v1, cpuset is copied from parent.
assert_eq!(true, set.cpus.len() > 0);
}
// 0
let r = cpuset.set_cpus("0");
assert_eq!(true, r.is_ok());
let set = cpuset.cpuset();
assert_eq!(1, set.cpus.len());
assert_eq!((0, 0), set.cpus[0]);
// all cpus in system
let cpus =
fs::read_to_string("/sys/fs/cgroup/cpuset.cpus.effective").unwrap_or("".to_string());
let cpus = cpus.trim();
if cpus != "" {
let r = cpuset.set_cpus(&cpus);
assert_eq!(true, r.is_ok());
let set = cpuset.cpuset();
assert_eq!(1, set.cpus.len());
assert_eq!(format!("{}-{}", set.cpus[0].0, set.cpus[0].1), cpus);
}
}
cg.delete();
}
#[test]
fn test_cpuset_set_cpus_add_task() {
let h = cgroups::hierarchies::auto();
let h = Box::new(&*h);
let cg = Cgroup::new(h, String::from("test_cpuset_set_cpus_add_task/sub-dir"));
let cpuset: &CpuSetController = cg.controller_of().unwrap();
let set = cpuset.cpuset();
if cg.v2() {
assert_eq!(0, set.cpus.len());
} else {
// for cgroup v1, cpuset is copied from parent.
assert_eq!(true, set.cpus.len() > 0);
}
// Add a task to the control group.
let pid_i = libc::pid_t::from(nix::unistd::getpid()) as u64;
let _ = cg.add_task(CgroupPid::from(pid_i));
let tasks = cg.tasks();
assert_eq!(true, tasks.len() > 0);
println!("tasks after added: {:?}", tasks);
// remove task
let _ = cg.remove_task(CgroupPid::from(pid_i));
let tasks = cg.tasks();
println!("tasks after deleted: {:?}", tasks);
assert_eq!(0, tasks.len());
cg.delete();
}

68
tests/devices.rs Normal file
View File

@@ -0,0 +1,68 @@
// Copyright (c) 2018 Levente Kurusa
// Copyright (c) 2020 And Group
//
// SPDX-License-Identifier: Apache-2.0 or MIT
//
//! Integration tests about the devices subsystem
use cgroups::devices::{DevicePermissions, DeviceType, DevicesController};
use cgroups::{Cgroup, DeviceResource, Hierarchy};
#[test]
fn test_devices_parsing() {
// now only v2
if cgroups::hierarchies::is_cgroup2_unified_mode() {
return;
}
let h = cgroups::hierarchies::auto();
let h = Box::new(&*h);
let cg = Cgroup::new(h, String::from("test_devices_parsing"));
{
let devices: &DevicesController = cg.controller_of().unwrap();
// Deny access to all devices first
devices.deny_device(
DeviceType::All,
-1,
-1,
&vec![
DevicePermissions::Read,
DevicePermissions::Write,
DevicePermissions::MkNod,
],
);
// Acquire the list of allowed devices after we denied all
let allowed_devices = devices.allowed_devices();
// Verify that there are no devices that we can access.
assert!(allowed_devices.is_ok());
assert_eq!(allowed_devices.unwrap(), Vec::new());
// Now add mknod access to /dev/null device
devices.allow_device(DeviceType::Char, 1, 3, &vec![DevicePermissions::MkNod]);
let allowed_devices = devices.allowed_devices();
assert!(allowed_devices.is_ok());
let allowed_devices = allowed_devices.unwrap();
assert_eq!(allowed_devices.len(), 1);
assert_eq!(
allowed_devices[0],
DeviceResource {
allow: true,
devtype: DeviceType::Char,
major: 1,
minor: 3,
access: vec![DevicePermissions::MkNod],
}
);
// Now deny, this device explicitly.
devices.deny_device(DeviceType::Char, 1, 3, &DevicePermissions::all());
// Finally, check that.
let allowed_devices = devices.allowed_devices();
// Verify that there are no devices that we can access.
assert!(allowed_devices.is_ok());
assert_eq!(allowed_devices.unwrap(), Vec::new());
}
cg.delete();
}

48
tests/hugetlb.rs Normal file
View File

@@ -0,0 +1,48 @@
// Copyright (c) 2020 And Group
//
// SPDX-License-Identifier: Apache-2.0 or MIT
//
//! Integration tests about the hugetlb subsystem
use cgroups::hugetlb::{self, HugeTlbController};
use cgroups::Controller;
use cgroups::{Cgroup, Hierarchy};
use cgroups::error::ErrorKind::*;
use cgroups::error::*;
use std::fs;
#[test]
fn test_hugetlb_sizes() {
// now only v2
if cgroups::hierarchies::is_cgroup2_unified_mode() {
return;
}
let h = cgroups::hierarchies::auto();
let h = Box::new(&*h);
let cg = Cgroup::new(h, String::from("test_hugetlb_sizes"));
{
let hugetlb_controller: &HugeTlbController = cg.controller_of().unwrap();
let sizes = hugetlb_controller.get_sizes();
// test sizes count
let sizes = hugetlb_controller.get_sizes();
let sizes_count = fs::read_dir(hugetlb::HUGEPAGESIZE_DIR).unwrap().count();
assert_eq!(sizes.len(), sizes_count);
for size in sizes {
let supported = hugetlb_controller.size_supported(&size);
assert_eq!(supported, true);
assert_no_error(hugetlb_controller.failcnt(&size));
assert_no_error(hugetlb_controller.limit_in_bytes(&size));
assert_no_error(hugetlb_controller.usage_in_bytes(&size));
assert_no_error(hugetlb_controller.max_usage_in_bytes(&size));
}
}
cg.delete();
}
fn assert_no_error(r: Result<u64>) {
assert_eq!(!r.is_err(), true)
}

93
tests/memory.rs Normal file
View File

@@ -0,0 +1,93 @@
// Copyright (c) 2020 And Group
//
// SPDX-License-Identifier: Apache-2.0 or MIT
//
//! Integration tests about the hugetlb subsystem
use cgroups::memory::{MemController, SetMemory};
use cgroups::Controller;
use cgroups::{Cgroup, MaxValue};
#[test]
fn test_disable_oom_killer() {
let h = cgroups::hierarchies::auto();
let h = Box::new(&*h);
let cg = Cgroup::new(h, String::from("test_disable_oom_killer"));
{
let mem_controller: &MemController = cg.controller_of().unwrap();
// before disable
let m = mem_controller.memory_stat();
assert_eq!(m.oom_control.oom_kill_disable, false);
// now only v1
if !mem_controller.v2() {
// disable oom killer
let r = mem_controller.disable_oom_killer();
assert_eq!(r.is_err(), false);
// after disable
let m = mem_controller.memory_stat();
assert_eq!(m.oom_control.oom_kill_disable, true);
}
}
cg.delete();
}
#[test]
fn set_mem_v2() {
let h = cgroups::hierarchies::auto();
if !h.v2() {
return;
}
let h = Box::new(&*h);
let cg = Cgroup::new(h, String::from("set_mem_v2"));
{
let mem_controller: &MemController = cg.controller_of().unwrap();
// before disable
let m = mem_controller.get_mem().unwrap();
// case 1: get default value
assert_eq!(m.low, Some(MaxValue::Value(0)));
assert_eq!(m.min, Some(MaxValue::Value(0)));
assert_eq!(m.high, Some(MaxValue::Max));
assert_eq!(m.max, Some(MaxValue::Max));
// case 2: set parts
let m = SetMemory {
low: Some(MaxValue::Value(1024 * 1024 * 2)),
high: Some(MaxValue::Value(1024 * 1024 * 1024 * 2)),
min: Some(MaxValue::Value(1024 * 1024 * 3)),
max: None,
};
let r = mem_controller.set_mem(m);
assert_eq!(true, r.is_ok());
let m = mem_controller.get_mem().unwrap();
// get
assert_eq!(m.low, Some(MaxValue::Value(1024 * 1024 * 2)));
assert_eq!(m.min, Some(MaxValue::Value(1024 * 1024 * 3)));
assert_eq!(m.high, Some(MaxValue::Value(1024 * 1024 * 1024 * 2)));
assert_eq!(m.max, Some(MaxValue::Max));
// case 3: set parts
let m = SetMemory {
max: Some(MaxValue::Value(1024 * 1024 * 1024 * 2)),
min: Some(MaxValue::Value(1024 * 1024 * 4)),
high: Some(MaxValue::Max),
low: None,
};
let r = mem_controller.set_mem(m);
assert_eq!(true, r.is_ok());
let m = mem_controller.get_mem().unwrap();
// get
assert_eq!(m.low, Some(MaxValue::Value(1024 * 1024 * 2)));
assert_eq!(m.min, Some(MaxValue::Value(1024 * 1024 * 4)));
assert_eq!(m.max, Some(MaxValue::Value(1024 * 1024 * 1024 * 2)));
assert_eq!(m.high, Some(MaxValue::Max));
}
cg.delete();
}

109
tests/pids.rs Normal file
View File

@@ -0,0 +1,109 @@
// Copyright (c) 2018 Levente Kurusa
// Copyright (c) 2020 And Group
//
// SPDX-License-Identifier: Apache-2.0 or MIT
//
//! Integration tests about the pids subsystem
use cgroups::pid::PidController;
use cgroups::Controller;
use cgroups::{Cgroup, CgroupPid, Hierarchy, MaxValue, PidResources, Resources};
use nix::sys::wait::{waitpid, WaitStatus};
use nix::unistd::{fork, ForkResult, Pid};
use libc::pid_t;
use std::thread;
#[test]
fn create_and_delete_cgroup() {
let h = cgroups::hierarchies::auto();
let h = Box::new(&*h);
let cg = Cgroup::new(h, String::from("create_and_delete_cgroup"));
{
let pidcontroller: &PidController = cg.controller_of().unwrap();
pidcontroller.set_pid_max(MaxValue::Value(1337));
let max = pidcontroller.get_pid_max();
assert!(max.is_ok());
assert_eq!(max.unwrap(), MaxValue::Value(1337));
}
cg.delete();
}
#[test]
fn test_pids_current_is_zero() {
let h = cgroups::hierarchies::auto();
let h = Box::new(&*h);
let cg = Cgroup::new(h, String::from("test_pids_current_is_zero"));
{
let pidcontroller: &PidController = cg.controller_of().unwrap();
let current = pidcontroller.get_pid_current();
assert_eq!(current.unwrap(), 0);
}
cg.delete();
}
#[test]
fn test_pids_events_is_zero() {
let h = cgroups::hierarchies::auto();
let h = Box::new(&*h);
let cg = Cgroup::new(h, String::from("test_pids_events_is_zero"));
{
let pidcontroller: &PidController = cg.controller_of().unwrap();
let events = pidcontroller.get_pid_events();
assert!(events.is_ok());
assert_eq!(events.unwrap(), 0);
}
cg.delete();
}
#[test]
fn test_pid_events_is_not_zero() {
let h = cgroups::hierarchies::auto();
let h = Box::new(&*h);
let cg = Cgroup::new(h, String::from("test_pid_events_is_not_zero"));
{
let pids: &PidController = cg.controller_of().unwrap();
let before = pids.get_pid_events();
let before = before.unwrap();
match fork() {
Ok(ForkResult::Parent { child, .. }) => {
// move the process into the control group
let _ = pids.add_task(&(pid_t::from(child) as u64).into());
println!("added task to cg: {:?}", child);
// Set limit to one
let _ = pids.set_pid_max(MaxValue::Value(1));
println!("current pid.max = {:?}", pids.get_pid_max());
// wait on the child
let res = waitpid(child, None);
if let Ok(WaitStatus::Exited(_, e)) = res {
assert_eq!(e, 0i32);
} else {
panic!("found result: {:?}", res);
}
// Check pids.events
let events = pids.get_pid_events();
assert!(events.is_ok());
assert_eq!(events.unwrap(), before + 1);
}
Ok(ForkResult::Child) => loop {
let pids_max = pids.get_pid_max();
if pids_max.is_ok() && pids_max.unwrap() == MaxValue::Value(1) {
if let Err(_) = fork() {
unsafe { libc::exit(0) };
} else {
unsafe { libc::exit(1) };
}
}
},
Err(_) => panic!("failed to fork"),
}
}
cg.delete();
}

33
tests/resources.rs Normal file
View File

@@ -0,0 +1,33 @@
// Copyright (c) 2018 Levente Kurusa
// Copyright (c) 2020 And Group
//
// SPDX-License-Identifier: Apache-2.0 or MIT
//
//! Integration test about setting resources using `apply()`
use cgroups::pid::PidController;
use cgroups::{Cgroup, Hierarchy, MaxValue, PidResources, Resources};
#[test]
fn pid_resources() {
let h = cgroups::hierarchies::auto();
let h = Box::new(&*h);
let cg = Cgroup::new(h, String::from("pid_resources"));
{
let res = Resources {
pid: PidResources {
update_values: true,
maximum_number_of_processes: MaxValue::Value(512),
},
..Default::default()
};
cg.apply(&res);
// verify
let pidcontroller: &PidController = cg.controller_of().unwrap();
let pid_max = pidcontroller.get_pid_max();
assert_eq!(pid_max.is_ok(), true);
assert_eq!(pid_max.unwrap(), MaxValue::Value(512));
}
cg.delete();
}

24
tools/create_cgroup.sh Executable file
View File

@@ -0,0 +1,24 @@
#!/bin/sh
#
# Copyright (c) 2018 Levente Kurusa
#
# SPDX-License-Identifier: Apache-2.0 or MIT
#
CONTROL_GROUPS=`cargo test -- --list 2>/dev/null | egrep 'test$' | egrep -v '^src' | cut -d':' -f1`
echo This script will create a control group in every subsystem of the V1 hierarchy.
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.
sudo -v
for i in ${CONTROL_GROUPS}
do sudo mkdir -p /sys/fs/cgroup/{blkio,cpu,cpuacct,cpuset,devices,freezer,hugetlb,memory,net_cls,net_prio,perf_event,pids}/$i/
done
echo
echo We will now set up permissions...
echo
for i in ${CONTROL_GROUPS}
do sudo chown -R ${USER} /sys/fs/cgroup/{blkio,cpu,cpuacct,cpuset,devices,freezer,hugetlb,memory,net_cls,net_prio,perf_event,pids}/$i/
done

19
tools/delete_cgroup.sh Executable file
View File

@@ -0,0 +1,19 @@
#!/bin/sh
#
# Copyright (c) 2018 Levente Kurusa
#
# SPDX-License-Identifier: Apache-2.0 or MIT
#
CONTROL_GROUPS=`cargo test -- --list 2>/dev/null | egrep 'test$' | egrep -v '^src' | cut -d':' -f1`
echo This script will delete the control groups created by the create_cgroup.sh shell script.
echo
echo It may spit out some errors, but that is fine.
echo
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.
sudo -v
for i in ${CONTROL_GROUPS}
do sudo rmdir /sys/fs/cgroup/{blkio,cpu,cpuacct,cpuset,devices,freezer,hugetlb,memory,net_cls,net_prio,perf_event,pids}/$i/
done