Compare commits

...

126 Commits
0.1.1 ... 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
Fupan Li
87f05873f0 Merge pull request #35 from Tim-Zhang/v0.2.3
release: v0.2.3
2021-02-02 11:46:03 +08:00
Tim Zhang
79868d6bfc release: v0.2.3
Bump version from 0.2.2 to 0.2.3

Signed-off-by: Tim Zhang <tim@hyper.sh>
2021-02-02 10:24:58 +08:00
Fupan Li
d1d78fe943 Merge pull request #34 from quanweiZhou/get_mountinfo_without_procinfo
cgroup: get the mount info without procinfo
2021-02-01 15:13:59 +08:00
quanweiZhou
81d286f31d cgroup: get the mount info without procinfo
The procinfo library gets all the mount info,
but we don't need all the mount info.

Signed-off-by: quanweiZhou <quanweiZhou@linux.alibaba.com>
2021-02-01 11:26:54 +08:00
Fupan Li
5e538ae286 Merge pull request #33 from lifupan/master
cgroup: fix the issue of remove cgroup
2021-01-25 17:39:38 +08:00
fupan.lfp
cd5645bb14 cgroup: fix the issue of remove cgroup
It should remove the child cgroups recursively
and then remove the parent cgroup, otherwise,
it would remove failed.

Signed-off-by: fupan.lfp <fupan.lfp@antfin.com>
2021-01-25 17:36:49 +08:00
Tim Zhang
4a2042068e Merge pull request #28 from Tim-Zhang/update-cargo-toml
Add field homepage and readme for Cargo.toml
2021-01-25 17:34:43 +08:00
Tim Zhang
9ac2f5d5ab Add field homepage and readme for Cargo.toml
Display readme on the crate's page.

Signed-off-by: Tim Zhang <tim@hyper.sh>
2021-01-25 17:32:24 +08:00
Tim Zhang
a58cd1d553 Merge pull request #31 from Tim-Zhang/switch-travis-to-github-action
Migrate the CI from travis-ci to Github Actions
2021-01-25 17:30:37 +08:00
Tim Zhang
88a45d4922 Migrate the CI from travis-ci to Github Actions
Because travis-ci.org will be shutting down.

Signed-off-by: Tim Zhang <tim@hyper.sh>
2021-01-25 17:24:45 +08:00
Tim Zhang
0c58b5dd84 Add Makefile
So we can use make to build, check and test

Signed-off-by: Tim Zhang <tim@hyper.sh>
2021-01-25 17:20:56 +08:00
Tim Zhang
8448548cb3 Merge pull request #30 from bergwolf/issue-templates
github: add issue templates
2021-01-25 15:33:01 +08:00
Peng Tao
d50245e116 github: add issue templates
cgroups-rs should not use the repository default issue templates.
Let's add a private version to override the default ones.

Fixes: #29
Signed-off-by: Peng Tao <bergwolf@hyper.sh>
2021-01-25 15:01:48 +08:00
Tim Zhang
bbdfa88596 Merge pull request #26 from Tim-Zhang/memory-apply-customized-attrs
Apply customized attributes in MemController.apply
2021-01-25 14:37:34 +08:00
Tim Zhang
853ed46993 release: v0.2.2
Bump version to 0.2.2

Signed-off-by: Tim Zhang <tim@hyper.sh>
2021-01-25 12:20:30 +08:00
Tim Zhang
15b65c5e5e Apply customized attributes in MemController.apply
Fixes: #25

Signed-off-by: Tim Zhang <tim@hyper.sh>
2021-01-25 12:11:43 +08:00
Tim Zhang
87f8ac7f0d Merge pull request #23 from Tim-Zhang/add-trait-bound-for-hierarchy
Add trait bound for hierarchy
2021-01-07 16:27:15 +08:00
Tim Zhang
225388ff7c release: v0.2.1
Bump version to 0.2.1

Signed-off-by: Tim Zhang <tim@hyper.sh>
2021-01-06 22:31:04 +08:00
Tim Zhang
f0a695cc00 Add trait bound Sync for Hierarchy
So that the struct Cgroup could across await

Signed-off-by: Tim Zhang <tim@hyper.sh>
2021-01-06 22:31:04 +08:00
Tim Zhang
50d3c398a0 Merge pull request #22 from Apokleos/master
optimize and refactor: read_to_string and read_i[u]64_from
2021-01-06 22:30:17 +08:00
LiYa'nan
033fa4b857 optimize and refactor: read_to_string and read_i[u]64_from
There's so many Duplicated read_string_from method in different
subsystem's implementation, so as read_u64_from/read_i64_from methods.
(1) Move the read_string_from method into `lib.rs`,
called by each subsystem implementation as needed.
(2) Refactor read_u[i]64_from method with the help Rust Generic f
unction `read_from` and wrapped by read_u64_from or read_i64_from.

fix: #20

Signed-off-by: LiYa'nan <oliverliyn@gmail.com>
2020-12-24 09:25:00 +08:00
Peng Tao
ac4e6eda66 Merge pull request #21 from Tim-Zhang/change-name
Change package name to cgroups-rs
2020-12-21 16:22:06 +08:00
Tim Zhang
35ecd6fd77 Update readme
- Change the travis badge
- Update the description about v2 supporting.

Signed-off-by: Tim Zhang <tim@hyper.sh>
2020-12-21 15:51:34 +08:00
Tim Zhang
eb6577e3e0 Change package name to cgroups-rs
We need a new package name to create a new crate

Signed-off-by: Tim Zhang <tim@hyper.sh>
2020-12-21 15:46:30 +08:00
Fupan Li
8f6a7e0a31 Merge pull request #19 from Tim-Zhang/0.2.0
Ready to release 0.2.0
2020-12-21 11:21:07 +08:00
Tim Zhang
9baa065226 release: v0.2.0
Bump vertion to 0.2.0

Signed-off-by: Tim Zhang <tim@hyper.sh>
2020-12-17 17:56:46 +08:00
Tim Zhang
e160df0751 Make read_i64_from private and merge read_str_from to its caller
Also remove duplicated read_i64_from.

Signed-off-by: Tim Zhang <tim@hyper.sh>
2020-12-17 17:56:46 +08:00
Tim Zhang
e1e05d3a1c Make new_with_relative_paths=new and load_with_relative_paths=new in v2
Because the relative_paths is only valid for cgroup v1, the v2 use
unified hierarchy.

Signed-off-by: Tim Zhang <tim@hyper.sh>
2020-12-17 17:56:46 +08:00
Qingyuan Hou
a89f4a062e Support set notify_on_release & release_agent
Support set notify_on_release & release_agent

Signed-off-by: Qingyuan Hou <qingyuan.hou@linux.alibaba.com>
2020-12-17 17:56:46 +08:00
Tim Zhang
61a0957a65 Fix set_swappiness in cgroup v2
The file should be memory.swap.max in cgroup v2.

Signed-off-by: Tim Zhang <tim@hyper.sh>
2020-12-17 17:56:46 +08:00
Tim Zhang
059204589c Ignore kmem in cgroup v2
Because there is no kmem in cgroup v2.

Signed-off-by: Tim Zhang <tim@hyper.sh>
2020-12-17 17:56:46 +08:00
Tim Zhang
c254fffbe0 Update readme
Update readme

Signed-off-by: Tim Zhang <tim@hyper.sh>
2020-12-17 17:56:46 +08:00
Tim Zhang
438d774866 Fix test
Fix test

Signed-off-by: Tim Zhang <tim@hyper.sh>
2020-12-17 17:56:46 +08:00
Tim Zhang
42ee1bafbd Make Cgroup can be stored in struct
- Change type of hier to remove lifetimes
- impl Clone, Default, Debug for Cgroup

Signed-off-by: Tim Zhang <tim@hyper.sh>
2020-12-17 17:56:46 +08:00
Tim Zhang
b6bb5ae947 docs: Hide Re-exports
Make `pub use crate::cgroup::Cgroup` display as struct in docs.

Signed-off-by: Tim Zhang <tim@hyper.sh>
2020-12-17 17:56:46 +08:00
Tim Zhang
d2882b1d85 Print cause when println!("{}")
> Print like following:
unable to write to a control group file caused by: Os { code: 22, kind: InvalidInput, message: "Invalid argument" }) }

Signed-off-by: Tim Zhang <tim@hyper.sh>
2020-12-17 17:56:46 +08:00
Tim Zhang
abcb5ed031 Add more logs for create_dir error in controller.create
We need know the path name which failed to create.

Signed-off-by: Tim Zhang <tim@hyper.sh>
2020-12-17 17:56:46 +08:00
Tim Zhang
1f188be405 Detect subsystems and get root from /proc/self/mountinfo
Delete check_support and stop detecting subsystems by finding in the
root folders because the detecting method is not accurate.

Signed-off-by: Tim Zhang <tim@hyper.sh>
2020-12-17 17:56:46 +08:00
Tim Zhang
fbd7164c29 Fix warnings in tests
Remove following warnings
- unused import
- unused Result

Signed-off-by: Tim Zhang <tim@hyper.sh>
2020-12-17 17:56:46 +08:00
Tim Zhang
f34225411e Remove Box wrap of Cgroup.hire
It's unnecessary.

Signed-off-by: Tim Zhang <tim@hyper.sh>
2020-12-17 17:56:46 +08:00
Tim Zhang
cd998f3f9b Do not place cgroup under relative path read from cgroup by default
Add new_with_corresponding_relative_paths to do the original action.

Signed-off-by: Tim Zhang <tim@hyper.sh>
2020-12-17 17:56:46 +08:00
Tim Zhang
1ac76b69ba Make function find_v1_mount pub
The function find_v1_mount is useful for customized impl for Hierarchy.

Signed-off-by: Tim Zhang <tim@hyper.sh>
2020-12-17 17:56:46 +08:00
Tim Zhang
121f78d8e8 Expose deletion error
So that users can retry or do some aftercare.

Fixes: #18

Signed-off-by: Tim Zhang <tim@hyper.sh>
2020-12-17 17:56:46 +08:00
Tim Zhang
0f76570677 Avoid exception caused by cgroup writeback feature
The cgroup writeback feature requires cooperation
between memcgs and blkcgs.
To avoid exceptions, we should add_task for blkcg before
memcg(push BlkIo before Mem).

For more Information:
https://www.alibabacloud.com/help/doc-detail/155509.ht

Signed-off-by: Tim Zhang <tim@hyper.sh>
2020-12-17 17:56:46 +08:00
Tim Zhang
10650e2b16 Update tests to adapt new type of fields in resource
The type has been changed to Option type.

Signed-off-by: Tim Zhang <tim@hyper.sh>
2020-12-17 17:56:46 +08:00
Tim Zhang
567cdb43b3 Use Option as resource fields, remove the update switch: update_values
Use idiomatic Option::None to represent optional fields.
This enables updates where not all fields need to be specified.

Signed-off-by: Tim Zhang <tim@hyper.sh>
2020-12-17 17:56:41 +08:00
Tim Zhang
0c18b0855e Support customized attributes for CpuController and MemController
Customized attributes are useful for customized kernels.

Usage:
	let resource = &mut cgroups::Resources::default();
	resource.cpu.attrs.insert("cpu.cfs_init_buffer_us", "10".to_string());
	// apply here

Signed-off-by: Tim Zhang <tim@hyper.sh>
2020-12-17 16:45:41 +08:00
Yang, Wei
ca610bb57e add add_task_by_tgid
Add task by writing thread group id to cgroup.procs.

Signed-off-by: Yang, Wei <wei.yang1@linux.alibaba.com>
Signed-off-by: Tim Zhang <tim@hyper.sh>
2020-12-17 15:57:22 +08:00
Fupan Li
af6fc63bed Merge pull request #17 from Tim-Zhang/minor-updates
Minor updates
2020-10-30 10:15:05 +08:00
Tim Zhang
6f9e89572e MemController: add method reset_max_usage
We need a method to reset the max memory usage recorded.

Signed-off-by: Tim Zhang <tim@hyper.sh>
2020-10-29 19:35:39 +08:00
Tim Zhang
42eb32765b Make methods to_controller and controller_name of Subsystem public
We need these functions to detect whether subsystem path exists.

Signed-off-by: Tim Zhang <tim@hyper.sh>
2020-10-29 19:32:55 +08:00
Tim Zhang
efb98108fc Fix warnings
Got:
cargo build --target x86_64-unknown-linux-musl
   Compiling cgroups v0.1.1-alpha.0 (/home/tim/project/cgroups-rs)
warning: unused import: `nix::sys::statfs`
   --> src/hierarchies.rs:220:5
    |
220 | use nix::sys::statfs;
    |     ^^^^^^^^^^^^^^^^
    |
    = note: `#[warn(unused_imports)]` on by default

warning: 1 warning emitted

Signed-off-by: Tim Zhang <tim@hyper.sh>
2020-10-29 19:31:55 +08:00
James O. D. Hunt
c310b30c52 Merge pull request #14 from liubin/enhancement/13-fix-warnings
fix build warnings
2020-10-01 17:28:16 +01:00
bin liu
152af17f8f fix build warnings
And in Travis CI, treat warnings as error
to keep from merging codes with warning.

Fixes: #13

Signed-off-by: bin liu <bin@hyper.sh>
2020-09-23 16:19:51 +08:00
Bin Liu
d18b3bac2f Merge pull request #16 from liubin/enhancement/15-add-workflow-checks
add basic GitHub workflow for basic checks
2020-09-23 16:17:33 +08:00
bin liu
414fa281cc add basic github workflow for basic checks
Basic checks includes:

- Must have commit title/body
- Must have DCO(SoC)
- Title/Body line length are limited 75/72
- Add wip/do-not-merge to label PRs that could be merged

Fixes: #15

Signed-off-by: bin liu <bin@hyper.sh>
2020-09-23 16:15:08 +08:00
Peng Tao
f0eac7859a Merge pull request #12 from liubin/fix/11-add-trivas
update Travis configs
2020-09-23 14:01:37 +08:00
bin liu
db40fe8cd3 update Travis configs
Fixes: #11

Signed-off-by: bin liu <bin@hyper.sh>
2020-09-22 20:39:18 +08:00
Fupan Li
8717524f2c Merge pull request #10 from Kvasscn/fix-type-i8-compile-error-in-aarch64
lib: fix compiling error for type i8 in aarch64 environment
2020-09-22 10:36:14 +08:00
zhanghj
cd7e737149 lib: fix compiling error for type i8 in aarch64 environment
c_char is equal to i8 on x86, but it is equal to u8 on arm arch,
so we need to use type c_char instead of i8 to avoid compiling error.

Fixes: #9

Signed-off-by: zhanghj <zhanghj.lc@inspur.com>
2020-09-21 05:40:57 -04:00
40 changed files with 2104 additions and 1441 deletions

1
.clippy.toml Normal file
View File

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

17
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,17 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: 'bug, needs-review'
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**Expected behavior**
A clear and concise description of what you expected to happen.
**Additional context**
Add any other context about the problem here.

View File

@@ -0,0 +1,20 @@
---
name: Enhancement request
about: Suggest an improvement to an existing feature
title: ''
labels: enhancement, needs-review
assignees: ''
---
**Which feature do you think can be improved?**
Specify the feature you think could be made better.
**How can it be improved?**
Describe how specifically you think it could be improved.
**Additional Information**
Anything else to add?

View File

@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: 'feature, needs-review'
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

21
.github/workflows/PR-wip-checks.yaml vendored Normal file
View File

@@ -0,0 +1,21 @@
name: Pull request WIP checks
on:
pull_request:
types:
- opened
- synchronize
- reopened
- edited
- labeled
- unlabeled
jobs:
pr_wip_check:
runs-on: ubuntu-latest
name: WIP Check
steps:
- name: WIP Check
uses: tim-actions/wip-check@1c2a1ca6c110026b3e2297bb2ef39e1747b5a755
with:
labels: '["do-not-merge", "wip", "rfc"]'
keywords: '["WIP", "wip", "RFC", "rfc", "dnm", "DNM", "do-not-merge"]'

38
.github/workflows/bvt.yaml vendored Normal file
View File

@@ -0,0 +1,38 @@
name: BVT
on: [pull_request]
env:
RUST_VERSION: 1.52
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- 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

@@ -0,0 +1,53 @@
name: Commit Message Check
on:
pull_request:
types:
- opened
- reopened
- synchronize
env:
error_msg: |+
See the document below for help on formatting commits for the project.
https://github.com/kata-containers/community/blob/master/CONTRIBUTING.md#patch-forma
jobs:
commit-message-check:
runs-on: ubuntu-latest
name: Commit Message Check
steps:
- name: Get PR Commits
id: 'get-pr-commits'
uses: tim-actions/get-pr-commits@v1.0.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: DCO Check
uses: tim-actions/dco@2fd0504dc0d27b33f542867c300c60840c6dcb20
with:
commits: ${{ steps.get-pr-commits.outputs.commits }}
- name: Commit Body Missing Check
if: ${{ success() || failure() }}
uses: tim-actions/commit-body-check@v1.0.2
with:
commits: ${{ steps.get-pr-commits.outputs.commits }}
- name: Check Subject Line Length
if: ${{ success() || failure() }}
uses: tim-actions/commit-message-checker-with-regex@v0.3.1
with:
commits: ${{ steps.get-pr-commits.outputs.commits }}
pattern: '^.{0,75}(\n.*)*$'
error: 'Subject too long (max 75)'
post_error: ${{ env.error_msg }}
- name: Check Body Line Length
if: ${{ success() || failure() }}
uses: tim-actions/commit-message-checker-with-regex@v0.3.1
with:
commits: ${{ steps.get-pr-commits.outputs.commits }}
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

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

View File

@@ -1,19 +1,26 @@
[package] [package]
name = "cgroups" name = "cgroups-rs"
description = "Native Rust crate for managing control groups on Linux" description = "Native Rust crate for managing control groups on Linux"
repository = "https://github.com/levex/cgroups-rs" repository = "https://github.com/kata-containers/cgroups-rs"
keywords = ["linux", "cgroup", "containers", "isolation"] keywords = ["linux", "cgroup", "containers", "isolation"]
categories = ["os", "api-bindings", "os::unix-apis"] categories = ["os", "api-bindings", "os::unix-apis"]
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
version = "0.1.1-alpha.0" version = "0.2.11"
authors = ["Levente Kurusa <lkurusa@acm.org>", "Sam Wilson <tecywiz121@hotmail.com>"] authors = ["The Kata Containers community <kata-dev@lists.katacontainers.io>", "Levente Kurusa <lkurusa@acm.org>", "Sam Wilson <tecywiz121@hotmail.com>"]
edition = "2018" edition = "2018"
homepage = "https://github.com/kata-containers/cgroups-rs"
readme = "README.md"
[dependencies] [dependencies]
log = "0.4" log = "0.4"
regex = "1.1" regex = "1.1"
nix = "0.18.0" nix = { version = "0.25.0", default-features = false, features = ["event", "fs", "process"] }
libc = "0.2" libc = "0.2"
serde = { version = "1.0", features = ["derive"], optional = true }
thiserror = "1"
[dev-dependencies] [dev-dependencies]
libc = "0.2.76" libc = "0.2.76"
[features]
default = []

37
Makefile Normal file
View File

@@ -0,0 +1,37 @@
all: debug fmt test
#
# Build
#
.PHONY: debug
debug:
RUSTFLAGS="--deny warnings" cargo build
.PHONY: release
release:
cargo build --release
.PHONY: build
build: debug
#
# Tests and linters
#
.PHONY: test
test:
cargo test -- --color always --nocapture
.PHONY: check
check: fmt clippy
.PHONY: fmt
fmt:
cargo fmt --all -- --check
.PHONY: clippy
clippy:
cargo clippy --all-targets --all-features -- -D warnings

View File

@@ -1,32 +1,36 @@
# cgroups-rs ![Build](https://travis-ci.org/levex/cgroups-rs.svg?branch=master) # cgroups-rs ![Build](https://travis-ci.org/kata-containers/cgroups-rs.svg?branch=master)
Native Rust library for managing control groups under Linux Native Rust library for managing control groups under Linux
Right now the crate only support the original, V1 hierarchy, however support Both v1 and v2 of cgroups are supported.
is planned for the Unified hierarchy.
# Examples # Examples
## Create a control group using the builder pattern ## Create a control group using the builder pattern
``` rust ``` rust
// Acquire a handle for the V1 cgroup hierarchy.
let hier = ::hierarchies::V1::new();
use cgroups_rs::*;
use cgroups_rs::cgroup_builder::*;
// Acquire a handle for the cgroup hierarchy.
let hier = cgroups_rs::hierarchies::auto();
// Use the builder pattern (see the documentation to create the control group) // Use the builder pattern (see the documentation to create the control group)
// //
// This creates a control group named "example" in the V1 hierarchy. // This creates a control group named "example" in the V1 hierarchy.
let cg: Cgroup = CgroupBuilder::new("example", &v1) let cg: Cgroup = CgroupBuilder::new("example")
.cpu() .cpu()
.shares(85) .shares(85)
.done() .done()
.build(); .build(hier);
// Now `cg` is a control group that gets 85% of the CPU time in relative to // Now `cg` is a control group that gets 85% of the CPU time in relative to
// other control groups. // other control groups.
// Get a handle to the CPU controller. // Get a handle to the CPU controller.
let cpus: &CpuController = cg.controller_of().unwrap(); let cpus: &cgroups_rs::cpu::CpuController = cg.controller_of().unwrap();
cpus.add_task(1234u64); cpus.add_task(&CgroupPid::from(1234u64));
// [...] // [...]

View File

