Compare commits

...

115 Commits

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

Fixes: #7

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

Fixes: #5

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

Fixes: #2

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

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

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

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

2
.cargo/config Normal file
View File

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

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"]'

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.{0,72})*$|^.+\n\s*[^a-zA-Z\s\n]|^.+\n\S+$'
error: 'Body line too long (max 72)'
post_error: ${{ env.error_msg }}

31
.travis.yml Normal file
View File

@@ -0,0 +1,31 @@
# Copyright (c) 2018 Levente Kurusa
# Copyright (c) 2020 Ant Group
#
# SPDX-License-Identifier: Apache-2.0
#
dist: bionic
os: linux
language: rust
cache: cargo
arch:
- amd64
- arm64
install:
- rustup component add rustfmt
script:
- RUSTFLAGS="--deny warnings" cargo build
- if [ "$TRAVIS_CPU_ARCH" == "amd64" ]; then cargo test -- --color always --nocapture ; fi
- cargo fmt -- --check
rust:
- 1.44.1
- nightly
jobs:
allow_failures:
- rust: nightly
fast_finish: true

View File

@@ -1,11 +1,20 @@
[package]
name = "cgroups"
name = "cgroups-rs"
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"]
categories = ["os", "api-bindings", "os::unix-apis"]
license = "BSD-2-Clause OR Apache-2.0"
version = "0.0.1"
authors = ["Levente Kurusa <lkurusa@acm.org>"]
license = "MIT OR Apache-2.0"
version = "0.2.1"
authors = ["The Kata Containers community <kata-dev@lists.katacontainers.io>", "Levente Kurusa <lkurusa@acm.org>", "Sam Wilson <tecywiz121@hotmail.com>"]
edition = "2018"
[dependencies]
log = "0.4"
regex = "1.1"
nix = "0.18.0"
libc = "0.2"
procinfo = "0.4.2"
[dev-dependencies]
libc = "0.2.76"

23
LICENSE
View File

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

13
LICENSE-Apache-2.0 Normal file
View File

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

21
LICENSE-MIT Normal file
View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

390
src/cgroup_builder.rs Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

112
src/error.rs Normal file
View File

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

90
src/events.rs Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

72
src/systemd.rs Normal file
View File

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

164
tests/builder.rs Normal file
View File

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

105
tests/cgroup.rs Normal file
View File

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

61
tests/cpu.rs Normal file
View File

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

93
tests/cpuset.rs Normal file
View File

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

73
tests/devices.rs Normal file
View File

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

44
tests/hugetlb.rs Normal file
View File

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

91
tests/memory.rs Normal file
View File

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

103
tests/pids.rs Normal file
View File

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

31
tests/resources.rs Normal file
View File

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

24
tools/create_cgroup.sh Executable file
View File

@@ -0,0 +1,24 @@
#!/bin/sh
#
# Copyright (c) 2018 Levente Kurusa
#
# SPDX-License-Identifier: Apache-2.0 or MIT
#
CONTROL_GROUPS=`cargo test -- --list 2>/dev/null | egrep 'test$' | egrep -v '^src' | cut -d':' -f1`
echo This script will create a control group in every subsystem of the V1 hierarchy.
echo For this, we will need your sudo privileges. Please do not trust this shell script and have a look to check that it does something that you are okay with.
sudo -v
for i in ${CONTROL_GROUPS}
do sudo mkdir -p /sys/fs/cgroup/{blkio,cpu,cpuacct,cpuset,devices,freezer,hugetlb,memory,net_cls,net_prio,perf_event,pids}/$i/
done
echo
echo We will now set up permissions...
echo
for i in ${CONTROL_GROUPS}
do sudo chown -R ${USER} /sys/fs/cgroup/{blkio,cpu,cpuacct,cpuset,devices,freezer,hugetlb,memory,net_cls,net_prio,perf_event,pids}/$i/
done

19
tools/delete_cgroup.sh Executable file
View File

@@ -0,0 +1,19 @@
#!/bin/sh
#
# Copyright (c) 2018 Levente Kurusa
#
# SPDX-License-Identifier: Apache-2.0 or MIT
#
CONTROL_GROUPS=`cargo test -- --list 2>/dev/null | egrep 'test$' | egrep -v '^src' | cut -d':' -f1`
echo This script will delete the control groups created by the create_cgroup.sh shell script.
echo
echo It may spit out some errors, but that is fine.
echo
echo For this, we will need your sudo privileges. Please do not trust this shell script and have a look to check that it does something that you are okay with.
sudo -v
for i in ${CONTROL_GROUPS}
do sudo rmdir /sys/fs/cgroup/{blkio,cpu,cpuacct,cpuset,devices,freezer,hugetlb,memory,net_cls,net_prio,perf_event,pids}/$i/
done