Compare commits

..

66 Commits
v0.2.3 ... v0.2

Author SHA1 Message Date
Tim Zhang
0348f0a95e Merge pull request #94 from liubin/fix/93-include-path-in-error-message
error: introduce thiserror to add path to error message
2022-11-28 10:42:47 +08:00
bin liu
d387c6edc7 error: introduce thiserror to add path to error message
For some cgroup file operations, when failed, add the path
and the value (for write operation) to the error message.

Fixes: #93

Signed-off-by: bin liu <liubin0329@gmail.com>
2022-11-18 17:46:02 +08:00
Fupan Li
4d8f704a4b Merge pull request #89 from Tim-Zhang/release-0.2.11
release: v0.2.11
2022-09-23 19:14:29 +08:00
Tim Zhang
860b484a30 release: v0.2.11
Release version 0.2.11

Signed-off-by: Tim Zhang <tim@hyper.sh>
2022-09-23 19:10:10 +08:00
Tim Zhang
99da9eeb1f Merge pull request #88 from Tim-Zhang/customized-attr-blkio
blkio: Add support for customized-attributes
2022-09-23 19:09:11 +08:00
Tim Zhang
ed49cf77e2 blkio: Add support for customized-attributes
Customized attributes are useful for customized kernels

Signed-off-by: Tim Zhang <tim@hyper.sh>
2022-09-23 17:43:17 +08:00
Tim Zhang
8f65a0ef89 Merge pull request #87 from quanweiZhou/add-retry-for-rmdir
fix: support retry for rmdir cgroup path
2022-09-23 11:07:20 +08:00
quanwei.zqw
f863f31395 fix: support retry for rmdir cgroup path
Compatible with runC for remove dir operation
https://github.com/opencontainers/runc/blob/main/libcontainer/cgroups/utils.go#L272

Signed-off-by: quanwei.zqw <quanwei.zqw@alibaba-inc.com>
2022-09-23 10:26:03 +08:00
Fupan Li
91e66f0197 Merge pull request #83 from Tim-Zhang/release-0.2.10
release: v0.2.10
2022-06-29 16:48:56 +08:00
Tim Zhang
3340211c6e release: v0.2.10
Release version 0.2.10