@@ -8,15 +8,16 @@
//! //!
//! See the Kernel's documentation for more information about this subsystem, found at: //! See the Kernel's documentation for more information about this subsystem, found at:
//! [Documentation/cgroup-v1/blkio-controller.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/blkio-controller.txt) //! [Documentation/cgroup-v1/blkio-controller.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/blkio-controller.txt)
use std::fs::File; use std::io::Write;
use std::io::{Read, Write};
use std::path::PathBuf; use std::path::PathBuf;
use crate::error::ErrorKind::*; use crate::error::ErrorKind::*;
use crate::error::*; use crate::error::*;
use crate::{read_string_from, read_u64_from};
use crate::{ 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. /// A controller that allows controlling the `blkio` subsystem of a Cgroup.
@@ -31,6 +32,7 @@ pub struct BlkIoController {
} }
#[derive(Eq, PartialEq, Debug)] #[derive(Eq, PartialEq, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
/// Per-device information /// Per-device information
pub struct BlkIoData { pub struct BlkIoData {
/// The major number of the device. /// The major number of the device.
@@ -42,6 +44,7 @@ pub struct BlkIoData {
} }
#[derive(Eq, PartialEq, Debug)] #[derive(Eq, PartialEq, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
/// Per-device activity from the control group. /// Per-device activity from the control group.
pub struct IoService { pub struct IoService {
/// The major number of the device. /// The major number of the device.
@@ -61,6 +64,7 @@ pub struct IoService {
} }
#[derive(Eq, PartialEq, Debug)] #[derive(Eq, PartialEq, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
/// Per-device activity from the control group. /// Per-device activity from the control group.
/// Only for cgroup v2 /// Only for cgroup v2
pub struct IoStat { pub struct IoStat {
@@ -84,14 +88,14 @@ pub struct IoStat {
fn parse_io_service(s: String) -> Result<Vec<IoService>> { fn parse_io_service(s: String) -> Result<Vec<IoService>> {
s.lines() s.lines()
.filter(|x| x.split_whitespace().collect::<Vec<_>>().len() == 3) .filter(|x| x.split_whitespace().count() == 3)
.map(|x| { .map(|x| {
let mut spl = x.split_whitespace(); 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)| { .map(|(a, b, c)| {
let mut spl = a.split(":"); let mut spl = a.split(':');
(spl.nth(0).unwrap(), spl.nth(0).unwrap(), b, c) (spl.next().unwrap(), spl.next().unwrap(), b, c)
}) })
.collect::<Vec<_>>() .collect::<Vec<_>>()
.chunks(5) .chunks(5)
@@ -131,15 +135,14 @@ fn get_value(s: &str) -> String {
arr[1].to_string() arr[1].to_string()
} }
fn parse_io_stat(s: String) -> Result<Vec<IoStat>> { fn parse_io_stat(s: String) -> Vec<IoStat> {
// line: // line:
// 8:0 rbytes=180224 wbytes=0 rios=3 wios=0 dbytes=0 dios=0 // 8:0 rbytes=180224 wbytes=0 rios=3 wios=0 dbytes=0 dios=0
let v = s s.lines()
.lines() .filter(|x| x.split_whitespace().count() == 7)
.filter(|x| x.split_whitespace().collect::<Vec<_>>().len() == 7)
.map(|x| { .map(|x| {
let arr = x.split_whitespace().collect::<Vec<&str>>(); 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]); let (major, minor) = (device[0], device[1]);
IoStat { IoStat {
@@ -153,14 +156,12 @@ fn parse_io_stat(s: String) -> Result<Vec<IoStat>> {
dios: get_value(arr[6]).parse::<u64>().unwrap(), dios: get_value(arr[6]).parse::<u64>().unwrap(),
} }
}) })
.collect::<Vec<IoStat>>(); .collect::<Vec<IoStat>>()
Ok(v)
} }
fn parse_io_service_total(s: String) -> Result<u64> { fn parse_io_service_total(s: String) -> Result<u64> {
s.lines() s.lines()
.filter(|x| x.split_whitespace().collect::<Vec<_>>().len() == 2) .filter(|x| x.split_whitespace().count() == 2)
.fold(Err(Error::new(ParseError)), |_, x| { .fold(Err(Error::new(ParseError)), |_, x| {
match x.split_whitespace().collect::<Vec<_>>().as_slice() { match x.split_whitespace().collect::<Vec<_>>().as_slice() {
["Total", val] => val.parse::<u64>().map_err(|_| Error::new(ParseError)), ["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() { if err.is_err() {
return Err(Error::new(ParseError)); Err(Error::new(ParseError))
} else { } else {
return Ok(res); Ok(res)
} }
} }
/// Current state and statistics about how throttled are the block devices when accessed from the /// Current state and statistics about how throttled are the block devices when accessed from the
/// controller's control group. /// controller's control group.
#[derive(Default, Debug)] #[derive(Default, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct BlkIoThrottle { pub struct BlkIoThrottle {
/// Statistics about the bytes transferred between the block devices by the tasks in this /// Statistics about the bytes transferred between the block devices by the tasks in this
/// control group. /// control group.
@@ -241,6 +243,7 @@ pub struct BlkIoThrottle {
/// Statistics and state of the block devices. /// Statistics and state of the block devices.
#[derive(Default, Debug)] #[derive(Default, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct BlkIo { pub struct BlkIo {
/// The number of BIOS requests merged into I/O requests by the control group's tasks. /// The number of BIOS requests merged into I/O requests by the control group's tasks.
pub io_merged: Vec<IoService>, pub io_merged: Vec<IoService>,
@@ -344,41 +347,42 @@ impl ControllerInternal for BlkIoController {
// get the resources that apply to this controller // get the resources that apply to this controller
let res: &BlkIoResources = &res.blkio; let res: &BlkIoResources = &res.blkio;
if res.update_values { if let Some(weight) = res.weight {
if let Some(weight) = res.weight { let _ = self.set_weight(weight as u64);
let _ = self.set_weight(weight as u64); }
} if let Some(leaf_weight) = res.leaf_weight {
if let Some(leaf_weight) = res.leaf_weight { let _ = self.set_leaf_weight(leaf_weight as u64);
let _ = self.set_leaf_weight(leaf_weight as u64); }
}
for dev in &res.weight_device { for dev in &res.weight_device {
if let Some(weight) = dev.weight { if let Some(weight) = dev.weight {
let _ = self.set_weight_for_device(dev.major, dev.minor, weight as u64); let _ = self.set_weight_for_device(dev.major, dev.minor, weight as u64);
}
if let Some(leaf_weight) = dev.leaf_weight {
let _ =
self.set_leaf_weight_for_device(dev.major, dev.minor, leaf_weight as u64);
}
} }
if let Some(leaf_weight) = dev.leaf_weight {
for dev in &res.throttle_read_bps_device { let _ = self.set_leaf_weight_for_device(dev.major, dev.minor, leaf_weight as u64);
let _ = self.throttle_read_bps_for_device(dev.major, dev.minor, dev.rate);
}
for dev in &res.throttle_write_bps_device {
let _ = self.throttle_write_bps_for_device(dev.major, dev.minor, dev.rate);
}
for dev in &res.throttle_read_iops_device {
let _ = self.throttle_read_iops_for_device(dev.major, dev.minor, dev.rate);
}
for dev in &res.throttle_write_iops_device {
let _ = self.throttle_write_iops_for_device(dev.major, dev.minor, dev.rate);
} }
} }
for dev in &res.throttle_read_bps_device {
let _ = self.throttle_read_bps_for_device(dev.major, dev.minor, dev.rate);
}
for dev in &res.throttle_write_bps_device {
let _ = self.throttle_write_bps_for_device(dev.major, dev.minor, dev.rate);
}
for dev in &res.throttle_read_iops_device {
let _ = self.throttle_read_iops_for_device(dev.major, dev.minor, dev.rate);
}
for dev in &res.throttle_write_iops_device {
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(()) Ok(())
} }
} }
@@ -396,55 +400,33 @@ impl<'a> From<&'a Subsystem> for &'a BlkIoController {
Subsystem::BlkIo(c) => c, Subsystem::BlkIo(c) => c,
_ => { _ => {
assert_eq!(1, 0); assert_eq!(1, 0);
::std::mem::uninitialized() let v = std::mem::MaybeUninit::uninit();
v.assume_init()
} }
} }
} }
} }
} }
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)),
}
}
fn read_u64_from(mut file: File) -> Result<u64> {
let mut string = String::new();
match file.read_to_string(&mut string) {
Ok(_) => string
.trim()
.parse()
.map_err(|e| Error::with_cause(ParseError, e)),
Err(e) => Err(Error::with_cause(ReadFailed, e)),
}
}
impl BlkIoController { impl BlkIoController {
/// Constructs a new `BlkIoController` with `oroot` serving as the root of the control group. /// Constructs a new `BlkIoController` with `root` serving as the root of the control group.
pub fn new(oroot: PathBuf, v2: bool) -> Self { pub fn new(root: PathBuf, v2: bool) -> Self {
let mut root = oroot;
if !v2 {
root.push(Self::controller_type().to_string());
}
Self { Self {
base: root.clone(), base: root.clone(),
path: root, path: root,
v2: v2, v2,
} }
} }
fn blkio_v2(&self) -> BlkIo { fn blkio_v2(&self) -> BlkIo {
let mut blkio: BlkIo = Default::default(); BlkIo {
blkio.io_stat = self io_stat: self
.open_path("io.stat", false) .open_path("io.stat", false)
.and_then(read_string_from) .and_then(read_string_from)
.and_then(parse_io_stat) .map(parse_io_stat)
.unwrap_or(Vec::new()); .unwrap_or_default(),
..Default::default()
blkio }
} }
/// Gathers statistics about and reports the state of the block devices used by the control /// Gathers statistics about and reports the state of the block devices used by the control
@@ -458,222 +440,222 @@ impl BlkIoController {
.open_path("blkio.io_merged", false) .open_path("blkio.io_merged", false)
.and_then(read_string_from) .and_then(read_string_from)
.and_then(parse_io_service) .and_then(parse_io_service)
.unwrap_or(Vec::new()), .unwrap_or_default(),
io_merged_total: self io_merged_total: self
.open_path("blkio.io_merged", false) .open_path("blkio.io_merged", false)
.and_then(read_string_from) .and_then(read_string_from)
.and_then(parse_io_service_total) .and_then(parse_io_service_total)
.unwrap_or(0), .unwrap_or_default(),
io_merged_recursive: self io_merged_recursive: self
.open_path("blkio.io_merged_recursive", false) .open_path("blkio.io_merged_recursive", false)
.and_then(read_string_from) .and_then(read_string_from)
.and_then(parse_io_service) .and_then(parse_io_service)
.unwrap_or(Vec::new()), .unwrap_or_default(),
io_merged_recursive_total: self io_merged_recursive_total: self
.open_path("blkio.io_merged_recursive", false) .open_path("blkio.io_merged_recursive", false)
.and_then(read_string_from) .and_then(read_string_from)
.and_then(parse_io_service_total) .and_then(parse_io_service_total)
.unwrap_or(0), .unwrap_or_default(),
io_queued: self io_queued: self
.open_path("blkio.io_queued", false) .open_path("blkio.io_queued", false)
.and_then(read_string_from) .and_then(read_string_from)
.and_then(parse_io_service) .and_then(parse_io_service)
.unwrap_or(Vec::new()), .unwrap_or_default(),
io_queued_total: self io_queued_total: self
.open_path("blkio.io_queued", false) .open_path("blkio.io_queued", false)
.and_then(read_string_from) .and_then(read_string_from)
.and_then(parse_io_service_total) .and_then(parse_io_service_total)
.unwrap_or(0), .unwrap_or_default(),
io_queued_recursive: self io_queued_recursive: self
.open_path("blkio.io_queued_recursive", false) .open_path("blkio.io_queued_recursive", false)
.and_then(read_string_from) .and_then(read_string_from)
.and_then(parse_io_service) .and_then(parse_io_service)
.unwrap_or(Vec::new()), .unwrap_or_default(),
io_queued_recursive_total: self io_queued_recursive_total: self
.open_path("blkio.io_queued_recursive", false) .open_path("blkio.io_queued_recursive", false)
.and_then(read_string_from) .and_then(read_string_from)
.and_then(parse_io_service_total) .and_then(parse_io_service_total)
.unwrap_or(0), .unwrap_or_default(),
io_service_bytes: self io_service_bytes: self
.open_path("blkio.io_service_bytes", false) .open_path("blkio.io_service_bytes", false)
.and_then(read_string_from) .and_then(read_string_from)
.and_then(parse_io_service) .and_then(parse_io_service)
.unwrap_or(Vec::new()), .unwrap_or_default(),
io_service_bytes_total: self io_service_bytes_total: self
.open_path("blkio.io_service_bytes", false) .open_path("blkio.io_service_bytes", false)
.and_then(read_string_from) .and_then(read_string_from)
.and_then(parse_io_service_total) .and_then(parse_io_service_total)
.unwrap_or(0), .unwrap_or_default(),
io_service_bytes_recursive: self io_service_bytes_recursive: self
.open_path("blkio.io_service_bytes_recursive", false) .open_path("blkio.io_service_bytes_recursive", false)
.and_then(read_string_from) .and_then(read_string_from)
.and_then(parse_io_service) .and_then(parse_io_service)
.unwrap_or(Vec::new()), .unwrap_or_default(),
io_service_bytes_recursive_total: self io_service_bytes_recursive_total: self
.open_path("blkio.io_service_bytes_recursive", false) .open_path("blkio.io_service_bytes_recursive", false)
.and_then(read_string_from) .and_then(read_string_from)
.and_then(parse_io_service_total) .and_then(parse_io_service_total)
.unwrap_or(0), .unwrap_or_default(),
io_serviced: self io_serviced: self
.open_path("blkio.io_serviced", false) .open_path("blkio.io_serviced", false)
.and_then(read_string_from) .and_then(read_string_from)
.and_then(parse_io_service) .and_then(parse_io_service)
.unwrap_or(Vec::new()), .unwrap_or_default(),
io_serviced_total: self io_serviced_total: self
.open_path("blkio.io_serviced", false) .open_path("blkio.io_serviced", false)
.and_then(read_string_from) .and_then(read_string_from)
.and_then(parse_io_service_total) .and_then(parse_io_service_total)
.unwrap_or(0), .unwrap_or_default(),
io_serviced_recursive: self io_serviced_recursive: self
.open_path("blkio.io_serviced_recursive", false) .open_path("blkio.io_serviced_recursive", false)
.and_then(read_string_from) .and_then(read_string_from)
.and_then(parse_io_service) .and_then(parse_io_service)
.unwrap_or(Vec::new()), .unwrap_or_default(),
io_serviced_recursive_total: self io_serviced_recursive_total: self
.open_path("blkio.io_serviced_recursive", false) .open_path("blkio.io_serviced_recursive", false)
.and_then(read_string_from) .and_then(read_string_from)
.and_then(parse_io_service_total) .and_then(parse_io_service_total)
.unwrap_or(0), .unwrap_or_default(),
io_service_time: self io_service_time: self
.open_path("blkio.io_service_time", false) .open_path("blkio.io_service_time", false)
.and_then(read_string_from) .and_then(read_string_from)
.and_then(parse_io_service) .and_then(parse_io_service)
.unwrap_or(Vec::new()), .unwrap_or_default(),
io_service_time_total: self io_service_time_total: self
.open_path("blkio.io_service_time", false) .open_path("blkio.io_service_time", false)
.and_then(read_string_from) .and_then(read_string_from)
.and_then(parse_io_service_total) .and_then(parse_io_service_total)
.unwrap_or(0), .unwrap_or_default(),
io_service_time_recursive: self io_service_time_recursive: self
.open_path("blkio.io_service_time_recursive", false) .open_path("blkio.io_service_time_recursive", false)
.and_then(read_string_from) .and_then(read_string_from)
.and_then(parse_io_service) .and_then(parse_io_service)
.unwrap_or(Vec::new()), .unwrap_or_default(),
io_service_time_recursive_total: self io_service_time_recursive_total: self
.open_path("blkio.io_service_time_recursive", false) .open_path("blkio.io_service_time_recursive", false)
.and_then(read_string_from) .and_then(read_string_from)
.and_then(parse_io_service_total) .and_then(parse_io_service_total)
.unwrap_or(0), .unwrap_or_default(),
io_wait_time: self io_wait_time: self
.open_path("blkio.io_wait_time", false) .open_path("blkio.io_wait_time", false)
.and_then(read_string_from) .and_then(read_string_from)
.and_then(parse_io_service) .and_then(parse_io_service)
.unwrap_or(Vec::new()), .unwrap_or_default(),
io_wait_time_total: self io_wait_time_total: self
.open_path("blkio.io_wait_time", false) .open_path("blkio.io_wait_time", false)
.and_then(read_string_from) .and_then(read_string_from)
.and_then(parse_io_service_total) .and_then(parse_io_service_total)
.unwrap_or(0), .unwrap_or_default(),
io_wait_time_recursive: self io_wait_time_recursive: self
.open_path("blkio.io_wait_time_recursive", false) .open_path("blkio.io_wait_time_recursive", false)
.and_then(read_string_from) .and_then(read_string_from)
.and_then(parse_io_service) .and_then(parse_io_service)
.unwrap_or(Vec::new()), .unwrap_or_default(),
io_wait_time_recursive_total: self io_wait_time_recursive_total: self
.open_path("blkio.io_wait_time_recursive", false) .open_path("blkio.io_wait_time_recursive", false)
.and_then(read_string_from) .and_then(read_string_from)
.and_then(parse_io_service_total) .and_then(parse_io_service_total)
.unwrap_or(0), .unwrap_or_default(),
leaf_weight: self leaf_weight: self
.open_path("blkio.leaf_weight", false) .open_path("blkio.leaf_weight", false)
.and_then(|file| read_u64_from(file)) .and_then(read_u64_from)
.unwrap_or(0u64), .unwrap_or(0u64),
leaf_weight_device: self leaf_weight_device: self
.open_path("blkio.leaf_weight_device", false) .open_path("blkio.leaf_weight_device", false)
.and_then(read_string_from) .and_then(read_string_from)
.and_then(parse_blkio_data) .and_then(parse_blkio_data)
.unwrap_or(Vec::new()), .unwrap_or_default(),
sectors: self sectors: self
.open_path("blkio.sectors", false) .open_path("blkio.sectors", false)
.and_then(read_string_from) .and_then(read_string_from)
.and_then(parse_blkio_data) .and_then(parse_blkio_data)
.unwrap_or(Vec::new()), .unwrap_or_default(),
sectors_recursive: self sectors_recursive: self
.open_path("blkio.sectors_recursive", false) .open_path("blkio.sectors_recursive", false)
.and_then(read_string_from) .and_then(read_string_from)
.and_then(parse_blkio_data) .and_then(parse_blkio_data)
.unwrap_or(Vec::new()), .unwrap_or_default(),
throttle: BlkIoThrottle { throttle: BlkIoThrottle {
io_service_bytes: self io_service_bytes: self
.open_path("blkio.throttle.io_service_bytes", false) .open_path("blkio.throttle.io_service_bytes", false)
.and_then(read_string_from) .and_then(read_string_from)
.and_then(parse_io_service) .and_then(parse_io_service)
.unwrap_or(Vec::new()), .unwrap_or_default(),
io_service_bytes_total: self io_service_bytes_total: self
.open_path("blkio.throttle.io_service_bytes", false) .open_path("blkio.throttle.io_service_bytes", false)
.and_then(read_string_from) .and_then(read_string_from)
.and_then(parse_io_service_total) .and_then(parse_io_service_total)
.unwrap_or(0), .unwrap_or_default(),
io_service_bytes_recursive: self io_service_bytes_recursive: self
.open_path("blkio.throttle.io_service_bytes_recursive", false) .open_path("blkio.throttle.io_service_bytes_recursive", false)
.and_then(read_string_from) .and_then(read_string_from)
.and_then(parse_io_service) .and_then(parse_io_service)
.unwrap_or(Vec::new()), .unwrap_or_default(),
io_service_bytes_recursive_total: self io_service_bytes_recursive_total: self
.open_path("blkio.throttle.io_service_bytes_recursive", false) .open_path("blkio.throttle.io_service_bytes_recursive", false)
.and_then(read_string_from) .and_then(read_string_from)
.and_then(parse_io_service_total) .and_then(parse_io_service_total)
.unwrap_or(0), .unwrap_or_default(),
io_serviced: self io_serviced: self
.open_path("blkio.throttle.io_serviced", false) .open_path("blkio.throttle.io_serviced", false)
.and_then(read_string_from) .and_then(read_string_from)
.and_then(parse_io_service) .and_then(parse_io_service)
.unwrap_or(Vec::new()), .unwrap_or_default(),
io_serviced_total: self io_serviced_total: self
.open_path("blkio.throttle.io_serviced", false) .open_path("blkio.throttle.io_serviced", false)
.and_then(read_string_from) .and_then(read_string_from)
.and_then(parse_io_service_total) .and_then(parse_io_service_total)
.unwrap_or(0), .unwrap_or_default(),
io_serviced_recursive: self io_serviced_recursive: self
.open_path("blkio.throttle.io_serviced_recursive", false) .open_path("blkio.throttle.io_serviced_recursive", false)
.and_then(read_string_from) .and_then(read_string_from)
.and_then(parse_io_service) .and_then(parse_io_service)
.unwrap_or(Vec::new()), .unwrap_or_default(),
io_serviced_recursive_total: self io_serviced_recursive_total: self
.open_path("blkio.throttle.io_serviced_recursive", false) .open_path("blkio.throttle.io_serviced_recursive", false)
.and_then(read_string_from) .and_then(read_string_from)
.and_then(parse_io_service_total) .and_then(parse_io_service_total)
.unwrap_or(0), .unwrap_or_default(),
read_bps_device: self read_bps_device: self
.open_path("blkio.throttle.read_bps_device", false) .open_path("blkio.throttle.read_bps_device", false)
.and_then(read_string_from) .and_then(read_string_from)
.and_then(parse_blkio_data) .and_then(parse_blkio_data)
.unwrap_or(Vec::new()), .unwrap_or_default(),
read_iops_device: self read_iops_device: self
.open_path("blkio.throttle.read_iops_device", false) .open_path("blkio.throttle.read_iops_device", false)
.and_then(read_string_from) .and_then(read_string_from)
.and_then(parse_blkio_data) .and_then(parse_blkio_data)
.unwrap_or(Vec::new()), .unwrap_or_default(),
write_bps_device: self write_bps_device: self
.open_path("blkio.throttle.write_bps_device", false) .open_path("blkio.throttle.write_bps_device", false)
.and_then(read_string_from) .and_then(read_string_from)
.and_then(parse_blkio_data) .and_then(parse_blkio_data)
.unwrap_or(Vec::new()), .unwrap_or_default(),
write_iops_device: self write_iops_device: self
.open_path("blkio.throttle.write_iops_device", false) .open_path("blkio.throttle.write_iops_device", false)
.and_then(read_string_from) .and_then(read_string_from)
.and_then(parse_blkio_data) .and_then(parse_blkio_data)
.unwrap_or(Vec::new()), .unwrap_or_default(),
}, },
time: self time: self
.open_path("blkio.time", false) .open_path("blkio.time", false)
.and_then(read_string_from) .and_then(read_string_from)
.and_then(parse_blkio_data) .and_then(parse_blkio_data)
.unwrap_or(Vec::new()), .unwrap_or_default(),
time_recursive: self time_recursive: self
.open_path("blkio.time_recursive", false) .open_path("blkio.time_recursive", false)
.and_then(read_string_from) .and_then(read_string_from)
.and_then(parse_blkio_data) .and_then(parse_blkio_data)
.unwrap_or(Vec::new()), .unwrap_or_default(),
weight: self weight: self
.open_path("blkio.weight", false) .open_path("blkio.weight", false)
.and_then(|file| read_u64_from(file)) .and_then(read_u64_from)
.unwrap_or(0u64), .unwrap_or(0u64),
weight_device: self weight_device: self
.open_path("blkio.weight_device", false) .open_path("blkio.weight_device", false)
.and_then(read_string_from) .and_then(read_string_from)
.and_then(parse_blkio_data) .and_then(parse_blkio_data)
.unwrap_or(Vec::new()), .unwrap_or_default(),
io_stat: Vec::new(), io_stat: Vec::new(),
} }
} }
@@ -683,8 +665,12 @@ impl BlkIoController {
pub fn set_leaf_weight(&self, w: u64) -> Result<()> { pub fn set_leaf_weight(&self, w: u64) -> Result<()> {
self.open_path("blkio.leaf_weight", true) self.open_path("blkio.leaf_weight", true)
.and_then(|mut file| { .and_then(|mut file| {
file.write_all(w.to_string().as_ref()) file.write_all(w.to_string().as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(
WriteFailed("blkio.leaf_weight".to_string(), w.to_string()),
e,
)
})
}) })
} }
@@ -693,7 +679,15 @@ impl BlkIoController {
self.open_path("blkio.leaf_weight_device", true) self.open_path("blkio.leaf_weight_device", true)
.and_then(|mut file| { .and_then(|mut file| {
file.write_all(format!("{}:{} {}", major, minor, weight).as_ref()) 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,
)
})
}) })
} }
@@ -701,99 +695,117 @@ impl BlkIoController {
pub fn reset_stats(&self) -> Result<()> { pub fn reset_stats(&self) -> Result<()> {
self.open_path("blkio.reset_stats", true) self.open_path("blkio.reset_stats", true)
.and_then(|mut file| { .and_then(|mut file| {
file.write_all("1".to_string().as_ref()) file.write_all("1".to_string().as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, 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 /// Throttle the bytes per second rate of read operation affecting the block device
/// `major:minor` to `bps`. /// `major:minor` to `bps`.
pub fn throttle_read_bps_for_device(&self, major: u64, minor: u64, bps: u64) -> Result<()> { 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); let mut content = format!("{}:{} {}", major, minor, bps);
if self.v2 { if self.v2 {
file = "io.max"; file_name = "io.max";
content = format!("{}:{} rbps={}", major, minor, bps); content = format!("{}:{} rbps={}", major, minor, bps);
} }
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()) file.write_all(content.as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, 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 /// Throttle the I/O operations per second rate of read operation affecting the block device
/// `major:minor` to `bps`. /// `major:minor` to `bps`.
pub fn throttle_read_iops_for_device(&self, major: u64, minor: u64, iops: u64) -> Result<()> { 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); let mut content = format!("{}:{} {}", major, minor, iops);
if self.v2 { if self.v2 {
file = "io.max"; file_name = "io.max";
content = format!("{}:{} riops={}", major, minor, iops); content = format!("{}:{} riops={}", major, minor, iops);
} }
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()) file.write_all(content.as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, 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 /// Throttle the bytes per second rate of write operation affecting the block device
/// `major:minor` to `bps`. /// `major:minor` to `bps`.
pub fn throttle_write_bps_for_device(&self, major: u64, minor: u64, bps: u64) -> Result<()> { 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); let mut content = format!("{}:{} {}", major, minor, bps);
if self.v2 { if self.v2 {
file = "io.max"; file_name = "io.max";
content = format!("{}:{} wbps={}", major, minor, bps); content = format!("{}:{} wbps={}", major, minor, bps);
} }
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()) file.write_all(content.as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, 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 /// Throttle the I/O operations per second rate of write operation affecting the block device
/// `major:minor` to `bps`. /// `major:minor` to `bps`.
pub fn throttle_write_iops_for_device(&self, major: u64, minor: u64, iops: u64) -> Result<()> { 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); let mut content = format!("{}:{} {}", major, minor, iops);
if self.v2 { if self.v2 {
file = "io.max"; file_name = "io.max";
content = format!("{}:{} wiops={}", major, minor, iops); content = format!("{}:{} wiops={}", major, minor, iops);
} }
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()) file.write_all(content.as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(WriteFailed(file_name.to_string(), content.to_string()), e)
})
}) })
} }
/// Set the weight of the control group's tasks. /// Set the weight of the control group's tasks.
pub fn set_weight(&self, w: u64) -> Result<()> { pub fn set_weight(&self, w: u64) -> Result<()> {
// Attation: may not find in high kernel version. // Attation: may not find in high kernel version.
let mut file = "blkio.weight"; let mut file_name = "blkio.weight";
if self.v2 { if self.v2 {
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(w.to_string().as_ref()) file.write_all(w.to_string().as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(WriteFailed(file_name.to_string(), w.to_string()), e)
})
}) })
} }
/// Same as `set_weight()`, but settable per each block device. /// Same as `set_weight()`, but settable per each block device.
pub fn set_weight_for_device(&self, major: u64, minor: u64, weight: u64) -> Result<()> { 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 { if self.v2 {
// Attation: there is no weight for device in runc // Attation: there is no weight for device in runc
// https://github.com/opencontainers/runc/blob/46be7b612e2533c494e6a251111de46d8e286ed5/libcontainer/cgroups/fs2/io.go#L30 // 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 // 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()) 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)] #[cfg(test)]
mod test { mod test {
use crate::blkio::{parse_blkio_data, BlkIoData}; use crate::blkio::{parse_blkio_data, BlkIoData};

View File

@@ -9,8 +9,6 @@
use crate::error::ErrorKind::*; use crate::error::ErrorKind::*;
use crate::error::*; use crate::error::*;
use crate::libc_rmdir;
use crate::{CgroupPid, ControllIdentifier, Controller, Hierarchy, Resources, Subsystem}; use crate::{CgroupPid, ControllIdentifier, Controller, Hierarchy, Resources, Subsystem};
use std::collections::HashMap; use std::collections::HashMap;
@@ -30,20 +28,41 @@ use std::path::{Path, PathBuf};
/// > specialized behaviour. /// > specialized behaviour.
/// ///
/// This crate is an attempt at providing a Rust-native way of managing these cgroups. /// This crate is an attempt at providing a Rust-native way of managing these cgroups.
pub struct Cgroup<'b> { #[derive(Debug)]
pub struct Cgroup {
/// The list of subsystems that control this cgroup /// The list of subsystems that control this cgroup
subsystems: Vec<Subsystem>, subsystems: Vec<Subsystem>,
/// The hierarchy. /// The hierarchy.
hier: Box<&'b dyn Hierarchy>, hier: Box<dyn Hierarchy>,
path: String, path: String,
} }
impl<'b> Cgroup<'b> { impl Clone for Cgroup {
fn clone(&self) -> Self {
Cgroup {
subsystems: self.subsystems.clone(),
path: self.path.clone(),
hier: crate::hierarchies::auto(),
}
}
}
impl Default for Cgroup {
fn default() -> Self {
Cgroup {
subsystems: Vec::new(),
hier: crate::hierarchies::auto(),
path: "".to_string(),
}
}
}
impl Cgroup {
/// Create this control group. /// Create this control group.
fn create(&self) { fn create(&self) {
if self.hier.v2() { if self.hier.v2() {
create_v2_cgroup(self.hier.root().clone(), &self.path); let _ret = create_v2_cgroup(self.hier.root(), &self.path);
} else { } else {
for subsystem in &self.subsystems { for subsystem in &self.subsystems {
subsystem.to_controller().create(); subsystem.to_controller().create();
@@ -58,56 +77,64 @@ impl<'b> Cgroup<'b> {
/// Create a new control group in the hierarchy `hier`, with name `path`. /// Create a new control group in the hierarchy `hier`, with name `path`.
/// ///
/// Returns a handle to the control group that can be used to manipulate it. /// Returns a handle to the control group that can be used to manipulate it.
pub fn new<P: AsRef<Path>>(hier: Box<dyn Hierarchy>, path: P) -> Cgroup {
let cg = Cgroup::load(hier, path);
cg.create();
cg
}
/// Create a new control group in the hierarchy `hier`, with name `path` and `relative_paths`
/// ///
/// Note that if the handle goes out of scope and is dropped, the control group is _not_ /// Returns a handle to the control group that can be used to manipulate it.
/// destroyed. ///
pub fn new<P: AsRef<Path>>(hier: Box<&'b dyn Hierarchy>, path: P) -> Cgroup<'b> { /// Note that this method is only meaningful for cgroup v1, call it is equivalent to call `new` in the v2 mode
let relative_paths = get_cgroups_relative_paths().unwrap(); pub fn new_with_relative_paths<P: AsRef<Path>>(
Cgroup::new_with_relative_paths(hier, path, relative_paths) hier: Box<dyn Hierarchy>,
path: P,
relative_paths: HashMap<String, String>,
) -> Cgroup {
let cg = Cgroup::load_with_relative_paths(hier, path, relative_paths);
cg.create();
cg
} }
/// Create a handle for a control group in the hierarchy `hier`, with name `path`. /// Create a handle for a control group in the hierarchy `hier`, with name `path`.
/// ///
/// Returns a handle to the control group (that possibly does not exist until `create()` has /// Returns a handle to the control group (that possibly does not exist until `create()` has
/// been called on the cgroup. /// been called on the cgroup.
/// pub fn load<P: AsRef<Path>>(hier: Box<dyn Hierarchy>, path: P) -> Cgroup {
/// Note that if the handle goes out of scope and is dropped, the control group is _not_ let path = path.as_ref();
/// destroyed. let mut subsystems = hier.subsystems();
pub fn load<P: AsRef<Path>>(hier: Box<&'b dyn Hierarchy>, path: P) -> Cgroup<'b> { if path.as_os_str() != "" {
let relative_paths = get_cgroups_relative_paths().unwrap(); subsystems = subsystems
Cgroup::load_with_relative_paths(hier, path, relative_paths) .into_iter()
.map(|x| x.enter(path))
.collect::<Vec<_>>();
}
Cgroup {
path: path.to_str().unwrap().to_string(),
subsystems,
hier,
}
} }
/// Create a new control group in the hierarchy `hier`, with name `path`. /// Create a handle for a control group in the hierarchy `hier`, with name `path` and `relative_paths`
/// and relative paths from `/proc/self/cgroup`
///
/// Returns a handle to the control group that can be used to manipulate it.
///
/// Note that if the handle goes out of scope and is dropped, the control group is _not_
/// destroyed.
pub fn new_with_relative_paths<P: AsRef<Path>>(
hier: Box<&'b dyn Hierarchy>,
path: P,
relative_paths: HashMap<String, String>,
) -> Cgroup<'b> {
let cg = Cgroup::load_with_relative_paths(hier, path, relative_paths);
cg.create();
cg
}
/// Create a handle for a control group in the hierarchy `hier`, with name `path`,
/// and relative paths from `/proc/self/cgroup`
/// ///
/// Returns a handle to the control group (that possibly does not exist until `create()` has /// Returns a handle to the control group (that possibly does not exist until `create()` has
/// been called on the cgroup. /// been called on the cgroup.
/// ///
/// Note that if the handle goes out of scope and is dropped, the control group is _not_ /// Note that this method is only meaningful for cgroup v1, call it is equivalent to call `load` in the v2 mode
/// destroyed.
pub fn load_with_relative_paths<P: AsRef<Path>>( pub fn load_with_relative_paths<P: AsRef<Path>>(
hier: Box<&'b dyn Hierarchy>, hier: Box<dyn Hierarchy>,
path: P, path: P,
relative_paths: HashMap<String, String>, relative_paths: HashMap<String, String>,
) -> Cgroup<'b> { ) -> Cgroup {
// relative_paths only valid for cgroup v1
if hier.v2() {
return Self::load(hier, path);
}
let path = path.as_ref(); let path = path.as_ref();
let mut subsystems = hier.subsystems(); let mut subsystems = hier.subsystems();
if path.as_os_str() != "" { if path.as_os_str() != "" {
@@ -117,7 +144,7 @@ impl<'b> Cgroup<'b> {
let cn = x.controller_name(); let cn = x.controller_name();
if relative_paths.contains_key(&cn) { if relative_paths.contains_key(&cn) {
let rp = relative_paths.get(&cn).unwrap(); 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); let mut p = PathBuf::from(valid_path);
p.push(path); p.push(path);
x.enter(p.as_ref()) x.enter(p.as_ref())
@@ -128,13 +155,11 @@ impl<'b> Cgroup<'b> {
.collect::<Vec<_>>(); .collect::<Vec<_>>();
} }
let cg = Cgroup { Cgroup {
subsystems: subsystems, subsystems,
hier: hier, hier,
path: path.to_str().unwrap().to_string(), path: path.to_str().unwrap().to_string(),
}; }
cg
} }
/// The list of subsystems that this control group supports. /// The list of subsystems that this control group supports.
@@ -148,17 +173,17 @@ impl<'b> Cgroup<'b> {
/// system call will fail if there are any descendants. Thus, one should check whether it was /// system call will fail if there are any descendants. Thus, one should check whether it was
/// actually removed, and remove the descendants first if not. In the future, this behavior /// actually removed, and remove the descendants first if not. In the future, this behavior
/// will change. /// will change.
pub fn delete(self) { pub fn delete(&self) -> Result<()> {
if self.v2() { if self.v2() {
if self.path != "" { if !self.path.is_empty() {
let mut p = self.hier.root().clone(); let mut p = self.hier.root();
p.push(self.path); p.push(self.path.clone());
libc_rmdir(p.to_str().unwrap()); return fs::remove_dir(p).map_err(|e| Error::with_cause(RemoveFailed, e));
} }
return; return Ok(());
} }
self.subsystems.into_iter().for_each(|sub| match sub { self.subsystems.iter().try_for_each(|sub| match sub {
Subsystem::Pid(pidc) => pidc.delete(), Subsystem::Pid(pidc) => pidc.delete(),
Subsystem::Mem(c) => c.delete(), Subsystem::Mem(c) => c.delete(),
Subsystem::CpuSet(c) => c.delete(), Subsystem::CpuSet(c) => c.delete(),
@@ -173,7 +198,7 @@ impl<'b> Cgroup<'b> {
Subsystem::HugeTlb(c) => c.delete(), Subsystem::HugeTlb(c) => c.delete(),
Subsystem::Rdma(c) => c.delete(), Subsystem::Rdma(c) => c.delete(),
Subsystem::Systemd(c) => c.delete(), Subsystem::Systemd(c) => c.delete(),
}); })
} }
/// Apply a set of resource limits to the control group. /// Apply a set of resource limits to the control group.
@@ -193,7 +218,7 @@ impl<'b> Cgroup<'b> {
/// let cpu: &CpuController = control_group.controller_of() /// let cpu: &CpuController = control_group.controller_of()
/// .expect("No cpu controller attached!"); /// .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 where
&'a T: From<&'a Subsystem>, &'a T: From<&'a Subsystem>,
T: Controller + ControllIdentifier, T: Controller + ControllIdentifier,
@@ -220,7 +245,7 @@ impl<'b> Cgroup<'b> {
pub fn add_task(&self, pid: CgroupPid) -> Result<()> { pub fn add_task(&self, pid: CgroupPid) -> Result<()> {
if self.v2() { if self.v2() {
let subsystems = self.subsystems(); let subsystems = self.subsystems();
if subsystems.len() > 0 { if !subsystems.is_empty() {
let c = subsystems[0].to_controller(); let c = subsystems[0].to_controller();
c.add_task(&pid) c.add_task(&pid)
} else { } else {
@@ -233,13 +258,36 @@ impl<'b> Cgroup<'b> {
} }
} }
/// Attach a task to the control group by thread group id.
pub fn add_task_by_tgid(&self, pid: CgroupPid) -> Result<()> {
self.subsystems()
.iter()
.try_for_each(|sub| sub.to_controller().add_task_by_tgid(&pid))
}
/// Set notify_on_release to the control group.
pub fn set_notify_on_release(&self, enable: bool) -> Result<()> {
self.subsystems()
.iter()
.try_for_each(|sub| sub.to_controller().set_notify_on_release(enable))
}
/// Set release_agent
pub fn set_release_agent(&self, path: &str) -> Result<()> {
self.hier
.root_control_group()
.subsystems()
.iter()
.try_for_each(|sub| sub.to_controller().set_release_agent(path))
}
/// Returns an Iterator that can be used to iterate over the tasks that are currently in the /// Returns an Iterator that can be used to iterate over the tasks that are currently in the
/// control group. /// control group.
pub fn tasks(&self) -> Vec<CgroupPid> { pub fn tasks(&self) -> Vec<CgroupPid> {
// Collect the tasks from all subsystems // Collect the tasks from all subsystems
let mut v = if self.v2() { let mut v = if self.v2() {
let subsystems = self.subsystems(); let subsystems = self.subsystems();
if subsystems.len() > 0 { if !subsystems.is_empty() {
let c = subsystems[0].to_controller(); let c = subsystems[0].to_controller();
c.tasks() c.tasks()
} else { } else {
@@ -261,45 +309,43 @@ impl<'b> Cgroup<'b> {
} }
} }
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) { fn enable_controllers(controllers: &[String], path: &Path) {
let mut f = path.clone(); let f = path.join("cgroup.subtree_control");
f.push("cgroup.subtree_control");
for c in controllers { for c in controllers {
let body = format!("+{}", c); let body = format!("+{}", c);
let _rest = fs::write(f.as_path(), body.as_bytes()); let _rest = fs::write(f.as_path(), body.as_bytes());
} }
} }
fn supported_controllers(p: &PathBuf) -> Vec<String> { fn supported_controllers() -> Vec<String> {
let p = format!("{}/{}", UNIFIED_MOUNTPOINT, "cgroup.controllers"); let p = format!("{}/{}", UNIFIED_MOUNTPOINT, "cgroup.controllers");
let ret = fs::read_to_string(p.as_str()); let ret = fs::read_to_string(p.as_str());
ret.unwrap_or(String::new()) ret.unwrap_or_default()
.split(" ") .split(' ')
.map(|x| x.to_string()) .map(|x| x.to_string())
.collect::<Vec<String>>() .collect::<Vec<String>>()
} }
fn create_v2_cgroup(root: PathBuf, path: &str) -> Result<()> { fn create_v2_cgroup(root: PathBuf, path: &str) -> Result<()> {
// controler list ["memory", "cpu"] // controler list ["memory", "cpu"]
let controllers = supported_controllers(&root); let controllers = supported_controllers();
let mut fp = root; let mut fp = root;
// enable for root // enable for root
enable_controllers(&controllers, &fp); enable_controllers(&controllers, &fp);
// path: "a/b/c" // path: "a/b/c"
let elements = path.split("/").collect::<Vec<&str>>(); let elements = path.split('/').collect::<Vec<&str>>();
let last_index = elements.len() - 1; let last_index = elements.len() - 1;
for (i, ele) in elements.iter().enumerate() { for (i, ele) in elements.iter().enumerate() {
// ROOT/a // ROOT/a
fp.push(ele); fp.push(ele);
// create dir, need not check if is a file or directory // create dir, need not check if is a file or directory
if !fp.exists() { if !fp.exists() {
match ::std::fs::create_dir(fp.clone()) { if let Err(e) = std::fs::create_dir(fp.clone()) {
Err(e) => return Err(Error::with_cause(ErrorKind::FsError, e)), return Err(Error::with_cause(ErrorKind::FsError, e));
Ok(_) => {}
} }
} }
@@ -313,9 +359,19 @@ fn create_v2_cgroup(root: PathBuf, path: &str) -> Result<()> {
} }
pub fn get_cgroups_relative_paths() -> Result<HashMap<String, String>> { 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 mut m = HashMap::new();
let content = 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() { for l in content.lines() {
let fl: Vec<&str> = l.split(':').collect(); let fl: Vec<&str> = l.split(':').collect();
if fl.len() != 3 { if fl.len() != 3 {
@@ -324,13 +380,7 @@ pub fn get_cgroups_relative_paths() -> Result<HashMap<String, String>> {
let keys: Vec<&str> = fl[1].split(',').collect(); let keys: Vec<&str> = fl[1].split(',').collect();
for key in &keys { for key in &keys {
// this is a workaround, cgroup file are using `name=systemd`, m.insert(key.to_string(), fl[2].to_string());
// but if file system the name is `systemd`
if *key == "name=systemd" {
m.insert("systemd".to_string(), fl[2].to_string());
} else {
m.insert(key.to_string(), fl[2].to_string());
}
} }
} }
Ok(m) Ok(m)

View File

@@ -16,12 +16,11 @@
//! by a call to `build()`. //! by a call to `build()`.
//! //!
//! ```rust,no_run //! ```rust,no_run
//! # use cgroups::*; //! # use cgroups_rs::*;
//! # use cgroups::devices::*; //! # use cgroups_rs::devices::*;
//! # use cgroups::cgroup_builder::*; //! # use cgroups_rs::cgroup_builder::*;
//! let h = cgroups::hierarchies::auto(); //! let h = cgroups_rs::hierarchies::auto();
//! let h = Box::new(&*h); //! let cgroup: Cgroup = CgroupBuilder::new("hello")
//! let cgroup: Cgroup = CgroupBuilder::new("hello", h)
//! .memory() //! .memory()
//! .kernel_memory_limit(1024 * 1024) //! .kernel_memory_limit(1024 * 1024)
//! .memory_hard_limit(1024 * 1024) //! .memory_hard_limit(1024 * 1024)
@@ -47,8 +46,8 @@
//! .limit("2G".to_string(), 2 * 1024 * 1024 * 1024) //! .limit("2G".to_string(), 2 * 1024 * 1024 * 1024)
//! .done() //! .done()
//! .blkio() //! .blkio()
//! .weight(Some(123)) //! .weight(123)
//! .leaf_weight(Some(99)) //! .leaf_weight(99)
//! .weight_device(6, 1, Some(100), Some(55)) //! .weight_device(6, 1, Some(100), Some(55))
//! .weight_device(6, 1, Some(100), Some(55)) //! .weight_device(6, 1, Some(100), Some(55))
//! .throttle_iops() //! .throttle_iops()
@@ -58,12 +57,11 @@
//! .read(6, 1, 10) //! .read(6, 1, 10)
//! .write(11, 1, 100) //! .write(11, 1, 100)
//! .done() //! .done()
//! .build(); //! .build(h);
//! ``` //! ```
use crate::error::*;
use crate::{ use crate::{
pid, BlkIoDeviceResource, BlkIoDeviceThrottleResource, Cgroup, DeviceResource, Hierarchy, BlkIoDeviceResource, BlkIoDeviceThrottleResource, Cgroup, DeviceResource, Hierarchy,
HugePageResource, MaxValue, NetworkPriority, Resources, HugePageResource, MaxValue, NetworkPriority, Resources,
}; };
@@ -71,67 +69,64 @@ macro_rules! gen_setter {
($res:ident, $cont:ident, $func:ident, $name:ident, $ty:ty) => { ($res:ident, $cont:ident, $func:ident, $name:ident, $ty:ty) => {
/// See the similarly named function in the respective controller. /// See the similarly named function in the respective controller.
pub fn $name(mut self, $name: $ty) -> Self { pub fn $name(mut self, $name: $ty) -> Self {
self.cgroup.resources.$res.update_values = true; self.cgroup.resources.$res.$name = Some($name);
self.cgroup.resources.$res.$name = $name;
self self
} }
}; };
} }
/// A control group builder instance /// A control group builder instance
pub struct CgroupBuilder<'a> { pub struct CgroupBuilder {
name: String, name: String,
hierarchy: Box<&'a dyn Hierarchy>,
/// Internal, unsupported field: use the associated builders instead. /// Internal, unsupported field: use the associated builders instead.
resources: Resources, resources: Resources,
} }
impl<'a> CgroupBuilder<'a> { impl CgroupBuilder {
/// Start building a control group with the supplied hierarchy and name pair. /// Start building a control group with the supplied hierarchy and name pair.
/// ///
/// Note that this does not actually create the control group until `build()` is called. /// Note that this does not actually create the control group until `build()` is called.
pub fn new(name: &'a str, hierarchy: Box<&'a dyn Hierarchy>) -> CgroupBuilder<'a> { pub fn new(name: &str) -> CgroupBuilder {
CgroupBuilder { CgroupBuilder {
name: name.to_owned(), name: name.to_owned(),
hierarchy: hierarchy,
resources: Resources::default(), resources: Resources::default(),
} }
} }
/// Builds the memory resources of the control group. /// Builds the memory resources of the control group.
pub fn memory(self) -> MemoryResourceBuilder<'a> { pub fn memory(self) -> MemoryResourceBuilder {
MemoryResourceBuilder { cgroup: self } MemoryResourceBuilder { cgroup: self }
} }
/// Builds the pid resources of the control group. /// Builds the pid resources of the control group.
pub fn pid(self) -> PidResourceBuilder<'a> { pub fn pid(self) -> PidResourceBuilder {
PidResourceBuilder { cgroup: self } PidResourceBuilder { cgroup: self }
} }
/// Builds the cpu resources of the control group. /// Builds the cpu resources of the control group.
pub fn cpu(self) -> CpuResourceBuilder<'a> { pub fn cpu(self) -> CpuResourceBuilder {
CpuResourceBuilder { cgroup: self } CpuResourceBuilder { cgroup: self }
} }
/// Builds the devices resources of the control group, disallowing or /// Builds the devices resources of the control group, disallowing or
/// allowing access to certain devices in the system. /// allowing access to certain devices in the system.
pub fn devices(self) -> DeviceResourceBuilder<'a> { pub fn devices(self) -> DeviceResourceBuilder {
DeviceResourceBuilder { cgroup: self } DeviceResourceBuilder { cgroup: self }
} }
/// Builds the network resources of the control group, setting class id, or /// Builds the network resources of the control group, setting class id, or
/// various priorities on networking interfaces. /// various priorities on networking interfaces.
pub fn network(self) -> NetworkResourceBuilder<'a> { pub fn network(self) -> NetworkResourceBuilder {
NetworkResourceBuilder { cgroup: self } NetworkResourceBuilder { cgroup: self }
} }
/// Builds the hugepage/hugetlb resources available to the control group. /// Builds the hugepage/hugetlb resources available to the control group.
pub fn hugepages(self) -> HugepagesResourceBuilder<'a> { pub fn hugepages(self) -> HugepagesResourceBuilder {
HugepagesResourceBuilder { cgroup: self } HugepagesResourceBuilder { cgroup: self }
} }
/// Builds the block I/O resources available for the control group. /// Builds the block I/O resources available for the control group.
pub fn blkio(self) -> BlkIoResourcesBuilder<'a> { pub fn blkio(self) -> BlkIoResourcesBuilder {
BlkIoResourcesBuilder { BlkIoResourcesBuilder {
cgroup: self, cgroup: self,
throttling_iops: false, throttling_iops: false,
@@ -139,19 +134,19 @@ impl<'a> CgroupBuilder<'a> {
} }
/// Finalize the control group, consuming the builder and creating the control group. /// Finalize the control group, consuming the builder and creating the control group.
pub fn build(self) -> Cgroup<'a> { pub fn build(self, hier: Box<dyn Hierarchy>) -> Cgroup {
let cg = Cgroup::new(self.hierarchy, self.name); let cg = Cgroup::new(hier, self.name);
cg.apply(&self.resources); let _ret = cg.apply(&self.resources);
cg cg
} }
} }
/// A builder that configures the memory controller of a control group. /// A builder that configures the memory controller of a control group.
pub struct MemoryResourceBuilder<'a> { pub struct MemoryResourceBuilder {
cgroup: CgroupBuilder<'a>, cgroup: CgroupBuilder,
} }
impl<'a> MemoryResourceBuilder<'a> { impl MemoryResourceBuilder {
gen_setter!( gen_setter!(
memory, memory,
MemController, MemController,
@@ -184,17 +179,17 @@ impl<'a> MemoryResourceBuilder<'a> {
gen_setter!(memory, MemController, set_swappiness, swappiness, u64); gen_setter!(memory, MemController, set_swappiness, swappiness, u64);
/// Finish the construction of the memory resources of a control group. /// Finish the construction of the memory resources of a control group.
pub fn done(self) -> CgroupBuilder<'a> { pub fn done(self) -> CgroupBuilder {
self.cgroup self.cgroup
} }
} }
/// A builder that configures the pid controller of a control group. /// A builder that configures the pid controller of a control group.
pub struct PidResourceBuilder<'a> { pub struct PidResourceBuilder {
cgroup: CgroupBuilder<'a>, cgroup: CgroupBuilder,
} }
impl<'a> PidResourceBuilder<'a> { impl PidResourceBuilder {
gen_setter!( gen_setter!(
pid, pid,
PidController, PidController,
@@ -204,19 +199,18 @@ impl<'a> PidResourceBuilder<'a> {
); );
/// Finish the construction of the pid resources of a control group. /// Finish the construction of the pid resources of a control group.
pub fn done(self) -> CgroupBuilder<'a> { pub fn done(self) -> CgroupBuilder {
self.cgroup self.cgroup
} }
} }
/// A builder that configures the cpuset & cpu controllers of a control group. /// A builder that configures the cpuset & cpu controllers of a control group.
pub struct CpuResourceBuilder<'a> { pub struct CpuResourceBuilder {
cgroup: CgroupBuilder<'a>, cgroup: CgroupBuilder,
} }
impl<'a> CpuResourceBuilder<'a> { impl CpuResourceBuilder {
// FIXME this should all changed to options. gen_setter!(cpu, CpuSetController, set_cpus, cpus, String);
gen_setter!(cpu, CpuSetController, set_cpus, cpus, Option<String>);
gen_setter!(cpu, CpuSetController, set_mems, mems, String); gen_setter!(cpu, CpuSetController, set_mems, mems, String);
gen_setter!(cpu, CpuController, set_shares, shares, u64); gen_setter!(cpu, CpuController, set_shares, shares, u64);
gen_setter!(cpu, CpuController, set_cfs_quota, quota, i64); gen_setter!(cpu, CpuController, set_cfs_quota, quota, i64);
@@ -225,17 +219,17 @@ impl<'a> CpuResourceBuilder<'a> {
gen_setter!(cpu, CpuController, set_rt_period, realtime_period, u64); gen_setter!(cpu, CpuController, set_rt_period, realtime_period, u64);
/// Finish the construction of the cpu resources of a control group. /// Finish the construction of the cpu resources of a control group.
pub fn done(self) -> CgroupBuilder<'a> { pub fn done(self) -> CgroupBuilder {
self.cgroup self.cgroup
} }
} }
/// A builder that configures the devices controller of a control group. /// A builder that configures the devices controller of a control group.
pub struct DeviceResourceBuilder<'a> { pub struct DeviceResourceBuilder {
cgroup: CgroupBuilder<'a>, cgroup: CgroupBuilder,
} }
impl<'a> DeviceResourceBuilder<'a> { impl DeviceResourceBuilder {
/// Restrict (or allow) a device to the tasks inside the control group. /// Restrict (or allow) a device to the tasks inside the control group.
pub fn device( pub fn device(
mut self, mut self,
@@ -244,36 +238,34 @@ impl<'a> DeviceResourceBuilder<'a> {
devtype: crate::devices::DeviceType, devtype: crate::devices::DeviceType,
allow: bool, allow: bool,
access: Vec<crate::devices::DevicePermissions>, access: Vec<crate::devices::DevicePermissions>,
) -> DeviceResourceBuilder<'a> { ) -> DeviceResourceBuilder {
self.cgroup.resources.devices.update_values = true;
self.cgroup.resources.devices.devices.push(DeviceResource { self.cgroup.resources.devices.devices.push(DeviceResource {
allow,
devtype,
major, major,
minor, minor,
devtype,
allow,
access, access,
}); });
self self
} }
/// Finish the construction of the devices resources of a control group. /// Finish the construction of the devices resources of a control group.
pub fn done(self) -> CgroupBuilder<'a> { pub fn done(self) -> CgroupBuilder {
self.cgroup self.cgroup
} }
} }
/// A builder that configures the net_cls & net_prio controllers of a control group. /// A builder that configures the net_cls & net_prio controllers of a control group.
pub struct NetworkResourceBuilder<'a> { pub struct NetworkResourceBuilder {
cgroup: CgroupBuilder<'a>, cgroup: CgroupBuilder,
} }
impl<'a> NetworkResourceBuilder<'a> { impl NetworkResourceBuilder {
gen_setter!(network, NetclsController, set_class, class_id, u64); gen_setter!(network, NetclsController, set_class, class_id, u64);
/// Set the priority of the tasks when operating on a networking device defined by `name` to be /// Set the priority of the tasks when operating on a networking device defined by `name` to be
/// `priority`. /// `priority`.
pub fn priority(mut self, name: String, priority: u64) -> NetworkResourceBuilder<'a> { pub fn priority(mut self, name: String, priority: u64) -> NetworkResourceBuilder {
self.cgroup.resources.network.update_values = true;
self.cgroup self.cgroup
.resources .resources
.network .network
@@ -283,20 +275,19 @@ impl<'a> NetworkResourceBuilder<'a> {
} }
/// Finish the construction of the network resources of a control group. /// Finish the construction of the network resources of a control group.
pub fn done(self) -> CgroupBuilder<'a> { pub fn done(self) -> CgroupBuilder {
self.cgroup self.cgroup
} }
} }
/// A builder that configures the hugepages controller of a control group. /// A builder that configures the hugepages controller of a control group.
pub struct HugepagesResourceBuilder<'a> { pub struct HugepagesResourceBuilder {
cgroup: CgroupBuilder<'a>, cgroup: CgroupBuilder,
} }
impl<'a> HugepagesResourceBuilder<'a> { impl HugepagesResourceBuilder {
/// Limit the usage of certain hugepages (determined by `size`) to be at most `limit` bytes. /// Limit the usage of certain hugepages (determined by `size`) to be at most `limit` bytes.
pub fn limit(mut self, size: String, limit: u64) -> HugepagesResourceBuilder<'a> { pub fn limit(mut self, size: String, limit: u64) -> HugepagesResourceBuilder {
self.cgroup.resources.hugepages.update_values = true;
self.cgroup self.cgroup
.resources .resources
.hugepages .hugepages
@@ -306,26 +297,20 @@ impl<'a> HugepagesResourceBuilder<'a> {
} }
/// Finish the construction of the network resources of a control group. /// Finish the construction of the network resources of a control group.
pub fn done(self) -> CgroupBuilder<'a> { pub fn done(self) -> CgroupBuilder {
self.cgroup self.cgroup
} }
} }
/// A builder that configures the blkio controller of a control group. /// A builder that configures the blkio controller of a control group.
pub struct BlkIoResourcesBuilder<'a> { pub struct BlkIoResourcesBuilder {
cgroup: CgroupBuilder<'a>, cgroup: CgroupBuilder,
throttling_iops: bool, throttling_iops: bool,
} }
impl<'a> BlkIoResourcesBuilder<'a> { impl BlkIoResourcesBuilder {
gen_setter!(blkio, BlkIoController, set_weight, weight, Option<u16>); gen_setter!(blkio, BlkIoController, set_weight, weight, u16);
gen_setter!( gen_setter!(blkio, BlkIoController, set_leaf_weight, leaf_weight, u16);
blkio,
BlkIoController,
set_leaf_weight,
leaf_weight,
Option<u16>
);
/// Set the weight of a certain device. /// Set the weight of a certain device.
pub fn weight_device( pub fn weight_device(
@@ -334,8 +319,7 @@ impl<'a> BlkIoResourcesBuilder<'a> {
minor: u64, minor: u64,
weight: Option<u16>, weight: Option<u16>,
leaf_weight: Option<u16>, leaf_weight: Option<u16>,
) -> BlkIoResourcesBuilder<'a> { ) -> BlkIoResourcesBuilder {
self.cgroup.resources.blkio.update_values = true;
self.cgroup self.cgroup
.resources .resources
.blkio .blkio
@@ -350,20 +334,19 @@ impl<'a> BlkIoResourcesBuilder<'a> {
} }
/// Start configuring the I/O operations per second metric. /// Start configuring the I/O operations per second metric.
pub fn throttle_iops(mut self) -> BlkIoResourcesBuilder<'a> { pub fn throttle_iops(mut self) -> BlkIoResourcesBuilder {
self.throttling_iops = true; self.throttling_iops = true;
self self
} }
/// Start configuring the bytes per second metric. /// Start configuring the bytes per second metric.
pub fn throttle_bps(mut self) -> BlkIoResourcesBuilder<'a> { pub fn throttle_bps(mut self) -> BlkIoResourcesBuilder {
self.throttling_iops = false; self.throttling_iops = false;
self self
} }
/// Limit the read rate of the current metric for a certain device. /// Limit the read rate of the current metric for a certain device.
pub fn read(mut self, major: u64, minor: u64, rate: u64) -> BlkIoResourcesBuilder<'a> { pub fn read(mut self, major: u64, minor: u64, rate: u64) -> BlkIoResourcesBuilder {
self.cgroup.resources.blkio.update_values = true;
let throttle = BlkIoDeviceThrottleResource { major, minor, rate }; let throttle = BlkIoDeviceThrottleResource { major, minor, rate };
if self.throttling_iops { if self.throttling_iops {
self.cgroup self.cgroup
@@ -382,8 +365,7 @@ impl<'a> BlkIoResourcesBuilder<'a> {
} }
/// Limit the write rate of the current metric for a certain device. /// Limit the write rate of the current metric for a certain device.
pub fn write(mut self, major: u64, minor: u64, rate: u64) -> BlkIoResourcesBuilder<'a> { pub fn write(mut self, major: u64, minor: u64, rate: u64) -> BlkIoResourcesBuilder {
self.cgroup.resources.blkio.update_values = true;
let throttle = BlkIoDeviceThrottleResource { major, minor, rate }; let throttle = BlkIoDeviceThrottleResource { major, minor, rate };
if self.throttling_iops { if self.throttling_iops {
self.cgroup self.cgroup
@@ -402,7 +384,7 @@ impl<'a> BlkIoResourcesBuilder<'a> {
} }
/// Finish the construction of the blkio resources of a control group. /// Finish the construction of the blkio resources of a control group.
pub fn done(self) -> CgroupBuilder<'a> { pub fn done(self) -> CgroupBuilder {
self.cgroup self.cgroup
} }
} }