Signed-off-by: Tim Zhang <tim@hyper.sh>
2022-06-29 16:44:00 +08:00
Tim Zhang
bd31dc0e7d Merge pull request #82 from dubek/ignore-set-kmem-limit-unsupported
memory: set_kmem_limit: ignore Unsupported error
2022-06-29 10:21:49 +08:00
Dov Murik
0c908cddf8 memory: set_kmem_limit: ignore Unsupported error
Setting (writing into) `memory.kmem.limit_in_bytes` is not supported in
Linux kernel >= 5.16 (see kernel commit 58056f77502: "memcg, kmem:
further deprecate kmem.limit_in_bytes"):
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=58056f77502

If the write call in `set_kmem_limit()` returns EOPNOTSUPP, log a
warning message but ignore the error (do nothing).

Add a unit-test for `set_kmem_limit` in cgroups v1.

Fix #81

Signed-off-by: Dov Murik <dov.murik1@il.ibm.com>
2022-06-27 08:29:40 +00:00
Tim Zhang
556dea62b9 Merge pull request #78 from esrlabs/pr-clippy
clippy: Fix clippy version 0.1.60
2022-06-01 18:50:25 +08:00
Bin Liu
1a8f9823eb Merge pull request #79 from esrlabs/pr-android
fix: build for aarch64-linux-android
2022-05-25 23:01:33 +08:00
Bin Liu
dda639f5ab Merge pull request #77 from rtzoeller/nix_0.24
Upgrade nix to 0.24, limit features
2022-05-25 22:58:46 +08:00
Ryan Zoeller
476219ab93 Upgrade nix to 0.24, limit features
This removes memoffset as an indirect dependency, and should decrease build times slightly.

Signed-off-by: Ryan Zoeller <rtzoeller@rtzoeller.com>
2022-04-30 18:37:57 -05:00
Felix Obenhuber
138c85c4b1 fix: build for aarch64-linux-android
With #77 nix provides statfs::CGROUP2_SUPER_MAGIC for the target_os
"android". Fix the build build for `target_os = "android"` by adding
this cfg setting to the "linux and not musl" impl of
`is_cgroup2_unified_mode`.

This patch depends opn #77 beeing merged.

Signed-off-by: Felix Obenhuber <felix@obenhuber.de>
2022-04-28 16:39:58 +02:00
Felix Obenhuber
92122de48d clippy: Fix clippy version 0.1.60
Fix clippy lints from clippy 0.1.60 (7737e0b 2022-04-04).

Signed-off-by: Felix Obenhuber <felix@obenhuber.de>
2022-04-28 16:36:57 +02:00
Fupan Li
1df6e7a26e Merge pull request #75 from Tim-Zhang/release-0.2.9
release: v0.2.9
2022-03-03 11:39:13 +08:00
Tim Zhang
1bdd52470f release: v0.2.9
Release version 0.2.9

Signed-off-by: Tim Zhang <tim@hyper.sh>
2022-03-03 10:37:13 +08:00
Fupan Li
231d9d599e Merge pull request #74 from Tim-Zhang/avoid-panic-in-hugetlb
hugetlb: avoid panic caused by readdir in get_hugepage_sizes()
2022-03-02 18:12:48 +08:00
Fupan Li
a3bd03c662 Merge pull request #73 from Burning1020/pid-cgroup-path
cgroup: support get cgroup relative paths by process pid
2022-03-02 18:12:10 +08:00
Tim Zhang
8932df3fa0 hugetlb: avoid panic caused by readdir in get_hugepage_sizes()
The error EBADF might be occured during readdir()
Skip the size when the error apperar, do not panic.

Signed-off-by: Tim Zhang <tim@hyper.sh>
2022-03-02 17:07:50 +08:00
Zhang Tianyang
fa94a1174f cgroup: support get cgroup relative paths by process pid
support get cgroup relative paths by process pid

Signed-off-by: Zhang Tianyang <burning9699@gmail.com>
2022-03-02 16:48:05 +08:00
Tim Zhang
b4df6016b3 Merge pull request #71 from Tim-Zhang/release-0.2.8
release: v0.2.8
2021-12-17 18:27:13 +08:00
Tim Zhang
0686400268 release: v0.2.8
Bump version from 0.2.7 to 0.2.8

Signed-off-by: Tim Zhang <tim@hyper.sh>
2021-12-17 17:40:45 +08:00
Tim Zhang
6986c49e70 Merge pull request #67 from jodh-intel/update-nix-version
cargo: Update nix to latest version
2021-11-30 17:58:31 +08:00
James O. D. Hunt
e0bf36ae23 cargo: Update nix to latest version
Update the `nix` crate to version `0.23.0` to resolve a dependency
issue related to making `cargo audit` run cleanly for the Kata
Containers agent.

See:

https://github.com/kata-containers/kata-containers/pull/3125

Fixes: #66.

Signed-off-by: James O. D. Hunt <james.o.hunt@intel.com>
2021-11-26 11:17:58 +00:00
Fupan Li
2c68f36488 Merge pull request #65 from Tim-Zhang/release-0.2.7
release: v0.2.7
2021-11-11 11:36:20 +08:00
Tim Zhang
0bc1b9821b release: v0.2.7
Bump version from 0.2.6 to 0.2.7

Signed-off-by: Tim Zhang <tim@hyper.sh>
2021-11-11 11:25:28 +08:00
Tim Zhang
da75ba25d6 Merge pull request #59 from jmagnuson/update-nix-0.20.2
deps: update nix to 0.20.2
2021-11-11 11:23:08 +08:00
Jon Magnuson
01475ad515 clippy: fix needless-borrow in test
Fix needless-borrow in test.

Signed-off-by: Jon Magnuson <jon.magnuson@gmail.com>
2021-11-11 11:20:49 +08:00
Tim Zhang
417badd05f Merge pull request #61 from mjerabek/fix-freezer-for-v2
fix: support FreezerController in cgroups v2
2021-11-11 11:11:38 +08:00
Tim Zhang
1720443d58 Merge pull request #63 from quanweiZhou/fix_read_task_hang
fix: read task hang when the cgroup dir removed after open
2021-11-11 11:09:35 +08:00
quanwei.zqw
fb55383273 fix: read task hang when the cgroup dir removed after open
Fixes #62
Signed-off-by: quanweiZhou <quanweiZhou@linux.alibaba.com>
2021-11-10 19:52:42 +08:00
Tim Zhang
0233c1e046 Merge pull request #64 from Tim-Zhang/action-lock-rust-version
action: Lock rust version and fix commit-body checker
2021-11-10 19:39:00 +08:00
Tim Zhang
bf5af7b195 action: fix the the body line checker
the original regex can not handle pasted error message
rightly. this commit will fix this.

Signed-off-by: Tim Zhang <tim@hyper.sh>
2021-11-10 19:34:30 +08:00
Tim Zhang
2e3719dbd5 action: Lock rust version to 1.52
Got the following error in the clippy:
error: this expression borrows a reference (`&str`) that is immediately dereferenced by the compiler
  --> tests/cpuset.rs:54:37
   |
54 |             let r = cpuset.set_cpus(&cpus);
   |                                     ^^^^^ help: change this to: `cpus`
   |
   = note: `-D clippy::needless-borrow` implied by `-D warnings`
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow

But we did not touch the code, just becuase the clippy rules was updated
follow the rust update.

To avoid this problem we lock the rust version in CI and make it
steady.

Signed-off-by: Tim Zhang <tim@hyper.sh>
2021-11-10 19:29:14 +08:00
Martin Jeřábek
4231f35a44 fix: support FreezerController in cgroups v2
Fixes #60

Signed-off-by: Martin Jeřábek <martin.jerabek01@gmail.com>
2021-11-10 11:05:33 +01:00
Jon Magnuson
88b7aafd05 deps: update nix to 0.20.2
Enforces use of a version of nix which contains a patch for
RUSTSEC-2021-0119.

Fixes #58

Signed-off-by: Jon Magnuson <jon.magnuson@gmail.com>
2021-10-28 22:27:58 -05:00
Fupan Li
63750887c2 Merge pull request #56 from flxo/pr-serde
Add Serialize/Deserialize to resource and stat types
2021-08-04 10:10:28 +08:00
Felix Obenhuber
09499e7614 Add Serialize/Deserialize to resource and stat types
Add feature "serde" that derives all resource and statistics types from
Serialize and Deserialize. The feature is turned off by default.

Fixes #54
Fixes #55
Fixes #56

Signed-off-by: Felix Obenhuber <felix@obenhuber.de>
2021-08-03 09:55:10 +02:00
Fupan Li
bcb7c6cd54 Merge pull request #51 from Tim-Zhang/release-0.2.6
release: v0.2.6
2021-06-30 20:53:39 +08:00
Tim Zhang
2554aa65d0 release: v0.2.6
Bump version from 0.2.5 to 0.2.6

Signed-off-by: Tim Zhang <tim@hyper.sh>
2021-06-30 20:36:34 +08:00
Tim Zhang
34f935be89 Merge pull request #49 from lifupan/master
freezer: fix the issue of missing trim the str
2021-06-30 20:35:22 +08:00
Fupan Li
5485d8dd46 Merge pull request #50 from Tim-Zhang/fix-clippy-for-rust-1.53
Fix clippy for rust 1.53
2021-06-30 19:58:48 +08:00
Tim Zhang
ec4cda1dd9 Fix clippy for rust 1.53
There are new lints are added in clippy for rust 1.53

Signed-off-by: Tim Zhang <tim@hyper.sh>
2021-06-30 19:07:56 +08:00
fupan.lfp
4b5a190ecc freezer: fix the issue of missing trim the str
When reading from the freezer file, it should trim it
first, and the string may container an '\n'.

Fixes: #48

Signed-off-by: fupan.lfp <fupan.lfp@antgroup.com>
2021-06-30 18:39:33 +08:00
Fupan Li
45b626e0c0 Merge pull request #46 from Tim-Zhang/fix-clippy-for-rust-1.52
Fix clippy for rust 1.52
2021-05-21 16:24:16 +08:00
Tim Zhang
0e2430fde1 clippy: turn on lint upper_case_acronyms
cargo-clippy has moved the upper_case_acronyms lint to
pedantic(removed from the default list), but we need
the lint to keep names consistent and follow the rust
naming conventions.

Signed-off-by: Tim Zhang <tim@hyper.sh>
2021-05-21 00:48:50 +08:00
Tim Zhang
5aa7e6c90e Fix clippy for rust 1.52
There are new lints are added in clippy for rust 1.52

Signed-off-by: Tim Zhang <tim@hyper.sh>
2021-05-21 00:45:27 +08:00
Fupan Li
5bb27a2692 Merge pull request #45 from Tim-Zhang/release-0.2.5
release: v0.2.5
2021-03-24 14:52:27 +08:00
Tim Zhang
0b2a0405e2 release: v0.2.5
Bump version to 0.2.5

Signed-off-by: Tim Zhang <tim@hyper.sh>
2021-03-24 14:37:19 +08:00
Bin Liu
c4850ef2ef Merge pull request #44 from Tim-Zhang/impl-clone
Impl Clone for hierarchies:V1, hierarchies:V2
2021-03-23 22:31:27 +08:00
Tim Zhang
aa207edca8 Impl Clone for hierarchies:V1, hierarchies:V2
Clone is useful.

Signed-off-by: Tim Zhang <tim@hyper.sh>
2021-03-23 21:50:18 +08:00
Bin Liu
af7a75ce9e Merge pull request #40 from Jakob-Naucke/v0.2.4
release: v0.2.4
2021-03-03 15:39:15 +08:00
Bin Liu
fb56c817ca Merge pull request #32 from Tim-Zhang/fix-clippy
Fix clippy warnings
2021-03-03 15:38:37 +08:00
Tim Zhang
fdabe52401 github action: Add clippy check
Because the existing warnings are all fixed.

Signed-off-by: Tim Zhang <tim@hyper.sh>
2021-03-03 13:46:19 +08:00
Tim Zhang
9ec1010a15 Fix clippy warnings
Fix clippy warnings.

Signed-off-by: Tim Zhang <tim@hyper.sh>
2021-03-03 13:44:29 +08:00
Fupan Li
610071730d Merge pull request #41 from bergwolf/fix-panic
memory: fix parse_oom_control panic on older kernels
2021-03-03 00:17:54 +08:00
Peng Tao
0a3e4a828f memory: fix parse_oom_control panic on older kernels
Where the oom control fields might not be present.

Fixes: #36
Signed-off-by: Peng Tao <bergwolf@hyper.sh>
2021-02-26 01:40:19 +08:00
Jakob Naucke
60820bfffb release: v0.2.4
Bump version from 0.2.3 to 0.2.4

Signed-off-by: Jakob Naucke <jakob.naucke@ibm.com>
2021-02-25 11:22:47 +01:00
Fupan Li
2209189244 Merge pull request #39 from bergwolf/fix-panic
memory: fix panic when no hierarchical numa stat is available
2021-02-25 14:57:39 +08:00
Peng Tao
8720aed656 memory: fix panic when no hierarchical numa stat is available
For older kernels, it is possible that there is no hierarchical numa
stats.

Fixes: #36
Signed-off-by: Peng Tao <bergwolf@hyper.sh>
2021-02-25 11:00:48 +08:00
Fupan Li
16d73e1d59 Merge pull request #38 from Jakob-Naucke/update-nix
Update nix to 0.20.0
2021-02-25 09:47:01 +08:00
Jakob Naucke
b9ca0a51f1 Update nix to 0.20.0
to pull in https://github.com/nix-rust/nix/pull/1372 and get statfs
MAGIC constants on s390x. Additionally, fork() calls in tests now have
to be marked unsafe.

Fixes: #37

Signed-off-by: Jakob Naucke <jakob.naucke@ibm.com>
2021-02-22 14:28:16 +01:00
31 changed files with 1114 additions and 641 deletions

1
.clippy.toml Normal file
View File

@@ -0,0 +1 @@
upper-case-acronyms-aggressive = true

View File

@@ -1,26 +1,38 @@
name: BVT
on: [pull_request]
env:
RUST_VERSION: 1.52
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Build
run: make debug
- uses: actions/checkout@v2
- run: rustup install ${{ env.RUST_VERSION }} && rustup default ${{ env.RUST_VERSION }}
- run: make debug
fmt:
name: Format Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: rustup install ${{ env.RUST_VERSION }} && rustup default ${{ env.RUST_VERSION }}
- run: rustup component add rustfmt
- run: make fmt
clippy:
name: Clippy Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: rustup install ${{ env.RUST_VERSION }} && rustup default ${{ env.RUST_VERSION }}
- run: rustup component add clippy
- run: make clippy
test:
name: Run Unit Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: rustup install ${{ env.RUST_VERSION }} && rustup default ${{ env.RUST_VERSION }}
- run: make test

View File

@@ -48,6 +48,6 @@ jobs:
uses: tim-actions/commit-message-checker-with-regex@v0.3.1
with:
commits: ${{ steps.get-pr-commits.outputs.commits }}
pattern: '^.+(\n.{0,72})*$|^.+\n\s*[^a-zA-Z\s\n]|^.+\n\S+$'
pattern: '^.+(\n([a-zA-Z].{0,149}|[^a-zA-Z\n].*|Signed-off-by:.*|))+$'
error: 'Body line too long (max 72)'
post_error: ${{ env.error_msg }}

View File

@@ -5,7 +5,7 @@ repository = "https://github.com/kata-containers/cgroups-rs"
keywords = ["linux", "cgroup", "containers", "isolation"]
categories = ["os", "api-bindings", "os::unix-apis"]
license = "MIT OR Apache-2.0"
version = "0.2.3"
version = "0.2.11"
authors = ["The Kata Containers community <kata-dev@lists.katacontainers.io>", "Levente Kurusa <lkurusa@acm.org>", "Sam Wilson <tecywiz121@hotmail.com>"]
edition = "2018"
homepage = "https://github.com/kata-containers/cgroups-rs"
@@ -14,8 +14,13 @@ readme = "README.md"
[dependencies]
log = "0.4"
regex = "1.1"
nix = "0.18.0"
nix = { version = "0.25.0", default-features = false, features = ["event", "fs", "process"] }
libc = "0.2"
serde = { version = "1.0", features = ["derive"], optional = true }
thiserror = "1"
[dev-dependencies]
libc = "0.2.76"
[features]
default = []

View File

@@ -16,7 +16,8 @@ use crate::error::*;
use crate::{read_string_from, read_u64_from};
use crate::{
BlkIoResources, ControllIdentifier, ControllerInternal, Controllers, Resources, Subsystem,
BlkIoResources, ControllIdentifier, ControllerInternal, Controllers, CustomizedAttribute,
Resources, Subsystem,
};
/// A controller that allows controlling the `blkio` subsystem of a Cgroup.
@@ -31,6 +32,7 @@ pub struct BlkIoController {
}
#[derive(Eq, PartialEq, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
/// Per-device information
pub struct BlkIoData {
/// The major number of the device.
@@ -42,6 +44,7 @@ pub struct BlkIoData {
}
#[derive(Eq, PartialEq, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
/// Per-device activity from the control group.
pub struct IoService {
/// The major number of the device.
@@ -61,6 +64,7 @@ pub struct IoService {
}
#[derive(Eq, PartialEq, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
/// Per-device activity from the control group.
/// Only for cgroup v2
pub struct IoStat {
@@ -84,14 +88,14 @@ pub struct IoStat {
fn parse_io_service(s: String) -> Result<Vec<IoService>> {
s.lines()
.filter(|x| x.split_whitespace().collect::<Vec<_>>().len() == 3)
.filter(|x| x.split_whitespace().count() == 3)
.map(|x| {
let mut spl = x.split_whitespace();
(spl.nth(0).unwrap(), spl.nth(0).unwrap(), spl.nth(0).unwrap())
(spl.next().unwrap(), spl.next().unwrap(), spl.next().unwrap())
})
.map(|(a, b, c)| {
let mut spl = a.split(":");
(spl.nth(0).unwrap(), spl.nth(0).unwrap(), b, c)
let mut spl = a.split(':');
(spl.next().unwrap(), spl.next().unwrap(), b, c)
})
.collect::<Vec<_>>()
.chunks(5)
@@ -131,15 +135,14 @@ fn get_value(s: &str) -> String {
arr[1].to_string()
}
fn parse_io_stat(s: String) -> Result<Vec<IoStat>> {
fn parse_io_stat(s: String) -> Vec<IoStat> {
// line:
// 8:0 rbytes=180224 wbytes=0 rios=3 wios=0 dbytes=0 dios=0
let v = s
.lines()
.filter(|x| x.split_whitespace().collect::<Vec<_>>().len() == 7)
s.lines()
.filter(|x| x.split_whitespace().count() == 7)
.map(|x| {
let arr = x.split_whitespace().collect::<Vec<&str>>();
let device = arr[0].split(":").collect::<Vec<&str>>();
let device = arr[0].split(':').collect::<Vec<&str>>();
let (major, minor) = (device[0], device[1]);
IoStat {
@@ -153,14 +156,12 @@ fn parse_io_stat(s: String) -> Result<Vec<IoStat>> {
dios: get_value(arr[6]).parse::<u64>().unwrap(),
}
})
.collect::<Vec<IoStat>>();
Ok(v)
.collect::<Vec<IoStat>>()
}
fn parse_io_service_total(s: String) -> Result<u64> {
s.lines()
.filter(|x| x.split_whitespace().collect::<Vec<_>>().len() == 2)
.filter(|x| x.split_whitespace().count() == 2)
.fold(Err(Error::new(ParseError)), |_, x| {
match x.split_whitespace().collect::<Vec<_>>().as_slice() {
["Total", val] => val.parse::<u64>().map_err(|_| Error::new(ParseError)),
@@ -197,15 +198,16 @@ fn parse_blkio_data(s: String) -> Result<Vec<BlkIoData>> {
});
if err.is_err() {
return Err(Error::new(ParseError));
Err(Error::new(ParseError))
} else {
return Ok(res);
Ok(res)
}
}
/// Current state and statistics about how throttled are the block devices when accessed from the
/// controller's control group.
#[derive(Default, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct BlkIoThrottle {
/// Statistics about the bytes transferred between the block devices by the tasks in this
/// control group.
@@ -241,6 +243,7 @@ pub struct BlkIoThrottle {
/// Statistics and state of the block devices.
#[derive(Default, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct BlkIo {
/// The number of BIOS requests merged into I/O requests by the control group's tasks.
pub io_merged: Vec<IoService>,
@@ -376,6 +379,10 @@ impl ControllerInternal for BlkIoController {
let _ = self.throttle_write_iops_for_device(dev.major, dev.minor, dev.rate);
}
res.attrs.iter().for_each(|(k, v)| {
let _ = self.set(k, v);
});
Ok(())
}
}
@@ -407,19 +414,19 @@ impl BlkIoController {
Self {
base: root.clone(),
path: root,
v2: v2,
v2,
}
}
fn blkio_v2(&self) -> BlkIo {
let mut blkio: BlkIo = Default::default();
blkio.io_stat = self
.open_path("io.stat", false)
.and_then(read_string_from)
.and_then(parse_io_stat)
.unwrap_or(Vec::new());
blkio
BlkIo {
io_stat: self
.open_path("io.stat", false)
.and_then(read_string_from)
.map(parse_io_stat)
.unwrap_or_default(),
..Default::default()
}
}
/// Gathers statistics about and reports the state of the block devices used by the control
@@ -433,222 +440,222 @@ impl BlkIoController {
.open_path("blkio.io_merged", false)
.and_then(read_string_from)
.and_then(parse_io_service)
.unwrap_or(Vec::new()),
.unwrap_or_default(),
io_merged_total: self
.open_path("blkio.io_merged", false)
.and_then(read_string_from)
.and_then(parse_io_service_total)
.unwrap_or(0),
.unwrap_or_default(),
io_merged_recursive: self
.open_path("blkio.io_merged_recursive", false)
.and_then(read_string_from)
.and_then(parse_io_service)
.unwrap_or(Vec::new()),
.unwrap_or_default(),
io_merged_recursive_total: self
.open_path("blkio.io_merged_recursive", false)
.and_then(read_string_from)
.and_then(parse_io_service_total)
.unwrap_or(0),
.unwrap_or_default(),
io_queued: self
.open_path("blkio.io_queued", false)
.and_then(read_string_from)
.and_then(parse_io_service)
.unwrap_or(Vec::new()),
.unwrap_or_default(),
io_queued_total: self
.open_path("blkio.io_queued", false)
.and_then(read_string_from)
.and_then(parse_io_service_total)
.unwrap_or(0),
.unwrap_or_default(),
io_queued_recursive: self
.open_path("blkio.io_queued_recursive", false)
.and_then(read_string_from)
.and_then(parse_io_service)
.unwrap_or(Vec::new()),
.unwrap_or_default(),
io_queued_recursive_total: self
.open_path("blkio.io_queued_recursive", false)
.and_then(read_string_from)
.and_then(parse_io_service_total)
.unwrap_or(0),
.unwrap_or_default(),
io_service_bytes: self
.open_path("blkio.io_service_bytes", false)
.and_then(read_string_from)
.and_then(parse_io_service)
.unwrap_or(Vec::new()),
.unwrap_or_default(),
io_service_bytes_total: self
.open_path("blkio.io_service_bytes", false)
.and_then(read_string_from)
.and_then(parse_io_service_total)
.unwrap_or(0),
.unwrap_or_default(),
io_service_bytes_recursive: self
.open_path("blkio.io_service_bytes_recursive", false)
.and_then(read_string_from)
.and_then(parse_io_service)
.unwrap_or(Vec::new()),
.unwrap_or_default(),
io_service_bytes_recursive_total: self
.open_path("blkio.io_service_bytes_recursive", false)
.and_then(read_string_from)
.and_then(parse_io_service_total)
.unwrap_or(0),
.unwrap_or_default(),
io_serviced: self
.open_path("blkio.io_serviced", false)
.and_then(read_string_from)
.and_then(parse_io_service)
.unwrap_or(Vec::new()),
.unwrap_or_default(),
io_serviced_total: self
.open_path("blkio.io_serviced", false)
.and_then(read_string_from)
.and_then(parse_io_service_total)
.unwrap_or(0),
.unwrap_or_default(),
io_serviced_recursive: self
.open_path("blkio.io_serviced_recursive", false)
.and_then(read_string_from)
.and_then(parse_io_service)
.unwrap_or(Vec::new()),
.unwrap_or_default(),
io_serviced_recursive_total: self
.open_path("blkio.io_serviced_recursive", false)
.and_then(read_string_from)
.and_then(parse_io_service_total)
.unwrap_or(0),
.unwrap_or_default(),
io_service_time: self
.open_path("blkio.io_service_time", false)
.and_then(read_string_from)
.and_then(parse_io_service)
.unwrap_or(Vec::new()),
.unwrap_or_default(),
io_service_time_total: self
.open_path("blkio.io_service_time", false)
.and_then(read_string_from)
.and_then(parse_io_service_total)
.unwrap_or(0),
.unwrap_or_default(),
io_service_time_recursive: self
.open_path("blkio.io_service_time_recursive", false)
.and_then(read_string_from)
.and_then(parse_io_service)
.unwrap_or(Vec::new()),
.unwrap_or_default(),
io_service_time_recursive_total: self
.open_path("blkio.io_service_time_recursive", false)
.and_then(read_string_from)
.and_then(parse_io_service_total)
.unwrap_or(0),
.unwrap_or_default(),
io_wait_time: self
.open_path("blkio.io_wait_time", false)
.and_then(read_string_from)
.and_then(parse_io_service)
.unwrap_or(Vec::new()),
.unwrap_or_default(),
io_wait_time_total: self
.open_path("blkio.io_wait_time", false)
.and_then(read_string_from)
.and_then(parse_io_service_total)
.unwrap_or(0),
.unwrap_or_default(),
io_wait_time_recursive: self
.open_path("blkio.io_wait_time_recursive", false)
.and_then(read_string_from)
.and_then(parse_io_service)
.unwrap_or(Vec::new()),
.unwrap_or_default(),
io_wait_time_recursive_total: self
.open_path("blkio.io_wait_time_recursive", false)
.and_then(read_string_from)
.and_then(parse_io_service_total)
.unwrap_or(0),
.unwrap_or_default(),
leaf_weight: self
.open_path("blkio.leaf_weight", false)
.and_then(|file| read_u64_from(file))
.and_then(read_u64_from)
.unwrap_or(0u64),
leaf_weight_device: self
.open_path("blkio.leaf_weight_device", false)
.and_then(read_string_from)
.and_then(parse_blkio_data)
.unwrap_or(Vec::new()),
.unwrap_or_default(),
sectors: self
.open_path("blkio.sectors", false)
.and_then(read_string_from)
.and_then(parse_blkio_data)
.unwrap_or(Vec::new()),
.unwrap_or_default(),
sectors_recursive: self
.open_path("blkio.sectors_recursive", false)
.and_then(read_string_from)
.and_then(parse_blkio_data)
.unwrap_or(Vec::new()),
.unwrap_or_default(),
throttle: BlkIoThrottle {
io_service_bytes: self
.open_path("blkio.throttle.io_service_bytes", false)
.and_then(read_string_from)
.and_then(parse_io_service)
.unwrap_or(Vec::new()),
.unwrap_or_default(),
io_service_bytes_total: self
.open_path("blkio.throttle.io_service_bytes", false)
.and_then(read_string_from)
.and_then(parse_io_service_total)
.unwrap_or(0),
.unwrap_or_default(),
io_service_bytes_recursive: self
.open_path("blkio.throttle.io_service_bytes_recursive", false)
.and_then(read_string_from)
.and_then(parse_io_service)
.unwrap_or(Vec::new()),
.unwrap_or_default(),
io_service_bytes_recursive_total: self
.open_path("blkio.throttle.io_service_bytes_recursive", false)
.and_then(read_string_from)
.and_then(parse_io_service_total)
.unwrap_or(0),
.unwrap_or_default(),
io_serviced: self
.open_path("blkio.throttle.io_serviced", false)
.and_then(read_string_from)
.and_then(parse_io_service)
.unwrap_or(Vec::new()),
.unwrap_or_default(),
io_serviced_total: self
.open_path("blkio.throttle.io_serviced", false)
.and_then(read_string_from)
.and_then(parse_io_service_total)
.unwrap_or(0),
.unwrap_or_default(),
io_serviced_recursive: self
.open_path("blkio.throttle.io_serviced_recursive", false)
.and_then(read_string_from)
.and_then(parse_io_service)
.unwrap_or(Vec::new()),
.unwrap_or_default(),
io_serviced_recursive_total: self
.open_path("blkio.throttle.io_serviced_recursive", false)
.and_then(read_string_from)
.and_then(parse_io_service_total)
.unwrap_or(0),
.unwrap_or_default(),
read_bps_device: self
.open_path("blkio.throttle.read_bps_device", false)
.and_then(read_string_from)
.and_then(parse_blkio_data)
.unwrap_or(Vec::new()),
.unwrap_or_default(),
read_iops_device: self
.open_path("blkio.throttle.read_iops_device", false)
.and_then(read_string_from)
.and_then(parse_blkio_data)
.unwrap_or(Vec::new()),
.unwrap_or_default(),
write_bps_device: self
.open_path("blkio.throttle.write_bps_device", false)
.and_then(read_string_from)
.and_then(parse_blkio_data)
.unwrap_or(Vec::new()),
.unwrap_or_default(),
write_iops_device: self
.open_path("blkio.throttle.write_iops_device", false)
.and_then(read_string_from)
.and_then(parse_blkio_data)
.unwrap_or(Vec::new()),
.unwrap_or_default(),
},
time: self
.open_path("blkio.time", false)
.and_then(read_string_from)
.and_then(parse_blkio_data)
.unwrap_or(Vec::new()),
.unwrap_or_default(),
time_recursive: self
.open_path("blkio.time_recursive", false)
.and_then(read_string_from)
.and_then(parse_blkio_data)
.unwrap_or(Vec::new()),
.unwrap_or_default(),
weight: self
.open_path("blkio.weight", false)
.and_then(|file| read_u64_from(file))
.and_then(read_u64_from)
.unwrap_or(0u64),
weight_device: self
.open_path("blkio.weight_device", false)
.and_then(read_string_from)
.and_then(parse_blkio_data)
.unwrap_or(Vec::new()),
.unwrap_or_default(),
io_stat: Vec::new(),
}
}
@@ -658,8 +665,12 @@ impl BlkIoController {
pub fn set_leaf_weight(&self, w: u64) -> Result<()> {
self.open_path("blkio.leaf_weight", true)
.and_then(|mut file| {
file.write_all(w.to_string().as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
file.write_all(w.to_string().as_ref()).map_err(|e| {
Error::with_cause(
WriteFailed("blkio.leaf_weight".to_string(), w.to_string()),
e,
)
})
})
}
@@ -668,7 +679,15 @@ impl BlkIoController {
self.open_path("blkio.leaf_weight_device", true)
.and_then(|mut file| {
file.write_all(format!("{}:{} {}", major, minor, weight).as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
.map_err(|e| {
Error::with_cause(
WriteFailed(
"blkio.leaf_weight_device".to_string(),
format!("{}:{} {}", major, minor, weight),
),
e,
)
})
})
}
@@ -676,99 +695,117 @@ impl BlkIoController {
pub fn reset_stats(&self) -> Result<()> {
self.open_path("blkio.reset_stats", true)
.and_then(|mut file| {
file.write_all("1".to_string().as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
file.write_all("1".to_string().as_ref()).map_err(|e| {
Error::with_cause(
WriteFailed("blkio.reset_stats".to_string(), "1".to_string()),
e,
)
})
})
}
/// Throttle the bytes per second rate of read operation affecting the block device
/// `major:minor` to `bps`.
pub fn throttle_read_bps_for_device(&self, major: u64, minor: u64, bps: u64) -> Result<()> {
let mut file = "blkio.throttle.read_bps_device";
let mut file_name = "blkio.throttle.read_bps_device";
let mut content = format!("{}:{} {}", major, minor, bps);
if self.v2 {
file = "io.max";
file_name = "io.max";
content = format!("{}:{} rbps={}", major, minor, bps);
}
self.open_path(file, true).and_then(|mut file| {
file.write_all(content.as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
self.open_path(file_name, true).and_then(|mut file| {
file.write_all(content.as_ref()).map_err(|e| {
Error::with_cause(WriteFailed(file_name.to_string(), content.to_string()), e)
})
})
}
/// Throttle the I/O operations per second rate of read operation affecting the block device
/// `major:minor` to `bps`.
pub fn throttle_read_iops_for_device(&self, major: u64, minor: u64, iops: u64) -> Result<()> {
let mut file = "blkio.throttle.read_iops_device";
let mut file_name = "blkio.throttle.read_iops_device";
let mut content = format!("{}:{} {}", major, minor, iops);
if self.v2 {
file = "io.max";
file_name = "io.max";
content = format!("{}:{} riops={}", major, minor, iops);
}
self.open_path(file, true).and_then(|mut file| {
file.write_all(content.as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
self.open_path(file_name, true).and_then(|mut file| {
file.write_all(content.as_ref()).map_err(|e| {
Error::with_cause(WriteFailed(file_name.to_string(), content.to_string()), e)
})
})
}
/// Throttle the bytes per second rate of write operation affecting the block device
/// `major:minor` to `bps`.
pub fn throttle_write_bps_for_device(&self, major: u64, minor: u64, bps: u64) -> Result<()> {
let mut file = "blkio.throttle.write_bps_device";
let mut file_name = "blkio.throttle.write_bps_device";
let mut content = format!("{}:{} {}", major, minor, bps);
if self.v2 {
file = "io.max";
file_name = "io.max";
content = format!("{}:{} wbps={}", major, minor, bps);
}
self.open_path(file, true).and_then(|mut file| {
file.write_all(content.as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
self.open_path(file_name, true).and_then(|mut file| {
file.write_all(content.as_ref()).map_err(|e| {
Error::with_cause(WriteFailed(file_name.to_string(), content.to_string()), e)
})
})
}
/// Throttle the I/O operations per second rate of write operation affecting the block device
/// `major:minor` to `bps`.
pub fn throttle_write_iops_for_device(&self, major: u64, minor: u64, iops: u64) -> Result<()> {
let mut file = "blkio.throttle.write_iops_device";
let mut file_name = "blkio.throttle.write_iops_device";
let mut content = format!("{}:{} {}", major, minor, iops);
if self.v2 {
file = "io.max";
file_name = "io.max";
content = format!("{}:{} wiops={}", major, minor, iops);
}
self.open_path(file, true).and_then(|mut file| {
file.write_all(content.as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
self.open_path(file_name, true).and_then(|mut file| {
file.write_all(content.as_ref()).map_err(|e| {
Error::with_cause(WriteFailed(file_name.to_string(), content.to_string()), e)
})
})
}
/// Set the weight of the control group's tasks.
pub fn set_weight(&self, w: u64) -> Result<()> {
// Attation: may not find in high kernel version.
let mut file = "blkio.weight";
let mut file_name = "blkio.weight";
if self.v2 {
file = "io.bfq.weight";
file_name = "io.bfq.weight";
}
self.open_path(file, true).and_then(|mut file| {
file.write_all(w.to_string().as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
self.open_path(file_name, true).and_then(|mut file| {
file.write_all(w.to_string().as_ref()).map_err(|e| {
Error::with_cause(WriteFailed(file_name.to_string(), w.to_string()), e)
})
})
}
/// Same as `set_weight()`, but settable per each block device.
pub fn set_weight_for_device(&self, major: u64, minor: u64, weight: u64) -> Result<()> {
let mut file = "blkio.weight_device";
let mut file_name = "blkio.weight_device";
if self.v2 {
// Attation: there is no weight for device in runc
// https://github.com/opencontainers/runc/blob/46be7b612e2533c494e6a251111de46d8e286ed5/libcontainer/cgroups/fs2/io.go#L30
// may depends on IO schedulers https://wiki.ubuntu.com/Kernel/Reference/IOSchedulers
file = "io.bfq.weight";
file_name = "io.bfq.weight";
}
self.open_path(file, true).and_then(|mut file| {
self.open_path(file_name, true).and_then(|mut file| {
file.write_all(format!("{}:{} {}", major, minor, weight).as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
.map_err(|e| {
Error::with_cause(
WriteFailed(
file_name.to_string(),
format!("{}:{} {}", major, minor, weight),
),
e,
)
})
})
}
}
impl CustomizedAttribute for BlkIoController {}
#[cfg(test)]
mod test {
use crate::blkio::{parse_blkio_data, BlkIoData};

View File

@@ -62,7 +62,7 @@ impl Cgroup {
/// Create this control group.
fn create(&self) {
if self.hier.v2() {
let _ret = create_v2_cgroup(self.hier.root().clone(), &self.path);
let _ret = create_v2_cgroup(self.hier.root(), &self.path);
} else {
for subsystem in &self.subsystems {
subsystem.to_controller().create();
@@ -112,13 +112,11 @@ impl Cgroup {
.collect::<Vec<_>>();
}
let cg = Cgroup {
Cgroup {
path: path.to_str().unwrap().to_string(),
subsystems: subsystems,
subsystems,
hier,
};
cg
}
}
/// Create a handle for a control group in the hierarchy `hier`, with name `path` and `relative_paths`
@@ -146,7 +144,7 @@ impl Cgroup {
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 valid_path = rp.trim_start_matches('/').to_string();
let mut p = PathBuf::from(valid_path);
p.push(path);
x.enter(p.as_ref())
@@ -157,13 +155,11 @@ impl Cgroup {
.collect::<Vec<_>>();
}
let cg = Cgroup {
subsystems: subsystems,
Cgroup {
subsystems,
hier,
path: path.to_str().unwrap().to_string(),
};
cg
}
}
/// The list of subsystems that this control group supports.
@@ -179,8 +175,8 @@ impl Cgroup {
/// will change.
pub fn delete(&self) -> Result<()> {
if self.v2() {
if self.path != "" {
let mut p = self.hier.root().clone();
if !self.path.is_empty() {
let mut p = self.hier.root();
p.push(self.path.clone());
return fs::remove_dir(p).map_err(|e| Error::with_cause(RemoveFailed, e));
}
@@ -222,7 +218,7 @@ impl Cgroup {
/// let cpu: &CpuController = control_group.controller_of()
/// .expect("No cpu controller attached!");
/// ```
pub fn controller_of<'a, T>(self: &'a Self) -> Option<&'a T>
pub fn controller_of<'a, T>(&'a self) -> Option<&'a T>
where
&'a T: From<&'a Subsystem>,
T: Controller + ControllIdentifier,
@@ -249,7 +245,7 @@ impl Cgroup {
pub fn add_task(&self, pid: CgroupPid) -> Result<()> {
if self.v2() {
let subsystems = self.subsystems();
if subsystems.len() > 0 {
if !subsystems.is_empty() {
let c = subsystems[0].to_controller();
c.add_task(&pid)
} else {
@@ -291,7 +287,7 @@ impl Cgroup {
// Collect the tasks from all subsystems
let mut v = if self.v2() {
let subsystems = self.subsystems();
if subsystems.len() > 0 {
if !subsystems.is_empty() {
let c = subsystems[0].to_controller();
c.tasks()
} else {
@@ -313,11 +309,10 @@ impl Cgroup {
}
}
pub const UNIFIED_MOUNTPOINT: &'static str = "/sys/fs/cgroup";
pub const UNIFIED_MOUNTPOINT: &str = "/sys/fs/cgroup";
fn enable_controllers(controllers: &Vec<String>, path: &PathBuf) {
let mut f = path.clone();
f.push("cgroup.subtree_control");
fn enable_controllers(controllers: &[String], path: &Path) {
let f = path.join("cgroup.subtree_control");
for c in controllers {
let body = format!("+{}", c);
let _rest = fs::write(f.as_path(), body.as_bytes());
@@ -327,8 +322,8 @@ fn enable_controllers(controllers: &Vec<String>, path: &PathBuf) {
fn supported_controllers() -> Vec<String> {
let p = format!("{}/{}", UNIFIED_MOUNTPOINT, "cgroup.controllers");
let ret = fs::read_to_string(p.as_str());
ret.unwrap_or(String::new())
.split(" ")
ret.unwrap_or_default()
.split(' ')
.map(|x| x.to_string())
.collect::<Vec<String>>()
}
@@ -342,16 +337,15 @@ fn create_v2_cgroup(root: PathBuf, path: &str) -> Result<()> {
enable_controllers(&controllers, &fp);
// path: "a/b/c"
let elements = path.split("/").collect::<Vec<&str>>();
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 let Err(e) = std::fs::create_dir(fp.clone()) {
return Err(Error::with_cause(ErrorKind::FsError, e));
}
}
@@ -365,9 +359,19 @@ fn create_v2_cgroup(root: PathBuf, path: &str) -> Result<()> {
}
pub fn get_cgroups_relative_paths() -> Result<HashMap<String, String>> {
let path = "/proc/self/cgroup".to_string();
get_cgroups_relative_paths_by_path(path)
}
pub fn get_cgroups_relative_paths_by_pid(pid: u32) -> Result<HashMap<String, String>> {
let path = format!("/proc/{}/cgroup", pid);
get_cgroups_relative_paths_by_path(path)
}
fn get_cgroups_relative_paths_by_path(path: String) -> 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))?;
fs::read_to_string(path.clone()).map_err(|e| Error::with_cause(ReadFailed(path), e))?;
for l in content.lines() {
let fl: Vec<&str> = l.split(':').collect();
if fl.len() != 3 {

View File

@@ -240,10 +240,10 @@ impl DeviceResourceBuilder {
access: Vec<crate::devices::DevicePermissions>,
) -> DeviceResourceBuilder {
self.cgroup.resources.devices.devices.push(DeviceResource {
allow,
devtype,
major,
minor,
devtype,
allow,
access,
});
self

View File

@@ -36,6 +36,7 @@ pub struct CpuController {
/// The current state of the control group and its processes.
#[derive(Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Cpu {
/// Reports CPU time statistics.
///
@@ -45,7 +46,7 @@ pub struct Cpu {
/// The current state of the control group and its processes.
#[derive(Debug)]
struct CFSQuotaAndPeriod {
struct CfsQuotaAndPeriod {
quota: MaxValue,
period: u64,
}
@@ -116,7 +117,7 @@ impl CpuController {
Self {
base: root.clone(),
path: root,
v2: v2,
v2,
}
}
@@ -130,10 +131,10 @@ impl CpuController {
let res = file.read_to_string(&mut s);
match res {
Ok(_) => Ok(s),
Err(e) => Err(Error::with_cause(ReadFailed, e)),
Err(e) => Err(Error::with_cause(ReadFailed("cpu.stat".to_string()), e)),
}
})
.unwrap_or("".to_string()),
.unwrap_or_default(),
}
}
@@ -144,14 +145,15 @@ impl CpuController {
/// `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, shares: u64) -> Result<()> {
let mut file = "cpu.shares";
let mut file_name = "cpu.shares";
if self.v2 {
file = "cpu.weight";
file_name = "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))
self.open_path(file_name, true).and_then(|mut file| {
file.write_all(shares.to_string().as_ref()).map_err(|e| {
Error::with_cause(WriteFailed(file_name.to_string(), shares.to_string()), e)
})
})
}
@@ -173,8 +175,12 @@ impl CpuController {
}
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))
file.write_all(us.to_string().as_ref()).map_err(|e| {
Error::with_cause(
WriteFailed("cpu.cfs_period_us".to_string(), us.to_string()),
e,
)
})
})
}
@@ -199,8 +205,12 @@ impl CpuController {
}
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))
file.write_all(us.to_string().as_ref()).map_err(|e| {
Error::with_cause(
WriteFailed("cpu.cfs_quota_us".to_string(), us.to_string()),
e,
)
})
})
}
@@ -261,46 +271,51 @@ impl CpuController {
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))
.map_err(|e| Error::with_cause(WriteFailed("cpu.max".to_string(), line), 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))
file.write_all(us.to_string().as_ref()).map_err(|e| {
Error::with_cause(
WriteFailed("cpu.rt_runtime_us".to_string(), us.to_string()),
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))
file.write_all(us.to_string().as_ref()).map_err(|e| {
Error::with_cause(
WriteFailed("cpu.rt_period_us".to_string(), us.to_string()),
e,
)
})
})
}
}
impl CustomizedAttribute for CpuController {}
fn parse_cfs_quota_and_period(mut file: File) -> Result<CFSQuotaAndPeriod> {
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))?;
.map_err(|e| Error::with_cause(ReadFailed("cpu.max".to_string()), 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 quota = parse_max_value(fields[0])?;
let period = fields[1]
.parse::<u64>()
.map_err(|e| Error::with_cause(ParseError, e))?;
Ok(CFSQuotaAndPeriod {
quota: quota,
period: period,
})
Ok(CfsQuotaAndPeriod { quota, period })
}

View File

@@ -27,6 +27,7 @@ pub struct CpuAcctController {
}
/// Represents the statistics retrieved from the control group.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct CpuAcct {
/// Divides the time used by the tasks into `user` time and `system` time.
pub stat: String,
@@ -111,35 +112,35 @@ impl CpuAcctController {
CpuAcct {
stat: self
.open_path("cpuacct.stat", false)
.and_then(|file| read_string_from(file))
.unwrap_or("".to_string()),
.and_then(read_string_from)
.unwrap_or_default(),
usage: self
.open_path("cpuacct.usage", false)
.and_then(|file| read_u64_from(file))
.and_then(read_u64_from)
.unwrap_or(0),
usage_all: self
.open_path("cpuacct.usage_all", false)
.and_then(|file| read_string_from(file))
.unwrap_or("".to_string()),
.and_then(read_string_from)
.unwrap_or_default(),
usage_percpu: self
.open_path("cpuacct.usage_percpu", false)
.and_then(|file| read_string_from(file))
.unwrap_or("".to_string()),
.and_then(read_string_from)
.unwrap_or_default(),
usage_percpu_sys: self
.open_path("cpuacct.usage_percpu_sys", false)
.and_then(|file| read_string_from(file))
.unwrap_or("".to_string()),
.and_then(read_string_from)
.unwrap_or_default(),
usage_percpu_user: self
.open_path("cpuacct.usage_percpu_user", false)
.and_then(|file| read_string_from(file))
.unwrap_or("".to_string()),
.and_then(read_string_from)
.unwrap_or_default(),
usage_sys: self
.open_path("cpuacct.usage_sys", false)
.and_then(|file| read_u64_from(file))
.and_then(read_u64_from)
.unwrap_or(0),
usage_user: self
.open_path("cpuacct.usage_user", false)
.and_then(|file| read_u64_from(file))
.and_then(read_u64_from)
.unwrap_or(0),
}
}
@@ -147,8 +148,9 @@ impl CpuAcctController {
/// Reset the statistics the kernel has gathered about the control group.
pub fn reset(&self) -> Result<()> {
self.open_path("cpuacct.usage", true).and_then(|mut file| {
file.write_all(b"0")
.map_err(|e| Error::with_cause(WriteFailed, e))
file.write_all(b"0").map_err(|e| {
Error::with_cause(WriteFailed("cpuacct.usage".to_string(), "0".to_string()), e)
})
})
}
}

View File

@@ -33,6 +33,7 @@ pub struct CpuSetController {
}
/// The current state of the `cpuset` controller for this control group.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct CpuSet {
/// If true, no other control groups can share the CPUs listed in the `cpus` field.
pub cpu_exclusive: bool,
@@ -143,10 +144,15 @@ fn find_no_empty_parent(from: &str, file: &str) -> Result<(String, Vec<PathBuf>)
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)),
Err(e) => {
return Err(Error::with_cause(
ReadFailed(current_path.display().to_string()),
e,
))
}
};
if current_value != "" {
if !current_value.is_empty() {
return Ok((current_value, v));
}
v.push(current_path.clone());
@@ -167,7 +173,7 @@ 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 {
if value.is_empty() || parents.is_empty() {
return Ok(());
}
@@ -176,7 +182,12 @@ fn copy_from_parent(current: &str, file: &str) -> Result<()> {
pb.push(file);
match ::std::fs::write(pb.to_str().unwrap(), value.as_bytes()) {
Ok(_) => (),
Err(e) => return Err(Error::with_cause(WriteFailed, e)),
Err(e) => {
return Err(Error::with_cause(
WriteFailed(pb.display().to_string(), pb.display().to_string()),
e,
))
}
}
}
@@ -208,17 +219,17 @@ impl<'a> From<&'a Subsystem> for &'a CpuSetController {
fn parse_range(s: String) -> Result<Vec<(u64, u64)>> {
let mut fin = Vec::new();
if s == "".to_string() {
if s.is_empty() {
return Ok(fin);
}
// first split by commas
let comma_split = s.split(",");
let comma_split = s.split(',');
for sp in comma_split {
if sp.contains("-") {
if sp.contains('-') {
// this is a true range
let dash_split = sp.split("-").collect::<Vec<_>>();
let dash_split = sp.split('-').collect::<Vec<_>>();
if dash_split.len() != 2 {
return Err(Error::new(ParseError));
}
@@ -247,7 +258,7 @@ impl CpuSetController {
Self {
base: root.clone(),
path: root,
v2: v2,
v2,
}
}
@@ -257,7 +268,7 @@ impl CpuSetController {
CpuSet {
cpu_exclusive: {
self.open_path("cpuset.cpu_exclusive", false)
.and_then(|file| read_u64_from(file))
.and_then(read_u64_from)
.map(|x| x == 1)
.unwrap_or(false)
},
@@ -265,19 +276,19 @@ impl CpuSetController {
self.open_path("cpuset.cpus", false)
.and_then(read_string_from)
.and_then(parse_range)
.unwrap_or(Vec::new())
.unwrap_or_default()
},
effective_cpus: {
self.open_path("cpuset.effective_cpus", false)
.and_then(read_string_from)
.and_then(parse_range)
.unwrap_or(Vec::new())
.unwrap_or_default()
},
effective_mems: {
self.open_path("cpuset.effective_mems", false)
.and_then(read_string_from)
.and_then(parse_range)
.unwrap_or(Vec::new())
.unwrap_or_default()
},
mem_exclusive: {
self.open_path("cpuset.mem_exclusive", false)
@@ -324,7 +335,7 @@ impl CpuSetController {
self.open_path("cpuset.mems", false)
.and_then(read_string_from)
.and_then(parse_range)
.unwrap_or(Vec::new())
.unwrap_or_default()
},
sched_load_balance: {
self.open_path("cpuset.sched_load_balance", false)
@@ -346,11 +357,19 @@ impl CpuSetController {
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))
file.write_all(b"1").map_err(|e| {
Error::with_cause(
WriteFailed("cpuset.cpu_exclusive".to_string(), "1".to_string()),
e,
)
})
} else {
file.write_all(b"0")
.map_err(|e| Error::with_cause(WriteFailed, e))
file.write_all(b"0").map_err(|e| {
Error::with_cause(
WriteFailed("cpuset.cpu_exclusive".to_string(), "0".to_string()),
e,
)
})
}
})
}
@@ -361,11 +380,19 @@ impl CpuSetController {
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))
file.write_all(b"1").map_err(|e| {
Error::with_cause(
WriteFailed("cpuset.mem_exclusive".to_string(), "1".to_string()),
e,
)
})
} else {
file.write_all(b"0")
.map_err(|e| Error::with_cause(WriteFailed, e))
file.write_all(b"0").map_err(|e| {
Error::with_cause(
WriteFailed("cpuset.mem_exclusive".to_string(), "0".to_string()),
e,
)
})
}
})
}
@@ -376,8 +403,9 @@ impl CpuSetController {
/// be represented via dashes.
pub fn set_cpus(&self, cpus: &str) -> Result<()> {
self.open_path("cpuset.cpus", true).and_then(|mut file| {
file.write_all(cpus.as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
file.write_all(cpus.as_ref()).map_err(|e| {
Error::with_cause(WriteFailed("cpuset.cpus".to_string(), cpus.to_string()), e)
})
})
}
@@ -386,8 +414,9 @@ impl CpuSetController {
/// Syntax is the same as with `set_cpus()`.
pub fn set_mems(&self, mems: &str) -> Result<()> {
self.open_path("cpuset.mems", true).and_then(|mut file| {
file.write_all(mems.as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
file.write_all(mems.as_ref()).map_err(|e| {
Error::with_cause(WriteFailed("cpuset.mems".to_string(), mems.to_string()), e)
})
})
}
@@ -400,11 +429,19 @@ impl CpuSetController {
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))
file.write_all(b"1").map_err(|e| {
Error::with_cause(
WriteFailed("cpuset.mem_hardwall".to_string(), "1".to_string()),
e,
)
})
} else {
file.write_all(b"0")
.map_err(|e| Error::with_cause(WriteFailed, e))
file.write_all(b"0").map_err(|e| {
Error::with_cause(
WriteFailed("cpuset.mem_hardwall".to_string(), "0".to_string()),
e,
)
})
}
})
}
@@ -415,11 +452,19 @@ impl CpuSetController {
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))
file.write_all(b"1").map_err(|e| {
Error::with_cause(
WriteFailed("cpuset.sched_load_balance".to_string(), "1".to_string()),
e,
)
})
} else {
file.write_all(b"0")
.map_err(|e| Error::with_cause(WriteFailed, e))
file.write_all(b"0").map_err(|e| {
Error::with_cause(
WriteFailed("cpuset.sched_load_balance".to_string(), "0".to_string()),
e,
)
})
}
})
}
@@ -430,8 +475,12 @@ impl CpuSetController {
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))
file.write_all(i.to_string().as_ref()).map_err(|e| {
Error::with_cause(
WriteFailed("cpuset.sched_relax_domain_level".to_string(), i.to_string()),
e,
)
})
})
}
@@ -441,11 +490,19 @@ impl CpuSetController {
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))
file.write_all(b"1").map_err(|e| {
Error::with_cause(
WriteFailed("cpuset.memory_migrate".to_string(), "1".to_string()),
e,
)
})
} else {
file.write_all(b"0")
.map_err(|e| Error::with_cause(WriteFailed, e))
file.write_all(b"0").map_err(|e| {
Error::with_cause(
WriteFailed("cpuset.memory_migrate".to_string(), "0".to_string()),
e,
)
})
}
})
}
@@ -456,11 +513,19 @@ impl CpuSetController {
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))
file.write_all(b"1").map_err(|e| {
Error::with_cause(
WriteFailed("cpuset.memory_spread_page".to_string(), "1".to_string()),
e,
)
})
} else {
file.write_all(b"0")
.map_err(|e| Error::with_cause(WriteFailed, e))
file.write_all(b"0").map_err(|e| {
Error::with_cause(
WriteFailed("cpuset.memory_spread_page".to_string(), "0".to_string()),
e,
)
})
}
})
}
@@ -471,11 +536,19 @@ impl CpuSetController {
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))
file.write_all(b"1").map_err(|e| {
Error::with_cause(
WriteFailed("cpuset.memory_spread_slab".to_string(), "1".to_string()),
e,
)
})
} else {
file.write_all(b"0")
.map_err(|e| Error::with_cause(WriteFailed, e))
file.write_all(b"0").map_err(|e| {
Error::with_cause(
WriteFailed("cpuset.memory_spread_slab".to_string(), "0".to_string()),
e,
)
})
}
})
}
@@ -492,11 +565,25 @@ impl CpuSetController {
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))
file.write_all(b"1").map_err(|e| {
Error::with_cause(
WriteFailed(
"cpuset.memory_pressure_enabled".to_string(),
"1".to_string(),
),
e,
)
})
} else {
file.write_all(b"0")
.map_err(|e| Error::with_cause(WriteFailed, e))
file.write_all(b"0").map_err(|e| {
Error::with_cause(
WriteFailed(
"cpuset.memory_pressure_enabled".to_string(),
"0".to_string(),
),
e,
)
})
}
})
}

View File

@@ -32,6 +32,11 @@ pub struct DevicesController {
/// An enum holding the different types of devices that can be manipulated using this controller.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "snake_case")
)]
pub enum DeviceType {
/// The rule applies to all devices.
All,
@@ -49,6 +54,7 @@ impl Default for DeviceType {
impl DeviceType {
/// Convert a DeviceType into the character that the kernel recognizes.
#[allow(clippy::should_implement_trait, clippy::wrong_self_convention)]
pub fn to_char(&self) -> char {
match self {
DeviceType::All => 'a',
@@ -70,6 +76,11 @@ impl DeviceType {
/// An enum with the permissions that can be allowed/denied to the control group.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "snake_case")
)]
pub enum DevicePermissions {
/// Permission to read from the device.
Read,
@@ -82,6 +93,7 @@ pub enum DevicePermissions {
impl DevicePermissions {
/// Convert a DevicePermissions into the character that the kernel recognizes.
#[allow(clippy::should_implement_trait, clippy::wrong_self_convention)]
pub fn to_char(&self) -> char {
match self {
DevicePermissions::Read => 'r',
@@ -102,7 +114,7 @@ impl DevicePermissions {
/// Checks whether the string is a valid descriptor of DevicePermissions.
pub fn is_valid(s: &str) -> bool {
if s == "" {
if s.is_empty() {
return false;
}
for i in s.chars() {
@@ -110,7 +122,7 @@ impl DevicePermissions {
return false;
}
}
return true;
true
}
/// Returns a Vec will all the permissions that a device can have.
@@ -123,9 +135,10 @@ impl DevicePermissions {
}
/// Convert a string into DevicePermissions.
#[allow(clippy::should_implement_trait)]
pub fn from_str(s: &str) -> Result<Vec<DevicePermissions>> {
let mut v = Vec::new();
if s == "" {
if s.is_empty() {
return Ok(v);
}
for e in s.chars() {
@@ -206,7 +219,7 @@ impl DevicesController {
devtype: DeviceType,
major: i64,
minor: i64,
perm: &Vec<DevicePermissions>,
perm: &[DevicePermissions],
) -> Result<()> {
let perms = perm
.iter()
@@ -224,8 +237,9 @@ impl DevicesController {
};
let final_str = format!("{} {}:{} {}", devtype.to_char(), major, minor, perms);
self.open_path("devices.allow", true).and_then(|mut file| {
file.write_all(final_str.as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
file.write_all(final_str.as_ref()).map_err(|e| {
Error::with_cause(WriteFailed("devices.allow".to_string(), final_str), e)
})
})
}
@@ -238,7 +252,7 @@ impl DevicesController {
devtype: DeviceType,
major: i64,
minor: i64,
perm: &Vec<DevicePermissions>,
perm: &[DevicePermissions],
) -> Result<()> {
let perms = perm
.iter()
@@ -256,8 +270,9 @@ impl DevicesController {
};
let final_str = format!("{} {}:{} {}", devtype.to_char(), major, minor, perms);
self.open_path("devices.deny", true).and_then(|mut file| {
file.write_all(final_str.as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
file.write_all(final_str.as_ref()).map_err(|e| {
Error::with_cause(WriteFailed("devices.deny".to_string(), final_str), e)
})
})
}
@@ -274,13 +289,13 @@ impl DevicesController {
error!("allowed_devices: acc: {:?}, ls: {:?}", acc, ls);
Err(Error::new(ParseError))
} else {
let devtype = DeviceType::from_char(ls[0].chars().nth(0));
let devtype = DeviceType::from_char(ls[0].chars().next());
let mut major = ls[1].parse::<i64>();
let mut minor = ls[2].parse::<i64>();
if major.is_err() && ls[1] == "*".to_string() {
if major.is_err() && ls[1] == "*" {
major = Ok(-1);
}
if minor.is_err() && ls[2] == "*".to_string() {
if minor.is_err() && ls[2] == "*" {
minor = Ok(-1);
}
if devtype.is_none() || major.is_err() || minor.is_err() || !DevicePermissions::is_valid(&ls[3]) {
@@ -295,14 +310,14 @@ impl DevicesController {
devtype: devtype.unwrap(),
major: major.unwrap(),
minor: minor.unwrap(),
access: access,
access,
});
Ok(acc)
}
}
})
},
Err(e) => Err(Error::with_cause(ReadFailed, e)),
Err(e) => Err(Error::with_cause(ReadFailed("devices.list".to_string()), e)),
}
})
}

View File

@@ -8,40 +8,51 @@ 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)]
#[derive(thiserror::Error, Debug, Eq, PartialEq)]
pub enum ErrorKind {
#[error("fs error")]
FsError,
#[error("common error: {0}")]
Common(String),
/// An error occured while writing to a control group file.
WriteFailed,
#[error("unable to write to a control group file {0}, value {1}")]
WriteFailed(String, String),
/// An error occured while trying to read from a control group file.
ReadFailed,
#[error("unable to read a control group file {0}")]
ReadFailed(String),
/// An error occured while trying to remove a control group.
#[error("unable to remove a control group")]
RemoveFailed,
/// 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.
#[error("unable to parse control group file")]
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.
#[error("the requested operation is invalid")]
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.
#[error("the given path is invalid")]
InvalidPath,
#[error("invalid bytes size")]
InvalidBytesSize,
/// An unknown error has occured.
#[error("an unknown error")]
Other,
}
@@ -53,29 +64,17 @@ pub struct Error {
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::RemoveFailed => "unable to remove a control group".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(),
};
if let Some(cause) = &self.cause {
write!(f, "{} caused by: {:?}", msg, cause)
write!(f, "{} caused by: {:?}", &self.kind, cause)
} else {
write!(f, "{}", msg)
write!(f, "{}", &self.kind)
}
}
}
impl StdError for Error {
fn cause(&self) -> Option<&dyn StdError> {
#[allow(clippy::manual_map)]
match self.cause {
Some(ref x) => Some(&**x),
None => None,

View File

@@ -8,7 +8,7 @@ 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::path::Path;
use std::sync::mpsc::{self, Receiver};
use std::thread;
@@ -17,18 +17,18 @@ 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>> {
pub fn notify_on_oom_v2(key: &str, dir: &Path) -> 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>> {
pub fn notify_on_oom_v1(key: &str, dir: &Path) -> 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>> {
pub fn notify_memory_pressure(key: &str, dir: &Path, level: &str) -> Result<Receiver<String>> {
if level != "low" && level != "medium" && level != "critical" {
return Err(Error::from_string(format!(
"invalid pressure level {}",
@@ -41,26 +41,31 @@ pub fn notify_memory_pressure(key: &str, dir: &PathBuf, level: &str) -> Result<R
fn register_memory_event(
key: &str,
cg_dir: &PathBuf,
cg_dir: &Path,
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 event_file = File::open(path.clone())
.map_err(|e| Error::with_cause(ReadFailed(path.display().to_string()), e))?;
let eventfd =
eventfd(0, EfdFlags::EFD_CLOEXEC).map_err(|e| Error::with_cause(ReadFailed, e))?;
let eventfd = eventfd(0, EfdFlags::EFD_CLOEXEC)
.map_err(|e| Error::with_cause(ReadFailed("eventfd".to_string()), e))?;
let event_control_path = cg_dir.join("cgroup.event_control");
let data;
if arg == "" {
data = format!("{} {}", eventfd, event_file.as_raw_fd());
let data = if arg.is_empty() {
format!("{} {}", eventfd, event_file.as_raw_fd())
} else {
data = format!("{} {} {}", eventfd, event_file.as_raw_fd(), arg);
}
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))?;
fs::write(&event_control_path, data.clone()).map_err(|e| {
Error::with_cause(
WriteFailed(event_control_path.display().to_string(), data),
e,
)
})?;
let mut eventfd_file = unsafe { File::from_raw_fd(eventfd) };
@@ -70,11 +75,8 @@ fn register_memory_event(
thread::spawn(move || {
loop {
let mut buf = [0; 8];
match eventfd_file.read(&mut buf) {
Err(_err) => {
return;
}
Ok(_) => {}
if eventfd_file.read(&mut buf).is_err() {
return;
}
// When a cgroup is destroyed, an event is sent to eventfd.

View File

@@ -32,6 +32,7 @@ pub struct FreezerController {
}
/// The current state of the control group
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum FreezerState {
/// The processes in the control group are _not_ frozen.
Thawed,
@@ -87,50 +88,50 @@ impl FreezerController {
Self {
base: root.clone(),
path: root,
v2: v2,
v2,
}
}
/// Freezes the processes in the control group.
pub fn freeze(&self) -> Result<()> {
let mut file = "freezer.state";
let mut file_name = "freezer.state";
let mut content = "FROZEN".to_string();
if self.v2 {
file = "cgroup.freeze";
file_name = "cgroup.freeze";
content = "1".to_string();
}
self.open_path(file, true).and_then(|mut file| {
self.open_path(file_name, true).and_then(|mut file| {
file.write_all(content.as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
.map_err(|e| Error::with_cause(WriteFailed(file_name.to_string(), content), e))
})
}
/// Thaws, that is, unfreezes the processes in the control group.
pub fn thaw(&self) -> Result<()> {
let mut file = "freezer.state";
let mut file_name = "freezer.state";
let mut content = "THAWED".to_string();
if self.v2 {
file = "cgroup.freeze";
file_name = "cgroup.freeze";
content = "0".to_string();
}
self.open_path(file, true).and_then(|mut file| {
self.open_path(file_name, true).and_then(|mut file| {
file.write_all(content.as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
.map_err(|e| Error::with_cause(WriteFailed(file_name.to_string(), content), e))
})
}
/// Retrieve the state of processes in the control group.
pub fn state(&self) -> Result<FreezerState> {
let mut file = "freezer.state";
let mut file_name = "freezer.state";
if self.v2 {
file = "cgroup.freeze";
file_name = "cgroup.freeze";
}
self.open_path(file, false).and_then(|mut file| {
self.open_path(file_name, false).and_then(|mut file| {
let mut s = String::new();
let res = file.read_to_string(&mut s);
match res {
Ok(_) => match s.as_ref() {
Ok(_) => match s.trim() {
"FROZEN" => Ok(FreezerState::Frozen),
"THAWED" => Ok(FreezerState::Thawed),
"1" => Ok(FreezerState::Frozen),
@@ -138,7 +139,7 @@ impl FreezerController {
"FREEZING" => Ok(FreezerState::Freezing),
_ => Err(Error::new(ParseError)),
},
Err(e) => Err(Error::with_cause(ReadFailed, e)),
Err(e) => Err(Error::with_cause(ReadFailed(file_name.to_string()), e)),
}
})
}

View File

@@ -35,7 +35,7 @@ use crate::cgroup::Cgroup;
/// Process mounts information.
///
/// See `proc(5)` for format details.
#[derive(Debug, PartialEq, Eq, Hash)]
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct Mountinfo {
/// Mount pathname relative to the process's root.
pub mount_point: PathBuf,
@@ -87,7 +87,7 @@ fn mountinfo_file(file: &mut File) -> Vec<Mountinfo> {
}
}
}
Err(_) => continue,
Err(_) => break,
}
}
r
@@ -102,12 +102,12 @@ pub fn mountinfo_self() -> Vec<Mountinfo> {
}
/// The standard, original cgroup implementation. Often referred to as "cgroupv1".
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct V1 {
mountinfo: Vec<Mountinfo>,
}
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct V2 {
root: String,
}
@@ -170,7 +170,7 @@ impl Hierarchy for V1 {
}
fn root_control_group(&self) -> Cgroup {
Cgroup::load(auto(), "".to_string())
Cgroup::load(auto(), "")
}
fn root(&self) -> PathBuf {
@@ -202,7 +202,12 @@ impl Hierarchy for V2 {
let mut subs = vec![];
let controllers = ret.unwrap().trim().to_string();
let controller_list: Vec<&str> = controllers.split(' ').collect();
let mut controller_list: Vec<&str> = controllers.split(' ').collect();
// The freezer functionality is present in V2, but not as a controller,
// but apparently as a core functionality. FreezerController supports
// that, but we must explicitly fake the controller here.
controller_list.push("freezer");
for s in controller_list {
match s {
@@ -241,7 +246,7 @@ impl Hierarchy for V2 {
}
fn root_control_group(&self) -> Cgroup {
Cgroup::load(auto(), "".to_string())
Cgroup::load(auto(), "")
}
fn root(&self) -> PathBuf {
@@ -268,6 +273,12 @@ impl V1 {
}
}
impl Default for V1 {
fn default() -> Self {
Self::new()
}
}
impl V2 {
/// Finds where control groups are mounted to and returns a hierarchy in which control groups
/// can be created.
@@ -278,9 +289,18 @@ impl V2 {
}
}
pub const UNIFIED_MOUNTPOINT: &'static str = "/sys/fs/cgroup";
impl Default for V2 {
fn default() -> Self {
Self::new()
}
}
#[cfg(all(target_os = "linux", not(target_env = "musl")))]
pub const UNIFIED_MOUNTPOINT: &str = "/sys/fs/cgroup";
#[cfg(any(
all(target_os = "linux", not(target_env = "musl")),
target_os = "android"
))]
pub fn is_cgroup2_unified_mode() -> bool {
use nix::sys::statfs;
@@ -294,7 +314,7 @@ pub fn is_cgroup2_unified_mode() -> bool {
fs_stat.unwrap().filesystem_type() == statfs::CGROUP2_SUPER_MAGIC
}
pub const INIT_CGROUP_PATHS: &'static str = "/proc/1/cgroup";
pub const INIT_CGROUP_PATHS: &str = "/proc/1/cgroup";
#[cfg(all(target_os = "linux", target_env = "musl"))]
pub fn is_cgroup2_unified_mode() -> bool {

View File

@@ -8,6 +8,7 @@
//!
//! 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 log::warn;
use std::io::Write;
use std::path::PathBuf;
@@ -88,12 +89,12 @@ impl<'a> From<&'a Subsystem> for &'a HugeTlbController {
impl HugeTlbController {
/// Constructs a new `HugeTlbController` with `root` serving as the root of the control group.
pub fn new(root: PathBuf, v2: bool) -> Self {
let sizes = get_hugepage_sizes().unwrap();
let sizes = get_hugepage_sizes();
Self {
base: root.clone(),
path: root,
sizes: sizes,
v2: v2,
sizes,
v2,
}
}
@@ -115,7 +116,7 @@ impl HugeTlbController {
self.open_path(&format!("hugetlb.{}.events", hugetlb_size), false)
.and_then(flat_keyed_to_vec)
.and_then(|x| {
if x.len() == 0 {
if x.is_empty() {
return Err(Error::from_string(format!(
"get empty from hugetlb.{}.events",
hugetlb_size
@@ -164,43 +165,46 @@ impl HugeTlbController {
/// 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, hugetlb_size: &str, limit: u64) -> Result<()> {
let mut file = format!("hugetlb.{}.limit_in_bytes", hugetlb_size);
let mut file_name = format!("hugetlb.{}.limit_in_bytes", hugetlb_size);
if self.v2 {
file = format!("hugetlb.{}.max", hugetlb_size);
file_name = 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))
self.open_path(&file_name, true).and_then(|mut file| {
file.write_all(limit.to_string().as_ref()).map_err(|e| {
Error::with_cause(WriteFailed(file_name.to_string(), limit.to_string()), e)
})
})
}
}
pub const HUGEPAGESIZE_DIR: &'static str = "/sys/kernel/mm/hugepages";
pub const HUGEPAGESIZE_DIR: &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();
fn get_hugepage_sizes() -> Vec<String> {
let dirs = fs::read_dir(HUGEPAGESIZE_DIR);
if dirs.is_err() {
return Ok(m);
return Vec::new();
}
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));
}
dirs.unwrap()
.filter_map(|e| {
let entry = e.map_err(|e| warn!("readdir error: {:?}", e)).ok()?;
let name = entry.file_name().into_string().unwrap();
let parts: Vec<&str> = name.split('-').collect();
if parts.len() != 2 {
return None;
}
let bmap = get_binary_size_map();
let size = parse_size(parts[1], &bmap)
.map_err(|e| warn!("parse_size error: {:?}", e))
.ok()?;
let dabbrs = get_decimal_abbrs();
Ok(m)
Some(custom_size(size as f64, 1024.0, &dabbrs))
})
.collect()
}
pub const KB: u128 = 1000;
@@ -264,8 +268,8 @@ fn parse_size(s: &str, m: &HashMap<String, u128>) -> Result<u128> {
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>();
let size: u128 = if let Some(num) = num {
let n = num.as_str().trim().parse::<u128>();
if n.is_err() {
return Err(Error::new(InvalidBytesSize));
}
@@ -275,10 +279,10 @@ fn parse_size(s: &str, m: &HashMap<String, u128>) -> Result<u128> {
};
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()
let mul: u128 = if let Some(q) = q {
let t = m.get(q.as_str());
if let Some(t) = t {
*t
} else {
return Err(Error::new(InvalidBytesSize));
}
@@ -289,7 +293,7 @@ fn parse_size(s: &str, m: &HashMap<String, u128>) -> Result<u128> {
Ok(size * mul)
}
fn custom_size(mut size: f64, base: f64, m: &Vec<String>) -> String {
fn custom_size(mut size: f64, base: f64, m: &[String]) -> String {
let mut i = 0;
while size >= base && i < m.len() - 1 {
size /= base;

View File

@@ -4,9 +4,11 @@
// SPDX-License-Identifier: Apache-2.0 or MIT
//
#![allow(clippy::unnecessary_unwrap)]
use log::*;
use std::collections::HashMap;
use std::fmt;
use std::fs::{self, File};
use std::io::{BufRead, BufReader, Read, Write};
use std::path::{Path, PathBuf};
@@ -123,23 +125,23 @@ pub enum Controllers {
Systemd,
}
impl Controllers {
pub fn to_string(&self) -> String {
impl fmt::Display for Controllers {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Controllers::Pids => return "pids".to_string(),
Controllers::Mem => return "memory".to_string(),
Controllers::CpuSet => return "cpuset".to_string(),
Controllers::CpuAcct => return "cpuacct".to_string(),
Controllers::Cpu => return "cpu".to_string(),
Controllers::Devices => return "devices".to_string(),
Controllers::Freezer => return "freezer".to_string(),
Controllers::NetCls => return "net_cls".to_string(),
Controllers::BlkIo => return "blkio".to_string(),
Controllers::PerfEvent => return "perf_event".to_string(),
Controllers::NetPrio => return "net_prio".to_string(),
Controllers::HugeTlb => return "hugetlb".to_string(),
Controllers::Rdma => return "rdma".to_string(),
Controllers::Systemd => return "name=systemd".to_string(),
Controllers::Pids => write!(f, "pids"),
Controllers::Mem => write!(f, "memory"),
Controllers::CpuSet => write!(f, "cpuset"),
Controllers::CpuAcct => write!(f, "cpuacct"),
Controllers::Cpu => write!(f, "cpu"),
Controllers::Devices => write!(f, "devices"),
Controllers::Freezer => write!(f, "freezer"),
Controllers::NetCls => write!(f, "net_cls"),
Controllers::BlkIo => write!(f, "blkio"),
Controllers::PerfEvent => write!(f, "perf_event"),
Controllers::NetPrio => write!(f, "net_prio"),
Controllers::HugeTlb => write!(f, "hugetlb"),
Controllers::Rdma => write!(f, "rdma"),
Controllers::Systemd => write!(f, "name=systemd"),
}
}
}
@@ -179,13 +181,22 @@ mod sealed {
if w {
match File::create(&path) {
Err(e) => return Err(Error::with_cause(ErrorKind::WriteFailed, e)),
Ok(file) => return Ok(file),
Err(e) => Err(Error::with_cause(
ErrorKind::WriteFailed(
path.display().to_string(),
"[CREATE FILE]".to_string(),
),
e,
)),
Ok(file) => Ok(file),
}
} else {
match File::open(&path) {
Err(e) => return Err(Error::with_cause(ErrorKind::ReadFailed, e)),
Ok(file) => return Ok(file),
Err(e) => Err(Error::with_cause(
ErrorKind::ReadFailed(path.display().to_string()),
e,
)),
Ok(file) => Ok(file),
}
}
}
@@ -196,14 +207,14 @@ mod sealed {
let res = file.read_to_string(&mut string);
match res {
Ok(_) => parse_max_value(&string),
Err(e) => Err(Error::with_cause(ReadFailed, e)),
Err(e) => Err(Error::with_cause(ReadFailed(f.to_string()), e)),
}
})
}
#[doc(hidden)]
fn path_exists(&self, p: &str) -> bool {
if let Err(_) = self.verify_path() {
if self.verify_path().is_err() {
return false;
}
@@ -214,8 +225,9 @@ mod sealed {
pub trait CustomizedAttribute: ControllerInternal {
fn set(&self, key: &str, value: &str) -> Result<()> {
self.open_path(key, true).and_then(|mut file| {
file.write_all(value.as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
file.write_all(value.as_ref()).map_err(|e| {
Error::with_cause(WriteFailed(key.to_string(), value.to_string()), e)
})
})
}
@@ -224,7 +236,7 @@ mod sealed {
let mut string = String::new();
match file.read_to_string(&mut string) {
Ok(_) => Ok(string.trim().to_owned()),
Err(e) => Err(Error::with_cause(ReadFailed, e)),
Err(e) => Err(Error::with_cause(ReadFailed(key.to_string()), e)),
}
})
}
@@ -295,7 +307,7 @@ where
/// Create this controller
fn create(&self) {
self.verify_path()
.expect(format!("path should be valid: {:?}", self.path()).as_str());
.unwrap_or_else(|_| panic!("path should be valid: {:?}", self.path()));
match ::std::fs::create_dir_all(self.get_path()) {
Ok(_) => self.post_create(),
@@ -307,16 +319,24 @@ where
fn set_notify_on_release(&self, enable: bool) -> Result<()> {
self.open_path("notify_on_release", true)
.and_then(|mut file| {
write!(file, "{}", enable as i32)
.map_err(|e| Error::with_cause(ErrorKind::WriteFailed, e))
write!(file, "{}", enable as i32).map_err(|e| {
Error::with_cause(
ErrorKind::WriteFailed("notify_on_release".to_string(), enable.to_string()),
e,
)
})
})
}
/// Set release_agent
fn set_release_agent(&self, path: &str) -> Result<()> {
self.open_path("release_agent", true).and_then(|mut file| {
file.write_all(path.as_bytes())
.map_err(|e| Error::with_cause(ErrorKind::WriteFailed, e))
file.write_all(path.as_bytes()).map_err(|e| {
Error::with_cause(
ErrorKind::WriteFailed("release_agent".to_string(), path.to_string()),
e,
)
})
})
}
/// Does this controller already exist?
@@ -330,26 +350,50 @@ where
return Ok(());
}
remove_dir(self.get_path())
// Compatible with runC for remove dir operation
// https://github.com/opencontainers/runc/blob/main/libcontainer/cgroups/utils.go#L272
//
// We trying to remove all paths five times with increasing delay between tries.
// If after all there are not removed cgroups - appropriate error will be
// returned.
let mut delay = std::time::Duration::from_millis(10);
let cgroup_path = self.get_path();
for _i in 0..4 {
if let Ok(()) = remove_dir(cgroup_path) {
return Ok(());
}
std::thread::sleep(delay);
delay *= 2;
}
remove_dir(cgroup_path)
}
/// Attach a task to this controller.
fn add_task(&self, pid: &CgroupPid) -> Result<()> {
let mut file = "tasks";
let mut file_name = "tasks";
if self.is_v2() {
file = "cgroup.procs";
file_name = "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))
self.open_path(file_name, true).and_then(|mut file| {
file.write_all(pid.pid.to_string().as_ref()).map_err(|e| {
Error::with_cause(
ErrorKind::WriteFailed(file_name.to_string(), pid.pid.to_string()),
e,
)
})
})
}
/// Attach a task to this controller by thread group id.
fn add_task_by_tgid(&self, pid: &CgroupPid) -> Result<()> {
self.open_path("cgroup.procs", true).and_then(|mut file| {
file.write_all(pid.pid.to_string().as_ref())
.map_err(|e| Error::with_cause(ErrorKind::WriteFailed, e))
file.write_all(pid.pid.to_string().as_ref()).map_err(|e| {
Error::with_cause(
ErrorKind::WriteFailed("cgroup.procs".to_string(), pid.pid.to_string()),
e,
)
})
})
}
@@ -360,18 +404,21 @@ where
file = "cgroup.procs";
}
self.open_path(file, false)
.and_then(|file| {
.map(|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);
match line {
Ok(line) => {
let n = line.trim().parse().unwrap_or(0u64);
v.push(n);
}
Err(_) => break,
}
}
Ok(v.into_iter().map(CgroupPid::from).collect())
v.into_iter().map(CgroupPid::from).collect()
})
.unwrap_or(vec![])
.unwrap_or_default()
}
fn v2(&self) -> bool {
@@ -381,23 +428,24 @@ where
// remove_dir aims to remove cgroup path. It does so recursively,
// by removing any subdirectories (sub-cgroups) first.
fn remove_dir(dir: &PathBuf) -> Result<()> {
fn remove_dir(dir: &Path) -> Result<()> {
// try the fast path first.
if fs::remove_dir(dir).is_ok() {
return Ok(());
}
if dir.exists() {
if dir.is_dir() {
for entry in fs::read_dir(dir).map_err(|e| Error::with_cause(ReadFailed, e))? {
let entry = entry.map_err(|e| Error::with_cause(ReadFailed, e))?;
let path = entry.path();
if path.is_dir() {
remove_dir(&path)?;
}
if dir.exists() && dir.is_dir() {
for entry in fs::read_dir(dir)
.map_err(|e| Error::with_cause(ReadFailed(dir.display().to_string()), e))?
{
let entry =
entry.map_err(|e| Error::with_cause(ReadFailed(dir.display().to_string()), e))?;
let path = entry.path();
if path.is_dir() {
remove_dir(&path)?;
}
fs::remove_dir(dir).map_err(|e| Error::with_cause(RemoveFailed, e))?;
}
fs::remove_dir(dir).map_err(|e| Error::with_cause(RemoveFailed, e))?;
}
Ok(())
@@ -425,6 +473,7 @@ pub trait Hierarchy: std::fmt::Debug + Send + Sync {
/// Resource limits for the memory subsystem.
#[derive(Debug, Clone, Eq, PartialEq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MemoryResources {
/// How much memory (in bytes) can the kernel consume.
pub kernel_memory_limit: Option<i64>,
@@ -448,13 +497,15 @@ pub struct MemoryResources {
/// # Usage:
/// ```
/// let resource = &mut cgroups_rs::Resources::default();
/// resource.memory.attrs.insert("memory.numa_balancing", "true".to_string());
/// resource.memory.attrs.insert("memory.numa_balancing".to_string(), "true".to_string());
/// // apply here
pub attrs: std::collections::HashMap<&'static str, String>,
/// ```
pub attrs: HashMap<String, String>,
}
/// Resources limits on the number of processes.
#[derive(Debug, Clone, Eq, PartialEq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct PidResources {
/// The maximum number of processes that can exist in the control group.
///
@@ -466,6 +517,7 @@ pub struct PidResources {
/// Resources limits about how the tasks can use the CPU.
#[derive(Debug, Clone, Eq, PartialEq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct CpuResources {
// cpuset
/// A comma-separated list of CPU IDs where the task in the control group can run. Dashes
@@ -490,14 +542,15 @@ pub struct CpuResources {
/// # Usage:
/// ```
/// let resource = &mut cgroups_rs::Resources::default();
/// resource.cpu.attrs.insert("cpu.cfs_init_buffer_us", "10".to_string());
/// resource.cpu.attrs.insert("cpu.cfs_init_buffer_us".to_string(), "10".to_string());
/// // apply here
/// ```
pub attrs: std::collections::HashMap<&'static str, String>,
pub attrs: HashMap<String, String>,
}
/// A device resource that can be allowed or denied access to.
#[derive(Debug, Clone, Eq, PartialEq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct DeviceResource {
/// If true, access to the device is allowed, otherwise it's denied.
pub allow: bool,
@@ -513,6 +566,7 @@ pub struct DeviceResource {
/// Limit the usage of devices for the control group's tasks.
#[derive(Debug, Clone, Eq, PartialEq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct DeviceResources {
/// For each device in the list, the limits in the structure are applied.
pub devices: Vec<DeviceResource>,
@@ -520,6 +574,7 @@ pub struct DeviceResources {
/// Assigned priority for a network device.
#[derive(Debug, Clone, Eq, PartialEq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct NetworkPriority {
/// The name (as visible in `ifconfig`) of the interface.
pub name: String,
@@ -530,6 +585,7 @@ pub struct NetworkPriority {
/// Collections of limits and tags that can be imposed on packets emitted by the tasks in the
/// control group.
#[derive(Debug, Clone, Eq, PartialEq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct NetworkResources {
/// The networking class identifier to attach to the packets.
///
@@ -541,6 +597,7 @@ pub struct NetworkResources {
/// A hugepage type and its consumption limit for the control group.
#[derive(Debug, Clone, Eq, PartialEq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct HugePageResource {
/// The size of the hugepage, i.e. `2MB`, `1GB`, etc.
pub size: String,
@@ -551,6 +608,7 @@ pub struct HugePageResource {
/// Provides the ability to set consumption limit on each type of hugepages.
#[derive(Debug, Clone, Eq, PartialEq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct HugePageResources {
/// Set a limit of consumption for each hugepages type.
pub limits: Vec<HugePageResource>,
@@ -558,6 +616,7 @@ pub struct HugePageResources {
/// Weight for a particular block device.
#[derive(Debug, Clone, Eq, PartialEq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct BlkIoDeviceResource {
/// The major number of the device.
pub major: u64,
@@ -571,6 +630,7 @@ pub struct BlkIoDeviceResource {
/// Provides the ability to throttle a device (both byte/sec, and IO op/s)
#[derive(Debug, Clone, Eq, PartialEq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct BlkIoDeviceThrottleResource {
/// The major number of the device.
pub major: u64,
@@ -582,6 +642,7 @@ pub struct BlkIoDeviceThrottleResource {
/// General block I/O resource limits.
#[derive(Debug, Clone, Eq, PartialEq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct BlkIoResources {
/// The weight of the control group against descendant nodes.
pub weight: Option<u16>,
@@ -597,10 +658,20 @@ pub struct BlkIoResources {
pub throttle_write_bps_device: Vec<BlkIoDeviceThrottleResource>,
/// Throttled write IO operations per second can be provided for each device.
pub throttle_write_iops_device: Vec<BlkIoDeviceThrottleResource>,
/// Customized key-value attributes
/// # Usage:
/// ```
/// let resource = &mut cgroups_rs::Resources::default();
/// resource.blkio.attrs.insert("io.cost.weight".to_string(), "10".to_string());
/// // apply here
/// ```
pub attrs: HashMap<String, String>,
}
/// The resource limits and constraints that will be set on the control group.
#[derive(Debug, Clone, Eq, PartialEq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Resources {
/// Memory usage related limits.
pub memory: MemoryResources,
@@ -641,75 +712,61 @@ impl<'a> From<&'a std::process::Child> for CgroupPid {
impl Subsystem {
fn enter(self, path: &Path) -> Self {
match self {
Subsystem::Pid(cont) => Subsystem::Pid({
let mut c = cont.clone();
c.get_path_mut().push(path);
c
Subsystem::Pid(mut cont) => Subsystem::Pid({
cont.get_path_mut().push(path);
cont
}),
Subsystem::Mem(cont) => Subsystem::Mem({
let mut c = cont.clone();
c.get_path_mut().push(path);
c
Subsystem::Mem(mut cont) => Subsystem::Mem({
cont.get_path_mut().push(path);
cont
}),
Subsystem::CpuSet(cont) => Subsystem::CpuSet({
let mut c = cont.clone();
c.get_path_mut().push(path);
c
Subsystem::CpuSet(mut cont) => Subsystem::CpuSet({
cont.get_path_mut().push(path);
cont
}),
Subsystem::CpuAcct(cont) => Subsystem::CpuAcct({
let mut c = cont.clone();
c.get_path_mut().push(path);
c
Subsystem::CpuAcct(mut cont) => Subsystem::CpuAcct({
cont.get_path_mut().push(path);
cont
}),
Subsystem::Cpu(cont) => Subsystem::Cpu({
let mut c = cont.clone();
c.get_path_mut().push(path);
c
Subsystem::Cpu(mut cont) => Subsystem::Cpu({
cont.get_path_mut().push(path);
cont
}),
Subsystem::Devices(cont) => Subsystem::Devices({
let mut c = cont.clone();
c.get_path_mut().push(path);
c
Subsystem::Devices(mut cont) => Subsystem::Devices({
cont.get_path_mut().push(path);
cont
}),
Subsystem::Freezer(cont) => Subsystem::Freezer({
let mut c = cont.clone();
c.get_path_mut().push(path);
c
Subsystem::Freezer(mut cont) => Subsystem::Freezer({
cont.get_path_mut().push(path);
cont
}),
Subsystem::NetCls(cont) => Subsystem::NetCls({
let mut c = cont.clone();
c.get_path_mut().push(path);
c
Subsystem::NetCls(mut cont) => Subsystem::NetCls({
cont.get_path_mut().push(path);
cont
}),
Subsystem::BlkIo(cont) => Subsystem::BlkIo({
let mut c = cont.clone();
c.get_path_mut().push(path);
c
Subsystem::BlkIo(mut cont) => Subsystem::BlkIo({
cont.get_path_mut().push(path);
cont
}),
Subsystem::PerfEvent(cont) => Subsystem::PerfEvent({
let mut c = cont.clone();
c.get_path_mut().push(path);
c
Subsystem::PerfEvent(mut cont) => Subsystem::PerfEvent({
cont.get_path_mut().push(path);
cont
}),
Subsystem::NetPrio(cont) => Subsystem::NetPrio({
let mut c = cont.clone();
c.get_path_mut().push(path);
c
Subsystem::NetPrio(mut cont) => Subsystem::NetPrio({
cont.get_path_mut().push(path);
cont
}),
Subsystem::HugeTlb(cont) => Subsystem::HugeTlb({
let mut c = cont.clone();
c.get_path_mut().push(path);
c
Subsystem::HugeTlb(mut cont) => Subsystem::HugeTlb({
cont.get_path_mut().push(path);
cont
}),
Subsystem::Rdma(cont) => Subsystem::Rdma({
let mut c = cont.clone();
c.get_path_mut().push(path);
c
Subsystem::Rdma(mut cont) => Subsystem::Rdma({
cont.get_path_mut().push(path);
cont
}),
Subsystem::Systemd(cont) => Subsystem::Systemd({
let mut c = cont.clone();
c.get_path_mut().push(path);
c
Subsystem::Systemd(mut cont) => Subsystem::Systemd({
cont.get_path_mut().push(path);
cont
}),
}
}
@@ -740,6 +797,7 @@ impl Subsystem {
/// The values for `memory.hight` or `pids.max`
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum MaxValue {
/// This value is returned when the text is `"max"`.
Max,
@@ -754,22 +812,25 @@ impl Default for MaxValue {
}
impl MaxValue {
#[allow(clippy::should_implement_trait, clippy::wrong_self_convention)]
fn to_i64(&self) -> i64 {
match self {
MaxValue::Max => -1,
MaxValue::Value(num) => *num,
}
}
}
fn to_string(&self) -> String {
impl fmt::Display for MaxValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
MaxValue::Max => "max".to_string(),
MaxValue::Value(num) => num.to_string(),
MaxValue::Max => write!(f, "max"),
MaxValue::Value(num) => write!(f, "{}", num),
}
}
}
pub fn parse_max_value(s: &String) -> Result<MaxValue> {
pub fn parse_max_value(s: &str) -> Result<MaxValue> {
if s.trim() == "max" {
return Ok(MaxValue::Max);
}
@@ -785,17 +846,14 @@ pub fn parse_max_value(s: &String) -> Result<MaxValue> {
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))?;
.map_err(|e| Error::with_cause(ReadFailed("FIXME: read_string_from".to_string()), 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(_) => {}
if let Ok(i) = parts[1].parse::<i64>() {
v.push((parts[0].to_string(), i));
}
}
}
@@ -808,17 +866,14 @@ pub fn flat_keyed_to_vec(mut file: File) -> Result<Vec<(String, i64)>> {
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))?;
.map_err(|e| Error::with_cause(ReadFailed("FIXME: read_string_from".to_string()), 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(_) => {}
if let Ok(i) = parts[1].parse::<i64>() {
h.insert(parts[0].to_string(), i);
}
}
}
@@ -831,23 +886,20 @@ pub fn flat_keyed_to_hashmap(mut file: File) -> Result<HashMap<String, i64>> {
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))?;
.map_err(|e| Error::with_cause(ReadFailed("FIXME: read_string_from".to_string()), e))?;
let mut h = HashMap::new();
for line in content.lines() {
let parts: Vec<&str> = line.split(' ').collect();
if parts.len() == 0 {
if parts.is_empty() {
continue;
}
let mut th = HashMap::new();
for item in parts[1..].into_iter() {
for item in parts[1..].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(_) => {}
if let Ok(i) = fields[1].parse::<i64>() {
th.insert(fields[0].to_string(), i);
}
}
}
@@ -868,7 +920,10 @@ where
.trim()
.parse::<T>()
.map_err(|e| Error::with_cause(ParseError, e)),
Err(e) => Err(Error::with_cause(ReadFailed, e)),
Err(e) => Err(Error::with_cause(
ReadFailed("FIXME: can't get path in fn read_from".to_string()),
e,
)),
}
}
@@ -876,7 +931,10 @@ 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)),
Err(e) => Err(Error::with_cause(
ReadFailed("FIXME: can't get path in fn read_string_from".to_string()),
e,
)),
}
}

View File

@@ -8,6 +8,7 @@
//!
//! See the Kernel's documentation for more information about this subsystem, found at:
//! [Documentation/cgroup-v1/memory.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/memory.txt)
use log::warn;
use std::collections::HashMap;
use std::io::Write;
use std::path::PathBuf;
@@ -38,6 +39,7 @@ pub struct MemController {
}
#[derive(Default, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SetMemory {
pub low: Option<MaxValue>,
pub high: Option<MaxValue>,
@@ -47,6 +49,7 @@ pub struct SetMemory {
/// Controls statistics and controls about the OOM killer operating in this control group.
#[derive(Default, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct OomControl {
/// If true, the OOM killer has been disabled for the tasks in this control group.
pub oom_kill_disable: bool,
@@ -56,18 +59,38 @@ pub struct OomControl {
pub oom_kill: u64,
}
#[allow(clippy::unnecessary_wraps)]
fn parse_oom_control(s: String) -> Result<OomControl> {
let spl = s.split_whitespace().collect::<Vec<_>>();
let oom_kill_disable = if spl.len() > 1 {
spl[1].parse::<u64>().unwrap() == 1
} else {
false
};
let under_oom = if spl.len() > 3 {
spl[3].parse::<u64>().unwrap() == 1
} else {
false
};
let oom_kill = if spl.len() > 5 {
spl[5].parse::<u64>().unwrap()
} else {
0
};
Ok(OomControl {
oom_kill_disable: spl[1].parse::<u64>().unwrap() == 1,
under_oom: spl[3].parse::<u64>().unwrap() == 1,
oom_kill: spl[5].parse::<u64>().unwrap(),
oom_kill_disable,
under_oom,
oom_kill,
})
}
/// Contains statistics about the NUMA locality of the control group's tasks.
#[derive(Default, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct NumaStat {
/// Total amount of pages used by the control group.
pub total_pages: u64,
@@ -104,18 +127,19 @@ pub struct NumaStat {
pub hierarchical_unevictable_pages_per_node: Vec<u64>,
}
#[allow(clippy::unnecessary_wraps)]
fn parse_numa_stat(s: String) -> Result<NumaStat> {
// Parse the number of nodes
let _nodes = (s.split_whitespace().collect::<Vec<_>>().len() - 8) / 8;
let _nodes = (s.split_whitespace().count() - 8) / 8;
let mut ls = s.lines();
let total_line = ls.next().unwrap();
let file_line = ls.next().unwrap();
let anon_line = ls.next().unwrap();
let unevict_line = ls.next().unwrap();
let hier_total_line = ls.next().unwrap();
let hier_file_line = ls.next().unwrap();
let hier_anon_line = ls.next().unwrap();
let hier_unevict_line = ls.next().unwrap();
let hier_total_line = ls.next().unwrap_or_default();
let hier_file_line = ls.next().unwrap_or_default();
let hier_anon_line = ls.next().unwrap_or_default();
let hier_unevict_line = ls.next().unwrap_or_default();
Ok(NumaStat {
total_pages: total_line
@@ -124,10 +148,10 @@ fn parse_numa_stat(s: String) -> Result<NumaStat> {
.parse::<u64>()
.unwrap_or(0),
total_pages_per_node: {
let spl = &total_line.split(" ").collect::<Vec<_>>()[1..];
let spl = &total_line.split(' ').collect::<Vec<_>>()[1..];
spl.iter()
.map(|x| {
x.split("=").collect::<Vec<_>>()[1]
x.split('=').collect::<Vec<_>>()[1]
.parse::<u64>()
.unwrap_or(0)
})
@@ -139,10 +163,10 @@ fn parse_numa_stat(s: String) -> Result<NumaStat> {
.parse::<u64>()
.unwrap_or(0),
file_pages_per_node: {
let spl = &file_line.split(" ").collect::<Vec<_>>()[1..];
let spl = &file_line.split(' ').collect::<Vec<_>>()[1..];
spl.iter()
.map(|x| {
x.split("=").collect::<Vec<_>>()[1]
x.split('=').collect::<Vec<_>>()[1]
.parse::<u64>()
.unwrap_or(0)
})
@@ -154,10 +178,10 @@ fn parse_numa_stat(s: String) -> Result<NumaStat> {
.parse::<u64>()
.unwrap_or(0),
anon_pages_per_node: {
let spl = &anon_line.split(" ").collect::<Vec<_>>()[1..];
let spl = &anon_line.split(' ').collect::<Vec<_>>()[1..];
spl.iter()
.map(|x| {
x.split("=").collect::<Vec<_>>()[1]
x.split('=').collect::<Vec<_>>()[1]
.parse::<u64>()
.unwrap_or(0)
})
@@ -169,79 +193,120 @@ fn parse_numa_stat(s: String) -> Result<NumaStat> {
.parse::<u64>()
.unwrap_or(0),
unevictable_pages_per_node: {
let spl = &unevict_line.split(" ").collect::<Vec<_>>()[1..];
let spl = &unevict_line.split(' ').collect::<Vec<_>>()[1..];
spl.iter()
.map(|x| {
x.split("=").collect::<Vec<_>>()[1]
x.split('=').collect::<Vec<_>>()[1]
.parse::<u64>()
.unwrap_or(0)
})
.collect()
},
hierarchical_total_pages: hier_total_line
.split(|x| x == ' ' || x == '=')
.collect::<Vec<_>>()[1]
.parse::<u64>()
.unwrap_or(0),
hierarchical_total_pages: {
if !hier_total_line.is_empty() {
hier_total_line
.split(|x| x == ' ' || x == '=')
.collect::<Vec<_>>()[1]
.parse::<u64>()
.unwrap_or(0)
} else {
0
}
},
hierarchical_total_pages_per_node: {
let spl = &hier_total_line.split(" ").collect::<Vec<_>>()[1..];
spl.iter()
.map(|x| {
x.split("=").collect::<Vec<_>>()[1]
.parse::<u64>()
.unwrap_or(0)
})
.collect()
if !hier_total_line.is_empty() {
let spl = &hier_total_line.split(' ').collect::<Vec<_>>()[1..];
spl.iter()
.map(|x| {
x.split('=').collect::<Vec<_>>()[1]
.parse::<u64>()
.unwrap_or(0)
})
.collect()
} else {
Vec::new()
}
},
hierarchical_file_pages: {
if !hier_file_line.is_empty() {
hier_file_line
.split(|x| x == ' ' || x == '=')
.collect::<Vec<_>>()[1]
.parse::<u64>()
.unwrap_or(0)
} else {
0
}
},
hierarchical_file_pages: hier_file_line
.split(|x| x == ' ' || x == '=')
.collect::<Vec<_>>()[1]
.parse::<u64>()
.unwrap_or(0),
hierarchical_file_pages_per_node: {
let spl = &hier_file_line.split(" ").collect::<Vec<_>>()[1..];
spl.iter()
.map(|x| {
x.split("=").collect::<Vec<_>>()[1]
.parse::<u64>()
.unwrap_or(0)
})
.collect()
if !hier_file_line.is_empty() {
let spl = &hier_file_line.split(' ').collect::<Vec<_>>()[1..];
spl.iter()
.map(|x| {
x.split('=').collect::<Vec<_>>()[1]
.parse::<u64>()
.unwrap_or(0)
})
.collect()
} else {
Vec::new()
}
},
hierarchical_anon_pages: {
if !hier_anon_line.is_empty() {
hier_anon_line
.split(|x| x == ' ' || x == '=')
.collect::<Vec<_>>()[1]
.parse::<u64>()
.unwrap_or(0)
} else {
0
}
},
hierarchical_anon_pages: hier_anon_line
.split(|x| x == ' ' || x == '=')
.collect::<Vec<_>>()[1]
.parse::<u64>()
.unwrap_or(0),
hierarchical_anon_pages_per_node: {
let spl = &hier_anon_line.split(" ").collect::<Vec<_>>()[1..];
spl.iter()
.map(|x| {
x.split("=").collect::<Vec<_>>()[1]
.parse::<u64>()
.unwrap_or(0)
})
.collect()
if !hier_anon_line.is_empty() {
let spl = &hier_anon_line.split(' ').collect::<Vec<_>>()[1..];
spl.iter()
.map(|x| {
x.split('=').collect::<Vec<_>>()[1]
.parse::<u64>()
.unwrap_or(0)
})
.collect()
} else {
Vec::new()
}
},
hierarchical_unevictable_pages: {
if !hier_unevict_line.is_empty() {
hier_unevict_line
.split(|x| x == ' ' || x == '=')
.collect::<Vec<_>>()[1]
.parse::<u64>()
.unwrap_or(0)
} else {
0
}
},
hierarchical_unevictable_pages: hier_unevict_line
.split(|x| x == ' ' || x == '=')
.collect::<Vec<_>>()[1]
.parse::<u64>()
.unwrap_or(0),
hierarchical_unevictable_pages_per_node: {
let spl = &hier_unevict_line.split(" ").collect::<Vec<_>>()[1..];
spl.iter()
.map(|x| {
x.split("=").collect::<Vec<_>>()[1]
.parse::<u64>()
.unwrap_or(0)
})
.collect()
if !hier_unevict_line.is_empty() {
let spl = &hier_unevict_line.split(' ').collect::<Vec<_>>()[1..];
spl.iter()
.map(|x| {
x.split('=').collect::<Vec<_>>()[1]
.parse::<u64>()
.unwrap_or(0)
})
.collect()
} else {
Vec::new()
}
},
})
}
#[derive(Default, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MemoryStat {
pub cache: u64,
pub rss: u64,
@@ -282,6 +347,7 @@ pub struct MemoryStat {
pub raw: HashMap<String, u64>,
}
#[allow(clippy::unnecessary_wraps)]
fn parse_memory_stat(s: String) -> Result<MemoryStat> {
let mut raw = HashMap::new();
@@ -335,13 +401,14 @@ fn parse_memory_stat(s: String) -> Result<MemoryStat> {
total_inactive_file: *raw.get("total_inactive_file").unwrap_or(&0),
total_active_file: *raw.get("total_active_file").unwrap_or(&0),
total_unevictable: *raw.get("total_unevictable").unwrap_or(&0),
raw: raw,
raw,
})
}
/// Contains statistics about the current usage of memory and swap (together, not seperately) by
/// the control group's tasks.
#[derive(Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MemSwap {
/// How many times the limit has been hit.
pub fail_cnt: u64,
@@ -356,6 +423,7 @@ pub struct MemSwap {
/// State of and statistics gathered by the kernel about the memory usage of the control group's
/// tasks.
#[derive(Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Memory {
/// How many times the limit has been hit.
pub fail_cnt: u64,
@@ -400,6 +468,7 @@ pub struct Memory {
/// The current state of and gathered statistics about the kernel's memory usage for TCP-related
/// data structures.
#[derive(Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Tcp {
/// How many times the limit has been hit.
pub fail_cnt: u64,
@@ -418,6 +487,7 @@ pub struct Tcp {
/// these tasks if it would think that the limits here would be violated. It's important to note
/// that interrupts in particular might not be able to enforce these limits.
#[derive(Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Kmem {
/// How many times the limit has been hit.
pub fail_cnt: u64,
@@ -474,7 +544,7 @@ impl MemController {
Self {
base: root.clone(),
path: root,
v2: v2,
v2,
}
}
@@ -489,11 +559,12 @@ impl MemController {
for value in values {
let v = value.0;
let f = value.1;
if v.is_some() {
let v = v.unwrap().to_string();
if let Some(v) = v {
let v = v.to_string();
self.open_path(f, true).and_then(|mut file| {
file.write_all(v.as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
file.write_all(v.as_ref()).map_err(|e| {
Error::with_cause(WriteFailed(f.to_string(), format!("{:?}", v)), e)
})
})?;
}
}
@@ -531,7 +602,7 @@ impl MemController {
.open_path("memory.stat", false)
.and_then(read_string_from)
.and_then(parse_memory_stat)
.unwrap_or(MemoryStat::default()),
.unwrap_or_default(),
swappiness: self
.open_path("memory.swap.current", false)
.and_then(read_u64_from)
@@ -575,12 +646,12 @@ impl MemController {
.open_path("memory.numa_stat", false)
.and_then(read_string_from)
.and_then(parse_numa_stat)
.unwrap_or(NumaStat::default()),
.unwrap_or_default(),
oom_control: self
.open_path("memory.oom_control", false)
.and_then(read_string_from)
.and_then(parse_oom_control)
.unwrap_or(OomControl::default()),
.unwrap_or_default(),
soft_limit_in_bytes: self
.open_path("memory.soft_limit_in_bytes", false)
.and_then(read_i64_from)
@@ -589,7 +660,7 @@ impl MemController {
.open_path("memory.stat", false)
.and_then(read_string_from)
.and_then(parse_memory_stat)
.unwrap_or(MemoryStat::default()),
.unwrap_or_default(),
swappiness: self
.open_path("memory.swappiness", false)
.and_then(read_u64_from)
@@ -623,7 +694,7 @@ impl MemController {
slabinfo: self
.open_path("memory.kmem.slabinfo", false)
.and_then(read_string_from)
.unwrap_or("".to_string()),
.unwrap_or_default(),
}
}
@@ -655,7 +726,7 @@ impl MemController {
fail_cnt: self
.open_path("memory.swap.events", false)
.and_then(flat_keyed_to_hashmap)
.and_then(|x| Ok(*x.get("fail").unwrap_or(&0) as u64))
.map(|x| *x.get("fail").unwrap_or(&0) as u64)
.unwrap(),
limit_in_bytes: self
.open_path("memory.swap.max", false)
@@ -699,8 +770,12 @@ impl MemController {
/// Reset the fail counter
pub fn reset_fail_count(&self) -> Result<()> {
self.open_path("memory.failcnt", true).and_then(|mut file| {
file.write_all("0".to_string().as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
file.write_all("0".to_string().as_ref()).map_err(|e| {
Error::with_cause(
WriteFailed("memory.failcnt".to_string(), "0".to_string()),
e,
)
})
})
}
@@ -713,8 +788,12 @@ impl MemController {
self.open_path("memory.kmem.failcnt", true)
.and_then(|mut file| {
file.write_all("0".to_string().as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
file.write_all("0".to_string().as_ref()).map_err(|e| {
Error::with_cause(
WriteFailed("memory.kmem.failcnt".to_string(), "0".to_string()),
e,
)
})
})
}
@@ -727,8 +806,12 @@ impl MemController {
self.open_path("memory.kmem.tcp.failcnt", true)
.and_then(|mut file| {
file.write_all("0".to_string().as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
file.write_all("0".to_string().as_ref()).map_err(|e| {
Error::with_cause(
WriteFailed("memory.kmem.tcp.failcnt".to_string(), "0".to_string()),
e,
)
})
})
}
@@ -736,8 +819,12 @@ impl MemController {
pub fn reset_memswap_fail_count(&self) -> Result<()> {
self.open_path("memory.memsw.failcnt", true)
.and_then(|mut file| {
file.write_all("0".to_string().as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
file.write_all("0".to_string().as_ref()).map_err(|e| {
Error::with_cause(
WriteFailed("memory.memsw.failcnt".to_string(), "0".to_string()),
e,
)
})
})
}
@@ -745,20 +832,25 @@ impl MemController {
pub fn reset_max_usage(&self) -> Result<()> {
self.open_path("memory.max_usage_in_bytes", true)
.and_then(|mut file| {
file.write_all("0".to_string().as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
file.write_all("0".to_string().as_ref()).map_err(|e| {
Error::with_cause(
WriteFailed("memory.max_usage_in_bytes".to_string(), "0".to_string()),
e,
)
})
})
}
/// Set the memory usage limit of the control group, in bytes.
pub fn set_limit(&self, limit: i64) -> Result<()> {
let mut file = "memory.limit_in_bytes";
let mut file_name = "memory.limit_in_bytes";
if self.v2 {
file = "memory.max";
file_name = "memory.max";
}
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))
self.open_path(file_name, true).and_then(|mut file| {
file.write_all(limit.to_string().as_ref()).map_err(|e| {
Error::with_cause(WriteFailed(file_name.to_string(), limit.to_string()), e)
})
})
}
@@ -771,20 +863,31 @@ impl MemController {
self.open_path("memory.kmem.limit_in_bytes", true)
.and_then(|mut file| {
file.write_all(limit.to_string().as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
let r = file.write_all(limit.to_string().as_ref());
match r {
Ok(()) => Ok(()),
Err(ref e) if e.raw_os_error() == Some(libc::EOPNOTSUPP) => {
warn!("memory.kmem.limit_in_bytes is unsupported by the kernel");
Ok(())
}
Err(e) => Err(Error::with_cause(
WriteFailed("memory.kmem.limit_in_bytes".to_string(), limit.to_string()),
e,
)),
}
})
}
/// Set the memory+swap limit of the control group, in bytes.
pub fn set_memswap_limit(&self, limit: i64) -> Result<()> {
let mut file = "memory.memsw.limit_in_bytes";
let mut file_name = "memory.memsw.limit_in_bytes";
if self.v2 {
file = "memory.swap.max";
file_name = "memory.swap.max";
}
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))
self.open_path(file_name, true).and_then(|mut file| {
file.write_all(limit.to_string().as_ref()).map_err(|e| {
Error::with_cause(WriteFailed(file_name.to_string(), limit.to_string()), e)
})
})
}
@@ -797,8 +900,15 @@ impl MemController {
self.open_path("memory.kmem.tcp.limit_in_bytes", true)
.and_then(|mut file| {
file.write_all(limit.to_string().as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
file.write_all(limit.to_string().as_ref()).map_err(|e| {
Error::with_cause(
WriteFailed(
"memory.kmem.tcp.limit_in_bytes".to_string(),
limit.to_string(),
),
e,
)
})
})
}
@@ -807,13 +917,14 @@ impl MemController {
/// This limit is enforced when the system is nearing OOM conditions. Contrast this with the
/// hard limit, which is _always_ enforced.
pub fn set_soft_limit(&self, limit: i64) -> Result<()> {
let mut file = "memory.soft_limit_in_bytes";
let mut file_name = "memory.soft_limit_in_bytes";
if self.v2 {
file = "memory.low"
file_name = "memory.low"
}
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))
self.open_path(file_name, true).and_then(|mut file| {
file.write_all(limit.to_string().as_ref()).map_err(|e| {
Error::with_cause(WriteFailed(file_name.to_string(), limit.to_string()), e)
})
})
}
@@ -822,22 +933,27 @@ impl MemController {
///
/// Note that a value of zero does not imply that the process will not be swapped out.
pub fn set_swappiness(&self, swp: u64) -> Result<()> {
let mut file = "memory.swappiness";
let mut file_name = "memory.swappiness";
if self.v2 {
file = "memory.swap.max"
file_name = "memory.swap.max"
}
self.open_path(file, true).and_then(|mut file| {
file.write_all(swp.to_string().as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
self.open_path(file_name, true).and_then(|mut file| {
file.write_all(swp.to_string().as_ref()).map_err(|e| {
Error::with_cause(WriteFailed(file_name.to_string(), swp.to_string()), e)
})
})
}
pub fn disable_oom_killer(&self) -> Result<()> {
self.open_path("memory.oom_control", true)
.and_then(|mut file| {
file.write_all("1".to_string().as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
file.write_all("1".to_string().as_ref()).map_err(|e| {
Error::with_cause(
WriteFailed("memory.oom_control".to_string(), "1".to_string()),
e,
)
})
})
}
@@ -878,6 +994,7 @@ mod tests {
use crate::memory::{
parse_memory_stat, parse_numa_stat, parse_oom_control, MemoryStat, NumaStat, OomControl,
};
static GOOD_VALUE: &str = "\
total=51189 N0=51189 N1=123
file=50175 N0=50175 N1=123
@@ -889,7 +1006,24 @@ hierarchical_anon=770402 N0=770402 N1=123
hierarchical_unevictable=20 N0=20 N1=123
";
static GOOD_OOMCONTROL_VAL: &str = "\
static GOOD_VALUE_NON_HIERARCHICAL: &str = "\
total=51189 N0=51189 N1=123
file=50175 N0=50175 N1=123
anon=1014 N0=1014 N1=123
unevictable=0 N0=0 N1=123
";
static GOOD_OOMCONTROL_VAL_1: &str = "\
oom_kill_disable 0
oom_kill 1337
";
static GOOD_OOMCONTROL_VAL_2: &str = "\
oom_kill_disable 0
under_oom 1
";
static GOOD_OOMCONTROL_VAL_3: &str = "\
oom_kill_disable 0
under_oom 1
oom_kill 1337
@@ -959,11 +1093,61 @@ total_unevictable 81920
hierarchical_unevictable_pages_per_node: vec![20, 123],
}
);
let ok = parse_numa_stat(GOOD_VALUE_NON_HIERARCHICAL.to_string()).unwrap();
assert_eq!(
ok,
NumaStat {
total_pages: 51189,
total_pages_per_node: vec![51189, 123],
file_pages: 50175,
file_pages_per_node: vec![50175, 123],
anon_pages: 1014,
anon_pages_per_node: vec![1014, 123],
unevictable_pages: 0,
unevictable_pages_per_node: vec![0, 123],
hierarchical_total_pages: 0,
hierarchical_total_pages_per_node: vec![],
hierarchical_file_pages: 0,
hierarchical_file_pages_per_node: vec![],
hierarchical_anon_pages: 0,
hierarchical_anon_pages_per_node: vec![],
hierarchical_unevictable_pages: 0,
hierarchical_unevictable_pages_per_node: vec![],
}
);
}
#[test]
fn test_parse_oom_control() {
let ok = parse_oom_control(GOOD_OOMCONTROL_VAL.to_string()).unwrap();
let ok = parse_oom_control("".to_string()).unwrap();
assert_eq!(
ok,
OomControl {
oom_kill_disable: false,
under_oom: false,
oom_kill: 0,
}
);
let ok = parse_oom_control(GOOD_OOMCONTROL_VAL_1.to_string()).unwrap();
assert_eq!(
ok,
OomControl {
oom_kill_disable: false,
under_oom: false,
oom_kill: 0,
}
);
let ok = parse_oom_control(GOOD_OOMCONTROL_VAL_2.to_string()).unwrap();
assert_eq!(
ok,
OomControl {
oom_kill_disable: false,
under_oom: true,
oom_kill: 0,
}
);
let ok = parse_oom_control(GOOD_OOMCONTROL_VAL_3.to_string()).unwrap();
assert_eq!(
ok,
OomControl {
@@ -1017,7 +1201,7 @@ total_unevictable 81920
total_inactive_file: 1272135680,
total_active_file: 2338816000,
total_unevictable: 81920,
raw: raw,
raw,
}
);
}

View File

@@ -49,7 +49,7 @@ impl ControllerInternal for NetClsController {
update_and_test!(self, set_class, res.class_id, get_class);
return Ok(());
Ok(())
}
}
@@ -88,14 +88,15 @@ impl NetClsController {
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))
file.write_all(s.as_ref()).map_err(|e| {
Error::with_cause(WriteFailed("net_cls.classid".to_string(), s), e)
})
})
}
/// Get the network class id of the outgoing packets of the control group's tasks.
pub fn get_class(&self) -> Result<u64> {
self.open_path("net_cls.classid", false)
.and_then(|file| read_u64_from(file))
.and_then(read_u64_from)
}
}

View File

@@ -94,6 +94,7 @@ impl NetPrioController {
}
/// A map of priorities for each network interface.
#[allow(clippy::iter_nth_zero, clippy::unnecessary_unwrap)]
pub fn ifpriomap(&self) -> Result<HashMap<String, u64>> {
self.open_path("net_prio.ifpriomap", false)
.and_then(|file| {
@@ -105,6 +106,7 @@ impl NetPrioController {
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() {
@@ -130,7 +132,15 @@ impl NetPrioController {
self.open_path("net_prio.ifpriomap", true)
.and_then(|mut file| {
file.write_all(format!("{} {}", eif, prio).as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
.map_err(|e| {
Error::with_cause(
WriteFailed(
"net_prio.ifpriomap".to_string(),
format!("{} {}", eif, prio),
),
e,
)
})
})
}
}

View File

@@ -96,7 +96,7 @@ impl PidController {
Self {
base: root.clone(),
path: root,
v2: v2,
v2,
}
}
@@ -112,7 +112,7 @@ impl PidController {
},
None => Err(Error::new(ParseError)),
},
Err(e) => Err(Error::with_cause(ReadFailed, e)),
Err(e) => Err(Error::with_cause(ReadFailed("pids.events".to_string()), e)),
}
})
}
@@ -130,7 +130,7 @@ impl PidController {
let res = file.read_to_string(&mut string);
match res {
Ok(_) => parse_max_value(&string),
Err(e) => Err(Error::with_cause(ReadFailed, e)),
Err(e) => Err(Error::with_cause(ReadFailed("pids.max".to_string()), e)),
}
})
}
@@ -145,7 +145,10 @@ impl PidController {
let string_to_write = max_pid.to_string();
match file.write_all(string_to_write.as_ref()) {
Ok(_) => Ok(()),
Err(e) => Err(Error::with_cause(WriteFailed, e)),
Err(e) => Err(Error::with_cause(
WriteFailed("pids.max".to_string(), format!("{:?}", max_pid)),
e,
)),
}
})
}

View File

@@ -84,8 +84,9 @@ impl RdmaController {
/// Set a maximum usage for each RDMA/IB resource.
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))
file.write_all(max.as_ref()).map_err(|e| {
Error::with_cause(WriteFailed("rdma.max".to_string(), max.to_string()), e)
})
})
}
}

View File

@@ -17,7 +17,7 @@ use crate::{ControllIdentifier, ControllerInternal, Controllers, Resources, Subs
pub struct SystemdController {
base: PathBuf,
path: PathBuf,
v2: bool,
_v2: bool,
}
impl ControllerInternal for SystemdController {
@@ -66,7 +66,7 @@ impl SystemdController {
Self {
base: root.clone(),
path: root,
v2: v2,
_v2: v2,
}
}
}

View File

@@ -47,7 +47,8 @@ pub fn test_memory_res_build() {
{
let c: &MemController = cg.controller_of().unwrap();
if !c.v2() {
assert_eq!(c.kmem_stat().limit_in_bytes, 128 * 1024 * 1024);
// Note: we don't tests the value of c.kmem_stat().limit_in_bytes because on Linux
// kernel >= 5.16 setting this value is unsupported.
assert_eq!(c.memory_stat().swappiness, 70);
}
assert_eq!(c.memory_stat().limit_in_bytes, 1024 * 1024 * 1024);
@@ -137,11 +138,8 @@ pub fn test_hugepages_res_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
);
assert!(c.limit_in_bytes("2MB").is_ok());
assert_eq!(c.limit_in_bytes("2MB").unwrap(), 4 * 2 * 1024 * 1024);
}
cg.delete().unwrap();
}

View File

@@ -48,7 +48,7 @@ fn test_cgroup_with_relative_paths() {
let cg = Cgroup::load(h, String::from(cgroup_name));
{
let subsystems = cg.subsystems();
subsystems.into_iter().for_each(|sub| match sub {
subsystems.iter().for_each(|sub| match sub {
Subsystem::Pid(c) => {
let cgroup_path = c.path().to_str().unwrap();
let relative_path = "/pids/";

View File

@@ -36,24 +36,23 @@ fn test_cpuset_set_cpus() {
assert_eq!(0, set.cpus.len());
} else {
// for cgroup v1, cpuset is copied from parent.
assert_eq!(true, set.cpus.len() > 0);
assert!(!set.cpus.is_empty());
}
// 0
let r = cpuset.set_cpus("0");
assert_eq!(true, r.is_ok());
assert!(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 = fs::read_to_string("/sys/fs/cgroup/cpuset.cpus.effective").unwrap_or_default();
let cpus = cpus.trim();
if cpus != "" {
let r = cpuset.set_cpus(&cpus);
assert_eq!(true, r.is_ok());
if !cpus.is_empty() {
let r = cpuset.set_cpus(cpus);
assert!(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);
@@ -73,18 +72,18 @@ fn test_cpuset_set_cpus_add_task() {
assert_eq!(0, set.cpus.len());
} else {
// for cgroup v1, cpuset is copied from parent.
assert_eq!(true, set.cpus.len() > 0);
assert!(!set.cpus.is_empty());
}
// 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);
assert!(!tasks.is_empty());
println!("tasks after added: {:?}", tasks);
// remove task
let _ = cg.remove_task(CgroupPid::from(pid_i));
cg.remove_task(CgroupPid::from(pid_i));
let tasks = cg.tasks();
println!("tasks after deleted: {:?}", tasks);
assert_eq!(0, tasks.len());

View File

@@ -27,7 +27,7 @@ fn test_devices_parsing() {
DeviceType::All,
-1,
-1,
&vec![
&[
DevicePermissions::Read,
DevicePermissions::Write,
DevicePermissions::MkNod,
@@ -42,7 +42,7 @@ fn test_devices_parsing() {
// Now add mknod access to /dev/null device
devices
.allow_device(DeviceType::Char, 1, 3, &vec![DevicePermissions::MkNod])
.allow_device(DeviceType::Char, 1, 3, &[DevicePermissions::MkNod])
.unwrap();
let allowed_devices = devices.allowed_devices();
assert!(allowed_devices.is_ok());

View File

@@ -29,7 +29,7 @@ fn test_hugetlb_sizes() {
for size in sizes {
let supported = hugetlb_controller.size_supported(&size);
assert_eq!(supported, true);
assert!(supported);
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));
@@ -40,5 +40,5 @@ fn test_hugetlb_sizes() {
}
fn assert_no_error(r: Result<u64>) {
assert_eq!(!r.is_err(), true)
assert!(r.is_ok())
}

View File

@@ -17,22 +17,37 @@ fn test_disable_oom_killer() {
// before disable
let m = mem_controller.memory_stat();
assert_eq!(m.oom_control.oom_kill_disable, false);
assert!(!m.oom_control.oom_kill_disable);
// now only v1
if !mem_controller.v2() {
// disable oom killer
let r = mem_controller.disable_oom_killer();
assert_eq!(r.is_err(), false);
assert!(r.is_ok());
// after disable
let m = mem_controller.memory_stat();
assert_eq!(m.oom_control.oom_kill_disable, true);
assert!(m.oom_control.oom_kill_disable);
}
}
cg.delete().unwrap();
}
#[test]
fn set_kmem_limit_v1() {
let h = cgroups_rs::hierarchies::auto();
if h.v2() {
return;
}
let cg = Cgroup::new(h, String::from("set_kmem_limit_v1"));
{
let mem_controller: &MemController = cg.controller_of().unwrap();
mem_controller.set_kmem_limit(1).unwrap();
}
cg.delete().unwrap();
}
#[test]
fn set_mem_v2() {
let h = cgroups_rs::hierarchies::auto();
@@ -60,7 +75,7 @@ fn set_mem_v2() {
max: None,
};
let r = mem_controller.set_mem(m);
assert_eq!(true, r.is_ok());
assert!(r.is_ok());
let m = mem_controller.get_mem().unwrap();
// get
@@ -77,7 +92,7 @@ fn set_mem_v2() {
low: None,
};
let r = mem_controller.set_mem(m);
assert_eq!(true, r.is_ok());
assert!(r.is_ok());
let m = mem_controller.get_mem().unwrap();
// get

View File

@@ -62,7 +62,7 @@ fn test_pid_events_is_not_zero() {
let before = pids.get_pid_events();
let before = before.unwrap();
match fork() {
match unsafe { fork() } {
Ok(ForkResult::Parent { child, .. }) => {
// move the process into the control group
let _ = pids.add_task(&(pid_t::from(child) as u64).into());
@@ -89,7 +89,7 @@ fn test_pid_events_is_not_zero() {
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() {
if unsafe { fork() }.is_err() {
unsafe { libc::exit(0) };
} else {
unsafe { libc::exit(1) };

View File

@@ -24,7 +24,7 @@ fn pid_resources() {
// verify
let pidcontroller: &PidController = cg.controller_of().unwrap();
let pid_max = pidcontroller.get_pid_max();
assert_eq!(pid_max.is_ok(), true);
assert!(pid_max.is_ok());
assert_eq!(pid_max.unwrap(), MaxValue::Value(512));
}
cg.delete().unwrap();