View File

@@ -15,11 +15,11 @@ use std::path::PathBuf;
use crate::error::ErrorKind::*; use crate::error::ErrorKind::*;
use crate::error::*; use crate::error::*;
use crate::{parse_max_value, read_i64_from}; use crate::{parse_max_value, read_i64_from, read_u64_from};
use crate::{ use crate::{
ControllIdentifier, ControllerInternal, Controllers, CpuResources, MaxValue, Resources, ControllIdentifier, ControllerInternal, Controllers, CpuResources, CustomizedAttribute,
Subsystem, MaxValue, Resources, Subsystem,
}; };
/// A controller that allows controlling the `cpu` subsystem of a Cgroup. /// A controller that allows controlling the `cpu` subsystem of a Cgroup.
@@ -36,6 +36,7 @@ pub struct CpuController {
/// The current state of the control group and its processes. /// The current state of the control group and its processes.
#[derive(Debug)] #[derive(Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Cpu { pub struct Cpu {
/// Reports CPU time statistics. /// Reports CPU time statistics.
/// ///
@@ -45,7 +46,7 @@ pub struct Cpu {
/// The current state of the control group and its processes. /// The current state of the control group and its processes.
#[derive(Debug)] #[derive(Debug)]
struct CFSQuotaAndPeriod { struct CfsQuotaAndPeriod {
quota: MaxValue, quota: MaxValue,
period: u64, period: u64,
} }
@@ -75,24 +76,15 @@ impl ControllerInternal for CpuController {
// get the resources that apply to this controller // get the resources that apply to this controller
let res: &CpuResources = &res.cpu; let res: &CpuResources = &res.cpu;
if res.update_values { update_and_test!(self, set_shares, res.shares, shares);
let _ = self.set_shares(res.shares); update_and_test!(self, set_cfs_period, res.period, cfs_period);
if self.shares()? != res.shares as u64 { update_and_test!(self, set_cfs_quota, res.quota, cfs_quota);
return Err(Error::new(ErrorKind::Other));
}
let _ = self.set_cfs_period(res.period); res.attrs.iter().for_each(|(k, v)| {
if self.cfs_period()? != res.period as u64 { let _ = self.set(k, v);
return Err(Error::new(ErrorKind::Other)); });
}
let _ = self.set_cfs_quota(res.quota); // TODO: rt properties (CONFIG_RT_GROUP_SCHED) are not yet supported
if self.cfs_quota()? != res.quota {
return Err(Error::new(ErrorKind::Other));
}
// TODO: rt properties (CONFIG_RT_GROUP_SCHED) are not yet supported
}
Ok(()) Ok(())
} }
@@ -111,35 +103,21 @@ impl<'a> From<&'a Subsystem> for &'a CpuController {
Subsystem::Cpu(c) => c, Subsystem::Cpu(c) => c,
_ => { _ => {
assert_eq!(1, 0); assert_eq!(1, 0);
::std::mem::uninitialized() let v = std::mem::MaybeUninit::uninit();
v.assume_init()
} }
} }
} }
} }
} }
fn read_u64_from(mut file: File) -> Result<u64> {
let mut string = String::new();
match file.read_to_string(&mut string) {
Ok(_) => string
.trim()
.parse()
.map_err(|e| Error::with_cause(ParseError, e)),
Err(e) => Err(Error::with_cause(ReadFailed, e)),
}
}
impl CpuController { impl CpuController {
/// Contructs a new `CpuController` with `oroot` serving as the root of the control group. /// Contructs a new `CpuController` with `root` serving as the root of the control group.
pub fn new(oroot: PathBuf, v2: bool) -> Self { pub fn new(root: PathBuf, v2: bool) -> Self {
let mut root = oroot;
if !v2 {
root.push(Self::controller_type().to_string());
}
Self { Self {
base: root.clone(), base: root.clone(),
path: root, path: root,
v2: v2, v2,
} }
} }
@@ -153,10 +131,10 @@ impl CpuController {
let res = file.read_to_string(&mut s); let res = file.read_to_string(&mut s);
match res { match res {
Ok(_) => Ok(s), 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(),
} }
} }
@@ -167,14 +145,15 @@ impl CpuController {
/// `shares` to `200` ensures that control group `B` receives twice as much as CPU bandwidth. /// `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) /// (Assuming both `A` and `B` are of the same parent)
pub fn set_shares(&self, shares: u64) -> Result<()> { pub fn set_shares(&self, shares: u64) -> Result<()> {
let mut file = "cpu.shares"; let mut file_name = "cpu.shares";
if self.v2 { if self.v2 {
file = "cpu.weight"; file_name = "cpu.weight";
} }
// NOTE: .CpuShares is not used here. Conversion is the caller's responsibility. // NOTE: .CpuShares is not used here. Conversion is the caller's responsibility.
self.open_path(file, true).and_then(|mut file| { self.open_path(file_name, true).and_then(|mut file| {
file.write_all(shares.to_string().as_ref()) file.write_all(shares.to_string().as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(WriteFailed(file_name.to_string(), shares.to_string()), e)
})
}) })
} }
@@ -196,8 +175,12 @@ impl CpuController {
} }
self.open_path("cpu.cfs_period_us", true) self.open_path("cpu.cfs_period_us", true)
.and_then(|mut file| { .and_then(|mut file| {
file.write_all(us.to_string().as_ref()) file.write_all(us.to_string().as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(
WriteFailed("cpu.cfs_period_us".to_string(), us.to_string()),
e,
)
})
}) })
} }
@@ -222,8 +205,12 @@ impl CpuController {
} }
self.open_path("cpu.cfs_quota_us", true) self.open_path("cpu.cfs_quota_us", true)
.and_then(|mut file| { .and_then(|mut file| {
file.write_all(us.to_string().as_ref()) file.write_all(us.to_string().as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(
WriteFailed("cpu.cfs_quota_us".to_string(), us.to_string()),
e,
)
})
}) })
} }
@@ -284,44 +271,51 @@ impl CpuController {
let line = format!("{} {}", new_quota, new_period); let line = format!("{} {}", new_quota, new_period);
self.open_path("cpu.max", true).and_then(|mut file| { self.open_path("cpu.max", true).and_then(|mut file| {
file.write_all(line.as_ref()) 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<()> { pub fn set_rt_runtime(&self, us: i64) -> Result<()> {
self.open_path("cpu.rt_runtime_us", true) self.open_path("cpu.rt_runtime_us", true)
.and_then(|mut file| { .and_then(|mut file| {
file.write_all(us.to_string().as_ref()) file.write_all(us.to_string().as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, 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<()> { pub fn set_rt_period_us(&self, us: u64) -> Result<()> {
self.open_path("cpu.rt_period_us", true) self.open_path("cpu.rt_period_us", true)
.and_then(|mut file| { .and_then(|mut file| {
file.write_all(us.to_string().as_ref()) file.write_all(us.to_string().as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(
WriteFailed("cpu.rt_period_us".to_string(), us.to_string()),
e,
)
})
}) })
} }
} }
fn parse_cfs_quota_and_period(mut file: File) -> Result<CFSQuotaAndPeriod> { impl CustomizedAttribute for CpuController {}
fn parse_cfs_quota_and_period(mut file: File) -> Result<CfsQuotaAndPeriod> {
let mut content = String::new(); let mut content = String::new();
file.read_to_string(&mut content) 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>>(); let fields = content.trim().split(' ').collect::<Vec<&str>>();
if fields.len() != 2 { if fields.len() != 2 {
return Err(Error::from_string(format!("invaild format: {}", content))); 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] let period = fields[1]
.parse::<u64>() .parse::<u64>()
.map_err(|e| Error::with_cause(ParseError, e))?; .map_err(|e| Error::with_cause(ParseError, e))?;
Ok(CFSQuotaAndPeriod { Ok(CfsQuotaAndPeriod { quota, period })
quota: quota,
period: period,
})
} }

View File

@@ -7,13 +7,13 @@
//! //!
//! See the Kernel's documentation for more information about this subsystem, found at: //! See the Kernel's documentation for more information about this subsystem, found at:
//! [Documentation/cgroup-v1/cpuacct.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/cpuacct.txt) //! [Documentation/cgroup-v1/cpuacct.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/cpuacct.txt)
use std::fs::File; use std::io::Write;
use std::io::{Read, Write};
use std::path::PathBuf; use std::path::PathBuf;
use crate::error::ErrorKind::*; use crate::error::ErrorKind::*;
use crate::error::*; use crate::error::*;
use crate::{read_string_from, read_u64_from};
use crate::{ControllIdentifier, ControllerInternal, Controllers, Resources, Subsystem}; use crate::{ControllIdentifier, ControllerInternal, Controllers, Resources, Subsystem};
/// A controller that allows controlling the `cpuacct` subsystem of a Cgroup. /// A controller that allows controlling the `cpuacct` subsystem of a Cgroup.
@@ -27,6 +27,7 @@ pub struct CpuAcctController {
} }
/// Represents the statistics retrieved from the control group. /// Represents the statistics retrieved from the control group.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct CpuAcct { pub struct CpuAcct {
/// Divides the time used by the tasks into `user` time and `system` time. /// Divides the time used by the tasks into `user` time and `system` time.
pub stat: String, pub stat: String,
@@ -89,38 +90,17 @@ impl<'a> From<&'a Subsystem> for &'a CpuAcctController {
Subsystem::CpuAcct(c) => c, Subsystem::CpuAcct(c) => c,
_ => { _ => {
assert_eq!(1, 0); assert_eq!(1, 0);
::std::mem::uninitialized() let v = std::mem::MaybeUninit::uninit();
v.assume_init()
} }
} }
} }
} }
} }
fn read_u64_from(mut file: File) -> Result<u64> {
let mut string = String::new();
let res = file.read_to_string(&mut string);
match res {
Ok(_) => match string.trim().parse() {
Ok(e) => Ok(e),
Err(e) => Err(Error::with_cause(ParseError, e)),
},
Err(e) => Err(Error::with_cause(ReadFailed, e)),
}
}
fn read_string_from(mut file: File) -> Result<String> {
let mut string = String::new();
match file.read_to_string(&mut string) {
Ok(_) => Ok(string.trim().to_string()),
Err(e) => Err(Error::with_cause(ReadFailed, e)),
}
}
impl CpuAcctController { impl CpuAcctController {
/// Contructs a new `CpuAcctController` with `oroot` serving as the root of the control group. /// Contructs a new `CpuAcctController` with `root` serving as the root of the control group.
pub fn new(oroot: PathBuf) -> Self { pub fn new(root: PathBuf) -> Self {
let mut root = oroot;
root.push(Self::controller_type().to_string());
Self { Self {
base: root.clone(), base: root.clone(),
path: root, path: root,
@@ -132,35 +112,35 @@ impl CpuAcctController {
CpuAcct { CpuAcct {
stat: self stat: self
.open_path("cpuacct.stat", false) .open_path("cpuacct.stat", false)
.and_then(|file| read_string_from(file)) .and_then(read_string_from)
.unwrap_or("".to_string()), .unwrap_or_default(),
usage: self usage: self
.open_path("cpuacct.usage", false) .open_path("cpuacct.usage", false)
.and_then(|file| read_u64_from(file)) .and_then(read_u64_from)
.unwrap_or(0), .unwrap_or(0),
usage_all: self usage_all: self
.open_path("cpuacct.usage_all", false) .open_path("cpuacct.usage_all", false)
.and_then(|file| read_string_from(file)) .and_then(read_string_from)
.unwrap_or("".to_string()), .unwrap_or_default(),
usage_percpu: self usage_percpu: self
.open_path("cpuacct.usage_percpu", false) .open_path("cpuacct.usage_percpu", false)
.and_then(|file| read_string_from(file)) .and_then(read_string_from)
.unwrap_or("".to_string()), .unwrap_or_default(),
usage_percpu_sys: self usage_percpu_sys: self
.open_path("cpuacct.usage_percpu_sys", false) .open_path("cpuacct.usage_percpu_sys", false)
.and_then(|file| read_string_from(file)) .and_then(read_string_from)
.unwrap_or("".to_string()), .unwrap_or_default(),
usage_percpu_user: self usage_percpu_user: self
.open_path("cpuacct.usage_percpu_user", false) .open_path("cpuacct.usage_percpu_user", false)
.and_then(|file| read_string_from(file)) .and_then(read_string_from)
.unwrap_or("".to_string()), .unwrap_or_default(),
usage_sys: self usage_sys: self
.open_path("cpuacct.usage_sys", false) .open_path("cpuacct.usage_sys", false)
.and_then(|file| read_u64_from(file)) .and_then(read_u64_from)
.unwrap_or(0), .unwrap_or(0),
usage_user: self usage_user: self
.open_path("cpuacct.usage_user", false) .open_path("cpuacct.usage_user", false)
.and_then(|file| read_u64_from(file)) .and_then(read_u64_from)
.unwrap_or(0), .unwrap_or(0),
} }
} }
@@ -168,8 +148,9 @@ impl CpuAcctController {
/// Reset the statistics the kernel has gathered about the control group. /// Reset the statistics the kernel has gathered about the control group.
pub fn reset(&self) -> Result<()> { pub fn reset(&self) -> Result<()> {
self.open_path("cpuacct.usage", true).and_then(|mut file| { self.open_path("cpuacct.usage", true).and_then(|mut file| {
file.write_all(b"0") file.write_all(b"0").map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(WriteFailed("cpuacct.usage".to_string(), "0".to_string()), e)
})
}) })
} }
} }

View File

@@ -10,13 +10,13 @@
//! [Documentation/cgroup-v1/cpusets.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/cpusets.txt) //! [Documentation/cgroup-v1/cpusets.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/cpusets.txt)
use log::*; use log::*;
use std::fs::File; use std::io::Write;
use std::io::{Read, Write};
use std::path::PathBuf; use std::path::PathBuf;
use crate::error::ErrorKind::*; use crate::error::ErrorKind::*;
use crate::error::*; use crate::error::*;
use crate::{read_string_from, read_u64_from};
use crate::{ use crate::{
ControllIdentifier, ControllerInternal, Controllers, CpuResources, Resources, Subsystem, ControllIdentifier, ControllerInternal, Controllers, CpuResources, Resources, Subsystem,
}; };
@@ -33,6 +33,7 @@ pub struct CpuSetController {
} }
/// The current state of the `cpuset` controller for this control group. /// The current state of the `cpuset` controller for this control group.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct CpuSet { pub struct CpuSet {
/// If true, no other control groups can share the CPUs listed in the `cpus` field. /// If true, no other control groups can share the CPUs listed in the `cpus` field.
pub cpu_exclusive: bool, pub cpu_exclusive: bool,
@@ -110,12 +111,8 @@ impl ControllerInternal for CpuSetController {
// get the resources that apply to this controller // get the resources that apply to this controller
let res: &CpuResources = &res.cpu; let res: &CpuResources = &res.cpu;
if res.update_values { update!(self, set_cpus, res.cpus.as_ref());
if res.cpus.is_some() { update!(self, set_mems, res.mems.as_ref());
let _ = self.set_cpus(res.cpus.as_ref().unwrap().as_str());
}
let _ = self.set_mems(&res.mems);
}
Ok(()) Ok(())
} }
@@ -125,10 +122,6 @@ impl ControllerInternal for CpuSetController {
return; return;
} }
let current = self.get_path(); let current = self.get_path();
let parent = match current.parent() {
Some(p) => p,
None => return,
};
if current != self.get_base() { if current != self.get_base() {
match copy_from_parent(current.to_str().unwrap(), "cpuset.cpus") { match copy_from_parent(current.to_str().unwrap(), "cpuset.cpus") {
@@ -151,10 +144,15 @@ fn find_no_empty_parent(from: &str, file: &str) -> Result<(String, Vec<PathBuf>)
let current_value = let current_value =
match ::std::fs::read_to_string(current_path.clone().join(file).to_str().unwrap()) { match ::std::fs::read_to_string(current_path.clone().join(file).to_str().unwrap()) {
Ok(cpus) => String::from(cpus.trim()), 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)); return Ok((current_value, v));
} }
v.push(current_path.clone()); v.push(current_path.clone());
@@ -175,7 +173,7 @@ fn copy_from_parent(current: &str, file: &str) -> Result<()> {
// find not empty cpus/memes from current directory. // find not empty cpus/memes from current directory.
let (value, parents) = find_no_empty_parent(current, file)?; let (value, parents) = find_no_empty_parent(current, file)?;
if value == "" || parents.len() == 0 { if value.is_empty() || parents.is_empty() {
return Ok(()); return Ok(());
} }
@@ -184,7 +182,12 @@ fn copy_from_parent(current: &str, file: &str) -> Result<()> {
pb.push(file); pb.push(file);
match ::std::fs::write(pb.to_str().unwrap(), value.as_bytes()) { match ::std::fs::write(pb.to_str().unwrap(), value.as_bytes()) {
Ok(_) => (), 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,
))
}
} }
} }
@@ -204,47 +207,29 @@ impl<'a> From<&'a Subsystem> for &'a CpuSetController {
Subsystem::CpuSet(c) => c, Subsystem::CpuSet(c) => c,
_ => { _ => {
assert_eq!(1, 0); assert_eq!(1, 0);
::std::mem::uninitialized() let v = std::mem::MaybeUninit::uninit();
v.assume_init()
} }
} }
} }
} }
} }
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)),
}
}
fn read_u64_from(mut file: File) -> Result<u64> {
let mut string = String::new();
match file.read_to_string(&mut string) {
Ok(_) => string
.trim()
.parse()
.map_err(|e| Error::with_cause(ParseError, e)),
Err(e) => Err(Error::with_cause(ReadFailed, e)),
}
}
/// Parse a string like "1,2,4-5,8" into a list of (start, end) tuples. /// Parse a string like "1,2,4-5,8" into a list of (start, end) tuples.
fn parse_range(s: String) -> Result<Vec<(u64, u64)>> { fn parse_range(s: String) -> Result<Vec<(u64, u64)>> {
let mut fin = Vec::new(); let mut fin = Vec::new();
if s == "".to_string() { if s.is_empty() {
return Ok(fin); return Ok(fin);
} }
// first split by commas // first split by commas
let comma_split = s.split(","); let comma_split = s.split(',');
for sp in comma_split { for sp in comma_split {
if sp.contains("-") { if sp.contains('-') {
// this is a true range // this is a true range
let dash_split = sp.split("-").collect::<Vec<_>>(); let dash_split = sp.split('-').collect::<Vec<_>>();
if dash_split.len() != 2 { if dash_split.len() != 2 {
return Err(Error::new(ParseError)); return Err(Error::new(ParseError));
} }
@@ -268,16 +253,12 @@ fn parse_range(s: String) -> Result<Vec<(u64, u64)>> {
} }
impl CpuSetController { impl CpuSetController {
/// Contructs a new `CpuSetController` with `oroot` serving as the root of the control group. /// Contructs a new `CpuSetController` with `root` serving as the root of the control group.
pub fn new(oroot: PathBuf, v2: bool) -> Self { pub fn new(root: PathBuf, v2: bool) -> Self {
let mut root = oroot;
if !v2 {
root.push(Self::controller_type().to_string());
}
Self { Self {
base: root.clone(), base: root.clone(),
path: root, path: root,
v2: v2, v2,
} }
} }
@@ -287,7 +268,7 @@ impl CpuSetController {
CpuSet { CpuSet {
cpu_exclusive: { cpu_exclusive: {
self.open_path("cpuset.cpu_exclusive", false) self.open_path("cpuset.cpu_exclusive", false)
.and_then(|file| read_u64_from(file)) .and_then(read_u64_from)
.map(|x| x == 1) .map(|x| x == 1)
.unwrap_or(false) .unwrap_or(false)
}, },
@@ -295,19 +276,19 @@ impl CpuSetController {
self.open_path("cpuset.cpus", false) self.open_path("cpuset.cpus", false)
.and_then(read_string_from) .and_then(read_string_from)
.and_then(parse_range) .and_then(parse_range)
.unwrap_or(Vec::new()) .unwrap_or_default()
}, },
effective_cpus: { effective_cpus: {
self.open_path("cpuset.effective_cpus", false) self.open_path("cpuset.effective_cpus", false)
.and_then(read_string_from) .and_then(read_string_from)
.and_then(parse_range) .and_then(parse_range)
.unwrap_or(Vec::new()) .unwrap_or_default()
}, },
effective_mems: { effective_mems: {
self.open_path("cpuset.effective_mems", false) self.open_path("cpuset.effective_mems", false)
.and_then(read_string_from) .and_then(read_string_from)
.and_then(parse_range) .and_then(parse_range)
.unwrap_or(Vec::new()) .unwrap_or_default()
}, },
mem_exclusive: { mem_exclusive: {
self.open_path("cpuset.mem_exclusive", false) self.open_path("cpuset.mem_exclusive", false)
@@ -354,7 +335,7 @@ impl CpuSetController {
self.open_path("cpuset.mems", false) self.open_path("cpuset.mems", false)
.and_then(read_string_from) .and_then(read_string_from)
.and_then(parse_range) .and_then(parse_range)
.unwrap_or(Vec::new()) .unwrap_or_default()
}, },
sched_load_balance: { sched_load_balance: {
self.open_path("cpuset.sched_load_balance", false) self.open_path("cpuset.sched_load_balance", false)
@@ -376,11 +357,19 @@ impl CpuSetController {
self.open_path("cpuset.cpu_exclusive", true) self.open_path("cpuset.cpu_exclusive", true)
.and_then(|mut file| { .and_then(|mut file| {
if b { if b {
file.write_all(b"1") file.write_all(b"1").map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(
WriteFailed("cpuset.cpu_exclusive".to_string(), "1".to_string()),
e,
)
})
} else { } else {
file.write_all(b"0") file.write_all(b"0").map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(
WriteFailed("cpuset.cpu_exclusive".to_string(), "0".to_string()),
e,
)
})
} }
}) })
} }
@@ -391,11 +380,19 @@ impl CpuSetController {
self.open_path("cpuset.mem_exclusive", true) self.open_path("cpuset.mem_exclusive", true)
.and_then(|mut file| { .and_then(|mut file| {
if b { if b {
file.write_all(b"1") file.write_all(b"1").map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(
WriteFailed("cpuset.mem_exclusive".to_string(), "1".to_string()),
e,
)
})
} else { } else {
file.write_all(b"0") file.write_all(b"0").map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(
WriteFailed("cpuset.mem_exclusive".to_string(), "0".to_string()),
e,
)
})
} }
}) })
} }
@@ -406,8 +403,9 @@ impl CpuSetController {
/// be represented via dashes. /// be represented via dashes.
pub fn set_cpus(&self, cpus: &str) -> Result<()> { pub fn set_cpus(&self, cpus: &str) -> Result<()> {
self.open_path("cpuset.cpus", true).and_then(|mut file| { self.open_path("cpuset.cpus", true).and_then(|mut file| {
file.write_all(cpus.as_ref()) file.write_all(cpus.as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(WriteFailed("cpuset.cpus".to_string(), cpus.to_string()), e)
})
}) })
} }
@@ -416,8 +414,9 @@ impl CpuSetController {
/// Syntax is the same as with `set_cpus()`. /// Syntax is the same as with `set_cpus()`.
pub fn set_mems(&self, mems: &str) -> Result<()> { pub fn set_mems(&self, mems: &str) -> Result<()> {
self.open_path("cpuset.mems", true).and_then(|mut file| { self.open_path("cpuset.mems", true).and_then(|mut file| {
file.write_all(mems.as_ref()) file.write_all(mems.as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(WriteFailed("cpuset.mems".to_string(), mems.to_string()), e)
})
}) })
} }
@@ -430,11 +429,19 @@ impl CpuSetController {
self.open_path("cpuset.mem_hardwall", true) self.open_path("cpuset.mem_hardwall", true)
.and_then(|mut file| { .and_then(|mut file| {
if b { if b {
file.write_all(b"1") file.write_all(b"1").map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(
WriteFailed("cpuset.mem_hardwall".to_string(), "1".to_string()),
e,
)
})
} else { } else {
file.write_all(b"0") file.write_all(b"0").map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(
WriteFailed("cpuset.mem_hardwall".to_string(), "0".to_string()),
e,
)
})
} }
}) })
} }
@@ -445,11 +452,19 @@ impl CpuSetController {
self.open_path("cpuset.sched_load_balance", true) self.open_path("cpuset.sched_load_balance", true)
.and_then(|mut file| { .and_then(|mut file| {
if b { if b {
file.write_all(b"1") file.write_all(b"1").map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(
WriteFailed("cpuset.sched_load_balance".to_string(), "1".to_string()),
e,
)
})
} else { } else {
file.write_all(b"0") file.write_all(b"0").map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(
WriteFailed("cpuset.sched_load_balance".to_string(), "0".to_string()),
e,
)
})
} }
}) })
} }
@@ -460,8 +475,12 @@ impl CpuSetController {
pub fn set_rebalance_relax_domain_level(&self, i: i64) -> Result<()> { pub fn set_rebalance_relax_domain_level(&self, i: i64) -> Result<()> {
self.open_path("cpuset.sched_relax_domain_level", true) self.open_path("cpuset.sched_relax_domain_level", true)
.and_then(|mut file| { .and_then(|mut file| {
file.write_all(i.to_string().as_ref()) file.write_all(i.to_string().as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(
WriteFailed("cpuset.sched_relax_domain_level".to_string(), i.to_string()),
e,
)
})
}) })
} }
@@ -471,11 +490,19 @@ impl CpuSetController {
self.open_path("cpuset.memory_migrate", true) self.open_path("cpuset.memory_migrate", true)
.and_then(|mut file| { .and_then(|mut file| {
if b { if b {
file.write_all(b"1") file.write_all(b"1").map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(
WriteFailed("cpuset.memory_migrate".to_string(), "1".to_string()),
e,
)
})
} else { } else {
file.write_all(b"0") file.write_all(b"0").map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(
WriteFailed("cpuset.memory_migrate".to_string(), "0".to_string()),
e,
)
})
} }
}) })
} }
@@ -486,11 +513,19 @@ impl CpuSetController {
self.open_path("cpuset.memory_spread_page", true) self.open_path("cpuset.memory_spread_page", true)
.and_then(|mut file| { .and_then(|mut file| {
if b { if b {
file.write_all(b"1") file.write_all(b"1").map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(
WriteFailed("cpuset.memory_spread_page".to_string(), "1".to_string()),
e,
)
})
} else { } else {
file.write_all(b"0") file.write_all(b"0").map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(
WriteFailed("cpuset.memory_spread_page".to_string(), "0".to_string()),
e,
)
})
} }
}) })
} }
@@ -501,11 +536,19 @@ impl CpuSetController {
self.open_path("cpuset.memory_spread_slab", true) self.open_path("cpuset.memory_spread_slab", true)
.and_then(|mut file| { .and_then(|mut file| {
if b { if b {
file.write_all(b"1") file.write_all(b"1").map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(
WriteFailed("cpuset.memory_spread_slab".to_string(), "1".to_string()),
e,
)
})
} else { } else {
file.write_all(b"0") file.write_all(b"0").map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(
WriteFailed("cpuset.memory_spread_slab".to_string(), "0".to_string()),
e,
)
})
} }
}) })
} }
@@ -522,11 +565,25 @@ impl CpuSetController {
self.open_path("cpuset.memory_pressure_enabled", true) self.open_path("cpuset.memory_pressure_enabled", true)
.and_then(|mut file| { .and_then(|mut file| {
if b { if b {
file.write_all(b"1") file.write_all(b"1").map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(
WriteFailed(
"cpuset.memory_pressure_enabled".to_string(),
"1".to_string(),
),
e,
)
})
} else { } else {
file.write_all(b"0") file.write_all(b"0").map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, 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. /// An enum holding the different types of devices that can be manipulated using this controller.
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "snake_case")
)]
pub enum DeviceType { pub enum DeviceType {
/// The rule applies to all devices. /// The rule applies to all devices.
All, All,
@@ -49,6 +54,7 @@ impl Default for DeviceType {
impl DeviceType { impl DeviceType {
/// Convert a DeviceType into the character that the kernel recognizes. /// 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 { pub fn to_char(&self) -> char {
match self { match self {
DeviceType::All => 'a', DeviceType::All => 'a',
@@ -70,6 +76,11 @@ impl DeviceType {
/// An enum with the permissions that can be allowed/denied to the control group. /// An enum with the permissions that can be allowed/denied to the control group.
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "snake_case")
)]
pub enum DevicePermissions { pub enum DevicePermissions {
/// Permission to read from the device. /// Permission to read from the device.
Read, Read,
@@ -82,6 +93,7 @@ pub enum DevicePermissions {
impl DevicePermissions { impl DevicePermissions {
/// Convert a DevicePermissions into the character that the kernel recognizes. /// 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 { pub fn to_char(&self) -> char {
match self { match self {
DevicePermissions::Read => 'r', DevicePermissions::Read => 'r',
@@ -102,7 +114,7 @@ impl DevicePermissions {
/// Checks whether the string is a valid descriptor of DevicePermissions. /// Checks whether the string is a valid descriptor of DevicePermissions.
pub fn is_valid(s: &str) -> bool { pub fn is_valid(s: &str) -> bool {
if s == "" { if s.is_empty() {
return false; return false;
} }
for i in s.chars() { for i in s.chars() {
@@ -110,7 +122,7 @@ impl DevicePermissions {
return false; return false;
} }
} }
return true; true
} }
/// Returns a Vec will all the permissions that a device can have. /// Returns a Vec will all the permissions that a device can have.
@@ -123,9 +135,10 @@ impl DevicePermissions {
} }
/// Convert a string into DevicePermissions. /// Convert a string into DevicePermissions.
#[allow(clippy::should_implement_trait)]
pub fn from_str(s: &str) -> Result<Vec<DevicePermissions>> { pub fn from_str(s: &str) -> Result<Vec<DevicePermissions>> {
let mut v = Vec::new(); let mut v = Vec::new();
if s == "" { if s.is_empty() {
return Ok(v); return Ok(v);
} }
for e in s.chars() { for e in s.chars() {
@@ -155,13 +168,11 @@ impl ControllerInternal for DevicesController {
// get the resources that apply to this controller // get the resources that apply to this controller
let res: &DeviceResources = &res.devices; let res: &DeviceResources = &res.devices;
if res.update_values { for i in &res.devices {
for i in &res.devices { if i.allow {
if i.allow { let _ = self.allow_device(i.devtype, i.major, i.minor, &i.access);
let _ = self.allow_device(i.devtype, i.major, i.minor, &i.access); } else {
} else { let _ = self.deny_device(i.devtype, i.major, i.minor, &i.access);
let _ = self.deny_device(i.devtype, i.major, i.minor, &i.access);
}
} }
} }
@@ -182,7 +193,8 @@ impl<'a> From<&'a Subsystem> for &'a DevicesController {
Subsystem::Devices(c) => c, Subsystem::Devices(c) => c,
_ => { _ => {
assert_eq!(1, 0); assert_eq!(1, 0);
::std::mem::uninitialized() let v = std::mem::MaybeUninit::uninit();
v.assume_init()
} }
} }
} }
@@ -190,10 +202,8 @@ impl<'a> From<&'a Subsystem> for &'a DevicesController {
} }
impl DevicesController { impl DevicesController {
/// Constructs a new `DevicesController` with `oroot` serving as the root of the control group. /// Constructs a new `DevicesController` with `root` serving as the root of the control group.
pub fn new(oroot: PathBuf) -> Self { pub fn new(root: PathBuf) -> Self {
let mut root = oroot;
root.push(Self::controller_type().to_string());
Self { Self {
base: root.clone(), base: root.clone(),
path: root, path: root,
@@ -209,7 +219,7 @@ impl DevicesController {
devtype: DeviceType, devtype: DeviceType,
major: i64, major: i64,
minor: i64, minor: i64,
perm: &Vec<DevicePermissions>, perm: &[DevicePermissions],
) -> Result<()> { ) -> Result<()> {
let perms = perm let perms = perm
.iter() .iter()
@@ -227,8 +237,9 @@ impl DevicesController {
}; };
let final_str = format!("{} {}:{} {}", devtype.to_char(), major, minor, perms); let final_str = format!("{} {}:{} {}", devtype.to_char(), major, minor, perms);
self.open_path("devices.allow", true).and_then(|mut file| { self.open_path("devices.allow", true).and_then(|mut file| {
file.write_all(final_str.as_ref()) file.write_all(final_str.as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(WriteFailed("devices.allow".to_string(), final_str), e)
})
}) })
} }
@@ -241,7 +252,7 @@ impl DevicesController {
devtype: DeviceType, devtype: DeviceType,
major: i64, major: i64,
minor: i64, minor: i64,
perm: &Vec<DevicePermissions>, perm: &[DevicePermissions],
) -> Result<()> { ) -> Result<()> {
let perms = perm let perms = perm
.iter() .iter()
@@ -259,8 +270,9 @@ impl DevicesController {
}; };
let final_str = format!("{} {}:{} {}", devtype.to_char(), major, minor, perms); let final_str = format!("{} {}:{} {}", devtype.to_char(), major, minor, perms);
self.open_path("devices.deny", true).and_then(|mut file| { self.open_path("devices.deny", true).and_then(|mut file| {
file.write_all(final_str.as_ref()) file.write_all(final_str.as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(WriteFailed("devices.deny".to_string(), final_str), e)
})
}) })
} }
@@ -277,13 +289,13 @@ impl DevicesController {
error!("allowed_devices: acc: {:?}, ls: {:?}", acc, ls); error!("allowed_devices: acc: {:?}, ls: {:?}", acc, ls);
Err(Error::new(ParseError)) Err(Error::new(ParseError))
} else { } 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 major = ls[1].parse::<i64>();
let mut minor = ls[2].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); major = Ok(-1);
} }
if minor.is_err() && ls[2] == "*".to_string() { if minor.is_err() && ls[2] == "*" {
minor = Ok(-1); minor = Ok(-1);
} }
if devtype.is_none() || major.is_err() || minor.is_err() || !DevicePermissions::is_valid(&ls[3]) { if devtype.is_none() || major.is_err() || minor.is_err() || !DevicePermissions::is_valid(&ls[3]) {
@@ -298,14 +310,14 @@ impl DevicesController {
devtype: devtype.unwrap(), devtype: devtype.unwrap(),
major: major.unwrap(), major: major.unwrap(),
minor: minor.unwrap(), minor: minor.unwrap(),
access: access, access,
}); });
Ok(acc) 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,66 +8,73 @@ use std::error::Error as StdError;
use std::fmt; use std::fmt;
/// The different types of errors that can occur while manipulating control groups. /// 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 { pub enum ErrorKind {
#[error("fs error")]
FsError, FsError,
#[error("common error: {0}")]
Common(String), Common(String),
/// An error occured while writing to a control group file. /// 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. /// 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. /// 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. /// In the future, there will be some information attached to this field.
#[error("unable to parse control group file")]
ParseError, ParseError,
/// You tried to do something invalid. /// 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 /// 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. /// control group. Or, when using unified hierarchy, you tried to add a task in a leaf node.
#[error("the requested operation is invalid")]
InvalidOperation, InvalidOperation,
/// The path of the control group was invalid. /// 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 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. /// This crate checks against this and operations will fail with this error.
#[error("the given path is invalid")]
InvalidPath, InvalidPath,
#[error("invalid bytes size")]
InvalidBytesSize, InvalidBytesSize,
/// An unknown error has occured. /// An unknown error has occured.
#[error("an unknown error")]
Other, Other,
} }
#[derive(Debug)] #[derive(Debug)]
pub struct Error { pub struct Error {
kind: ErrorKind, kind: ErrorKind,
cause: Option<Box<StdError + Send + Sync>>, cause: Option<Box<dyn StdError + Send + Sync>>,
} }
impl fmt::Display for Error { impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let msg = match &self.kind { if let Some(cause) = &self.cause {
ErrorKind::FsError => "fs error".to_string(), write!(f, "{} caused by: {:?}", &self.kind, cause)
ErrorKind::Common(s) => s.clone(), } else {
ErrorKind::WriteFailed => "unable to write to a control group file".to_string(), write!(f, "{}", &self.kind)
ErrorKind::ReadFailed => "unable to read a control group file".to_string(), }
ErrorKind::ParseError => "unable to parse control group file".to_string(),
ErrorKind::InvalidOperation => "the requested operation is invalid".to_string(),
ErrorKind::InvalidPath => "the given path is invalid".to_string(),
ErrorKind::InvalidBytesSize => "invalid bytes size".to_string(),
ErrorKind::Other => "an unknown error".to_string(),
};
write!(f, "{}", msg)
} }
} }
impl StdError for Error { impl StdError for Error {
fn cause(&self) -> Option<&StdError> { fn cause(&self) -> Option<&dyn StdError> {
#[allow(clippy::manual_map)]
match self.cause { match self.cause {
Some(ref x) => Some(&**x), Some(ref x) => Some(&**x),
None => None, None => None,

View File

@@ -8,7 +8,7 @@ use nix::sys::eventfd;
use std::fs::{self, File}; use std::fs::{self, File};
use std::io::Read; use std::io::Read;
use std::os::unix::io::{AsRawFd, FromRawFd}; use std::os::unix::io::{AsRawFd, FromRawFd};
use std::path::{Path, PathBuf}; use std::path::Path;
use std::sync::mpsc::{self, Receiver}; use std::sync::mpsc::{self, Receiver};
use std::thread; use std::thread;
@@ -17,18 +17,18 @@ use crate::error::*;
// notify_on_oom returns channel on which you can expect event about OOM, // notify_on_oom returns channel on which you can expect event about OOM,
// if process died without OOM this channel will be closed. // 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", "") register_memory_event(key, dir, "memory.oom_control", "")
} }
// notify_on_oom returns channel on which you can expect event about OOM, // notify_on_oom returns channel on which you can expect event about OOM,
// if process died without OOM this channel will be closed. // 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", "") register_memory_event(key, dir, "memory.oom_control", "")
} }
// level is one of "low", "medium", or "critical" // 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" { if level != "low" && level != "medium" && level != "critical" {
return Err(Error::from_string(format!( return Err(Error::from_string(format!(
"invalid pressure level {}", "invalid pressure level {}",
@@ -41,26 +41,31 @@ pub fn notify_memory_pressure(key: &str, dir: &PathBuf, level: &str) -> Result<R
fn register_memory_event( fn register_memory_event(
key: &str, key: &str,
cg_dir: &PathBuf, cg_dir: &Path,
event_name: &str, event_name: &str,
arg: &str, arg: &str,
) -> Result<Receiver<String>> { ) -> Result<Receiver<String>> {
let path = cg_dir.join(event_name); 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 = let eventfd = eventfd(0, EfdFlags::EFD_CLOEXEC)
eventfd(0, EfdFlags::EFD_CLOEXEC).map_err(|e| Error::with_cause(ReadFailed, e))?; .map_err(|e| Error::with_cause(ReadFailed("eventfd".to_string()), e))?;
let event_control_path = cg_dir.join("cgroup.event_control"); let event_control_path = cg_dir.join("cgroup.event_control");
let data; let data = if arg.is_empty() {
if arg == "" { format!("{} {}", eventfd, event_file.as_raw_fd())
data = format!("{} {}", eventfd, event_file.as_raw_fd());
} else { } 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) // 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) }; let mut eventfd_file = unsafe { File::from_raw_fd(eventfd) };
@@ -70,11 +75,8 @@ fn register_memory_event(
thread::spawn(move || { thread::spawn(move || {
loop { loop {
let mut buf = [0; 8]; let mut buf = [0; 8];
match eventfd_file.read(&mut buf) { if eventfd_file.read(&mut buf).is_err() {
Err(err) => { return;
return;
}
Ok(_) => {}
} }
// When a cgroup is destroyed, an event is sent to eventfd. // 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 /// The current state of the control group
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum FreezerState { pub enum FreezerState {
/// The processes in the control group are _not_ frozen. /// The processes in the control group are _not_ frozen.
Thawed, Thawed,
@@ -73,7 +74,8 @@ impl<'a> From<&'a Subsystem> for &'a FreezerController {
Subsystem::Freezer(c) => c, Subsystem::Freezer(c) => c,
_ => { _ => {
assert_eq!(1, 0); assert_eq!(1, 0);
::std::mem::uninitialized() let v = std::mem::MaybeUninit::uninit();
v.assume_init()
} }
} }
} }
@@ -81,59 +83,55 @@ impl<'a> From<&'a Subsystem> for &'a FreezerController {
} }
impl FreezerController { impl FreezerController {
/// Contructs a new `FreezerController` with `oroot` serving as the root of the control group. /// Contructs a new `FreezerController` with `root` serving as the root of the control group.
pub fn new(oroot: PathBuf, v2: bool) -> Self { pub fn new(root: PathBuf, v2: bool) -> Self {
let mut root = oroot;
if !v2 {
root.push(Self::controller_type().to_string());
}
Self { Self {
base: root.clone(), base: root.clone(),
path: root, path: root,
v2: v2, v2,
} }
} }
/// Freezes the processes in the control group. /// Freezes the processes in the control group.
pub fn freeze(&self) -> Result<()> { pub fn freeze(&self) -> Result<()> {
let mut file = "freezer.state"; let mut file_name = "freezer.state";
let mut content = "FROZEN".to_string(); let mut content = "FROZEN".to_string();
if self.v2 { if self.v2 {
file = "cgroup.freeze"; file_name = "cgroup.freeze";
content = "1".to_string(); 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()) 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. /// Thaws, that is, unfreezes the processes in the control group.
pub fn thaw(&self) -> Result<()> { pub fn thaw(&self) -> Result<()> {
let mut file = "freezer.state"; let mut file_name = "freezer.state";
let mut content = "THAWED".to_string(); let mut content = "THAWED".to_string();
if self.v2 { if self.v2 {
file = "cgroup.freeze"; file_name = "cgroup.freeze";
content = "0".to_string(); 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()) 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. /// Retrieve the state of processes in the control group.
pub fn state(&self) -> Result<FreezerState> { pub fn state(&self) -> Result<FreezerState> {
let mut file = "freezer.state"; let mut file_name = "freezer.state";
if self.v2 { 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 mut s = String::new();
let res = file.read_to_string(&mut s); let res = file.read_to_string(&mut s);
match res { match res {
Ok(_) => match s.as_ref() { Ok(_) => match s.trim() {
"FROZEN" => Ok(FreezerState::Frozen), "FROZEN" => Ok(FreezerState::Frozen),
"THAWED" => Ok(FreezerState::Thawed), "THAWED" => Ok(FreezerState::Thawed),
"1" => Ok(FreezerState::Frozen), "1" => Ok(FreezerState::Frozen),
@@ -141,7 +139,7 @@ impl FreezerController {
"FREEZING" => Ok(FreezerState::Freezing), "FREEZING" => Ok(FreezerState::Freezing),
_ => Err(Error::new(ParseError)), _ => 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

@@ -8,14 +8,11 @@
//! //!
//! Currently, we only support the cgroupv1 hierarchy, but in the future we will add support for //! Currently, we only support the cgroupv1 hierarchy, but in the future we will add support for
//! the Unified Hierarchy. //! the Unified Hierarchy.
use nix::sys::statfs;
use std::fs::{self, File}; use std::fs;
use std::io::BufRead; use std::fs::File;
use std::io::BufReader; use std::io::{BufRead, BufReader};
use std::path::{Path, PathBuf}; use std::path::PathBuf;
use log::*;
use crate::blkio::BlkIoController; use crate::blkio::BlkIoController;
use crate::cpu::CpuController; use crate::cpu::CpuController;
@@ -35,11 +32,82 @@ use crate::{Controllers, Hierarchy, Subsystem};
use crate::cgroup::Cgroup; use crate::cgroup::Cgroup;
/// The standard, original cgroup implementation. Often referred to as "cgroupv1". /// Process mounts information.
pub struct V1 { ///
mount_point: String, /// See `proc(5)` for format details.
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct Mountinfo {
/// Mount pathname relative to the process's root.
pub mount_point: PathBuf,
/// Filesystem type (main type with optional sub-type).
pub fs_type: (String, Option<String>),
/// Superblock options.
pub super_opts: Vec<String>,
} }
pub(crate) fn parse_mountinfo_for_line(line: &str) -> Option<Mountinfo> {
let s_values: Vec<_> = line.split(" - ").collect();
if s_values.len() != 2 {
return None;
}
let s0_values: Vec<_> = s_values[0].trim().split(' ').collect();
let s1_values: Vec<_> = s_values[1].trim().split(' ').collect();
if s0_values.len() < 6 || s1_values.len() < 3 {
return None;
}
let mount_point = PathBuf::from(s0_values[4]);
let fs_type_values: Vec<_> = s1_values[0].trim().split('.').collect();
let fs_type = match fs_type_values.len() {
1 => (fs_type_values[0].to_string(), None),
2 => (
fs_type_values[0].to_string(),
Some(fs_type_values[1].to_string()),
),
_ => return None,
};
let super_opts: Vec<String> = s1_values[2].trim().split(',').map(String::from).collect();
Some(Mountinfo {
mount_point,
fs_type,
super_opts,
})
}
/// Parses the provided mountinfo file.
fn mountinfo_file(file: &mut File) -> Vec<Mountinfo> {
let mut r = Vec::new();
for line in BufReader::new(file).lines() {
match line {
Ok(line) => {
if let Some(mi) = parse_mountinfo_for_line(&line) {
if mi.fs_type.0 == "cgroup" {
r.push(mi);
}
}
}
Err(_) => break,
}
}
r
}
/// Returns mounts information for the current process.
pub fn mountinfo_self() -> Vec<Mountinfo> {
match File::open("/proc/self/mountinfo") {
Ok(mut file) => mountinfo_file(&mut file),
Err(_) => vec![],
}
}
/// The standard, original cgroup implementation. Often referred to as "cgroupv1".
#[derive(Debug, Clone)]
pub struct V1 {
mountinfo: Vec<Mountinfo>,
}
#[derive(Debug, Clone)]
pub struct V2 { pub struct V2 {
root: String, root: String,
} }
@@ -51,80 +119,71 @@ impl Hierarchy for V1 {
fn subsystems(&self) -> Vec<Subsystem> { fn subsystems(&self) -> Vec<Subsystem> {
let mut subs = vec![]; let mut subs = vec![];
if self.check_support(Controllers::Pids) {
subs.push(Subsystem::Pid(PidController::new(self.root(), false))); // The cgroup writeback feature requires cooperation between memcgs and blkcgs
// To avoid exceptions, we should add_task for blkcg before memcg(push BlkIo before Mem)
// For more Information: https://www.alibabacloud.com/help/doc-detail/155509.htm
if let Some(root) = self.get_mount_point(Controllers::BlkIo) {
subs.push(Subsystem::BlkIo(BlkIoController::new(root, false)));
} }
if self.check_support(Controllers::Mem) { if let Some(root) = self.get_mount_point(Controllers::Mem) {
subs.push(Subsystem::Mem(MemController::new(self.root(), false))); subs.push(Subsystem::Mem(MemController::new(root, false)));
} }
if self.check_support(Controllers::CpuSet) { if let Some(root) = self.get_mount_point(Controllers::Pids) {
subs.push(Subsystem::CpuSet(CpuSetController::new(self.root(), false))); subs.push(Subsystem::Pid(PidController::new(root, false)));
} }
if self.check_support(Controllers::CpuAcct) { if let Some(root) = self.get_mount_point(Controllers::CpuSet) {
subs.push(Subsystem::CpuAcct(CpuAcctController::new(self.root()))); subs.push(Subsystem::CpuSet(CpuSetController::new(root, false)));
} }
if self.check_support(Controllers::Cpu) { if let Some(root) = self.get_mount_point(Controllers::CpuAcct) {
subs.push(Subsystem::Cpu(CpuController::new(self.root(), false))); subs.push(Subsystem::CpuAcct(CpuAcctController::new(root)));
} }
if self.check_support(Controllers::Devices) { if let Some(root) = self.get_mount_point(Controllers::Cpu) {
subs.push(Subsystem::Devices(DevicesController::new(self.root()))); subs.push(Subsystem::Cpu(CpuController::new(root, false)));
} }
if self.check_support(Controllers::Freezer) { if let Some(root) = self.get_mount_point(Controllers::Devices) {
subs.push(Subsystem::Freezer(FreezerController::new( subs.push(Subsystem::Devices(DevicesController::new(root)));
self.root(),
false,
)));
} }
if self.check_support(Controllers::NetCls) { if let Some(root) = self.get_mount_point(Controllers::Freezer) {
subs.push(Subsystem::NetCls(NetClsController::new(self.root()))); subs.push(Subsystem::Freezer(FreezerController::new(root, false)));
} }
if self.check_support(Controllers::BlkIo) { if let Some(root) = self.get_mount_point(Controllers::NetCls) {
subs.push(Subsystem::BlkIo(BlkIoController::new(self.root(), false))); subs.push(Subsystem::NetCls(NetClsController::new(root)));
} }
if self.check_support(Controllers::PerfEvent) { if let Some(root) = self.get_mount_point(Controllers::PerfEvent) {
subs.push(Subsystem::PerfEvent(PerfEventController::new(self.root()))); subs.push(Subsystem::PerfEvent(PerfEventController::new(root)));
} }
if self.check_support(Controllers::NetPrio) { if let Some(root) = self.get_mount_point(Controllers::NetPrio) {
subs.push(Subsystem::NetPrio(NetPrioController::new(self.root()))); subs.push(Subsystem::NetPrio(NetPrioController::new(root)));
} }
if self.check_support(Controllers::HugeTlb) { if let Some(root) = self.get_mount_point(Controllers::HugeTlb) {
subs.push(Subsystem::HugeTlb(HugeTlbController::new( subs.push(Subsystem::HugeTlb(HugeTlbController::new(root, false)));
self.root(),
false,
)));
} }
if self.check_support(Controllers::Rdma) { if let Some(root) = self.get_mount_point(Controllers::Rdma) {
subs.push(Subsystem::Rdma(RdmaController::new(self.root()))); subs.push(Subsystem::Rdma(RdmaController::new(root)));
} }
if self.check_support(Controllers::Systemd) { if let Some(root) = self.get_mount_point(Controllers::Systemd) {
subs.push(Subsystem::Systemd(SystemdController::new( subs.push(Subsystem::Systemd(SystemdController::new(root, false)));
self.root(),
false,
)));
} }
subs subs
} }
fn root_control_group(&self) -> Cgroup { fn root_control_group(&self) -> Cgroup {
let b: &Hierarchy = self as &Hierarchy; Cgroup::load(auto(), "")
Cgroup::load(Box::new(&*b), "".to_string())
}
fn check_support(&self, sub: Controllers) -> bool {
let root = self.root().read_dir().unwrap();
for entry in root {
if let Ok(entry) = entry {
if entry.file_name().into_string().unwrap() == sub.to_string() {
return true;
}
}
}
return false;
} }
fn root(&self) -> PathBuf { fn root(&self) -> PathBuf {
PathBuf::from(self.mount_point.clone()) self.mountinfo
.iter()
.find_map(|m| {
if m.fs_type.0 == "cgroup" {
return Some(m.mount_point.parent().unwrap());
}
None
})
.unwrap()
.to_path_buf()
} }
} }
@@ -143,7 +202,12 @@ impl Hierarchy for V2 {
let mut subs = vec![]; let mut subs = vec![];
let controllers = ret.unwrap().trim().to_string(); 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 { for s in controller_list {
match s { match s {
@@ -182,12 +246,7 @@ impl Hierarchy for V2 {
} }
fn root_control_group(&self) -> Cgroup { fn root_control_group(&self) -> Cgroup {
let b: &Hierarchy = self as &Hierarchy; Cgroup::load(auto(), "")
Cgroup::load(Box::new(&*b), "".to_string())
}
fn check_support(&self, _sub: Controllers) -> bool {
return false;
} }
fn root(&self) -> PathBuf { fn root(&self) -> PathBuf {
@@ -199,11 +258,25 @@ impl V1 {
/// Finds where control groups are mounted to and returns a hierarchy in which control groups /// Finds where control groups are mounted to and returns a hierarchy in which control groups
/// can be created. /// can be created.
pub fn new() -> V1 { pub fn new() -> V1 {
let mount_point = find_v1_mount().unwrap();
V1 { V1 {
mount_point: mount_point, mountinfo: mountinfo_self(),
} }
} }
pub fn get_mount_point(&self, controller: Controllers) -> Option<PathBuf> {
self.mountinfo.iter().find_map(|m| {
if m.fs_type.0 == "cgroup" && m.super_opts.contains(&controller.to_string()) {
return Some(m.mount_point.clone());
}
None
})
}
}
impl Default for V1 {
fn default() -> Self {
Self::new()
}
} }
impl V2 { impl V2 {
@@ -216,11 +289,22 @@ 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 { pub fn is_cgroup2_unified_mode() -> bool {
let path = Path::new(UNIFIED_MOUNTPOINT); use nix::sys::statfs;
let path = std::path::Path::new(UNIFIED_MOUNTPOINT);
let fs_stat = statfs::statfs(path); let fs_stat = statfs::statfs(path);
if fs_stat.is_err() { if fs_stat.is_err() {
return false; return false;
@@ -230,7 +314,7 @@ pub fn is_cgroup2_unified_mode() -> bool {
fs_stat.unwrap().filesystem_type() == statfs::CGROUP2_SUPER_MAGIC 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"))] #[cfg(all(target_os = "linux", target_env = "musl"))]
pub fn is_cgroup2_unified_mode() -> bool { pub fn is_cgroup2_unified_mode() -> bool {
@@ -260,39 +344,35 @@ pub fn auto() -> Box<dyn Hierarchy> {
} }
} }
fn find_v1_mount() -> Option<String> { #[cfg(test)]
// Open mountinfo so we can get a parseable mount list mod tests {
let mountinfo_path = Path::new("/proc/self/mountinfo"); use super::*;
// If /proc isn't mounted, or something else happens, then bail out #[test]
if mountinfo_path.exists() == false { fn test_parse_mount() {
return None; let mountinfo = vec![
} ("29 26 0:26 / /sys/fs/cgroup/cpuset,cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:10 - cgroup cgroup rw,cpuset,cpu,cpuacct",
Mountinfo{mount_point: PathBuf::from("/sys/fs/cgroup/cpuset,cpu,cpuacct"), fs_type: ("cgroup".to_string(), None), super_opts: vec![
"rw".to_string(),
"cpuset".to_string(),
"cpu".to_string(),
"cpuacct".to_string(),
]}),
("121 1731 0:42 / /shm rw,nosuid,nodev,noexec,relatime shared:68 master:66 - tmpfs shm rw,size=65536k",
Mountinfo{mount_point: PathBuf::from("/shm"), fs_type: ("tmpfs".to_string(), None), super_opts: vec![
"rw".to_string(),
"size=65536k".to_string(),
]}),
("121 1731 0:42 / /shm rw,nosuid,nodev,noexec,relatime shared:68 master:66 - tmpfs.123 shm rw,size=65536k",
Mountinfo{mount_point: PathBuf::from("/shm"), fs_type: ("tmpfs".to_string(), Some("123".to_string())), super_opts: vec![
"rw".to_string(),
"size=65536k".to_string(),
]}),
];
let mountinfo_file = File::open(mountinfo_path).unwrap(); for mi in mountinfo {
let mountinfo_reader = BufReader::new(&mountinfo_file); let info = parse_mountinfo_for_line(mi.0).unwrap();
for _line in mountinfo_reader.lines() { assert_eq!(info, mi.1)
let line = _line.unwrap();
let mut fields = line.split_whitespace();
let index = line.find(" - ").unwrap();
let more_fields = line[index + 3..].split_whitespace().collect::<Vec<_>>();
if more_fields.len() == 0 {
continue;
}
if more_fields[0] == "cgroup" {
if more_fields.len() < 3 {
continue;
}
let cgroups_mount = fields.nth(4).unwrap();
if let Some(parent) = std::path::Path::new(cgroups_mount).parent() {
if let Some(path) = parent.as_os_str().to_str() {
debug!("found cgroups {:?} from {:?}", path, cgroups_mount);
return Some(path.to_string());
}
}
continue;
} }
} }
None
} }

View File

@@ -8,13 +8,13 @@
//! //!
//! See the Kernel's documentation for more information about this subsystem, found at: //! 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) //! [Documentation/cgroup-v1/hugetlb.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/hugetlb.txt)
use std::fs::File; use log::warn;
use std::io::{Read, Write}; use std::io::Write;
use std::path::PathBuf; use std::path::PathBuf;
use crate::error::ErrorKind::*; use crate::error::ErrorKind::*;
use crate::error::*; use crate::error::*;
use crate::flat_keyed_to_vec; use crate::{flat_keyed_to_vec, read_u64_from};
use crate::{ use crate::{
ControllIdentifier, ControllerInternal, Controllers, HugePageResources, Resources, Subsystem, ControllIdentifier, ControllerInternal, Controllers, HugePageResources, Resources, Subsystem,
@@ -54,14 +54,13 @@ impl ControllerInternal for HugeTlbController {
// get the resources that apply to this controller // get the resources that apply to this controller
let res: &HugePageResources = &res.hugepages; let res: &HugePageResources = &res.hugepages;
if res.update_values { for i in &res.limits {
for i in &res.limits { let _ = self.set_limit_in_bytes(&i.size, i.limit);
let _ = self.set_limit_in_bytes(&i.size, i.limit); if self.limit_in_bytes(&i.size)? != i.limit {
if self.limit_in_bytes(&i.size)? != i.limit { return Err(Error::new(Other));
return Err(Error::new(Other));
}
} }
} }
Ok(()) Ok(())
} }
} }
@@ -79,37 +78,23 @@ impl<'a> From<&'a Subsystem> for &'a HugeTlbController {
Subsystem::HugeTlb(c) => c, Subsystem::HugeTlb(c) => c,
_ => { _ => {
assert_eq!(1, 0); assert_eq!(1, 0);
::std::mem::uninitialized() let v = std::mem::MaybeUninit::uninit();
v.assume_init()
} }
} }
} }
} }
} }
fn read_u64_from(mut file: File) -> Result<u64> {
let mut string = String::new();
match file.read_to_string(&mut string) {
Ok(_) => string
.trim()
.parse()
.map_err(|e| Error::with_cause(ParseError, e)),
Err(e) => Err(Error::with_cause(ReadFailed, e)),
}
}
impl HugeTlbController { impl HugeTlbController {
/// Constructs a new `HugeTlbController` with `oroot` serving as the root of the control group. /// Constructs a new `HugeTlbController` with `root` serving as the root of the control group.
pub fn new(oroot: PathBuf, v2: bool) -> Self { pub fn new(root: PathBuf, v2: bool) -> Self {
let mut root = oroot; let sizes = get_hugepage_sizes();
if !v2 {
root.push(Self::controller_type().to_string());
}
let sizes = get_hugepage_sizes().unwrap();
Self { Self {
base: root.clone(), base: root.clone(),
path: root, path: root,
sizes: sizes, sizes,
v2: v2, v2,
} }
} }
@@ -131,7 +116,7 @@ impl HugeTlbController {
self.open_path(&format!("hugetlb.{}.events", hugetlb_size), false) self.open_path(&format!("hugetlb.{}.events", hugetlb_size), false)
.and_then(flat_keyed_to_vec) .and_then(flat_keyed_to_vec)
.and_then(|x| { .and_then(|x| {
if x.len() == 0 { if x.is_empty() {
return Err(Error::from_string(format!( return Err(Error::from_string(format!(
"get empty from hugetlb.{}.events", "get empty from hugetlb.{}.events",
hugetlb_size hugetlb_size
@@ -180,43 +165,46 @@ impl HugeTlbController {
/// Set the limit (in bytes) of how much memory can be backed by hugepages of a certain size /// Set the limit (in bytes) of how much memory can be backed by hugepages of a certain size
/// (`hugetlb_size`). /// (`hugetlb_size`).
pub fn set_limit_in_bytes(&self, hugetlb_size: &str, limit: u64) -> Result<()> { 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 { 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| { self.open_path(&file_name, true).and_then(|mut file| {
file.write_all(limit.to_string().as_ref()) file.write_all(limit.to_string().as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, 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 regex::Regex;
use std::collections::HashMap; use std::collections::HashMap;
use std::fs; use std::fs;
fn get_hugepage_sizes() -> Result<Vec<String>> { fn get_hugepage_sizes() -> Vec<String> {
let mut m = Vec::new();
let dirs = fs::read_dir(HUGEPAGESIZE_DIR); let dirs = fs::read_dir(HUGEPAGESIZE_DIR);
if dirs.is_err() { if dirs.is_err() {
return Ok(m); return Vec::new();
} }
for e in dirs.unwrap() { dirs.unwrap()
let entry = e.unwrap(); .filter_map(|e| {
let name = entry.file_name().into_string().unwrap(); let entry = e.map_err(|e| warn!("readdir error: {:?}", e)).ok()?;
let parts: Vec<&str> = name.split('-').collect(); let name = entry.file_name().into_string().unwrap();
if parts.len() != 2 { let parts: Vec<&str> = name.split('-').collect();
continue; if parts.len() != 2 {
} return None;
let bmap = get_binary_size_map(); }
let size = parse_size(parts[1], &bmap)?; let bmap = get_binary_size_map();
let dabbrs = get_decimal_abbrs(); let size = parse_size(parts[1], &bmap)
m.push(custom_size(size as f64, 1024.0, &dabbrs)); .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; pub const KB: u128 = 1000;
@@ -225,10 +213,15 @@ pub const GB: u128 = 1000 * MB;
pub const TB: u128 = 1000 * GB; pub const TB: u128 = 1000 * GB;
pub const PB: u128 = 1000 * TB; pub const PB: u128 = 1000 * TB;
#[allow(non_upper_case_globals)]
pub const KiB: u128 = 1024; pub const KiB: u128 = 1024;
#[allow(non_upper_case_globals)]
pub const MiB: u128 = 1024 * KiB; pub const MiB: u128 = 1024 * KiB;
#[allow(non_upper_case_globals)]
pub const GiB: u128 = 1024 * MiB; pub const GiB: u128 = 1024 * MiB;
#[allow(non_upper_case_globals)]
pub const TiB: u128 = 1024 * GiB; pub const TiB: u128 = 1024 * GiB;
#[allow(non_upper_case_globals)]
pub const PiB: u128 = 1024 * TiB; pub const PiB: u128 = 1024 * TiB;
pub fn get_binary_size_map() -> HashMap<String, u128> { pub fn get_binary_size_map() -> HashMap<String, u128> {
@@ -275,8 +268,8 @@ fn parse_size(s: &str, m: &HashMap<String, u128>) -> Result<u128> {
let caps = re.unwrap().captures(s).unwrap(); let caps = re.unwrap().captures(s).unwrap();
let num = caps.name("num"); let num = caps.name("num");
let size: u128 = if num.is_some() { let size: u128 = if let Some(num) = num {
let n = num.unwrap().as_str().trim().parse::<u128>(); let n = num.as_str().trim().parse::<u128>();
if n.is_err() { if n.is_err() {
return Err(Error::new(InvalidBytesSize)); return Err(Error::new(InvalidBytesSize));
} }
@@ -286,10 +279,10 @@ fn parse_size(s: &str, m: &HashMap<String, u128>) -> Result<u128> {
}; };
let q = caps.name("mul"); let q = caps.name("mul");
let mul: u128 = if q.is_some() { let mul: u128 = if let Some(q) = q {
let t = m.get(q.unwrap().as_str()); let t = m.get(q.as_str());
if t.is_some() { if let Some(t) = t {
*t.unwrap() *t
} else { } else {
return Err(Error::new(InvalidBytesSize)); return Err(Error::new(InvalidBytesSize));
} }
@@ -300,7 +293,7 @@ fn parse_size(s: &str, m: &HashMap<String, u128>) -> Result<u128> {
Ok(size * mul) 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; let mut i = 0;
while size >= base && i < m.len() - 1 { while size >= base && i < m.len() - 1 {
size /= base; size /= base;

View File

@@ -4,12 +4,34 @@
// SPDX-License-Identifier: Apache-2.0 or MIT // SPDX-License-Identifier: Apache-2.0 or MIT
// //
#![allow(clippy::unnecessary_unwrap)]
use log::*; use log::*;
use std::collections::HashMap; use std::collections::HashMap;
use std::fs::File; use std::fmt;
use std::fs::{self, File};
use std::io::{BufRead, BufReader, Read, Write}; use std::io::{BufRead, BufReader, Read, Write};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::str::FromStr;
macro_rules! update_and_test {
($self: ident, $set_func:ident, $value:expr, $get_func:ident) => {
if let Some(v) = $value {
$self.$set_func(v)?;
if $self.$get_func()? != v {
return Err(Error::new(Other));
}
}
};
}
macro_rules! update {
($self: ident, $set_func:ident, $value:expr) => {
if let Some(v) = $value {
let _ = $self.$set_func(v);
}
};
}
pub mod blkio; pub mod blkio;
pub mod cgroup; pub mod cgroup;
@@ -48,10 +70,11 @@ use crate::pid::PidController;
use crate::rdma::RdmaController; use crate::rdma::RdmaController;
use crate::systemd::SystemdController; use crate::systemd::SystemdController;
#[doc(inline)]
pub use crate::cgroup::Cgroup; pub use crate::cgroup::Cgroup;
/// Contains all the subsystems that are available in this crate. /// Contains all the subsystems that are available in this crate.
#[derive(Debug)] #[derive(Debug, Clone)]
pub enum Subsystem { pub enum Subsystem {
/// Controller for the `Pid` subsystem, see `PidController` for more information. /// Controller for the `Pid` subsystem, see `PidController` for more information.
Pid(PidController), Pid(PidController),
@@ -84,7 +107,7 @@ pub enum Subsystem {
} }
#[doc(hidden)] #[doc(hidden)]
#[derive(Eq, PartialEq, Debug)] #[derive(Eq, PartialEq, Debug, Clone)]
pub enum Controllers { pub enum Controllers {
Pids, Pids,
Mem, Mem,
@@ -102,23 +125,23 @@ pub enum Controllers {
Systemd, Systemd,
} }
impl Controllers { impl fmt::Display for Controllers {
pub fn to_string(&self) -> String { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
Controllers::Pids => return "pids".to_string(), Controllers::Pids => write!(f, "pids"),
Controllers::Mem => return "memory".to_string(), Controllers::Mem => write!(f, "memory"),
Controllers::CpuSet => return "cpuset".to_string(), Controllers::CpuSet => write!(f, "cpuset"),
Controllers::CpuAcct => return "cpuacct".to_string(), Controllers::CpuAcct => write!(f, "cpuacct"),
Controllers::Cpu => return "cpu".to_string(), Controllers::Cpu => write!(f, "cpu"),
Controllers::Devices => return "devices".to_string(), Controllers::Devices => write!(f, "devices"),
Controllers::Freezer => return "freezer".to_string(), Controllers::Freezer => write!(f, "freezer"),
Controllers::NetCls => return "net_cls".to_string(), Controllers::NetCls => write!(f, "net_cls"),
Controllers::BlkIo => return "blkio".to_string(), Controllers::BlkIo => write!(f, "blkio"),
Controllers::PerfEvent => return "perf_event".to_string(), Controllers::PerfEvent => write!(f, "perf_event"),
Controllers::NetPrio => return "net_prio".to_string(), Controllers::NetPrio => write!(f, "net_prio"),
Controllers::HugeTlb => return "hugetlb".to_string(), Controllers::HugeTlb => write!(f, "hugetlb"),
Controllers::Rdma => return "rdma".to_string(), Controllers::Rdma => write!(f, "rdma"),
Controllers::Systemd => return "systemd".to_string(), Controllers::Systemd => write!(f, "name=systemd"),
} }
} }
} }
@@ -158,13 +181,22 @@ mod sealed {
if w { if w {
match File::create(&path) { match File::create(&path) {
Err(e) => return Err(Error::with_cause(ErrorKind::WriteFailed, e)), Err(e) => Err(Error::with_cause(
Ok(file) => return Ok(file), ErrorKind::WriteFailed(
path.display().to_string(),
"[CREATE FILE]".to_string(),
),
e,
)),
Ok(file) => Ok(file),
} }
} else { } else {
match File::open(&path) { match File::open(&path) {
Err(e) => return Err(Error::with_cause(ErrorKind::ReadFailed, e)), Err(e) => Err(Error::with_cause(
Ok(file) => return Ok(file), ErrorKind::ReadFailed(path.display().to_string()),
e,
)),
Ok(file) => Ok(file),
} }
} }
} }
@@ -175,23 +207,43 @@ mod sealed {
let res = file.read_to_string(&mut string); let res = file.read_to_string(&mut string);
match res { match res {
Ok(_) => parse_max_value(&string), 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)] #[doc(hidden)]
fn path_exists(&self, p: &str) -> bool { fn path_exists(&self, p: &str) -> bool {
if let Err(_) = self.verify_path() { if self.verify_path().is_err() {
return false; return false;
} }
std::path::Path::new(p).exists() std::path::Path::new(p).exists()
} }
} }
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(key.to_string(), value.to_string()), e)
})
})
}
fn get(&self, key: &str) -> Result<String> {
self.open_path(key, false).and_then(|mut file: File| {
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(key.to_string()), e)),
}
})
}
}
} }
pub(crate) use crate::sealed::ControllerInternal; pub(crate) use crate::sealed::{ControllerInternal, CustomizedAttribute};
/// A Controller is a subsystem attached to the control group. /// A Controller is a subsystem attached to the control group.
/// ///
@@ -213,12 +265,21 @@ pub trait Controller {
/// Does this controller already exist? /// Does this controller already exist?
fn exists(&self) -> bool; fn exists(&self) -> bool;
/// Set notify_on_release
fn set_notify_on_release(&self, enable: bool) -> Result<()>;
/// Set release_agent
fn set_release_agent(&self, path: &str) -> Result<()>;
/// Delete the controller. /// Delete the controller.
fn delete(&self); fn delete(&self) -> Result<()>;
/// Attach a task to this controller. /// Attach a task to this controller.
fn add_task(&self, pid: &CgroupPid) -> Result<()>; fn add_task(&self, pid: &CgroupPid) -> Result<()>;
/// Attach a task to this controller.
fn add_task_by_tgid(&self, pid: &CgroupPid) -> Result<()>;
/// Get the list of tasks that this controller has. /// Get the list of tasks that this controller has.
fn tasks(&self) -> Vec<CgroupPid>; fn tasks(&self) -> Vec<CgroupPid>;
@@ -246,35 +307,93 @@ where
/// Create this controller /// Create this controller
fn create(&self) { fn create(&self) {
self.verify_path() 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()) { match ::std::fs::create_dir_all(self.get_path()) {
Ok(_) => self.post_create(), Ok(_) => self.post_create(),
Err(e) => warn!("error create_dir {:?}", e), Err(e) => warn!("error create_dir: {:?} error: {:?}", self.get_path(), e),
} }
} }
/// Set notify_on_release
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("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("release_agent".to_string(), path.to_string()),
e,
)
})
})
}
/// Does this controller already exist? /// Does this controller already exist?
fn exists(&self) -> bool { fn exists(&self) -> bool {
self.get_path().exists() self.get_path().exists()
} }
/// Delete the controller. /// Delete the controller.
fn delete(&self) { fn delete(&self) -> Result<()> {
if self.get_path().exists() { if !self.get_path().exists() {
libc_rmdir(self.get_path().to_str().unwrap()); return Ok(());
} }
// 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. /// Attach a task to this controller.
fn add_task(&self, pid: &CgroupPid) -> Result<()> { fn add_task(&self, pid: &CgroupPid) -> Result<()> {
let mut file = "tasks"; let mut file_name = "tasks";
if self.is_v2() { if self.is_v2() {
file = "cgroup.procs"; file_name = "cgroup.procs";
} }
self.open_path(file, true).and_then(|mut file| { self.open_path(file_name, true).and_then(|mut file| {
file.write_all(pid.pid.to_string().as_ref()) file.write_all(pid.pid.to_string().as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(ErrorKind::WriteFailed, 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("cgroup.procs".to_string(), pid.pid.to_string()),
e,
)
})
}) })
} }
@@ -285,18 +404,21 @@ where
file = "cgroup.procs"; file = "cgroup.procs";
} }
self.open_path(file, false) self.open_path(file, false)
.and_then(|file| { .map(|file| {
let bf = BufReader::new(file); let bf = BufReader::new(file);
let mut v = Vec::new(); let mut v = Vec::new();
for line in bf.lines() { for line in bf.lines() {
if let Ok(line) = line { match line {
let n = line.trim().parse().unwrap_or(0u64); Ok(line) => {
v.push(n); 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 { fn v2(&self) -> bool {
@@ -304,6 +426,31 @@ where
} }
} }
// remove_dir aims to remove cgroup path. It does so recursively,
// by removing any subdirectories (sub-cgroups) first.
fn remove_dir(dir: &Path) -> Result<()> {
// try the fast path first.
if fs::remove_dir(dir).is_ok() {
return Ok(());
}
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))?;
}
Ok(())
}
#[doc(hidden)] #[doc(hidden)]
pub trait ControllIdentifier { pub trait ControllIdentifier {
fn controller_type() -> Controllers; fn controller_type() -> Controllers;
@@ -311,7 +458,7 @@ pub trait ControllIdentifier {
/// Control group hierarchy (right now, only V1 is supported, but in the future Unified will be /// Control group hierarchy (right now, only V1 is supported, but in the future Unified will be
/// implemented as well). /// implemented as well).
pub trait Hierarchy { pub trait Hierarchy: std::fmt::Debug + Send + Sync {
/// Returns what subsystems are supported by the hierarchy. /// Returns what subsystems are supported by the hierarchy.
fn subsystems(&self) -> Vec<Subsystem>; fn subsystems(&self) -> Vec<Subsystem>;
@@ -322,79 +469,88 @@ pub trait Hierarchy {
fn root_control_group(&self) -> Cgroup; fn root_control_group(&self) -> Cgroup;
fn v2(&self) -> bool; fn v2(&self) -> bool;
/// Checks whether a certain subsystem is supported in the hierarchy.
///
/// This is an internal function and should not be used.
#[doc(hidden)]
fn check_support(&self, sub: Controllers) -> bool;
} }
/// Resource limits for the memory subsystem. /// Resource limits for the memory subsystem.
#[derive(Debug, Clone, Eq, PartialEq, Default)] #[derive(Debug, Clone, Eq, PartialEq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MemoryResources { pub struct MemoryResources {
/// Whether values should be applied to the controller.
pub update_values: bool,
/// How much memory (in bytes) can the kernel consume. /// How much memory (in bytes) can the kernel consume.
pub kernel_memory_limit: i64, pub kernel_memory_limit: Option<i64>,
/// Upper limit of memory usage of the control group's tasks. /// Upper limit of memory usage of the control group's tasks.
pub memory_hard_limit: i64, pub memory_hard_limit: Option<i64>,
/// How much memory the tasks in the control group can use when the system is under memory /// How much memory the tasks in the control group can use when the system is under memory
/// pressure. /// pressure.
pub memory_soft_limit: i64, pub memory_soft_limit: Option<i64>,
/// How much of the kernel's memory (in bytes) can be used for TCP-related buffers. /// How much of the kernel's memory (in bytes) can be used for TCP-related buffers.
pub kernel_tcp_memory_limit: i64, pub kernel_tcp_memory_limit: Option<i64>,
/// How much memory and swap together can the tasks in the control group use. /// How much memory and swap together can the tasks in the control group use.
pub memory_swap_limit: i64, pub memory_swap_limit: Option<i64>,
/// Controls the tendency of the kernel to swap out parts of the address space of the tasks to /// Controls the tendency of the kernel to swap out parts of the address space of the tasks to
/// disk. Lower value implies less likely. /// disk. Lower value implies less likely.
/// ///
/// Note, however, that a value of zero does not mean the process is never swapped out. Use the /// Note, however, that a value of zero does not mean the process is never swapped out. Use the
/// traditional `mlock(2)` system call for that purpose. /// traditional `mlock(2)` system call for that purpose.
pub swappiness: u64, pub swappiness: Option<u64>,
/// Customized key-value attributes
///
/// # Usage:
/// ```
/// let resource = &mut cgroups_rs::Resources::default();
/// resource.memory.attrs.insert("memory.numa_balancing".to_string(), "true".to_string());
/// // apply here
/// ```
pub attrs: HashMap<String, String>,
} }
/// Resources limits on the number of processes. /// Resources limits on the number of processes.
#[derive(Debug, Clone, Eq, PartialEq, Default)] #[derive(Debug, Clone, Eq, PartialEq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct PidResources { pub struct PidResources {
/// Whether values should be applied to the controller.
pub update_values: bool,
/// The maximum number of processes that can exist in the control group. /// The maximum number of processes that can exist in the control group.
/// ///
/// Note that attaching processes to the control group will still succeed _even_ if the limit /// Note that attaching processes to the control group will still succeed _even_ if the limit
/// would be violated, however forks/clones inside the control group will have with `EAGAIN` if /// would be violated, however forks/clones inside the control group will have with `EAGAIN` if
/// they would violate the limit set here. /// they would violate the limit set here.
pub maximum_number_of_processes: MaxValue, pub maximum_number_of_processes: Option<MaxValue>,
} }
/// Resources limits about how the tasks can use the CPU. /// Resources limits about how the tasks can use the CPU.
#[derive(Debug, Clone, Eq, PartialEq, Default)] #[derive(Debug, Clone, Eq, PartialEq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct CpuResources { pub struct CpuResources {
/// Whether values should be applied to the controller.
pub update_values: bool,
// cpuset // cpuset
/// A comma-separated list of CPU IDs where the task in the control group can run. Dashes /// A comma-separated list of CPU IDs where the task in the control group can run. Dashes
/// between numbers indicate ranges. /// between numbers indicate ranges.
pub cpus: Option<String>, pub cpus: Option<String>,
/// Same syntax as the `cpus` field of this structure, but applies to memory nodes instead of /// Same syntax as the `cpus` field of this structure, but applies to memory nodes instead of
/// processors. /// processors.
pub mems: String, pub mems: Option<String>,
// cpu // cpu
/// Weight of how much of the total CPU time should this control group get. Note that this is /// Weight of how much of the total CPU time should this control group get. Note that this is
/// hierarchical, so this is weighted against the siblings of this control group. /// hierarchical, so this is weighted against the siblings of this control group.
pub shares: u64, pub shares: Option<u64>,
/// In one `period`, how much can the tasks run in nanoseconds. /// In one `period`, how much can the tasks run in nanoseconds.
pub quota: i64, pub quota: Option<i64>,
/// Period of time in nanoseconds. /// Period of time in nanoseconds.
pub period: u64, pub period: Option<u64>,
/// This is currently a no-operation. /// This is currently a no-operation.
pub realtime_runtime: i64, pub realtime_runtime: Option<i64>,
/// This is currently a no-operation. /// This is currently a no-operation.
pub realtime_period: u64, pub realtime_period: Option<u64>,
/// Customized key-value attributes
/// # Usage:
/// ```
/// let resource = &mut cgroups_rs::Resources::default();
/// resource.cpu.attrs.insert("cpu.cfs_init_buffer_us".to_string(), "10".to_string());
/// // apply here
/// ```
pub attrs: HashMap<String, String>,
} }
/// A device resource that can be allowed or denied access to. /// A device resource that can be allowed or denied access to.
#[derive(Debug, Clone, Eq, PartialEq, Default)] #[derive(Debug, Clone, Eq, PartialEq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct DeviceResource { pub struct DeviceResource {
/// If true, access to the device is allowed, otherwise it's denied. /// If true, access to the device is allowed, otherwise it's denied.
pub allow: bool, pub allow: bool,
@@ -410,15 +566,15 @@ pub struct DeviceResource {
/// Limit the usage of devices for the control group's tasks. /// Limit the usage of devices for the control group's tasks.
#[derive(Debug, Clone, Eq, PartialEq, Default)] #[derive(Debug, Clone, Eq, PartialEq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct DeviceResources { pub struct DeviceResources {
/// Whether values should be applied to the controller.
pub update_values: bool,
/// For each device in the list, the limits in the structure are applied. /// For each device in the list, the limits in the structure are applied.
pub devices: Vec<DeviceResource>, pub devices: Vec<DeviceResource>,
} }
/// Assigned priority for a network device. /// Assigned priority for a network device.
#[derive(Debug, Clone, Eq, PartialEq, Default)] #[derive(Debug, Clone, Eq, PartialEq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct NetworkPriority { pub struct NetworkPriority {
/// The name (as visible in `ifconfig`) of the interface. /// The name (as visible in `ifconfig`) of the interface.
pub name: String, pub name: String,
@@ -429,19 +585,19 @@ pub struct NetworkPriority {
/// Collections of limits and tags that can be imposed on packets emitted by the tasks in the /// Collections of limits and tags that can be imposed on packets emitted by the tasks in the
/// control group. /// control group.
#[derive(Debug, Clone, Eq, PartialEq, Default)] #[derive(Debug, Clone, Eq, PartialEq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct NetworkResources { pub struct NetworkResources {
/// Whether values should be applied to the controller.
pub update_values: bool,
/// The networking class identifier to attach to the packets. /// The networking class identifier to attach to the packets.
/// ///
/// This can then later be used in iptables and such to have special rules. /// This can then later be used in iptables and such to have special rules.
pub class_id: u64, pub class_id: Option<u64>,
/// Priority of the egress traffic for each interface. /// Priority of the egress traffic for each interface.
pub priorities: Vec<NetworkPriority>, pub priorities: Vec<NetworkPriority>,
} }
/// A hugepage type and its consumption limit for the control group. /// A hugepage type and its consumption limit for the control group.
#[derive(Debug, Clone, Eq, PartialEq, Default)] #[derive(Debug, Clone, Eq, PartialEq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct HugePageResource { pub struct HugePageResource {
/// The size of the hugepage, i.e. `2MB`, `1GB`, etc. /// The size of the hugepage, i.e. `2MB`, `1GB`, etc.
pub size: String, pub size: String,
@@ -452,15 +608,15 @@ pub struct HugePageResource {
/// Provides the ability to set consumption limit on each type of hugepages. /// Provides the ability to set consumption limit on each type of hugepages.
#[derive(Debug, Clone, Eq, PartialEq, Default)] #[derive(Debug, Clone, Eq, PartialEq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct HugePageResources { pub struct HugePageResources {
/// Whether values should be applied to the controller.
pub update_values: bool,
/// Set a limit of consumption for each hugepages type. /// Set a limit of consumption for each hugepages type.
pub limits: Vec<HugePageResource>, pub limits: Vec<HugePageResource>,
} }
/// Weight for a particular block device. /// Weight for a particular block device.
#[derive(Debug, Clone, Eq, PartialEq, Default)] #[derive(Debug, Clone, Eq, PartialEq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct BlkIoDeviceResource { pub struct BlkIoDeviceResource {
/// The major number of the device. /// The major number of the device.
pub major: u64, pub major: u64,
@@ -474,6 +630,7 @@ pub struct BlkIoDeviceResource {
/// Provides the ability to throttle a device (both byte/sec, and IO op/s) /// Provides the ability to throttle a device (both byte/sec, and IO op/s)
#[derive(Debug, Clone, Eq, PartialEq, Default)] #[derive(Debug, Clone, Eq, PartialEq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct BlkIoDeviceThrottleResource { pub struct BlkIoDeviceThrottleResource {
/// The major number of the device. /// The major number of the device.
pub major: u64, pub major: u64,
@@ -485,9 +642,8 @@ pub struct BlkIoDeviceThrottleResource {
/// General block I/O resource limits. /// General block I/O resource limits.
#[derive(Debug, Clone, Eq, PartialEq, Default)] #[derive(Debug, Clone, Eq, PartialEq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct BlkIoResources { pub struct BlkIoResources {
/// Whether values should be applied to the controller.
pub update_values: bool,
/// The weight of the control group against descendant nodes. /// The weight of the control group against descendant nodes.
pub weight: Option<u16>, pub weight: Option<u16>,
/// The weight of the control group against sibling nodes. /// The weight of the control group against sibling nodes.
@@ -502,10 +658,20 @@ pub struct BlkIoResources {
pub throttle_write_bps_device: Vec<BlkIoDeviceThrottleResource>, pub throttle_write_bps_device: Vec<BlkIoDeviceThrottleResource>,
/// Throttled write IO operations per second can be provided for each device. /// Throttled write IO operations per second can be provided for each device.
pub throttle_write_iops_device: Vec<BlkIoDeviceThrottleResource>, 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. /// The resource limits and constraints that will be set on the control group.
#[derive(Debug, Clone, Eq, PartialEq, Default)] #[derive(Debug, Clone, Eq, PartialEq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Resources { pub struct Resources {
/// Memory usage related limits. /// Memory usage related limits.
pub memory: MemoryResources, pub memory: MemoryResources,
@@ -546,80 +712,66 @@ impl<'a> From<&'a std::process::Child> for CgroupPid {
impl Subsystem { impl Subsystem {
fn enter(self, path: &Path) -> Self { fn enter(self, path: &Path) -> Self {
match self { match self {
Subsystem::Pid(cont) => Subsystem::Pid({ Subsystem::Pid(mut cont) => Subsystem::Pid({
let mut c = cont.clone(); cont.get_path_mut().push(path);
c.get_path_mut().push(path); cont
c
}), }),
Subsystem::Mem(cont) => Subsystem::Mem({ Subsystem::Mem(mut cont) => Subsystem::Mem({
let mut c = cont.clone(); cont.get_path_mut().push(path);
c.get_path_mut().push(path); cont
c
}), }),
Subsystem::CpuSet(cont) => Subsystem::CpuSet({ Subsystem::CpuSet(mut cont) => Subsystem::CpuSet({
let mut c = cont.clone(); cont.get_path_mut().push(path);
c.get_path_mut().push(path); cont
c
}), }),
Subsystem::CpuAcct(cont) => Subsystem::CpuAcct({ Subsystem::CpuAcct(mut cont) => Subsystem::CpuAcct({
let mut c = cont.clone(); cont.get_path_mut().push(path);
c.get_path_mut().push(path); cont
c
}), }),
Subsystem::Cpu(cont) => Subsystem::Cpu({ Subsystem::Cpu(mut cont) => Subsystem::Cpu({
let mut c = cont.clone(); cont.get_path_mut().push(path);
c.get_path_mut().push(path); cont
c
}), }),
Subsystem::Devices(cont) => Subsystem::Devices({ Subsystem::Devices(mut cont) => Subsystem::Devices({
let mut c = cont.clone(); cont.get_path_mut().push(path);
c.get_path_mut().push(path); cont
c
}), }),
Subsystem::Freezer(cont) => Subsystem::Freezer({ Subsystem::Freezer(mut cont) => Subsystem::Freezer({
let mut c = cont.clone(); cont.get_path_mut().push(path);
c.get_path_mut().push(path); cont
c
}), }),
Subsystem::NetCls(cont) => Subsystem::NetCls({ Subsystem::NetCls(mut cont) => Subsystem::NetCls({
let mut c = cont.clone(); cont.get_path_mut().push(path);
c.get_path_mut().push(path); cont
c
}), }),
Subsystem::BlkIo(cont) => Subsystem::BlkIo({ Subsystem::BlkIo(mut cont) => Subsystem::BlkIo({
let mut c = cont.clone(); cont.get_path_mut().push(path);
c.get_path_mut().push(path); cont
c
}), }),
Subsystem::PerfEvent(cont) => Subsystem::PerfEvent({ Subsystem::PerfEvent(mut cont) => Subsystem::PerfEvent({
let mut c = cont.clone(); cont.get_path_mut().push(path);
c.get_path_mut().push(path); cont
c
}), }),
Subsystem::NetPrio(cont) => Subsystem::NetPrio({ Subsystem::NetPrio(mut cont) => Subsystem::NetPrio({
let mut c = cont.clone(); cont.get_path_mut().push(path);
c.get_path_mut().push(path); cont
c
}), }),
Subsystem::HugeTlb(cont) => Subsystem::HugeTlb({ Subsystem::HugeTlb(mut cont) => Subsystem::HugeTlb({
let mut c = cont.clone(); cont.get_path_mut().push(path);
c.get_path_mut().push(path); cont
c
}), }),
Subsystem::Rdma(cont) => Subsystem::Rdma({ Subsystem::Rdma(mut cont) => Subsystem::Rdma({
let mut c = cont.clone(); cont.get_path_mut().push(path);
c.get_path_mut().push(path); cont
c
}), }),
Subsystem::Systemd(cont) => Subsystem::Systemd({ Subsystem::Systemd(mut cont) => Subsystem::Systemd({
let mut c = cont.clone(); cont.get_path_mut().push(path);
c.get_path_mut().push(path); cont
c
}), }),
} }
} }
fn to_controller(&self) -> &dyn Controller { pub fn to_controller(&self) -> &dyn Controller {
match self { match self {
Subsystem::Pid(cont) => cont, Subsystem::Pid(cont) => cont,
Subsystem::Mem(cont) => cont, Subsystem::Mem(cont) => cont,
@@ -638,13 +790,14 @@ impl Subsystem {
} }
} }
fn controller_name(&self) -> String { pub fn controller_name(&self) -> String {
self.to_controller().control_type().to_string() self.to_controller().control_type().to_string()
} }
} }
/// The values for `memory.hight` or `pids.max` /// The values for `memory.hight` or `pids.max`
#[derive(Eq, PartialEq, Copy, Clone, Debug)] #[derive(Eq, PartialEq, Copy, Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum MaxValue { pub enum MaxValue {
/// This value is returned when the text is `"max"`. /// This value is returned when the text is `"max"`.
Max, Max,
@@ -659,22 +812,25 @@ impl Default for MaxValue {
} }
impl MaxValue { impl MaxValue {
#[allow(clippy::should_implement_trait, clippy::wrong_self_convention)]
fn to_i64(&self) -> i64 { fn to_i64(&self) -> i64 {
match self { match self {
MaxValue::Max => -1, MaxValue::Max => -1,
MaxValue::Value(num) => *num, 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 { match self {
MaxValue::Max => "max".to_string(), MaxValue::Max => write!(f, "max"),
MaxValue::Value(num) => num.to_string(), 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" { if s.trim() == "max" {
return Ok(MaxValue::Max); return Ok(MaxValue::Max);
} }
@@ -690,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)>> { pub fn flat_keyed_to_vec(mut file: File) -> Result<Vec<(String, i64)>> {
let mut content = String::new(); let mut content = String::new();
file.read_to_string(&mut content) 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(); let mut v = Vec::new();
for line in content.lines() { for line in content.lines() {
let parts: Vec<&str> = line.split(' ').collect(); let parts: Vec<&str> = line.split(' ').collect();
if parts.len() == 2 { if parts.len() == 2 {
match parts[1].parse::<i64>() { if let Ok(i) = parts[1].parse::<i64>() {
Ok(i) => { v.push((parts[0].to_string(), i));
v.push((parts[0].to_string(), i));
}
Err(_) => {}
} }
} }
} }
@@ -713,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>> { pub fn flat_keyed_to_hashmap(mut file: File) -> Result<HashMap<String, i64>> {
let mut content = String::new(); let mut content = String::new();
file.read_to_string(&mut content) 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(); let mut h = HashMap::new();
for line in content.lines() { for line in content.lines() {
let parts: Vec<&str> = line.split(' ').collect(); let parts: Vec<&str> = line.split(' ').collect();
if parts.len() == 2 { if parts.len() == 2 {
match parts[1].parse::<i64>() { if let Ok(i) = parts[1].parse::<i64>() {
Ok(i) => { h.insert(parts[0].to_string(), i);
h.insert(parts[0].to_string(), i);
}
Err(_) => {}
} }
} }
} }
@@ -736,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>>> { pub fn nested_keyed_to_hashmap(mut file: File) -> Result<HashMap<String, HashMap<String, i64>>> {
let mut content = String::new(); let mut content = String::new();
file.read_to_string(&mut content) 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(); let mut h = HashMap::new();
for line in content.lines() { for line in content.lines() {
let parts: Vec<&str> = line.split(' ').collect(); let parts: Vec<&str> = line.split(' ').collect();
if parts.len() == 0 { if parts.is_empty() {
continue; continue;
} }
let mut th = HashMap::new(); 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(); let fields: Vec<&str> = item.split('=').collect();
if fields.len() == 2 { if fields.len() == 2 {
match fields[1].parse::<i64>() { if let Ok(i) = fields[1].parse::<i64>() {
Ok(i) => { th.insert(fields[0].to_string(), i);
th.insert(fields[0].to_string(), i);
}
Err(_) => {}
} }
} }
} }
@@ -762,21 +909,41 @@ pub fn nested_keyed_to_hashmap(mut file: File) -> Result<HashMap<String, HashMap
Ok(h) Ok(h)
} }
/// fs::remove_dir_all or fs::remove_dir can't work with cgroup directory sometimes. fn read_from<T>(mut file: File) -> Result<T>
/// with error: `Os { code: 1, kind: PermissionDenied, message: "Operation not permitted" }` where
pub fn libc_rmdir(p: &str) { T: FromStr,
// with int return value <T as FromStr>::Err: 'static + Send + Sync + std::error::Error,
let _ = unsafe { libc::rmdir(p.as_ptr() as *const i8) }; {
}
/// read and parse an i64 data
pub fn read_i64_from(mut file: File) -> Result<i64> {
let mut string = String::new(); let mut string = String::new();
match file.read_to_string(&mut string) { match file.read_to_string(&mut string) {
Ok(_) => string Ok(_) => string
.trim() .trim()
.parse() .parse::<T>()
.map_err(|e| Error::with_cause(ParseError, e)), .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,
)),
} }
} }
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("FIXME: can't get path in fn read_string_from".to_string()),
e,
)),
}
}
/// read and parse an u64 data
fn read_u64_from(file: File) -> Result<u64> {
read_from::<u64>(file)
}
/// read and parse an i64 data
fn read_i64_from(file: File) -> Result<i64> {
read_from::<i64>(file)
}

View File

@@ -8,21 +8,22 @@
//! //!
//! See the Kernel's documentation for more information about this subsystem, found at: //! 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) //! [Documentation/cgroup-v1/memory.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/memory.txt)
use log::warn;
use std::collections::HashMap; use std::collections::HashMap;
use std::fs::File; use std::io::Write;
use std::io::{Read, Write};
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::mpsc::Receiver; use std::sync::mpsc::Receiver;
use crate::error::ErrorKind::*; use crate::error::ErrorKind::*;
use crate::error::*; use crate::error::*;
use crate::events; use crate::events;
use crate::{read_i64_from, read_string_from, read_u64_from};
use crate::flat_keyed_to_hashmap; use crate::flat_keyed_to_hashmap;
use crate::{ use crate::{
ControllIdentifier, ControllerInternal, Controllers, MaxValue, MemoryResources, Resources, ControllIdentifier, ControllerInternal, Controllers, CustomizedAttribute, MaxValue,
Subsystem, MemoryResources, Resources, Subsystem,
}; };
/// A controller that allows controlling the `memory` subsystem of a Cgroup. /// A controller that allows controlling the `memory` subsystem of a Cgroup.
@@ -38,6 +39,7 @@ pub struct MemController {
} }
#[derive(Default, Debug, PartialEq, Eq)] #[derive(Default, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SetMemory { pub struct SetMemory {
pub low: Option<MaxValue>, pub low: Option<MaxValue>,
pub high: 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. /// Controls statistics and controls about the OOM killer operating in this control group.
#[derive(Default, Debug, PartialEq, Eq)] #[derive(Default, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct OomControl { pub struct OomControl {
/// If true, the OOM killer has been disabled for the tasks in this control group. /// If true, the OOM killer has been disabled for the tasks in this control group.
pub oom_kill_disable: bool, pub oom_kill_disable: bool,
@@ -56,18 +59,38 @@ pub struct OomControl {
pub oom_kill: u64, pub oom_kill: u64,
} }
#[allow(clippy::unnecessary_wraps)]
fn parse_oom_control(s: String) -> Result<OomControl> { fn parse_oom_control(s: String) -> Result<OomControl> {
let spl = s.split_whitespace().collect::<Vec<_>>(); 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 { Ok(OomControl {
oom_kill_disable: spl[1].parse::<u64>().unwrap() == 1, oom_kill_disable,
under_oom: spl[3].parse::<u64>().unwrap() == 1, under_oom,
oom_kill: spl[5].parse::<u64>().unwrap(), oom_kill,
}) })
} }
/// Contains statistics about the NUMA locality of the control group's tasks. /// Contains statistics about the NUMA locality of the control group's tasks.
#[derive(Default, Debug, PartialEq, Eq)] #[derive(Default, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct NumaStat { pub struct NumaStat {
/// Total amount of pages used by the control group. /// Total amount of pages used by the control group.
pub total_pages: u64, pub total_pages: u64,
@@ -104,18 +127,19 @@ pub struct NumaStat {
pub hierarchical_unevictable_pages_per_node: Vec<u64>, pub hierarchical_unevictable_pages_per_node: Vec<u64>,
} }
#[allow(clippy::unnecessary_wraps)]
fn parse_numa_stat(s: String) -> Result<NumaStat> { fn parse_numa_stat(s: String) -> Result<NumaStat> {
// Parse the number of nodes // 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 mut ls = s.lines();
let total_line = ls.next().unwrap(); let total_line = ls.next().unwrap();
let file_line = ls.next().unwrap(); let file_line = ls.next().unwrap();
let anon_line = ls.next().unwrap(); let anon_line = ls.next().unwrap();
let unevict_line = ls.next().unwrap(); let unevict_line = ls.next().unwrap();
let hier_total_line = ls.next().unwrap(); let hier_total_line = ls.next().unwrap_or_default();
let hier_file_line = ls.next().unwrap(); let hier_file_line = ls.next().unwrap_or_default();
let hier_anon_line = ls.next().unwrap(); let hier_anon_line = ls.next().unwrap_or_default();
let hier_unevict_line = ls.next().unwrap(); let hier_unevict_line = ls.next().unwrap_or_default();
Ok(NumaStat { Ok(NumaStat {
total_pages: total_line total_pages: total_line
@@ -124,10 +148,10 @@ fn parse_numa_stat(s: String) -> Result<NumaStat> {
.parse::<u64>() .parse::<u64>()
.unwrap_or(0), .unwrap_or(0),
total_pages_per_node: { total_pages_per_node: {
let spl = &total_line.split(" ").collect::<Vec<_>>()[1..]; let spl = &total_line.split(' ').collect::<Vec<_>>()[1..];
spl.iter() spl.iter()
.map(|x| { .map(|x| {
x.split("=").collect::<Vec<_>>()[1] x.split('=').collect::<Vec<_>>()[1]
.parse::<u64>() .parse::<u64>()
.unwrap_or(0) .unwrap_or(0)
}) })
@@ -139,10 +163,10 @@ fn parse_numa_stat(s: String) -> Result<NumaStat> {
.parse::<u64>() .parse::<u64>()
.unwrap_or(0), .unwrap_or(0),
file_pages_per_node: { file_pages_per_node: {
let spl = &file_line.split(" ").collect::<Vec<_>>()[1..]; let spl = &file_line.split(' ').collect::<Vec<_>>()[1..];
spl.iter() spl.iter()
.map(|x| { .map(|x| {
x.split("=").collect::<Vec<_>>()[1] x.split('=').collect::<Vec<_>>()[1]
.parse::<u64>() .parse::<u64>()
.unwrap_or(0) .unwrap_or(0)
}) })
@@ -154,10 +178,10 @@ fn parse_numa_stat(s: String) -> Result<NumaStat> {
.parse::<u64>() .parse::<u64>()
.unwrap_or(0), .unwrap_or(0),
anon_pages_per_node: { anon_pages_per_node: {
let spl = &anon_line.split(" ").collect::<Vec<_>>()[1..]; let spl = &anon_line.split(' ').collect::<Vec<_>>()[1..];
spl.iter() spl.iter()
.map(|x| { .map(|x| {
x.split("=").collect::<Vec<_>>()[1] x.split('=').collect::<Vec<_>>()[1]
.parse::<u64>() .parse::<u64>()
.unwrap_or(0) .unwrap_or(0)
}) })
@@ -169,79 +193,120 @@ fn parse_numa_stat(s: String) -> Result<NumaStat> {
.parse::<u64>() .parse::<u64>()
.unwrap_or(0), .unwrap_or(0),
unevictable_pages_per_node: { unevictable_pages_per_node: {
let spl = &unevict_line.split(" ").collect::<Vec<_>>()[1..]; let spl = &unevict_line.split(' ').collect::<Vec<_>>()[1..];
spl.iter() spl.iter()
.map(|x| { .map(|x| {
x.split("=").collect::<Vec<_>>()[1] x.split('=').collect::<Vec<_>>()[1]
.parse::<u64>() .parse::<u64>()
.unwrap_or(0) .unwrap_or(0)
}) })
.collect() .collect()
}, },
hierarchical_total_pages: hier_total_line hierarchical_total_pages: {
.split(|x| x == ' ' || x == '=') if !hier_total_line.is_empty() {
.collect::<Vec<_>>()[1] hier_total_line
.parse::<u64>() .split(|x| x == ' ' || x == '=')
.unwrap_or(0), .collect::<Vec<_>>()[1]
.parse::<u64>()
.unwrap_or(0)
} else {
0
}
},
hierarchical_total_pages_per_node: { hierarchical_total_pages_per_node: {
let spl = &hier_total_line.split(" ").collect::<Vec<_>>()[1..]; if !hier_total_line.is_empty() {
spl.iter() let spl = &hier_total_line.split(' ').collect::<Vec<_>>()[1..];
.map(|x| { spl.iter()
x.split("=").collect::<Vec<_>>()[1] .map(|x| {
.parse::<u64>() x.split('=').collect::<Vec<_>>()[1]
.unwrap_or(0) .parse::<u64>()
}) .unwrap_or(0)
.collect() })
.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: { hierarchical_file_pages_per_node: {
let spl = &hier_file_line.split(" ").collect::<Vec<_>>()[1..]; if !hier_file_line.is_empty() {
spl.iter() let spl = &hier_file_line.split(' ').collect::<Vec<_>>()[1..];
.map(|x| { spl.iter()
x.split("=").collect::<Vec<_>>()[1] .map(|x| {
.parse::<u64>() x.split('=').collect::<Vec<_>>()[1]
.unwrap_or(0) .parse::<u64>()
}) .unwrap_or(0)
.collect() })
.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: { hierarchical_anon_pages_per_node: {
let spl = &hier_anon_line.split(" ").collect::<Vec<_>>()[1..]; if !hier_anon_line.is_empty() {
spl.iter() let spl = &hier_anon_line.split(' ').collect::<Vec<_>>()[1..];
.map(|x| { spl.iter()
x.split("=").collect::<Vec<_>>()[1] .map(|x| {
.parse::<u64>() x.split('=').collect::<Vec<_>>()[1]
.unwrap_or(0) .parse::<u64>()
}) .unwrap_or(0)
.collect() })
.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: { hierarchical_unevictable_pages_per_node: {
let spl = &hier_unevict_line.split(" ").collect::<Vec<_>>()[1..]; if !hier_unevict_line.is_empty() {
spl.iter() let spl = &hier_unevict_line.split(' ').collect::<Vec<_>>()[1..];
.map(|x| { spl.iter()
x.split("=").collect::<Vec<_>>()[1] .map(|x| {
.parse::<u64>() x.split('=').collect::<Vec<_>>()[1]
.unwrap_or(0) .parse::<u64>()
}) .unwrap_or(0)
.collect() })
.collect()
} else {
Vec::new()
}
}, },
}) })
} }
#[derive(Default, Debug, PartialEq, Eq)] #[derive(Default, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MemoryStat { pub struct MemoryStat {
pub cache: u64, pub cache: u64,
pub rss: u64, pub rss: u64,
@@ -282,6 +347,7 @@ pub struct MemoryStat {
pub raw: HashMap<String, u64>, pub raw: HashMap<String, u64>,
} }
#[allow(clippy::unnecessary_wraps)]
fn parse_memory_stat(s: String) -> Result<MemoryStat> { fn parse_memory_stat(s: String) -> Result<MemoryStat> {
let mut raw = HashMap::new(); 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_inactive_file: *raw.get("total_inactive_file").unwrap_or(&0),
total_active_file: *raw.get("total_active_file").unwrap_or(&0), total_active_file: *raw.get("total_active_file").unwrap_or(&0),
total_unevictable: *raw.get("total_unevictable").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 /// Contains statistics about the current usage of memory and swap (together, not seperately) by
/// the control group's tasks. /// the control group's tasks.
#[derive(Debug)] #[derive(Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MemSwap { pub struct MemSwap {
/// How many times the limit has been hit. /// How many times the limit has been hit.
pub fail_cnt: u64, 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 /// State of and statistics gathered by the kernel about the memory usage of the control group's
/// tasks. /// tasks.
#[derive(Debug)] #[derive(Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Memory { pub struct Memory {
/// How many times the limit has been hit. /// How many times the limit has been hit.
pub fail_cnt: u64, 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 /// The current state of and gathered statistics about the kernel's memory usage for TCP-related
/// data structures. /// data structures.
#[derive(Debug)] #[derive(Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Tcp { pub struct Tcp {
/// How many times the limit has been hit. /// How many times the limit has been hit.
pub fail_cnt: u64, 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 /// 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. /// that interrupts in particular might not be able to enforce these limits.
#[derive(Debug)] #[derive(Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Kmem { pub struct Kmem {
/// How many times the limit has been hit. /// How many times the limit has been hit.
pub fail_cnt: u64, pub fail_cnt: u64,
@@ -453,30 +523,28 @@ impl ControllerInternal for MemController {
// get the resources that apply to this controller // get the resources that apply to this controller
let memres: &MemoryResources = &res.memory; let memres: &MemoryResources = &res.memory;
if memres.update_values { update!(self, set_limit, memres.memory_hard_limit);
let _ = self.set_limit(memres.memory_hard_limit); update!(self, set_soft_limit, memres.memory_soft_limit);
let _ = self.set_soft_limit(memres.memory_soft_limit); update!(self, set_kmem_limit, memres.kernel_memory_limit);
let _ = self.set_kmem_limit(memres.kernel_memory_limit); update!(self, set_memswap_limit, memres.memory_swap_limit);
let _ = self.set_memswap_limit(memres.memory_swap_limit); update!(self, set_tcp_limit, memres.kernel_tcp_memory_limit);
let _ = self.set_tcp_limit(memres.kernel_tcp_memory_limit); update!(self, set_swappiness, memres.swappiness);
let _ = self.set_swappiness(memres.swappiness);
} memres.attrs.iter().for_each(|(k, v)| {
let _ = self.set(k, v);
});
Ok(()) Ok(())
} }
} }
impl MemController { impl MemController {
/// Contructs a new `MemController` with `oroot` serving as the root of the control group. /// Contructs a new `MemController` with `root` serving as the root of the control group.
pub fn new(oroot: PathBuf, v2: bool) -> Self { pub fn new(root: PathBuf, v2: bool) -> Self {
let mut root = oroot;
if !v2 {
root.push(Self::controller_type().to_string());
}
Self { Self {
base: root.clone(), base: root.clone(),
path: root, path: root,
v2: v2, v2,
} }
} }
@@ -491,11 +559,12 @@ impl MemController {
for value in values { for value in values {
let v = value.0; let v = value.0;
let f = value.1; let f = value.1;
if v.is_some() { if let Some(v) = v {
let v = v.unwrap().to_string(); let v = v.to_string();
self.open_path(f, true).and_then(|mut file| { self.open_path(f, true).and_then(|mut file| {
file.write_all(v.as_ref()) file.write_all(v.as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(WriteFailed(f.to_string(), format!("{:?}", v)), e)
})
})?; })?;
} }
} }
@@ -505,10 +574,11 @@ impl MemController {
// for v2 // for v2
pub fn get_mem(&self) -> Result<SetMemory> { pub fn get_mem(&self) -> Result<SetMemory> {
let mut m: SetMemory = Default::default(); let mut m: SetMemory = Default::default();
self.get_max_value("memory.high").map(|x| m.high = Some(x)); self.get_max_value("memory.high")
self.get_max_value("memory.low").map(|x| m.low = Some(x)); .map(|x| m.high = Some(x))?;
self.get_max_value("memory.max").map(|x| m.max = Some(x)); self.get_max_value("memory.low").map(|x| m.low = Some(x))?;
self.get_max_value("memory.min").map(|x| m.min = Some(x)); self.get_max_value("memory.max").map(|x| m.max = Some(x))?;
self.get_max_value("memory.min").map(|x| m.min = Some(x))?;
Ok(m) Ok(m)
} }
@@ -532,7 +602,7 @@ impl MemController {
.open_path("memory.stat", false) .open_path("memory.stat", false)
.and_then(read_string_from) .and_then(read_string_from)
.and_then(parse_memory_stat) .and_then(parse_memory_stat)
.unwrap_or(MemoryStat::default()), .unwrap_or_default(),
swappiness: self swappiness: self
.open_path("memory.swap.current", false) .open_path("memory.swap.current", false)
.and_then(read_u64_from) .and_then(read_u64_from)
@@ -576,12 +646,12 @@ impl MemController {
.open_path("memory.numa_stat", false) .open_path("memory.numa_stat", false)
.and_then(read_string_from) .and_then(read_string_from)
.and_then(parse_numa_stat) .and_then(parse_numa_stat)
.unwrap_or(NumaStat::default()), .unwrap_or_default(),
oom_control: self oom_control: self
.open_path("memory.oom_control", false) .open_path("memory.oom_control", false)
.and_then(read_string_from) .and_then(read_string_from)
.and_then(parse_oom_control) .and_then(parse_oom_control)
.unwrap_or(OomControl::default()), .unwrap_or_default(),
soft_limit_in_bytes: self soft_limit_in_bytes: self
.open_path("memory.soft_limit_in_bytes", false) .open_path("memory.soft_limit_in_bytes", false)
.and_then(read_i64_from) .and_then(read_i64_from)
@@ -590,7 +660,7 @@ impl MemController {
.open_path("memory.stat", false) .open_path("memory.stat", false)
.and_then(read_string_from) .and_then(read_string_from)
.and_then(parse_memory_stat) .and_then(parse_memory_stat)
.unwrap_or(MemoryStat::default()), .unwrap_or_default(),
swappiness: self swappiness: self
.open_path("memory.swappiness", false) .open_path("memory.swappiness", false)
.and_then(read_u64_from) .and_then(read_u64_from)
@@ -624,7 +694,7 @@ impl MemController {
slabinfo: self slabinfo: self
.open_path("memory.kmem.slabinfo", false) .open_path("memory.kmem.slabinfo", false)
.and_then(read_string_from) .and_then(read_string_from)
.unwrap_or("".to_string()), .unwrap_or_default(),
} }
} }
@@ -656,7 +726,7 @@ impl MemController {
fail_cnt: self fail_cnt: self
.open_path("memory.swap.events", false) .open_path("memory.swap.events", false)
.and_then(flat_keyed_to_hashmap) .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(), .unwrap(),
limit_in_bytes: self limit_in_bytes: self
.open_path("memory.swap.max", false) .open_path("memory.swap.max", false)
@@ -700,26 +770,48 @@ impl MemController {
/// Reset the fail counter /// Reset the fail counter
pub fn reset_fail_count(&self) -> Result<()> { pub fn reset_fail_count(&self) -> Result<()> {
self.open_path("memory.failcnt", true).and_then(|mut file| { self.open_path("memory.failcnt", true).and_then(|mut file| {
file.write_all("0".to_string().as_ref()) file.write_all("0".to_string().as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(
WriteFailed("memory.failcnt".to_string(), "0".to_string()),
e,
)
})
}) })
} }
/// Reset the kernel memory fail counter /// Reset the kernel memory fail counter
pub fn reset_kmem_fail_count(&self) -> Result<()> { pub fn reset_kmem_fail_count(&self) -> Result<()> {
// Ignore kmem because there is no kmem in cgroup v2
if self.v2 {
return Ok(());
}
self.open_path("memory.kmem.failcnt", true) self.open_path("memory.kmem.failcnt", true)
.and_then(|mut file| { .and_then(|mut file| {
file.write_all("0".to_string().as_ref()) file.write_all("0".to_string().as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(
WriteFailed("memory.kmem.failcnt".to_string(), "0".to_string()),
e,
)
})
}) })
} }
/// Reset the TCP related fail counter /// Reset the TCP related fail counter
pub fn reset_tcp_fail_count(&self) -> Result<()> { pub fn reset_tcp_fail_count(&self) -> Result<()> {
// Ignore kmem because there is no kmem in cgroup v2
if self.v2 {
return Ok(());
}
self.open_path("memory.kmem.tcp.failcnt", true) self.open_path("memory.kmem.tcp.failcnt", true)
.and_then(|mut file| { .and_then(|mut file| {
file.write_all("0".to_string().as_ref()) file.write_all("0".to_string().as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(
WriteFailed("memory.kmem.tcp.failcnt".to_string(), "0".to_string()),
e,
)
})
}) })
} }
@@ -727,50 +819,96 @@ impl MemController {
pub fn reset_memswap_fail_count(&self) -> Result<()> { pub fn reset_memswap_fail_count(&self) -> Result<()> {
self.open_path("memory.memsw.failcnt", true) self.open_path("memory.memsw.failcnt", true)
.and_then(|mut file| { .and_then(|mut file| {
file.write_all("0".to_string().as_ref()) file.write_all("0".to_string().as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(
WriteFailed("memory.memsw.failcnt".to_string(), "0".to_string()),
e,
)
})
})
}
/// Reset the max memory usage recorded
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("memory.max_usage_in_bytes".to_string(), "0".to_string()),
e,
)
})
}) })
} }
/// Set the memory usage limit of the control group, in bytes. /// Set the memory usage limit of the control group, in bytes.
pub fn set_limit(&self, limit: i64) -> Result<()> { 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 { if self.v2 {
file = "memory.max"; file_name = "memory.max";
} }
self.open_path(file, true).and_then(|mut file| { self.open_path(file_name, true).and_then(|mut file| {
file.write_all(limit.to_string().as_ref()) file.write_all(limit.to_string().as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(WriteFailed(file_name.to_string(), limit.to_string()), e)
})
}) })
} }
/// Set the kernel memory limit of the control group, in bytes. /// Set the kernel memory limit of the control group, in bytes.
pub fn set_kmem_limit(&self, limit: i64) -> Result<()> { pub fn set_kmem_limit(&self, limit: i64) -> Result<()> {
// Ignore kmem because there is no kmem in cgroup v2
if self.v2 {
return Ok(());
}
self.open_path("memory.kmem.limit_in_bytes", true) self.open_path("memory.kmem.limit_in_bytes", true)
.and_then(|mut file| { .and_then(|mut file| {
file.write_all(limit.to_string().as_ref()) let r = file.write_all(limit.to_string().as_ref());
.map_err(|e| Error::with_cause(WriteFailed, e)) 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. /// Set the memory+swap limit of the control group, in bytes.
pub fn set_memswap_limit(&self, limit: i64) -> Result<()> { 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 { if self.v2 {
file = "memory.swap.max"; file_name = "memory.swap.max";
} }
self.open_path(file, true).and_then(|mut file| { self.open_path(file_name, true).and_then(|mut file| {
file.write_all(limit.to_string().as_ref()) file.write_all(limit.to_string().as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(WriteFailed(file_name.to_string(), limit.to_string()), e)
})
}) })
} }
/// Set how much kernel memory can be used for TCP-related buffers by the control group. /// Set how much kernel memory can be used for TCP-related buffers by the control group.
pub fn set_tcp_limit(&self, limit: i64) -> Result<()> { pub fn set_tcp_limit(&self, limit: i64) -> Result<()> {
// Ignore kmem because there is no kmem in cgroup v2
if self.v2 {
return Ok(());
}
self.open_path("memory.kmem.tcp.limit_in_bytes", true) self.open_path("memory.kmem.tcp.limit_in_bytes", true)
.and_then(|mut file| { .and_then(|mut file| {
file.write_all(limit.to_string().as_ref()) file.write_all(limit.to_string().as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(
WriteFailed(
"memory.kmem.tcp.limit_in_bytes".to_string(),
limit.to_string(),
),
e,
)
})
}) })
} }
@@ -779,13 +917,14 @@ impl MemController {
/// This limit is enforced when the system is nearing OOM conditions. Contrast this with the /// This limit is enforced when the system is nearing OOM conditions. Contrast this with the
/// hard limit, which is _always_ enforced. /// hard limit, which is _always_ enforced.
pub fn set_soft_limit(&self, limit: i64) -> Result<()> { 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 { if self.v2 {
file = "memory.low" file_name = "memory.low"
} }
self.open_path(file, true).and_then(|mut file| { self.open_path(file_name, true).and_then(|mut file| {
file.write_all(limit.to_string().as_ref()) file.write_all(limit.to_string().as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(WriteFailed(file_name.to_string(), limit.to_string()), e)
})
}) })
} }
@@ -794,18 +933,27 @@ impl MemController {
/// ///
/// Note that a value of zero does not imply that the process will not be swapped out. /// 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<()> { pub fn set_swappiness(&self, swp: u64) -> Result<()> {
self.open_path("memory.swappiness", true) let mut file_name = "memory.swappiness";
.and_then(|mut file| { if self.v2 {
file.write_all(swp.to_string().as_ref()) file_name = "memory.swap.max"
.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<()> { pub fn disable_oom_killer(&self) -> Result<()> {
self.open_path("memory.oom_control", true) self.open_path("memory.oom_control", true)
.and_then(|mut file| { .and_then(|mut file| {
file.write_all("1".to_string().as_ref()) file.write_all("1".to_string().as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(
WriteFailed("memory.oom_control".to_string(), "1".to_string()),
e,
)
})
}) })
} }
@@ -824,6 +972,8 @@ impl ControllIdentifier for MemController {
} }
} }
impl CustomizedAttribute for MemController {}
impl<'a> From<&'a Subsystem> for &'a MemController { impl<'a> From<&'a Subsystem> for &'a MemController {
fn from(sub: &'a Subsystem) -> &'a MemController { fn from(sub: &'a Subsystem) -> &'a MemController {
unsafe { unsafe {
@@ -831,49 +981,19 @@ impl<'a> From<&'a Subsystem> for &'a MemController {
Subsystem::Mem(c) => c, Subsystem::Mem(c) => c,
_ => { _ => {
assert_eq!(1, 0); assert_eq!(1, 0);
::std::mem::uninitialized() let v = std::mem::MaybeUninit::uninit();
v.assume_init()
} }
} }
} }
} }
} }
fn read_u64_from(mut file: File) -> Result<u64> {
let mut string = String::new();
match file.read_to_string(&mut string) {
Ok(_) => string
.trim()
.parse()
.map_err(|e| Error::with_cause(ParseError, e)),
Err(e) => Err(Error::with_cause(ReadFailed, e)),
}
}
fn read_i64_from(mut file: File) -> Result<i64> {
let mut string = String::new();
match file.read_to_string(&mut string) {
Ok(_) => string
.trim()
.parse()
.map_err(|e| Error::with_cause(ParseError, e)),
Err(e) => Err(Error::with_cause(ReadFailed, e)),
}
}
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)),
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::memory::{ use crate::memory::{
parse_memory_stat, parse_numa_stat, parse_oom_control, MemoryStat, NumaStat, OomControl, parse_memory_stat, parse_numa_stat, parse_oom_control, MemoryStat, NumaStat, OomControl,
}; };
use std::collections::HashMap;
static GOOD_VALUE: &str = "\ static GOOD_VALUE: &str = "\
total=51189 N0=51189 N1=123 total=51189 N0=51189 N1=123
@@ -886,7 +1006,24 @@ hierarchical_anon=770402 N0=770402 N1=123
hierarchical_unevictable=20 N0=20 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 oom_kill_disable 0
under_oom 1 under_oom 1
oom_kill 1337 oom_kill 1337
@@ -956,11 +1093,61 @@ total_unevictable 81920
hierarchical_unevictable_pages_per_node: vec![20, 123], 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] #[test]
fn test_parse_oom_control() { 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!( assert_eq!(
ok, ok,
OomControl { OomControl {
@@ -1014,7 +1201,7 @@ total_unevictable 81920
total_inactive_file: 1272135680, total_inactive_file: 1272135680,
total_active_file: 2338816000, total_active_file: 2338816000,
total_unevictable: 81920, total_unevictable: 81920,
raw: raw, raw,
} }
); );
} }

View File

@@ -7,13 +7,13 @@
//! //!
//! See the Kernel's documentation for more information about this subsystem, found at: //! See the Kernel's documentation for more information about this subsystem, found at:
//! [Documentation/cgroup-v1/net_cls.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/net_cls.txt) //! [Documentation/cgroup-v1/net_cls.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/net_cls.txt)
use std::fs::File; use std::io::Write;
use std::io::{Read, Write};
use std::path::PathBuf; use std::path::PathBuf;
use crate::error::ErrorKind::*; use crate::error::ErrorKind::*;
use crate::error::*; use crate::error::*;
use crate::read_u64_from;
use crate::{ use crate::{
ControllIdentifier, ControllerInternal, Controllers, NetworkResources, Resources, Subsystem, ControllIdentifier, ControllerInternal, Controllers, NetworkResources, Resources, Subsystem,
}; };
@@ -47,13 +47,9 @@ impl ControllerInternal for NetClsController {
// get the resources that apply to this controller // get the resources that apply to this controller
let res: &NetworkResources = &res.network; let res: &NetworkResources = &res.network;
if res.update_values { update_and_test!(self, set_class, res.class_id, get_class);
let _ = self.set_class(res.class_id);
if self.get_class()? != res.class_id { Ok(())
return Err(Error::new(Other));
}
}
return Ok(());
} }
} }
@@ -70,29 +66,17 @@ impl<'a> From<&'a Subsystem> for &'a NetClsController {
Subsystem::NetCls(c) => c, Subsystem::NetCls(c) => c,
_ => { _ => {
assert_eq!(1, 0); assert_eq!(1, 0);
::std::mem::uninitialized() let v = std::mem::MaybeUninit::uninit();
v.assume_init()
} }
} }
} }
} }
} }
fn read_u64_from(mut file: File) -> Result<u64> {
let mut string = String::new();
match file.read_to_string(&mut string) {
Ok(_) => string
.trim()
.parse()
.map_err(|e| Error::with_cause(ParseError, e)),
Err(e) => Err(Error::with_cause(ReadFailed, e)),
}
}
impl NetClsController { impl NetClsController {
/// Constructs a new `NetClsController` with `oroot` serving as the root of the control group. /// Constructs a new `NetClsController` with `root` serving as the root of the control group.
pub fn new(oroot: PathBuf) -> Self { pub fn new(root: PathBuf) -> Self {
let mut root = oroot;
root.push(Self::controller_type().to_string());
Self { Self {
base: root.clone(), base: root.clone(),
path: root, path: root,
@@ -104,14 +88,15 @@ impl NetClsController {
self.open_path("net_cls.classid", true) self.open_path("net_cls.classid", true)
.and_then(|mut file| { .and_then(|mut file| {
let s = format!("{:#08X}", class); let s = format!("{:#08X}", class);
file.write_all(s.as_ref()) file.write_all(s.as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, 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. /// Get the network class id of the outgoing packets of the control group's tasks.
pub fn get_class(&self) -> Result<u64> { pub fn get_class(&self) -> Result<u64> {
self.open_path("net_cls.classid", false) self.open_path("net_cls.classid", false)
.and_then(|file| read_u64_from(file)) .and_then(read_u64_from)
} }
} }

View File

@@ -8,13 +8,13 @@
//! See the Kernel's documentation for more information about this subsystem, found at: //! See the Kernel's documentation for more information about this subsystem, found at:
//! [Documentation/cgroup-v1/net_prio.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/net_prio.txt) //! [Documentation/cgroup-v1/net_prio.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/net_prio.txt)
use std::collections::HashMap; use std::collections::HashMap;
use std::fs::File; use std::io::{BufRead, BufReader, Write};
use std::io::{BufRead, BufReader, Read, Write};
use std::path::PathBuf; use std::path::PathBuf;
use crate::error::ErrorKind::*; use crate::error::ErrorKind::*;
use crate::error::*; use crate::error::*;
use crate::read_u64_from;
use crate::{ use crate::{
ControllIdentifier, ControllerInternal, Controllers, NetworkResources, Resources, Subsystem, ControllIdentifier, ControllerInternal, Controllers, NetworkResources, Resources, Subsystem,
}; };
@@ -48,10 +48,8 @@ impl ControllerInternal for NetPrioController {
// get the resources that apply to this controller // get the resources that apply to this controller
let res: &NetworkResources = &res.network; let res: &NetworkResources = &res.network;
if res.update_values { for i in &res.priorities {
for i in &res.priorities { let _ = self.set_if_prio(&i.name, i.priority);
let _ = self.set_if_prio(&i.name, i.priority);
}
} }
Ok(()) Ok(())
@@ -71,29 +69,17 @@ impl<'a> From<&'a Subsystem> for &'a NetPrioController {
Subsystem::NetPrio(c) => c, Subsystem::NetPrio(c) => c,
_ => { _ => {
assert_eq!(1, 0); assert_eq!(1, 0);
::std::mem::uninitialized() let v = std::mem::MaybeUninit::uninit();
v.assume_init()
} }
} }
} }
} }
} }
fn read_u64_from(mut file: File) -> Result<u64> {
let mut string = String::new();
match file.read_to_string(&mut string) {
Ok(_) => string
.trim()
.parse()
.map_err(|e| Error::with_cause(ParseError, e)),
Err(e) => Err(Error::with_cause(ReadFailed, e)),
}
}
impl NetPrioController { impl NetPrioController {
/// Constructs a new `NetPrioController` with `oroot` serving as the root of the control group. /// Constructs a new `NetPrioController` with `root` serving as the root of the control group.
pub fn new(oroot: PathBuf) -> Self { pub fn new(root: PathBuf) -> Self {
let mut root = oroot;
root.push(Self::controller_type().to_string());
Self { Self {
base: root.clone(), base: root.clone(),
path: root, path: root,
@@ -108,6 +94,7 @@ impl NetPrioController {
} }
/// A map of priorities for each network interface. /// A map of priorities for each network interface.
#[allow(clippy::iter_nth_zero, clippy::unnecessary_unwrap)]
pub fn ifpriomap(&self) -> Result<HashMap<String, u64>> { pub fn ifpriomap(&self) -> Result<HashMap<String, u64>> {
self.open_path("net_prio.ifpriomap", false) self.open_path("net_prio.ifpriomap", false)
.and_then(|file| { .and_then(|file| {
@@ -119,6 +106,7 @@ impl NetPrioController {
let mut acc = acc.unwrap(); let mut acc = acc.unwrap();
let l = line.unwrap(); let l = line.unwrap();
let mut sp = l.split_whitespace(); let mut sp = l.split_whitespace();
let ifname = sp.nth(0); let ifname = sp.nth(0);
let ifprio = sp.nth(1); let ifprio = sp.nth(1);
if ifname.is_none() || ifprio.is_none() { if ifname.is_none() || ifprio.is_none() {
@@ -144,7 +132,15 @@ impl NetPrioController {
self.open_path("net_prio.ifpriomap", true) self.open_path("net_prio.ifpriomap", true)
.and_then(|mut file| { .and_then(|mut file| {
file.write_all(format!("{} {}", eif, prio).as_ref()) 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

@@ -55,7 +55,8 @@ impl<'a> From<&'a Subsystem> for &'a PerfEventController {
Subsystem::PerfEvent(c) => c, Subsystem::PerfEvent(c) => c,
_ => { _ => {
assert_eq!(1, 0); assert_eq!(1, 0);
::std::mem::uninitialized() let v = std::mem::MaybeUninit::uninit();
v.assume_init()
} }
} }
} }
@@ -63,10 +64,8 @@ impl<'a> From<&'a Subsystem> for &'a PerfEventController {
} }
impl PerfEventController { impl PerfEventController {
/// Constructs a new `PerfEventController` with `oroot` serving as the root of the control group. /// Constructs a new `PerfEventController` with `root` serving as the root of the control group.
pub fn new(oroot: PathBuf) -> Self { pub fn new(root: PathBuf) -> Self {
let mut root = oroot;
root.push(Self::controller_type().to_string());
Self { Self {
base: root.clone(), base: root.clone(),
path: root, path: root,

View File

@@ -8,13 +8,13 @@
//! //!
//! See the Kernel's documentation for more information about this subsystem, found at: //! See the Kernel's documentation for more information about this subsystem, found at:
//! [Documentation/cgroups-v1/pids.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/pids.txt) //! [Documentation/cgroups-v1/pids.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/pids.txt)
use std::fs::File;
use std::io::{Read, Write}; use std::io::{Read, Write};
use std::path::PathBuf; use std::path::PathBuf;
use crate::error::ErrorKind::*; use crate::error::ErrorKind::*;
use crate::error::*; use crate::error::*;
use crate::read_u64_from;
use crate::{ use crate::{
parse_max_value, ControllIdentifier, ControllerInternal, Controllers, MaxValue, PidResources, parse_max_value, ControllIdentifier, ControllerInternal, Controllers, MaxValue, PidResources,
Resources, Subsystem, Resources, Subsystem,
@@ -50,17 +50,13 @@ impl ControllerInternal for PidController {
// get the resources that apply to this controller // get the resources that apply to this controller
let pidres: &PidResources = &res.pid; let pidres: &PidResources = &res.pid;
if pidres.update_values { // apply pid_max
// apply pid_max update_and_test!(
let _ = self.set_pid_max(pidres.maximum_number_of_processes); self,
set_pid_max,
// now, verify pidres.maximum_number_of_processes,
if self.get_pid_max()? == pidres.maximum_number_of_processes { get_pid_max
return Ok(()); );
} else {
return Err(Error::new(Other));
}
}
Ok(()) Ok(())
} }
@@ -85,36 +81,22 @@ impl<'a> From<&'a Subsystem> for &'a PidController {
Subsystem::Pid(c) => c, Subsystem::Pid(c) => c,
_ => { _ => {
assert_eq!(1, 0); assert_eq!(1, 0);
::std::mem::uninitialized() let v = std::mem::MaybeUninit::uninit();
v.assume_init()
} }
} }
} }
} }
} }
fn read_u64_from(mut file: File) -> Result<u64> {
let mut string = String::new();
match file.read_to_string(&mut string) {
Ok(_) => string
.trim()
.parse()
.map_err(|e| Error::with_cause(ParseError, e)),
Err(e) => Err(Error::with_cause(ReadFailed, e)),
}
}
impl PidController { impl PidController {
/// Constructors a new `PidController` instance, with `oroot` serving as the controller's root /// Constructors a new `PidController` instance, with `root` serving as the controller's root
/// directory. /// directory.
pub fn new(oroot: PathBuf, v2: bool) -> Self { pub fn new(root: PathBuf, v2: bool) -> Self {
let mut root = oroot;
if !v2 {
root.push(Self::controller_type().to_string());
}
Self { Self {
base: root.clone(), base: root.clone(),
path: root, path: root,
v2: v2, v2,
} }
} }
@@ -130,7 +112,7 @@ impl PidController {
}, },
None => Err(Error::new(ParseError)), 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)),
} }
}) })
} }
@@ -148,7 +130,7 @@ impl PidController {
let res = file.read_to_string(&mut string); let res = file.read_to_string(&mut string);
match res { match res {
Ok(_) => parse_max_value(&string), 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)),
} }
}) })
} }
@@ -163,7 +145,10 @@ impl PidController {
let string_to_write = max_pid.to_string(); let string_to_write = max_pid.to_string();
match file.write_all(string_to_write.as_ref()) { match file.write_all(string_to_write.as_ref()) {
Ok(_) => Ok(()), 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

@@ -7,13 +7,13 @@
//! //!
//! See the Kernel's documentation for more information about this subsystem, found at: //! See the Kernel's documentation for more information about this subsystem, found at:
//! [Documentation/cgroup-v1/rdma.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/rdma.txt) //! [Documentation/cgroup-v1/rdma.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/rdma.txt)
use std::fs::File; use std::io::Write;
use std::io::{Read, Write};
use std::path::PathBuf; use std::path::PathBuf;
use crate::error::ErrorKind::*; use crate::error::ErrorKind::*;
use crate::error::*; use crate::error::*;
use crate::read_string_from;
use crate::{ControllIdentifier, ControllerInternal, Controllers, Resources, Subsystem}; use crate::{ControllIdentifier, ControllerInternal, Controllers, Resources, Subsystem};
/// A controller that allows controlling the `rdma` subsystem of a Cgroup. /// A controller that allows controlling the `rdma` subsystem of a Cgroup.
@@ -58,26 +58,17 @@ impl<'a> From<&'a Subsystem> for &'a RdmaController {
Subsystem::Rdma(c) => c, Subsystem::Rdma(c) => c,
_ => { _ => {
assert_eq!(1, 0); assert_eq!(1, 0);
::std::mem::uninitialized() let v = std::mem::MaybeUninit::uninit();
v.assume_init()
} }
} }
} }
} }
} }
fn read_string_from(mut file: File) -> Result<String> {
let mut string = String::new();
match file.read_to_string(&mut string) {
Ok(_) => Ok(string.trim().to_string()),
Err(e) => Err(Error::with_cause(ReadFailed, e)),
}
}
impl RdmaController { impl RdmaController {
/// Constructs a new `RdmaController` with `oroot` serving as the root of the control group. /// Constructs a new `RdmaController` with `root` serving as the root of the control group.
pub fn new(oroot: PathBuf) -> Self { pub fn new(root: PathBuf) -> Self {
let mut root = oroot;
root.push(Self::controller_type().to_string());
Self { Self {
base: root.clone(), base: root.clone(),
path: root, path: root,
@@ -93,8 +84,9 @@ impl RdmaController {
/// Set a maximum usage for each RDMA/IB resource. /// Set a maximum usage for each RDMA/IB resource.
pub fn set_max(&self, max: &str) -> Result<()> { pub fn set_max(&self, max: &str) -> Result<()> {
self.open_path("rdma.max", true).and_then(|mut file| { self.open_path("rdma.max", true).and_then(|mut file| {
file.write_all(max.as_ref()) file.write_all(max.as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(WriteFailed("rdma.max".to_string(), max.to_string()), e)
})
}) })
} }
} }

View File

@@ -7,7 +7,6 @@
//! //!
use std::path::PathBuf; use std::path::PathBuf;
use crate::error::ErrorKind::*;
use crate::error::*; use crate::error::*;
use crate::{ControllIdentifier, ControllerInternal, Controllers, Resources, Subsystem}; use crate::{ControllIdentifier, ControllerInternal, Controllers, Resources, Subsystem};
@@ -18,7 +17,7 @@ use crate::{ControllIdentifier, ControllerInternal, Controllers, Resources, Subs
pub struct SystemdController { pub struct SystemdController {
base: PathBuf, base: PathBuf,
path: PathBuf, path: PathBuf,
v2: bool, _v2: bool,
} }
impl ControllerInternal for SystemdController { impl ControllerInternal for SystemdController {
@@ -53,7 +52,8 @@ impl<'a> From<&'a Subsystem> for &'a SystemdController {
Subsystem::Systemd(c) => c, Subsystem::Systemd(c) => c,
_ => { _ => {
assert_eq!(1, 0); assert_eq!(1, 0);
::std::mem::uninitialized() let v = std::mem::MaybeUninit::uninit();
v.assume_init()
} }
} }
} }
@@ -61,16 +61,12 @@ impl<'a> From<&'a Subsystem> for &'a SystemdController {
} }
impl SystemdController { impl SystemdController {
/// Constructs a new `SystemdController` with `oroot` serving as the root of the control group. /// Constructs a new `SystemdController` with `root` serving as the root of the control group.
pub fn new(oroot: PathBuf, v2: bool) -> Self { pub fn new(root: PathBuf, v2: bool) -> Self {
let mut root = oroot;
if !v2 {
root.push(Self::controller_type().to_string());
}
Self { Self {
base: root.clone(), base: root.clone(),
path: root, path: root,
v2: v2, _v2: v2,
} }
} }
} }

View File

@@ -5,25 +5,24 @@
// //
//! Some simple tests covering the builder pattern for control groups. //! Some simple tests covering the builder pattern for control groups.
use cgroups::blkio::*; use cgroups_rs::blkio::*;
use cgroups::cgroup_builder::*; use cgroups_rs::cgroup_builder::*;
use cgroups::cpu::*; use cgroups_rs::cpu::*;
use cgroups::devices::*; use cgroups_rs::devices::*;
use cgroups::hugetlb::*; use cgroups_rs::hugetlb::*;
use cgroups::memory::*; use cgroups_rs::memory::*;
use cgroups::net_cls::*; use cgroups_rs::net_cls::*;
use cgroups::pid::*; use cgroups_rs::pid::*;
use cgroups::*; use cgroups_rs::*;
#[test] #[test]
pub fn test_cpu_res_build() { pub fn test_cpu_res_build() {
let h = cgroups::hierarchies::auto(); let h = cgroups_rs::hierarchies::auto();
let h = Box::new(&*h); let cg: Cgroup = CgroupBuilder::new("test_cpu_res_build")
let cg: Cgroup = CgroupBuilder::new("test_cpu_res_build", h)
.cpu() .cpu()
.shares(85) .shares(85)
.done() .done()
.build(); .build(h);
{ {
let cpu: &CpuController = cg.controller_of().unwrap(); let cpu: &CpuController = cg.controller_of().unwrap();
@@ -31,42 +30,41 @@ pub fn test_cpu_res_build() {
assert_eq!(cpu.shares().unwrap(), 85); assert_eq!(cpu.shares().unwrap(), 85);
} }
cg.delete(); cg.delete().unwrap();
} }
#[test] #[test]
pub fn test_memory_res_build() { pub fn test_memory_res_build() {
let h = cgroups::hierarchies::auto(); let h = cgroups_rs::hierarchies::auto();
let h = Box::new(&*h); let cg: Cgroup = CgroupBuilder::new("test_memory_res_build")
let cg: Cgroup = CgroupBuilder::new("test_memory_res_build", h)
.memory() .memory()
.kernel_memory_limit(128 * 1024 * 1024) .kernel_memory_limit(128 * 1024 * 1024)
.swappiness(70) .swappiness(70)
.memory_hard_limit(1024 * 1024 * 1024) .memory_hard_limit(1024 * 1024 * 1024)
.done() .done()
.build(); .build(h);
{ {
let c: &MemController = cg.controller_of().unwrap(); let c: &MemController = cg.controller_of().unwrap();
if !c.v2() { 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().swappiness, 70);
} }
assert_eq!(c.memory_stat().limit_in_bytes, 1024 * 1024 * 1024); assert_eq!(c.memory_stat().limit_in_bytes, 1024 * 1024 * 1024);
} }
cg.delete(); cg.delete().unwrap();
} }
#[test] #[test]
pub fn test_pid_res_build() { pub fn test_pid_res_build() {
let h = cgroups::hierarchies::auto(); let h = cgroups_rs::hierarchies::auto();
let h = Box::new(&*h); let cg: Cgroup = CgroupBuilder::new("test_pid_res_build")
let cg: Cgroup = CgroupBuilder::new("test_pid_res_build", h)
.pid() .pid()
.maximum_number_of_processes(MaxValue::Value(123)) .maximum_number_of_processes(MaxValue::Value(123))
.done() .done()
.build(); .build(h);
{ {
let c: &PidController = cg.controller_of().unwrap(); let c: &PidController = cg.controller_of().unwrap();
@@ -74,19 +72,18 @@ pub fn test_pid_res_build() {
assert_eq!(c.get_pid_max().unwrap(), MaxValue::Value(123)); assert_eq!(c.get_pid_max().unwrap(), MaxValue::Value(123));
} }
cg.delete(); cg.delete().unwrap();
} }
#[test] #[test]
#[ignore] // ignore this test for now, not sure why my kernel doesn't like it #[ignore] // ignore this test for now, not sure why my kernel doesn't like it
pub fn test_devices_res_build() { pub fn test_devices_res_build() {
let h = cgroups::hierarchies::auto(); let h = cgroups_rs::hierarchies::auto();
let h = Box::new(&*h); let cg: Cgroup = CgroupBuilder::new("test_devices_res_build")
let cg: Cgroup = CgroupBuilder::new("test_devices_res_build", h)
.devices() .devices()
.device(1, 6, DeviceType::Char, true, vec![DevicePermissions::Read]) .device(1, 6, DeviceType::Char, true, vec![DevicePermissions::Read])
.done() .done()
.build(); .build(h);
{ {
let c: &DevicesController = cg.controller_of().unwrap(); let c: &DevicesController = cg.controller_of().unwrap();
@@ -102,70 +99,64 @@ pub fn test_devices_res_build() {
}] }]
); );
} }
cg.delete(); cg.delete().unwrap();
} }
#[test] #[test]
pub fn test_network_res_build() { pub fn test_network_res_build() {
let h = cgroups::hierarchies::auto(); let h = cgroups_rs::hierarchies::auto();
if h.v2() { if h.v2() {
// FIXME add cases for v2 // FIXME add cases for v2
return; return;
} }
let h = Box::new(&*h); let cg: Cgroup = CgroupBuilder::new("test_network_res_build")
let cg: Cgroup = CgroupBuilder::new("test_network_res_build", h)
.network() .network()
.class_id(1337) .class_id(1337)
.done() .done()
.build(); .build(h);
{ {
let c: &NetClsController = cg.controller_of().unwrap(); let c: &NetClsController = cg.controller_of().unwrap();
assert!(c.get_class().is_ok()); assert!(c.get_class().is_ok());
assert_eq!(c.get_class().unwrap(), 1337); assert_eq!(c.get_class().unwrap(), 1337);
} }
cg.delete(); cg.delete().unwrap();
} }
#[test] #[test]
pub fn test_hugepages_res_build() { pub fn test_hugepages_res_build() {
let h = cgroups::hierarchies::auto(); let h = cgroups_rs::hierarchies::auto();
if h.v2() { if h.v2() {
// FIXME add cases for v2 // FIXME add cases for v2
return; return;
} }
let h = Box::new(&*h); let cg: Cgroup = CgroupBuilder::new("test_hugepages_res_build")
let cg: Cgroup = CgroupBuilder::new("test_hugepages_res_build", h)
.hugepages() .hugepages()
.limit("2MB".to_string(), 4 * 2 * 1024 * 1024) .limit("2MB".to_string(), 4 * 2 * 1024 * 1024)
.done() .done()
.build(); .build(h);
{ {
let c: &HugeTlbController = cg.controller_of().unwrap(); let c: &HugeTlbController = cg.controller_of().unwrap();
assert!(c.limit_in_bytes(&"2MB".to_string()).is_ok()); assert!(c.limit_in_bytes("2MB").is_ok());
assert_eq!( assert_eq!(c.limit_in_bytes("2MB").unwrap(), 4 * 2 * 1024 * 1024);
c.limit_in_bytes(&"2MB".to_string()).unwrap(),
4 * 2 * 1024 * 1024
);
} }
cg.delete(); cg.delete().unwrap();
} }
#[test] #[test]
#[ignore] // high version kernel not support `blkio.weight` #[ignore] // high version kernel not support `blkio.weight`
pub fn test_blkio_res_build() { pub fn test_blkio_res_build() {
let h = cgroups::hierarchies::auto(); let h = cgroups_rs::hierarchies::auto();
let h = Box::new(&*h); let cg: Cgroup = CgroupBuilder::new("test_blkio_res_build")
let cg: Cgroup = CgroupBuilder::new("test_blkio_res_build", h)
.blkio() .blkio()
.weight(Some(100)) .weight(100)
.done() .done()
.build(); .build(h);
{ {
let c: &BlkIoController = cg.controller_of().unwrap(); let c: &BlkIoController = cg.controller_of().unwrap();
assert_eq!(c.blkio().weight, 100); assert_eq!(c.blkio().weight, 100);
} }
cg.delete(); cg.delete().unwrap();
} }

View File

@@ -5,15 +5,13 @@
// //
//! Simple unit tests about the control groups system. //! Simple unit tests about the control groups system.
use cgroups::memory::{MemController, SetMemory}; use cgroups_rs::memory::MemController;
use cgroups::Controller; use cgroups_rs::Controller;
use cgroups::{Cgroup, CgroupPid, Hierarchy, Subsystem}; use cgroups_rs::{Cgroup, CgroupPid, Subsystem};
use std::collections::HashMap;
#[test] #[test]
fn test_tasks_iterator() { fn test_tasks_iterator() {
let h = cgroups::hierarchies::auto(); let h = cgroups_rs::hierarchies::auto();
let h = Box::new(&*h);
let pid = libc::pid_t::from(nix::unistd::getpid()) as u64; let pid = libc::pid_t::from(nix::unistd::getpid()) as u64;
let cg = Cgroup::new(h, String::from("test_tasks_iterator")); let cg = Cgroup::new(h, String::from("test_tasks_iterator"));
{ {
@@ -35,26 +33,22 @@ fn test_tasks_iterator() {
// Verify that it was indeed removed. // Verify that it was indeed removed.
assert_eq!(tasks.next(), None); assert_eq!(tasks.next(), None);
} }
cg.delete(); cg.delete().unwrap();
} }
#[test] #[test]
fn test_cgroup_with_relative_paths() { fn test_cgroup_with_relative_paths() {
if cgroups::hierarchies::is_cgroup2_unified_mode() { if cgroups_rs::hierarchies::is_cgroup2_unified_mode() {
return; return;
} }
let h = cgroups::hierarchies::auto(); let h = cgroups_rs::hierarchies::auto();
let cgroup_root = h.root(); let cgroup_root = h.root();
let h = Box::new(&*h);
let mut relative_paths = HashMap::new();
let mem_relative_path = "/mmm/abc/def";
relative_paths.insert("memory".to_string(), mem_relative_path.to_string());
let cgroup_name = "test_cgroup_with_relative_paths"; let cgroup_name = "test_cgroup_with_relative_paths";
let cg = Cgroup::new_with_relative_paths(h, String::from(cgroup_name), relative_paths); let cg = Cgroup::load(h, String::from(cgroup_name));
{ {
let subsystems = cg.subsystems(); let subsystems = cg.subsystems();
subsystems.into_iter().for_each(|sub| match sub { subsystems.iter().for_each(|sub| match sub {
Subsystem::Pid(c) => { Subsystem::Pid(c) => {
let cgroup_path = c.path().to_str().unwrap(); let cgroup_path = c.path().to_str().unwrap();
let relative_path = "/pids/"; let relative_path = "/pids/";
@@ -74,35 +68,29 @@ fn test_cgroup_with_relative_paths() {
// cgroup_path = cgroup_root + relative_path + cgroup_name // cgroup_path = cgroup_root + relative_path + cgroup_name
assert_eq!( assert_eq!(
cgroup_path, cgroup_path,
format!( format!("{}/memory/{}", cgroup_root.to_str().unwrap(), cgroup_name)
"{}/memory{}/{}",
cgroup_root.to_str().unwrap(),
mem_relative_path,
cgroup_name
)
); );
} }
_ => {} _ => {}
}); });
} }
cg.delete(); cg.delete().unwrap();
} }
#[test] #[test]
fn test_cgroup_v2() { fn test_cgroup_v2() {
if !cgroups::hierarchies::is_cgroup2_unified_mode() { if !cgroups_rs::hierarchies::is_cgroup2_unified_mode() {
return; return;
} }
let h = cgroups::hierarchies::auto(); let h = cgroups_rs::hierarchies::auto();
let h = Box::new(&*h); let cg = Cgroup::new(h, String::from("test_v2"));
let cg = Cgroup::new_with_relative_paths(h, String::from("test_v2"), HashMap::new());
let mem_controller: &MemController = cg.controller_of().unwrap(); let mem_controller: &MemController = cg.controller_of().unwrap();
let (mem, swp, rev) = (4 * 1024 * 1000, 2 * 1024 * 1000, 1024 * 1000); let (mem, swp, rev) = (4 * 1024 * 1000, 2 * 1024 * 1000, 1024 * 1000);
let _ = mem_controller.set_limit(mem); mem_controller.set_limit(mem).unwrap();
let _ = mem_controller.set_memswap_limit(swp); mem_controller.set_memswap_limit(swp).unwrap();
let _ = mem_controller.set_soft_limit(rev); mem_controller.set_soft_limit(rev).unwrap();
let memory_stat = mem_controller.memory_stat(); let memory_stat = mem_controller.memory_stat();
println!("memory_stat {:?}", memory_stat); println!("memory_stat {:?}", memory_stat);
@@ -113,5 +101,5 @@ fn test_cgroup_v2() {
println!("memswap {:?}", memswap); println!("memswap {:?}", memswap);
assert_eq!(swp, memswap.limit_in_bytes); assert_eq!(swp, memswap.limit_in_bytes);
cg.delete(); cg.delete().unwrap();
} }

View File

@@ -4,16 +4,12 @@
// //
//! Simple unit tests about the CPU control groups system. //! Simple unit tests about the CPU control groups system.
use cgroups::cpu::CpuController; use cgroups_rs::cpu::CpuController;
use cgroups::error::ErrorKind; use cgroups_rs::Cgroup;
use cgroups::{Cgroup, CgroupPid, CpuResources, Hierarchy, Resources};
use std::fs;
#[test] #[test]
fn test_cfs_quota_and_periods() { fn test_cfs_quota_and_periods() {
let h = cgroups::hierarchies::auto(); let h = cgroups_rs::hierarchies::auto();
let h = Box::new(&*h);
let cg = Cgroup::new(h, String::from("test_cfs_quota_and_periods")); let cg = Cgroup::new(h, String::from("test_cfs_quota_and_periods"));
let cpu_controller: &CpuController = cg.controller_of().unwrap(); let cpu_controller: &CpuController = cg.controller_of().unwrap();
@@ -27,7 +23,7 @@ fn test_cfs_quota_and_periods() {
assert_eq!(100000, current_peroid); assert_eq!(100000, current_peroid);
// case 1 set quota // case 1 set quota
let r = cpu_controller.set_cfs_quota(2000); let _ = cpu_controller.set_cfs_quota(2000);
let current_quota = cpu_controller.cfs_quota().unwrap(); let current_quota = cpu_controller.cfs_quota().unwrap();
let current_peroid = cpu_controller.cfs_period().unwrap(); let current_peroid = cpu_controller.cfs_period().unwrap();
@@ -35,14 +31,16 @@ fn test_cfs_quota_and_periods() {
assert_eq!(100000, current_peroid); assert_eq!(100000, current_peroid);
// case 2 set period // case 2 set period
cpu_controller.set_cfs_period(1000000); cpu_controller.set_cfs_period(1000000).unwrap();
let current_quota = cpu_controller.cfs_quota().unwrap(); let current_quota = cpu_controller.cfs_quota().unwrap();
let current_peroid = cpu_controller.cfs_period().unwrap(); let current_peroid = cpu_controller.cfs_period().unwrap();
assert_eq!(2000, current_quota); assert_eq!(2000, current_quota);
assert_eq!(1000000, current_peroid); assert_eq!(1000000, current_peroid);
// case 3 set both quota and period // case 3 set both quota and period
cpu_controller.set_cfs_quota_and_period(Some(5000), Some(100000)); cpu_controller
.set_cfs_quota_and_period(Some(5000), Some(100000))
.unwrap();
let current_quota = cpu_controller.cfs_quota().unwrap(); let current_quota = cpu_controller.cfs_quota().unwrap();
let current_peroid = cpu_controller.cfs_period().unwrap(); let current_peroid = cpu_controller.cfs_period().unwrap();
@@ -50,12 +48,14 @@ fn test_cfs_quota_and_periods() {
assert_eq!(100000, current_peroid); assert_eq!(100000, current_peroid);
// case 4 set both quota and period, set quota to -1 // case 4 set both quota and period, set quota to -1
cpu_controller.set_cfs_quota_and_period(Some(-1), None); cpu_controller
.set_cfs_quota_and_period(Some(-1), None)
.unwrap();
let current_quota = cpu_controller.cfs_quota().unwrap(); let current_quota = cpu_controller.cfs_quota().unwrap();
let current_peroid = cpu_controller.cfs_period().unwrap(); let current_peroid = cpu_controller.cfs_period().unwrap();
assert_eq!(-1, current_quota); assert_eq!(-1, current_quota);
assert_eq!(100000, current_peroid); assert_eq!(100000, current_peroid);
cg.delete(); cg.delete().unwrap();
} }

View File

@@ -4,16 +4,15 @@
// SPDX-License-Identifier: Apache-2.0 or MIT // SPDX-License-Identifier: Apache-2.0 or MIT
// //
use cgroups::cpuset::CpuSetController; use cgroups_rs::cpuset::CpuSetController;
use cgroups::error::ErrorKind; use cgroups_rs::error::ErrorKind;
use cgroups::{Cgroup, CgroupPid, CpuResources, Hierarchy, Resources}; use cgroups_rs::{Cgroup, CgroupPid};
use std::fs; use std::fs;
#[test] #[test]
fn test_cpuset_memory_pressure_root_cg() { fn test_cpuset_memory_pressure_root_cg() {
let h = cgroups::hierarchies::auto(); let h = cgroups_rs::hierarchies::auto();
let h = Box::new(&*h);
let cg = Cgroup::new(h, String::from("test_cpuset_memory_pressure_root_cg")); let cg = Cgroup::new(h, String::from("test_cpuset_memory_pressure_root_cg"));
{ {
let cpuset: &CpuSetController = cg.controller_of().unwrap(); let cpuset: &CpuSetController = cg.controller_of().unwrap();
@@ -22,13 +21,12 @@ fn test_cpuset_memory_pressure_root_cg() {
let res = cpuset.set_enable_memory_pressure(true); let res = cpuset.set_enable_memory_pressure(true);
assert_eq!(res.unwrap_err().kind(), &ErrorKind::InvalidOperation); assert_eq!(res.unwrap_err().kind(), &ErrorKind::InvalidOperation);
} }
cg.delete(); cg.delete().unwrap();
} }
#[test] #[test]
fn test_cpuset_set_cpus() { fn test_cpuset_set_cpus() {
let h = cgroups::hierarchies::auto(); let h = cgroups_rs::hierarchies::auto();
let h = Box::new(&*h);
let cg = Cgroup::new(h, String::from("test_cpuset_set_cpus")); let cg = Cgroup::new(h, String::from("test_cpuset_set_cpus"));
{ {
let cpuset: &CpuSetController = cg.controller_of().unwrap(); let cpuset: &CpuSetController = cg.controller_of().unwrap();
@@ -38,36 +36,34 @@ fn test_cpuset_set_cpus() {
assert_eq!(0, set.cpus.len()); assert_eq!(0, set.cpus.len());
} else { } else {
// for cgroup v1, cpuset is copied from parent. // for cgroup v1, cpuset is copied from parent.
assert_eq!(true, set.cpus.len() > 0); assert!(!set.cpus.is_empty());
} }
// 0 // 0
let r = cpuset.set_cpus("0"); let r = cpuset.set_cpus("0");
assert_eq!(true, r.is_ok()); assert!(r.is_ok());
let set = cpuset.cpuset(); let set = cpuset.cpuset();
assert_eq!(1, set.cpus.len()); assert_eq!(1, set.cpus.len());
assert_eq!((0, 0), set.cpus[0]); assert_eq!((0, 0), set.cpus[0]);
// all cpus in system // all cpus in system
let cpus = let cpus = fs::read_to_string("/sys/fs/cgroup/cpuset.cpus.effective").unwrap_or_default();
fs::read_to_string("/sys/fs/cgroup/cpuset.cpus.effective").unwrap_or("".to_string());
let cpus = cpus.trim(); let cpus = cpus.trim();
if cpus != "" { if !cpus.is_empty() {
let r = cpuset.set_cpus(&cpus); let r = cpuset.set_cpus(cpus);
assert_eq!(true, r.is_ok()); assert!(r.is_ok());
let set = cpuset.cpuset(); let set = cpuset.cpuset();
assert_eq!(1, set.cpus.len()); assert_eq!(1, set.cpus.len());
assert_eq!(format!("{}-{}", set.cpus[0].0, set.cpus[0].1), cpus); assert_eq!(format!("{}-{}", set.cpus[0].0, set.cpus[0].1), cpus);
} }
} }
cg.delete(); cg.delete().unwrap();
} }
#[test] #[test]
fn test_cpuset_set_cpus_add_task() { fn test_cpuset_set_cpus_add_task() {
let h = cgroups::hierarchies::auto(); let h = cgroups_rs::hierarchies::auto();
let h = Box::new(&*h);
let cg = Cgroup::new(h, String::from("test_cpuset_set_cpus_add_task/sub-dir")); let cg = Cgroup::new(h, String::from("test_cpuset_set_cpus_add_task/sub-dir"));
let cpuset: &CpuSetController = cg.controller_of().unwrap(); let cpuset: &CpuSetController = cg.controller_of().unwrap();
@@ -76,21 +72,21 @@ fn test_cpuset_set_cpus_add_task() {
assert_eq!(0, set.cpus.len()); assert_eq!(0, set.cpus.len());
} else { } else {
// for cgroup v1, cpuset is copied from parent. // 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. // Add a task to the control group.
let pid_i = libc::pid_t::from(nix::unistd::getpid()) as u64; let pid_i = libc::pid_t::from(nix::unistd::getpid()) as u64;
let _ = cg.add_task(CgroupPid::from(pid_i)); let _ = cg.add_task(CgroupPid::from(pid_i));
let tasks = cg.tasks(); let tasks = cg.tasks();
assert_eq!(true, tasks.len() > 0); assert!(!tasks.is_empty());
println!("tasks after added: {:?}", tasks); println!("tasks after added: {:?}", tasks);
// remove task // remove task
let _ = cg.remove_task(CgroupPid::from(pid_i)); cg.remove_task(CgroupPid::from(pid_i));
let tasks = cg.tasks(); let tasks = cg.tasks();
println!("tasks after deleted: {:?}", tasks); println!("tasks after deleted: {:?}", tasks);
assert_eq!(0, tasks.len()); assert_eq!(0, tasks.len());
cg.delete(); cg.delete().unwrap();
} }

View File

@@ -6,33 +6,34 @@
//! Integration tests about the devices subsystem //! Integration tests about the devices subsystem
use cgroups::devices::{DevicePermissions, DeviceType, DevicesController}; use cgroups_rs::devices::{DevicePermissions, DeviceType, DevicesController};
use cgroups::{Cgroup, DeviceResource, Hierarchy}; use cgroups_rs::{Cgroup, DeviceResource};
#[test] #[test]
fn test_devices_parsing() { fn test_devices_parsing() {
// now only v2 // now only v2
if cgroups::hierarchies::is_cgroup2_unified_mode() { if cgroups_rs::hierarchies::is_cgroup2_unified_mode() {
return; return;
} }
let h = cgroups::hierarchies::auto(); let h = cgroups_rs::hierarchies::auto();
let h = Box::new(&*h);
let cg = Cgroup::new(h, String::from("test_devices_parsing")); let cg = Cgroup::new(h, String::from("test_devices_parsing"));
{ {
let devices: &DevicesController = cg.controller_of().unwrap(); let devices: &DevicesController = cg.controller_of().unwrap();
// Deny access to all devices first // Deny access to all devices first
devices.deny_device( devices
DeviceType::All, .deny_device(
-1, DeviceType::All,
-1, -1,
&vec![ -1,
DevicePermissions::Read, &[
DevicePermissions::Write, DevicePermissions::Read,
DevicePermissions::MkNod, DevicePermissions::Write,
], DevicePermissions::MkNod,
); ],
)
.unwrap();
// Acquire the list of allowed devices after we denied all // Acquire the list of allowed devices after we denied all
let allowed_devices = devices.allowed_devices(); let allowed_devices = devices.allowed_devices();
// Verify that there are no devices that we can access. // Verify that there are no devices that we can access.
@@ -40,7 +41,9 @@ fn test_devices_parsing() {
assert_eq!(allowed_devices.unwrap(), Vec::new()); assert_eq!(allowed_devices.unwrap(), Vec::new());
// Now add mknod access to /dev/null device // Now add mknod access to /dev/null device
devices.allow_device(DeviceType::Char, 1, 3, &vec![DevicePermissions::MkNod]); devices
.allow_device(DeviceType::Char, 1, 3, &[DevicePermissions::MkNod])
.unwrap();
let allowed_devices = devices.allowed_devices(); let allowed_devices = devices.allowed_devices();
assert!(allowed_devices.is_ok()); assert!(allowed_devices.is_ok());
let allowed_devices = allowed_devices.unwrap(); let allowed_devices = allowed_devices.unwrap();
@@ -57,12 +60,14 @@ fn test_devices_parsing() {
); );
// Now deny, this device explicitly. // Now deny, this device explicitly.
devices.deny_device(DeviceType::Char, 1, 3, &DevicePermissions::all()); devices
.deny_device(DeviceType::Char, 1, 3, &DevicePermissions::all())
.unwrap();
// Finally, check that. // Finally, check that.
let allowed_devices = devices.allowed_devices(); let allowed_devices = devices.allowed_devices();
// Verify that there are no devices that we can access. // Verify that there are no devices that we can access.
assert!(allowed_devices.is_ok()); assert!(allowed_devices.is_ok());
assert_eq!(allowed_devices.unwrap(), Vec::new()); assert_eq!(allowed_devices.unwrap(), Vec::new());
} }
cg.delete(); cg.delete().unwrap();
} }

View File

@@ -4,27 +4,23 @@
// //
//! Integration tests about the hugetlb subsystem //! Integration tests about the hugetlb subsystem
use cgroups::hugetlb::{self, HugeTlbController}; use cgroups_rs::error::*;
use cgroups::Controller; use cgroups_rs::hugetlb::{self, HugeTlbController};
use cgroups::{Cgroup, Hierarchy}; use cgroups_rs::Cgroup;
use cgroups::error::ErrorKind::*;
use cgroups::error::*;
use std::fs; use std::fs;
#[test] #[test]
fn test_hugetlb_sizes() { fn test_hugetlb_sizes() {
// now only v2 // now only v2
if cgroups::hierarchies::is_cgroup2_unified_mode() { if cgroups_rs::hierarchies::is_cgroup2_unified_mode() {
return; return;
} }
let h = cgroups::hierarchies::auto(); let h = cgroups_rs::hierarchies::auto();
let h = Box::new(&*h);
let cg = Cgroup::new(h, String::from("test_hugetlb_sizes")); let cg = Cgroup::new(h, String::from("test_hugetlb_sizes"));
{ {
let hugetlb_controller: &HugeTlbController = cg.controller_of().unwrap(); let hugetlb_controller: &HugeTlbController = cg.controller_of().unwrap();
let sizes = hugetlb_controller.get_sizes(); let _ = hugetlb_controller.get_sizes();
// test sizes count // test sizes count
let sizes = hugetlb_controller.get_sizes(); let sizes = hugetlb_controller.get_sizes();
@@ -33,16 +29,16 @@ fn test_hugetlb_sizes() {
for size in sizes { for size in sizes {
let supported = hugetlb_controller.size_supported(&size); 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.failcnt(&size));
assert_no_error(hugetlb_controller.limit_in_bytes(&size)); assert_no_error(hugetlb_controller.limit_in_bytes(&size));
assert_no_error(hugetlb_controller.usage_in_bytes(&size)); assert_no_error(hugetlb_controller.usage_in_bytes(&size));
assert_no_error(hugetlb_controller.max_usage_in_bytes(&size)); assert_no_error(hugetlb_controller.max_usage_in_bytes(&size));
} }
} }
cg.delete(); cg.delete().unwrap();
} }
fn assert_no_error(r: Result<u64>) { fn assert_no_error(r: Result<u64>) {
assert_eq!(!r.is_err(), true) assert!(r.is_ok())
} }

View File

@@ -4,44 +4,57 @@
// //
//! Integration tests about the hugetlb subsystem //! Integration tests about the hugetlb subsystem
use cgroups::memory::{MemController, SetMemory}; use cgroups_rs::memory::{MemController, SetMemory};
use cgroups::Controller; use cgroups_rs::Controller;
use cgroups::{Cgroup, MaxValue}; use cgroups_rs::{Cgroup, MaxValue};
#[test] #[test]
fn test_disable_oom_killer() { fn test_disable_oom_killer() {
let h = cgroups::hierarchies::auto(); let h = cgroups_rs::hierarchies::auto();
let h = Box::new(&*h);
let cg = Cgroup::new(h, String::from("test_disable_oom_killer")); let cg = Cgroup::new(h, String::from("test_disable_oom_killer"));
{ {
let mem_controller: &MemController = cg.controller_of().unwrap(); let mem_controller: &MemController = cg.controller_of().unwrap();
// before disable // before disable
let m = mem_controller.memory_stat(); 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 // now only v1
if !mem_controller.v2() { if !mem_controller.v2() {
// disable oom killer // disable oom killer
let r = mem_controller.disable_oom_killer(); let r = mem_controller.disable_oom_killer();
assert_eq!(r.is_err(), false); assert!(r.is_ok());
// after disable // after disable
let m = mem_controller.memory_stat(); let m = mem_controller.memory_stat();
assert_eq!(m.oom_control.oom_kill_disable, true); assert!(m.oom_control.oom_kill_disable);
} }
} }
cg.delete(); 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] #[test]
fn set_mem_v2() { fn set_mem_v2() {
let h = cgroups::hierarchies::auto(); let h = cgroups_rs::hierarchies::auto();
if !h.v2() { if !h.v2() {
return; return;
} }
let h = Box::new(&*h);
let cg = Cgroup::new(h, String::from("set_mem_v2")); let cg = Cgroup::new(h, String::from("set_mem_v2"));
{ {
let mem_controller: &MemController = cg.controller_of().unwrap(); let mem_controller: &MemController = cg.controller_of().unwrap();
@@ -62,7 +75,7 @@ fn set_mem_v2() {
max: None, max: None,
}; };
let r = mem_controller.set_mem(m); let r = mem_controller.set_mem(m);
assert_eq!(true, r.is_ok()); assert!(r.is_ok());
let m = mem_controller.get_mem().unwrap(); let m = mem_controller.get_mem().unwrap();
// get // get
@@ -79,7 +92,7 @@ fn set_mem_v2() {
low: None, low: None,
}; };
let r = mem_controller.set_mem(m); let r = mem_controller.set_mem(m);
assert_eq!(true, r.is_ok()); assert!(r.is_ok());
let m = mem_controller.get_mem().unwrap(); let m = mem_controller.get_mem().unwrap();
// get // get
@@ -89,5 +102,5 @@ fn set_mem_v2() {
assert_eq!(m.high, Some(MaxValue::Max)); assert_eq!(m.high, Some(MaxValue::Max));
} }
cg.delete(); cg.delete().unwrap();
} }

View File

@@ -5,49 +5,44 @@
// //
//! Integration tests about the pids subsystem //! Integration tests about the pids subsystem
use cgroups::pid::PidController; use cgroups_rs::pid::PidController;
use cgroups::Controller; use cgroups_rs::Controller;
use cgroups::{Cgroup, CgroupPid, Hierarchy, MaxValue, PidResources, Resources}; use cgroups_rs::{Cgroup, MaxValue};
use nix::sys::wait::{waitpid, WaitStatus}; use nix::sys::wait::{waitpid, WaitStatus};
use nix::unistd::{fork, ForkResult, Pid}; use nix::unistd::{fork, ForkResult};
use libc::pid_t; use libc::pid_t;
use std::thread;
#[test] #[test]
fn create_and_delete_cgroup() { fn create_and_delete_cgroup() {
let h = cgroups::hierarchies::auto(); let h = cgroups_rs::hierarchies::auto();
let h = Box::new(&*h);
let cg = Cgroup::new(h, String::from("create_and_delete_cgroup")); let cg = Cgroup::new(h, String::from("create_and_delete_cgroup"));
{ {
let pidcontroller: &PidController = cg.controller_of().unwrap(); let pidcontroller: &PidController = cg.controller_of().unwrap();
pidcontroller.set_pid_max(MaxValue::Value(1337)); pidcontroller.set_pid_max(MaxValue::Value(1337)).unwrap();
let max = pidcontroller.get_pid_max(); let max = pidcontroller.get_pid_max();
assert!(max.is_ok()); assert!(max.is_ok());
assert_eq!(max.unwrap(), MaxValue::Value(1337)); assert_eq!(max.unwrap(), MaxValue::Value(1337));
} }
cg.delete(); cg.delete().unwrap();
} }
#[test] #[test]
fn test_pids_current_is_zero() { fn test_pids_current_is_zero() {
let h = cgroups::hierarchies::auto(); let h = cgroups_rs::hierarchies::auto();
let h = Box::new(&*h);
let cg = Cgroup::new(h, String::from("test_pids_current_is_zero")); let cg = Cgroup::new(h, String::from("test_pids_current_is_zero"));
{ {
let pidcontroller: &PidController = cg.controller_of().unwrap(); let pidcontroller: &PidController = cg.controller_of().unwrap();
let current = pidcontroller.get_pid_current(); let current = pidcontroller.get_pid_current();
assert_eq!(current.unwrap(), 0); assert_eq!(current.unwrap(), 0);
} }
cg.delete(); cg.delete().unwrap();
} }
#[test] #[test]
fn test_pids_events_is_zero() { fn test_pids_events_is_zero() {
let h = cgroups::hierarchies::auto(); let h = cgroups_rs::hierarchies::auto();
let h = Box::new(&*h);
let cg = Cgroup::new(h, String::from("test_pids_events_is_zero")); let cg = Cgroup::new(h, String::from("test_pids_events_is_zero"));
{ {
let pidcontroller: &PidController = cg.controller_of().unwrap(); let pidcontroller: &PidController = cg.controller_of().unwrap();
@@ -55,20 +50,19 @@ fn test_pids_events_is_zero() {
assert!(events.is_ok()); assert!(events.is_ok());
assert_eq!(events.unwrap(), 0); assert_eq!(events.unwrap(), 0);
} }
cg.delete(); cg.delete().unwrap();
} }
#[test] #[test]
fn test_pid_events_is_not_zero() { fn test_pid_events_is_not_zero() {
let h = cgroups::hierarchies::auto(); let h = cgroups_rs::hierarchies::auto();
let h = Box::new(&*h);
let cg = Cgroup::new(h, String::from("test_pid_events_is_not_zero")); let cg = Cgroup::new(h, String::from("test_pid_events_is_not_zero"));
{ {
let pids: &PidController = cg.controller_of().unwrap(); let pids: &PidController = cg.controller_of().unwrap();
let before = pids.get_pid_events(); let before = pids.get_pid_events();
let before = before.unwrap(); let before = before.unwrap();
match fork() { match unsafe { fork() } {
Ok(ForkResult::Parent { child, .. }) => { Ok(ForkResult::Parent { child, .. }) => {
// move the process into the control group // move the process into the control group
let _ = pids.add_task(&(pid_t::from(child) as u64).into()); let _ = pids.add_task(&(pid_t::from(child) as u64).into());
@@ -95,7 +89,7 @@ fn test_pid_events_is_not_zero() {
Ok(ForkResult::Child) => loop { Ok(ForkResult::Child) => loop {
let pids_max = pids.get_pid_max(); let pids_max = pids.get_pid_max();
if pids_max.is_ok() && pids_max.unwrap() == MaxValue::Value(1) { if pids_max.is_ok() && pids_max.unwrap() == MaxValue::Value(1) {
if let Err(_) = fork() { if unsafe { fork() }.is_err() {
unsafe { libc::exit(0) }; unsafe { libc::exit(0) };
} else { } else {
unsafe { libc::exit(1) }; unsafe { libc::exit(1) };
@@ -105,5 +99,5 @@ fn test_pid_events_is_not_zero() {
Err(_) => panic!("failed to fork"), Err(_) => panic!("failed to fork"),
} }
} }
cg.delete(); cg.delete().unwrap();
} }

View File

@@ -5,29 +5,27 @@
// //
//! Integration test about setting resources using `apply()` //! Integration test about setting resources using `apply()`
use cgroups::pid::PidController; use cgroups_rs::pid::PidController;
use cgroups::{Cgroup, Hierarchy, MaxValue, PidResources, Resources}; use cgroups_rs::{Cgroup, MaxValue, PidResources, Resources};
#[test] #[test]
fn pid_resources() { fn pid_resources() {
let h = cgroups::hierarchies::auto(); let h = cgroups_rs::hierarchies::auto();
let h = Box::new(&*h);
let cg = Cgroup::new(h, String::from("pid_resources")); let cg = Cgroup::new(h, String::from("pid_resources"));
{ {
let res = Resources { let res = Resources {
pid: PidResources { pid: PidResources {
update_values: true, maximum_number_of_processes: Some(MaxValue::Value(512)),
maximum_number_of_processes: MaxValue::Value(512),
}, },
..Default::default() ..Default::default()
}; };
cg.apply(&res); cg.apply(&res).unwrap();
// verify // verify
let pidcontroller: &PidController = cg.controller_of().unwrap(); let pidcontroller: &PidController = cg.controller_of().unwrap();
let pid_max = pidcontroller.get_pid_max(); 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)); assert_eq!(pid_max.unwrap(), MaxValue::Value(512));
} }
cg.delete(); cg.delete().unwrap();
} }