Compare commits

...

72 Commits

Author SHA1 Message Date
Tim Zhang
bbdfa88596 Merge pull request #26 from Tim-Zhang/memory-apply-customized-attrs
Apply customized attributes in MemController.apply
2021-01-25 14:37:34 +08:00
Tim Zhang
853ed46993 release: v0.2.2
Bump version to 0.2.2

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

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

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

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

fix: #20

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Fixes: #18

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

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

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

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

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

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

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

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

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

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

warning: 1 warning emitted

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

Fixes: #13

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

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

Fixes: #15

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

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

Fixes: #9

Signed-off-by: zhanghj <zhanghj.lc@inspur.com>
2020-09-21 05:40:57 -04:00
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
36 changed files with 2987 additions and 1084 deletions

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 }}

View File

@@ -1,11 +1,31 @@
# Copyright (c) 2018 Levente Kurusa
# Copyright (c) 2020 Ant Group
#
# SPDX-License-Identifier: Apache-2.0
#
dist: bionic
os: linux
language: rust 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: rust:
- stable - 1.44.1
- beta
- nightly - nightly
matrix:
jobs:
allow_failures: allow_failures:
- rust: nightly - rust: nightly
fast_finish: true fast_finish: true
script:
- cargo build --verbose --all

View File

@@ -1,16 +1,20 @@
[package] [package]
name = "cgroups" name = "cgroups-rs"
description = "Native Rust crate for managing control groups on Linux" description = "Native Rust crate for managing control groups on Linux"
repository = "https://github.com/levex/cgroups-rs" repository = "https://github.com/kata-containers/cgroups-rs"
keywords = ["linux", "cgroup", "containers", "isolation"] keywords = ["linux", "cgroup", "containers", "isolation"]
categories = ["os", "api-bindings", "os::unix-apis"] categories = ["os", "api-bindings", "os::unix-apis"]
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
version = "0.1.0" version = "0.2.2"
authors = ["Levente Kurusa <lkurusa@acm.org>", "Sam Wilson <tecywiz121@hotmail.com>"] authors = ["The Kata Containers community <kata-dev@lists.katacontainers.io>", "Levente Kurusa <lkurusa@acm.org>", "Sam Wilson <tecywiz121@hotmail.com>"]
edition = "2018"
[dependencies] [dependencies]
log = "0.4" log = "0.4"
regex = "1.1"
nix = "0.18.0"
libc = "0.2"
procinfo = "0.4.2"
[dev-dependencies] [dev-dependencies]
nix = "0.11.0" libc = "0.2.76"
libc = "0.2.43"

View File

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

View File

@@ -1,15 +1,21 @@
// Copyright (c) 2018 Levente Kurusa
// Copyright (c) 2020 And Group
//
// SPDX-License-Identifier: Apache-2.0 or MIT
//
//! This module contains the implementation of the `blkio` cgroup subsystem. //! This module contains the implementation of the `blkio` cgroup subsystem.
//! //!
//! See the Kernel's documentation for more information about this subsystem, found at: //! See the Kernel's documentation for more information about this subsystem, found at:
//! [Documentation/cgroup-v1/blkio-controller.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/blkio-controller.txt) //! [Documentation/cgroup-v1/blkio-controller.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/blkio-controller.txt)
use std::fs::File; use std::io::Write;
use std::io::{Read, Write};
use std::path::PathBuf; use std::path::PathBuf;
use error::*; use crate::error::ErrorKind::*;
use error::ErrorKind::*; use crate::error::*;
use { use crate::{read_string_from, read_u64_from};
use crate::{
BlkIoResources, ControllIdentifier, ControllerInternal, Controllers, Resources, Subsystem, BlkIoResources, ControllIdentifier, ControllerInternal, Controllers, Resources, Subsystem,
}; };
@@ -21,6 +27,7 @@ use {
pub struct BlkIoController { pub struct BlkIoController {
base: PathBuf, base: PathBuf,
path: PathBuf, path: PathBuf,
v2: bool,
} }
#[derive(Eq, PartialEq, Debug)] #[derive(Eq, PartialEq, Debug)]
@@ -48,11 +55,33 @@ pub struct IoService {
/// How many items were synchronously transferred. /// How many items were synchronously transferred.
pub sync: u64, pub sync: u64,
/// How many items were asynchronously transferred. /// How many items were asynchronously transferred.
pub async: u64, pub r#async: u64,
/// Total number of items transferred. /// Total number of items transferred.
pub total: u64, pub total: u64,
} }
#[derive(Eq, PartialEq, Debug)]
/// Per-device activity from the control group.
/// Only for cgroup v2
pub struct IoStat {
/// The major number of the device.
pub major: i16,
/// The minor number of the device.
pub minor: i16,
/// How many bytes were read from the device.
pub rbytes: u64,
/// How many bytes were written to the device.
pub wbytes: u64,
/// How many iops were read from the device.
pub rios: u64,
/// How many iops were written to the device.
pub wios: u64,
/// How many discard bytes were read from the device.
pub dbytes: u64,
/// How many discard iops were written to the device.
pub dios: u64,
}
fn parse_io_service(s: String) -> Result<Vec<IoService>> { fn parse_io_service(s: String) -> Result<Vec<IoService>> {
s.lines() s.lines()
.filter(|x| x.split_whitespace().collect::<Vec<_>>().len() == 3) .filter(|x| x.split_whitespace().collect::<Vec<_>>().len() == 3)
@@ -77,7 +106,7 @@ fn parse_io_service(s: String) -> Result<Vec<IoService>> {
read: read_val.parse::<u64>().unwrap(), read: read_val.parse::<u64>().unwrap(),
write: write_val.parse::<u64>().unwrap(), write: write_val.parse::<u64>().unwrap(),
sync: sync_val.parse::<u64>().unwrap(), sync: sync_val.parse::<u64>().unwrap(),
async: async_val.parse::<u64>().unwrap(), r#async: async_val.parse::<u64>().unwrap(),
total: total_val.parse::<u64>().unwrap(), total: total_val.parse::<u64>().unwrap(),
}), }),
_ => None, _ => None,
@@ -94,6 +123,41 @@ fn parse_io_service(s: String) -> Result<Vec<IoService>> {
}) })
} }
fn get_value(s: &str) -> String {
let arr = s.split(':').collect::<Vec<&str>>();
if arr.len() != 2 {
return "0".to_string();
}
arr[1].to_string()
}
fn parse_io_stat(s: String) -> Result<Vec<IoStat>> {
// line:
// 8:0 rbytes=180224 wbytes=0 rios=3 wios=0 dbytes=0 dios=0
let v = s
.lines()
.filter(|x| x.split_whitespace().collect::<Vec<_>>().len() == 7)
.map(|x| {
let arr = x.split_whitespace().collect::<Vec<&str>>();
let device = arr[0].split(":").collect::<Vec<&str>>();
let (major, minor) = (device[0], device[1]);
IoStat {
major: major.parse::<i16>().unwrap(),
minor: minor.parse::<i16>().unwrap(),
rbytes: get_value(arr[1]).parse::<u64>().unwrap(),
wbytes: get_value(arr[2]).parse::<u64>().unwrap(),
rios: get_value(arr[3]).parse::<u64>().unwrap(),
wios: get_value(arr[4]).parse::<u64>().unwrap(),
dbytes: get_value(arr[5]).parse::<u64>().unwrap(),
dios: get_value(arr[6]).parse::<u64>().unwrap(),
}
})
.collect::<Vec<IoStat>>();
Ok(v)
}
fn parse_io_service_total(s: String) -> Result<u64> { fn parse_io_service_total(s: String) -> Result<u64> {
s.lines() s.lines()
.filter(|x| x.split_whitespace().collect::<Vec<_>>().len() == 2) .filter(|x| x.split_whitespace().collect::<Vec<_>>().len() == 2)
@@ -141,7 +205,7 @@ fn parse_blkio_data(s: String) -> Result<Vec<BlkIoData>> {
/// Current state and statistics about how throttled are the block devices when accessed from the /// Current state and statistics about how throttled are the block devices when accessed from the
/// controller's control group. /// controller's control group.
#[derive(Debug)] #[derive(Default, Debug)]
pub struct BlkIoThrottle { pub struct BlkIoThrottle {
/// Statistics about the bytes transferred between the block devices by the tasks in this /// Statistics about the bytes transferred between the block devices by the tasks in this
/// control group. /// control group.
@@ -176,7 +240,7 @@ pub struct BlkIoThrottle {
} }
/// Statistics and state of the block devices. /// Statistics and state of the block devices.
#[derive(Debug)] #[derive(Default, Debug)]
pub struct BlkIo { pub struct BlkIo {
/// The number of BIOS requests merged into I/O requests by the control group's tasks. /// The number of BIOS requests merged into I/O requests by the control group's tasks.
pub io_merged: Vec<IoService>, pub io_merged: Vec<IoService>,
@@ -253,6 +317,9 @@ pub struct BlkIo {
pub weight: u64, pub weight: u64,
/// Same as `weight`, but per-block-device. /// Same as `weight`, but per-block-device.
pub weight_device: Vec<BlkIoData>, pub weight_device: Vec<BlkIoData>,
/// IoStat for cgroup v2
pub io_stat: Vec<IoStat>,
} }
impl ControllerInternal for BlkIoController { impl ControllerInternal for BlkIoController {
@@ -269,34 +336,44 @@ impl ControllerInternal for BlkIoController {
&self.base &self.base
} }
fn is_v2(&self) -> bool {
self.v2
}
fn apply(&self, res: &Resources) -> Result<()> { fn apply(&self, res: &Resources) -> Result<()> {
// get the resources that apply to this controller // get the resources that apply to this controller
let res: &BlkIoResources = &res.blkio; let res: &BlkIoResources = &res.blkio;
if res.update_values { if let Some(weight) = res.weight {
let _ = self.set_weight(res.weight as u64); let _ = self.set_weight(weight as u64);
let _ = self.set_leaf_weight(res.leaf_weight as u64); }
if let Some(leaf_weight) = res.leaf_weight {
let _ = self.set_leaf_weight(leaf_weight as u64);
}
for dev in &res.weight_device { for dev in &res.weight_device {
let _ = self.set_weight_for_device(dev.major, dev.minor, dev.weight as u64); if let Some(weight) = dev.weight {
let _ = self.set_leaf_weight_for_device(dev.major, dev.minor, dev.leaf_weight as u64); let _ = self.set_weight_for_device(dev.major, dev.minor, weight as u64);
} }
if let Some(leaf_weight) = dev.leaf_weight {
let _ = self.set_leaf_weight_for_device(dev.major, dev.minor, leaf_weight as u64);
}
}
for dev in &res.throttle_read_bps_device { for dev in &res.throttle_read_bps_device {
let _ = self.throttle_read_bps_for_device(dev.major, dev.minor, dev.rate); let _ = self.throttle_read_bps_for_device(dev.major, dev.minor, dev.rate);
} }
for dev in &res.throttle_write_bps_device { for dev in &res.throttle_write_bps_device {
let _ = self.throttle_write_bps_for_device(dev.major, dev.minor, dev.rate); let _ = self.throttle_write_bps_for_device(dev.major, dev.minor, dev.rate);
} }
for dev in &res.throttle_read_iops_device { for dev in &res.throttle_read_iops_device {
let _ = self.throttle_read_iops_for_device(dev.major, dev.minor, dev.rate); let _ = self.throttle_read_iops_for_device(dev.major, dev.minor, dev.rate);
} }
for dev in &res.throttle_write_iops_device { for dev in &res.throttle_write_iops_device {
let _ = self.throttle_write_iops_for_device(dev.major, dev.minor, dev.rate); let _ = self.throttle_write_iops_for_device(dev.major, dev.minor, dev.rate);
}
} }
Ok(()) Ok(())
@@ -316,43 +393,41 @@ impl<'a> From<&'a Subsystem> for &'a BlkIoController {
Subsystem::BlkIo(c) => c, Subsystem::BlkIo(c) => c,
_ => { _ => {
assert_eq!(1, 0); assert_eq!(1, 0);
::std::mem::uninitialized() let v = std::mem::MaybeUninit::uninit();
v.assume_init()
} }
} }
} }
} }
} }
fn read_string_from(mut file: File) -> Result<String> {
let mut string = String::new();
match file.read_to_string(&mut string) {
Ok(_) => Ok(string.trim().to_string()),
Err(e) => Err(Error::with_cause(ReadFailed, e)),
}
}
fn read_u64_from(mut file: File) -> Result<u64> {
let mut string = String::new();
match file.read_to_string(&mut string) {
Ok(_) => string.trim().parse().map_err(|e| Error::with_cause(ParseError, e)),
Err(e) => Err(Error::with_cause(ReadFailed, e)),
}
}
impl BlkIoController { impl BlkIoController {
/// Constructs a new `BlkIoController` with `oroot` serving as the root of the control group. /// Constructs a new `BlkIoController` with `root` serving as the root of the control group.
pub fn new(oroot: PathBuf) -> Self { pub fn new(root: PathBuf, v2: bool) -> Self {
let mut root = oroot;
root.push(Self::controller_type().to_string());
Self { Self {
base: root.clone(), base: root.clone(),
path: root, path: root,
v2: v2,
} }
} }
fn blkio_v2(&self) -> BlkIo {
let mut blkio: BlkIo = Default::default();
blkio.io_stat = self
.open_path("io.stat", false)
.and_then(read_string_from)
.and_then(parse_io_stat)
.unwrap_or(Vec::new());
blkio
}
/// Gathers statistics about and reports the state of the block devices used by the control /// Gathers statistics about and reports the state of the block devices used by the control
/// group's tasks. /// group's tasks.
pub fn blkio(&self) -> BlkIo { pub fn blkio(&self) -> BlkIo {
if self.v2 {
return self.blkio_v2();
}
BlkIo { BlkIo {
io_merged: self io_merged: self
.open_path("blkio.io_merged", false) .open_path("blkio.io_merged", false)
@@ -574,6 +649,7 @@ impl BlkIoController {
.and_then(read_string_from) .and_then(read_string_from)
.and_then(parse_blkio_data) .and_then(parse_blkio_data)
.unwrap_or(Vec::new()), .unwrap_or(Vec::new()),
io_stat: Vec::new(),
} }
} }
@@ -588,12 +664,7 @@ impl BlkIoController {
} }
/// Same as `set_leaf_weight()`, but settable per each block device. /// Same as `set_leaf_weight()`, but settable per each block device.
pub fn set_leaf_weight_for_device( pub fn set_leaf_weight_for_device(&self, major: u64, minor: u64, weight: u64) -> Result<()> {
&self,
major: u64,
minor: u64,
weight: u64,
) -> Result<()> {
self.open_path("blkio.leaf_weight_device", true) self.open_path("blkio.leaf_weight_device", true)
.and_then(|mut file| { .and_then(|mut file| {
file.write_all(format!("{}:{} {}", major, minor, weight).as_ref()) file.write_all(format!("{}:{} {}", major, minor, weight).as_ref())
@@ -612,92 +683,97 @@ impl BlkIoController {
/// Throttle the bytes per second rate of read operation affecting the block device /// Throttle the bytes per second rate of read operation affecting the block device
/// `major:minor` to `bps`. /// `major:minor` to `bps`.
pub fn throttle_read_bps_for_device( pub fn throttle_read_bps_for_device(&self, major: u64, minor: u64, bps: u64) -> Result<()> {
&self, let mut file = "blkio.throttle.read_bps_device";
major: u64, let mut content = format!("{}:{} {}", major, minor, bps);
minor: u64, if self.v2 {
bps: u64, file = "io.max";
) -> Result<()> { content = format!("{}:{} rbps={}", major, minor, bps);
self.open_path("blkio.throttle.read_bps_device", true) }
.and_then(|mut file| { self.open_path(file, true).and_then(|mut file| {
file.write_all(format!("{}:{} {}", major, minor, bps).to_string().as_ref()) file.write_all(content.as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e)) .map_err(|e| Error::with_cause(WriteFailed, e))
}) })
} }
/// Throttle the I/O operations per second rate of read operation affecting the block device /// Throttle the I/O operations per second rate of read operation affecting the block device
/// `major:minor` to `bps`. /// `major:minor` to `bps`.
pub fn throttle_read_iops_for_device( pub fn throttle_read_iops_for_device(&self, major: u64, minor: u64, iops: u64) -> Result<()> {
&self, let mut file = "blkio.throttle.read_iops_device";
major: u64, let mut content = format!("{}:{} {}", major, minor, iops);
minor: u64, if self.v2 {
iops: u64, file = "io.max";
) -> Result<()> { content = format!("{}:{} riops={}", major, minor, iops);
self.open_path("blkio.throttle.read_iops_device", true) }
.and_then(|mut file| { self.open_path(file, true).and_then(|mut file| {
file.write_all(format!("{}:{} {}", major, minor, iops).to_string().as_ref()) file.write_all(content.as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e)) .map_err(|e| Error::with_cause(WriteFailed, e))
}) })
} }
/// Throttle the bytes per second rate of write operation affecting the block device /// Throttle the bytes per second rate of write operation affecting the block device
/// `major:minor` to `bps`. /// `major:minor` to `bps`.
pub fn throttle_write_bps_for_device( pub fn throttle_write_bps_for_device(&self, major: u64, minor: u64, bps: u64) -> Result<()> {
&self, let mut file = "blkio.throttle.write_bps_device";
major: u64, let mut content = format!("{}:{} {}", major, minor, bps);
minor: u64, if self.v2 {
bps: u64, file = "io.max";
) -> Result<()> { content = format!("{}:{} wbps={}", major, minor, bps);
self.open_path("blkio.throttle.write_bps_device", true) }
.and_then(|mut file| { self.open_path(file, true).and_then(|mut file| {
file.write_all(format!("{}:{} {}", major, minor, bps).to_string().as_ref()) file.write_all(content.as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e)) .map_err(|e| Error::with_cause(WriteFailed, e))
}) })
} }
/// Throttle the I/O operations per second rate of write operation affecting the block device /// Throttle the I/O operations per second rate of write operation affecting the block device
/// `major:minor` to `bps`. /// `major:minor` to `bps`.
pub fn throttle_write_iops_for_device( pub fn throttle_write_iops_for_device(&self, major: u64, minor: u64, iops: u64) -> Result<()> {
&self, let mut file = "blkio.throttle.write_iops_device";
major: u64, let mut content = format!("{}:{} {}", major, minor, iops);
minor: u64, if self.v2 {
iops: u64, file = "io.max";
) -> Result<()> { content = format!("{}:{} wiops={}", major, minor, iops);
self.open_path("blkio.throttle.write_iops_device", true) }
.and_then(|mut file| { self.open_path(file, true).and_then(|mut file| {
file.write_all(format!("{}:{} {}", major, minor, iops).to_string().as_ref()) file.write_all(content.as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e)) .map_err(|e| Error::with_cause(WriteFailed, e))
}) })
} }
/// Set the weight of the control group's tasks. /// Set the weight of the control group's tasks.
pub fn set_weight(&self, w: u64) -> Result<()> { pub fn set_weight(&self, w: u64) -> Result<()> {
self.open_path("blkio.weight", true) // Attation: may not find in high kernel version.
.and_then(|mut file| { let mut file = "blkio.weight";
file.write_all(w.to_string().as_ref()) if self.v2 {
.map_err(|e| Error::with_cause(WriteFailed, e)) file = "io.bfq.weight";
}) }
self.open_path(file, true).and_then(|mut file| {
file.write_all(w.to_string().as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
})
} }
/// Same as `set_weight()`, but settable per each block device. /// Same as `set_weight()`, but settable per each block device.
pub fn set_weight_for_device( pub fn set_weight_for_device(&self, major: u64, minor: u64, weight: u64) -> Result<()> {
&self, let mut file = "blkio.weight_device";
major: u64, if self.v2 {
minor: u64, // Attation: there is no weight for device in runc
weight: u64, // https://github.com/opencontainers/runc/blob/46be7b612e2533c494e6a251111de46d8e286ed5/libcontainer/cgroups/fs2/io.go#L30
) -> Result<()> { // may depends on IO schedulers https://wiki.ubuntu.com/Kernel/Reference/IOSchedulers
self.open_path("blkio.weight_device", true) file = "io.bfq.weight";
.and_then(|mut file| { }
file.write_all(format!("{}:{} {}", major, minor, weight).as_ref()) self.open_path(file, true).and_then(|mut file| {
.map_err(|e| Error::with_cause(WriteFailed, e)) file.write_all(format!("{}:{} {}", major, minor, weight).as_ref())
}) .map_err(|e| Error::with_cause(WriteFailed, e))
})
} }
} }
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use blkio::{parse_blkio_data, BlkIoData}; use crate::blkio::{parse_blkio_data, BlkIoData};
use blkio::{parse_io_service, parse_io_service_total, IoService}; use crate::blkio::{parse_io_service, parse_io_service_total, IoService};
use error::*; use crate::error::*;
static TEST_VALUE: &str = "\ static TEST_VALUE: &str = "\
8:32 Read 4280320 8:32 Read 4280320
@@ -755,10 +831,7 @@ Total 61823067136
#[test] #[test]
fn test_parse_io_service_total() { fn test_parse_io_service_total() {
let ok = parse_io_service_total(TEST_VALUE.to_string()).unwrap(); let ok = parse_io_service_total(TEST_VALUE.to_string()).unwrap();
assert_eq!( assert_eq!(ok, 61823067136);
ok,
61823067136
);
} }
#[test] #[test]
@@ -773,7 +846,7 @@ Total 61823067136
read: 4280320, read: 4280320,
write: 0, write: 0,
sync: 4280320, sync: 4280320,
async: 0, r#async: 0,
total: 4280320, total: 4280320,
}, },
IoService { IoService {
@@ -782,7 +855,7 @@ Total 61823067136
read: 5705479168, read: 5705479168,
write: 56096055296, write: 56096055296,
sync: 11213923328, sync: 11213923328,
async: 50587611136, r#async: 50587611136,
total: 61801534464, total: 61801534464,
}, },
IoService { IoService {
@@ -791,7 +864,7 @@ Total 61823067136
read: 10059776, read: 10059776,
write: 0, write: 0,
sync: 10059776, sync: 10059776,
async: 0, r#async: 0,
total: 10059776, total: 10059776,
}, },
IoService { IoService {
@@ -800,16 +873,13 @@ Total 61823067136
read: 7192576, read: 7192576,
write: 0, write: 0,
sync: 7192576, sync: 7192576,
async: 0, r#async: 0,
total: 7192576, total: 7192576,
} }
] ]
); );
let err = parse_io_service(TEST_WRONG_VALUE.to_string()).unwrap_err(); let err = parse_io_service(TEST_WRONG_VALUE.to_string()).unwrap_err();
assert_eq!( assert_eq!(err.kind(), &ErrorKind::ParseError,);
err.kind(),
&ErrorKind::ParseError,
);
} }
#[test] #[test]

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 handles cgroup operations. Start here! //! This module handles cgroup operations. Start here!
use error::*; use crate::error::ErrorKind::*;
use crate::error::*;
use {CgroupPid, ControllIdentifier, Controller, Hierarchy, Resources, Subsystem}; use crate::{CgroupPid, ControllIdentifier, Controller, Hierarchy, Resources, Subsystem};
use std::collections::HashMap;
use std::convert::From; use std::convert::From;
use std::path::Path; use std::fs;
use std::path::{Path, PathBuf};
/// A control group is the central structure to this crate. /// A control group is the central structure to this crate.
/// ///
@@ -19,42 +28,81 @@ use std::path::Path;
/// > specialized behaviour. /// > specialized behaviour.
/// ///
/// This crate is an attempt at providing a Rust-native way of managing these cgroups. /// This crate is an attempt at providing a Rust-native way of managing these cgroups.
pub struct Cgroup<'b> { #[derive(Debug)]
pub struct Cgroup {
/// The list of subsystems that control this cgroup /// The list of subsystems that control this cgroup
subsystems: Vec<Subsystem>, subsystems: Vec<Subsystem>,
/// The hierarchy. /// The hierarchy.
hier: &'b Hierarchy, hier: Box<dyn Hierarchy>,
path: String,
} }
impl<'b> Cgroup<'b> { impl Clone for Cgroup {
fn clone(&self) -> Self {
Cgroup {
subsystems: self.subsystems.clone(),
path: self.path.clone(),
hier: crate::hierarchies::auto(),
}
}
}
impl Default for Cgroup {
fn default() -> Self {
Cgroup {
subsystems: Vec::new(),
hier: crate::hierarchies::auto(),
path: "".to_string(),
}
}
}
impl Cgroup {
/// Create this control group. /// Create this control group.
fn create(&self) { fn create(&self) {
for subsystem in &self.subsystems { if self.hier.v2() {
subsystem.to_controller().create(); 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`. /// Create a new control group in the hierarchy `hier`, with name `path`.
/// ///
/// Returns a handle to the control group that can be used to manipulate it. /// Returns a handle to the control group that can be used to manipulate it.
/// pub fn new<P: AsRef<Path>>(hier: Box<dyn Hierarchy>, path: P) -> Cgroup {
/// Note that if the handle goes out of scope and is dropped, the control group is _not_
/// destroyed.
pub fn new<P: AsRef<Path>>(hier: &Hierarchy, path: P) -> Cgroup {
let cg = Cgroup::load(hier, path); let cg = Cgroup::load(hier, path);
cg.create(); cg.create();
cg 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`. /// Create a handle for a control group in the hierarchy `hier`, with name `path`.
/// ///
/// Returns a handle to the control group (that possibly does not exist until `create()` has /// Returns a handle to the control group (that possibly does not exist until `create()` has
/// been called on the cgroup. /// been called on the cgroup.
/// pub fn load<P: AsRef<Path>>(hier: Box<dyn Hierarchy>, path: P) -> Cgroup {
/// Note that if the handle goes out of scope and is dropped, the control group is _not_
/// destroyed.
pub fn load<P: AsRef<Path>>(hier: &Hierarchy, path: P) -> Cgroup {
let path = path.as_ref(); let path = path.as_ref();
let mut subsystems = hier.subsystems(); let mut subsystems = hier.subsystems();
if path.as_os_str() != "" { if path.as_os_str() != "" {
@@ -65,8 +113,54 @@ impl<'b> Cgroup<'b> {
} }
let cg = Cgroup { let cg = Cgroup {
path: path.to_str().unwrap().to_string(),
subsystems: subsystems, subsystems: subsystems,
hier: hier, 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 cg
@@ -83,8 +177,17 @@ impl<'b> Cgroup<'b> {
/// system call will fail if there are any descendants. Thus, one should check whether it was /// system call will fail if there are any descendants. Thus, one should check whether it was
/// actually removed, and remove the descendants first if not. In the future, this behavior /// actually removed, and remove the descendants first if not. In the future, this behavior
/// will change. /// will change.
pub fn delete(self) { pub fn delete(&self) -> Result<()> {
self.subsystems.into_iter().for_each(|sub| match sub { 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::Pid(pidc) => pidc.delete(),
Subsystem::Mem(c) => c.delete(), Subsystem::Mem(c) => c.delete(),
Subsystem::CpuSet(c) => c.delete(), Subsystem::CpuSet(c) => c.delete(),
@@ -98,7 +201,8 @@ impl<'b> Cgroup<'b> {
Subsystem::NetPrio(c) => c.delete(), Subsystem::NetPrio(c) => c.delete(),
Subsystem::HugeTlb(c) => c.delete(), Subsystem::HugeTlb(c) => c.delete(),
Subsystem::Rdma(c) => c.delete(), Subsystem::Rdma(c) => c.delete(),
}); Subsystem::Systemd(c) => c.delete(),
})
} }
/// Apply a set of resource limits to the control group. /// Apply a set of resource limits to the control group.
@@ -143,25 +247,137 @@ impl<'b> Cgroup<'b> {
/// Attach a task to the control group. /// Attach a task to the control group.
pub fn add_task(&self, pid: CgroupPid) -> Result<()> { 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() self.subsystems()
.iter() .iter()
.try_for_each(|sub| sub.to_controller().add_task(&pid)) .try_for_each(|sub| sub.to_controller().add_task_by_tgid(&pid))
}
/// Set notify_on_release to the control group.
pub fn set_notify_on_release(&self, enable: bool) -> Result<()> {
self.subsystems()
.iter()
.try_for_each(|sub| sub.to_controller().set_notify_on_release(enable))
}
/// Set release_agent
pub fn set_release_agent(&self, path: &str) -> Result<()> {
self.hier
.root_control_group()
.subsystems()
.iter()
.try_for_each(|sub| sub.to_controller().set_release_agent(path))
} }
/// Returns an Iterator that can be used to iterate over the tasks that are currently in the /// Returns an Iterator that can be used to iterate over the tasks that are currently in the
/// control group. /// control group.
pub fn tasks(&self) -> Vec<CgroupPid> { pub fn tasks(&self) -> Vec<CgroupPid> {
// Collect the tasks from all subsystems // Collect the tasks from all subsystems
let mut v = self let mut v = if self.v2() {
.subsystems() let subsystems = self.subsystems();
.iter() if subsystems.len() > 0 {
.map(|x| x.to_controller().tasks()) let c = subsystems[0].to_controller();
.fold(vec![], |mut acc, mut x| { c.tasks()
acc.append(&mut x); } else {
acc 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.sort();
v.dedup(); v.dedup();
v 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)
}

View File

@@ -1,3 +1,9 @@
// 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. //! This module allows the user to create a control group using the Builder pattern.
//! # Example //! # Example
//! //!
@@ -10,11 +16,11 @@
//! by a call to `build()`. //! by a call to `build()`.
//! //!
//! ```rust,no_run //! ```rust,no_run
//! # use cgroups::*; //! # use cgroups_rs::*;
//! # use cgroups::devices::*; //! # use cgroups_rs::devices::*;
//! # use cgroups::cgroup_builder::*; //! # use cgroups_rs::cgroup_builder::*;
//! let v1 = cgroups::hierarchies::V1::new(); //! let h = cgroups_rs::hierarchies::auto();
//! let cgroup: Cgroup = CgroupBuilder::new("hello", &v1) //! let cgroup: Cgroup = CgroupBuilder::new("hello")
//! .memory() //! .memory()
//! .kernel_memory_limit(1024 * 1024) //! .kernel_memory_limit(1024 * 1024)
//! .memory_hard_limit(1024 * 1024) //! .memory_hard_limit(1024 * 1024)
@@ -42,8 +48,8 @@
//! .blkio() //! .blkio()
//! .weight(123) //! .weight(123)
//! .leaf_weight(99) //! .leaf_weight(99)
//! .weight_device(6, 1, 100, 55) //! .weight_device(6, 1, Some(100), Some(55))
//! .weight_device(6, 1, 100, 55) //! .weight_device(6, 1, Some(100), Some(55))
//! .throttle_iops() //! .throttle_iops()
//! .read(6, 1, 10) //! .read(6, 1, 10)
//! .write(11, 1, 100) //! .write(11, 1, 100)
@@ -51,89 +57,76 @@
//! .read(6, 1, 10) //! .read(6, 1, 10)
//! .write(11, 1, 100) //! .write(11, 1, 100)
//! .done() //! .done()
//! .build(); //! .build(h);
//! ``` //! ```
use error::*;
use {pid, BlkIoDeviceResource, BlkIoDeviceThrottleResource, Cgroup, DeviceResource, Hierarchy, HugePageResource, NetworkPriority, Resources}; use crate::{
BlkIoDeviceResource, BlkIoDeviceThrottleResource, Cgroup, DeviceResource, Hierarchy,
HugePageResource, MaxValue, NetworkPriority, Resources,
};
macro_rules! gen_setter { macro_rules! gen_setter {
($res:ident, $cont:ident, $func:ident, $name:ident, $ty:ty) => { ($res:ident, $cont:ident, $func:ident, $name:ident, $ty:ty) => {
/// See the similarly named function in the respective controller. /// See the similarly named function in the respective controller.
pub fn $name(mut self, $name: $ty) -> Self { pub fn $name(mut self, $name: $ty) -> Self {
self.cgroup.resources.$res.update_values = true; self.cgroup.resources.$res.$name = Some($name);
self.cgroup.resources.$res.$name = $name;
self self
} }
} };
} }
/// A control group builder instance /// A control group builder instance
pub struct CgroupBuilder<'a> { pub struct CgroupBuilder {
name: String, name: String,
hierarchy: &'a Hierarchy,
/// Internal, unsupported field: use the associated builders instead. /// Internal, unsupported field: use the associated builders instead.
resources: Resources, resources: Resources,
} }
impl<'a> CgroupBuilder<'a> { impl CgroupBuilder {
/// Start building a control group with the supplied hierarchy and name pair. /// Start building a control group with the supplied hierarchy and name pair.
/// ///
/// Note that this does not actually create the control group until `build()` is called. /// Note that this does not actually create the control group until `build()` is called.
pub fn new(name: &'a str, hierarchy: &'a Hierarchy) -> CgroupBuilder<'a> { pub fn new(name: &str) -> CgroupBuilder {
CgroupBuilder { CgroupBuilder {
name: name.to_owned(), name: name.to_owned(),
hierarchy: hierarchy,
resources: Resources::default(), resources: Resources::default(),
} }
} }
/// Builds the memory resources of the control group. /// Builds the memory resources of the control group.
pub fn memory(self) -> MemoryResourceBuilder<'a> { pub fn memory(self) -> MemoryResourceBuilder {
MemoryResourceBuilder { MemoryResourceBuilder { cgroup: self }
cgroup: self,
}
} }
/// Builds the pid resources of the control group. /// Builds the pid resources of the control group.
pub fn pid(self) -> PidResourceBuilder<'a> { pub fn pid(self) -> PidResourceBuilder {
PidResourceBuilder { PidResourceBuilder { cgroup: self }
cgroup: self,
}
} }
/// Builds the cpu resources of the control group. /// Builds the cpu resources of the control group.
pub fn cpu(self) -> CpuResourceBuilder<'a> { pub fn cpu(self) -> CpuResourceBuilder {
CpuResourceBuilder { CpuResourceBuilder { cgroup: self }
cgroup: self,
}
} }
/// Builds the devices resources of the control group, disallowing or /// Builds the devices resources of the control group, disallowing or
/// allowing access to certain devices in the system. /// allowing access to certain devices in the system.
pub fn devices(self) -> DeviceResourceBuilder<'a> { pub fn devices(self) -> DeviceResourceBuilder {
DeviceResourceBuilder { DeviceResourceBuilder { cgroup: self }
cgroup: self,
}
} }
/// Builds the network resources of the control group, setting class id, or /// Builds the network resources of the control group, setting class id, or
/// various priorities on networking interfaces. /// various priorities on networking interfaces.
pub fn network(self) -> NetworkResourceBuilder<'a> { pub fn network(self) -> NetworkResourceBuilder {
NetworkResourceBuilder { NetworkResourceBuilder { cgroup: self }
cgroup: self,
}
} }
/// Builds the hugepage/hugetlb resources available to the control group. /// Builds the hugepage/hugetlb resources available to the control group.
pub fn hugepages(self) -> HugepagesResourceBuilder<'a> { pub fn hugepages(self) -> HugepagesResourceBuilder {
HugepagesResourceBuilder { HugepagesResourceBuilder { cgroup: self }
cgroup: self,
}
} }
/// Builds the block I/O resources available for the control group. /// Builds the block I/O resources available for the control group.
pub fn blkio(self) -> BlkIoResourcesBuilder<'a> { pub fn blkio(self) -> BlkIoResourcesBuilder {
BlkIoResourcesBuilder { BlkIoResourcesBuilder {
cgroup: self, cgroup: self,
throttling_iops: false, throttling_iops: false,
@@ -141,55 +134,82 @@ impl<'a> CgroupBuilder<'a> {
} }
/// Finalize the control group, consuming the builder and creating the control group. /// Finalize the control group, consuming the builder and creating the control group.
pub fn build(self) -> Cgroup<'a> { pub fn build(self, hier: Box<dyn Hierarchy>) -> Cgroup {
let cg = Cgroup::new(self.hierarchy, self.name); let cg = Cgroup::new(hier, self.name);
cg.apply(&self.resources); let _ret = cg.apply(&self.resources);
cg cg
} }
} }
/// A builder that configures the memory controller of a control group. /// A builder that configures the memory controller of a control group.
pub struct MemoryResourceBuilder<'a> { pub struct MemoryResourceBuilder {
cgroup: CgroupBuilder<'a>, cgroup: CgroupBuilder,
} }
impl<'a> MemoryResourceBuilder<'a> { impl MemoryResourceBuilder {
gen_setter!(
gen_setter!(memory, MemController, set_kmem_limit, kernel_memory_limit, u64); memory,
gen_setter!(memory, MemController, set_limit, memory_hard_limit, u64); MemController,
gen_setter!(memory, MemController, set_soft_limit, memory_soft_limit, u64); set_kmem_limit,
gen_setter!(memory, MemController, set_tcp_limit, kernel_tcp_memory_limit, u64); kernel_memory_limit,
gen_setter!(memory, MemController, set_memswap_limit, memory_swap_limit, u64); 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); gen_setter!(memory, MemController, set_swappiness, swappiness, u64);
/// Finish the construction of the memory resources of a control group. /// Finish the construction of the memory resources of a control group.
pub fn done(self) -> CgroupBuilder<'a> { pub fn done(self) -> CgroupBuilder {
self.cgroup self.cgroup
} }
} }
/// A builder that configures the pid controller of a control group. /// A builder that configures the pid controller of a control group.
pub struct PidResourceBuilder<'a> { pub struct PidResourceBuilder {
cgroup: CgroupBuilder<'a>, cgroup: CgroupBuilder,
} }
impl<'a> PidResourceBuilder<'a> { impl PidResourceBuilder {
gen_setter!(
gen_setter!(pid, PidController, set_pid_max, maximum_number_of_processes, pid::PidMax); pid,
PidController,
set_pid_max,
maximum_number_of_processes,
MaxValue
);
/// Finish the construction of the pid resources of a control group. /// Finish the construction of the pid resources of a control group.
pub fn done(self) -> CgroupBuilder<'a> { pub fn done(self) -> CgroupBuilder {
self.cgroup self.cgroup
} }
} }
/// A builder that configures the cpuset & cpu controllers of a control group. /// A builder that configures the cpuset & cpu controllers of a control group.
pub struct CpuResourceBuilder<'a> { pub struct CpuResourceBuilder {
cgroup: CgroupBuilder<'a>, cgroup: CgroupBuilder,
} }
impl<'a> CpuResourceBuilder<'a> { impl CpuResourceBuilder {
gen_setter!(cpu, CpuSetController, set_cpus, cpus, String); gen_setter!(cpu, CpuSetController, set_cpus, cpus, String);
gen_setter!(cpu, CpuSetController, set_mems, mems, String); gen_setter!(cpu, CpuSetController, set_mems, mems, String);
gen_setter!(cpu, CpuController, set_shares, shares, u64); gen_setter!(cpu, CpuController, set_shares, shares, u64);
@@ -199,170 +219,172 @@ impl<'a> CpuResourceBuilder<'a> {
gen_setter!(cpu, CpuController, set_rt_period, realtime_period, u64); gen_setter!(cpu, CpuController, set_rt_period, realtime_period, u64);
/// Finish the construction of the cpu resources of a control group. /// Finish the construction of the cpu resources of a control group.
pub fn done(self) -> CgroupBuilder<'a> { pub fn done(self) -> CgroupBuilder {
self.cgroup self.cgroup
} }
} }
/// A builder that configures the devices controller of a control group. /// A builder that configures the devices controller of a control group.
pub struct DeviceResourceBuilder<'a> { pub struct DeviceResourceBuilder {
cgroup: CgroupBuilder<'a>, cgroup: CgroupBuilder,
} }
impl<'a> DeviceResourceBuilder<'a> { impl DeviceResourceBuilder {
/// Restrict (or allow) a device to the tasks inside the control group. /// Restrict (or allow) a device to the tasks inside the control group.
pub fn device(mut self, pub fn device(
major: i64, mut self,
minor: i64, major: i64,
devtype: ::devices::DeviceType, minor: i64,
allow: bool, devtype: crate::devices::DeviceType,
access: Vec<::devices::DevicePermissions>) allow: bool,
-> DeviceResourceBuilder<'a> { access: Vec<crate::devices::DevicePermissions>,
self.cgroup.resources.devices.update_values = true; ) -> DeviceResourceBuilder {
self.cgroup.resources.devices.devices.push(DeviceResource { self.cgroup.resources.devices.devices.push(DeviceResource {
major, major,
minor, minor,
devtype, devtype,
allow, allow,
access access,
}); });
self self
} }
/// Finish the construction of the devices resources of a control group. /// Finish the construction of the devices resources of a control group.
pub fn done(self) -> CgroupBuilder<'a> { pub fn done(self) -> CgroupBuilder {
self.cgroup self.cgroup
} }
} }
/// A builder that configures the net_cls & net_prio controllers of a control group. /// A builder that configures the net_cls & net_prio controllers of a control group.
pub struct NetworkResourceBuilder<'a> { pub struct NetworkResourceBuilder {
cgroup: CgroupBuilder<'a>, cgroup: CgroupBuilder,
} }
impl<'a> NetworkResourceBuilder<'a> { impl NetworkResourceBuilder {
gen_setter!(network, NetclsController, set_class, class_id, u64); gen_setter!(network, NetclsController, set_class, class_id, u64);
/// Set the priority of the tasks when operating on a networking device defined by `name` to be /// Set the priority of the tasks when operating on a networking device defined by `name` to be
/// `priority`. /// `priority`.
pub fn priority(mut self, name: String, priority: u64) pub fn priority(mut self, name: String, priority: u64) -> NetworkResourceBuilder {
-> NetworkResourceBuilder<'a> { self.cgroup
self.cgroup.resources.network.update_values = true; .resources
self.cgroup.resources.network.priorities.push(NetworkPriority { .network
name, .priorities
priority, .push(NetworkPriority { name, priority });
});
self self
} }
/// Finish the construction of the network resources of a control group. /// Finish the construction of the network resources of a control group.
pub fn done(self) -> CgroupBuilder<'a> { pub fn done(self) -> CgroupBuilder {
self.cgroup self.cgroup
} }
} }
/// A builder that configures the hugepages controller of a control group. /// A builder that configures the hugepages controller of a control group.
pub struct HugepagesResourceBuilder<'a> { pub struct HugepagesResourceBuilder {
cgroup: CgroupBuilder<'a>, cgroup: CgroupBuilder,
} }
impl<'a> HugepagesResourceBuilder<'a> { impl HugepagesResourceBuilder {
/// Limit the usage of certain hugepages (determined by `size`) to be at most `limit` bytes. /// Limit the usage of certain hugepages (determined by `size`) to be at most `limit` bytes.
pub fn limit(mut self, size: String, limit: u64) pub fn limit(mut self, size: String, limit: u64) -> HugepagesResourceBuilder {
-> HugepagesResourceBuilder<'a> { self.cgroup
self.cgroup.resources.hugepages.update_values = true; .resources
self.cgroup.resources.hugepages.limits.push(HugePageResource { .hugepages
size, .limits
limit, .push(HugePageResource { size, limit });
});
self self
} }
/// Finish the construction of the network resources of a control group. /// Finish the construction of the network resources of a control group.
pub fn done(self) -> CgroupBuilder<'a> { pub fn done(self) -> CgroupBuilder {
self.cgroup self.cgroup
} }
} }
/// A builder that configures the blkio controller of a control group. /// A builder that configures the blkio controller of a control group.
pub struct BlkIoResourcesBuilder<'a> { pub struct BlkIoResourcesBuilder {
cgroup: CgroupBuilder<'a>, cgroup: CgroupBuilder,
throttling_iops: bool, throttling_iops: bool,
} }
impl<'a> BlkIoResourcesBuilder<'a> { impl BlkIoResourcesBuilder {
gen_setter!(blkio, BlkIoController, set_weight, weight, u16); gen_setter!(blkio, BlkIoController, set_weight, weight, u16);
gen_setter!(blkio, BlkIoController, set_leaf_weight, leaf_weight, u16); gen_setter!(blkio, BlkIoController, set_leaf_weight, leaf_weight, u16);
/// Set the weight of a certain device. /// Set the weight of a certain device.
pub fn weight_device(mut self, pub fn weight_device(
major: u64, mut self,
minor: u64, major: u64,
weight: u16, minor: u64,
leaf_weight: u16) weight: Option<u16>,
-> BlkIoResourcesBuilder<'a> { leaf_weight: Option<u16>,
self.cgroup.resources.blkio.update_values = true; ) -> BlkIoResourcesBuilder {
self.cgroup.resources.blkio.weight_device.push(BlkIoDeviceResource { self.cgroup
major, .resources
minor, .blkio
weight, .weight_device
leaf_weight, .push(BlkIoDeviceResource {
}); major,
minor,
weight,
leaf_weight,
});
self self
} }
/// Start configuring the I/O operations per second metric. /// Start configuring the I/O operations per second metric.
pub fn throttle_iops(mut self) -> BlkIoResourcesBuilder<'a> { pub fn throttle_iops(mut self) -> BlkIoResourcesBuilder {
self.throttling_iops = true; self.throttling_iops = true;
self self
} }
/// Start configuring the bytes per second metric. /// Start configuring the bytes per second metric.
pub fn throttle_bps(mut self) -> BlkIoResourcesBuilder<'a> { pub fn throttle_bps(mut self) -> BlkIoResourcesBuilder {
self.throttling_iops = false; self.throttling_iops = false;
self self
} }
/// Limit the read rate of the current metric for a certain device. /// Limit the read rate of the current metric for a certain device.
pub fn read(mut self, major: u64, minor: u64, rate: u64) pub fn read(mut self, major: u64, minor: u64, rate: u64) -> BlkIoResourcesBuilder {
-> BlkIoResourcesBuilder<'a> { let throttle = BlkIoDeviceThrottleResource { major, minor, rate };
self.cgroup.resources.blkio.update_values = true;
let throttle = BlkIoDeviceThrottleResource {
major,
minor,
rate,
};
if self.throttling_iops { if self.throttling_iops {
self.cgroup.resources.blkio.throttle_read_iops_device.push(throttle); self.cgroup
.resources
.blkio
.throttle_read_iops_device
.push(throttle);
} else { } else {
self.cgroup.resources.blkio.throttle_read_bps_device.push(throttle); self.cgroup
.resources
.blkio
.throttle_read_bps_device
.push(throttle);
} }
self self
} }
/// Limit the write rate of the current metric for a certain device. /// Limit the write rate of the current metric for a certain device.
pub fn write(mut self, major: u64, minor: u64, rate: u64) pub fn write(mut self, major: u64, minor: u64, rate: u64) -> BlkIoResourcesBuilder {
-> BlkIoResourcesBuilder<'a> { let throttle = BlkIoDeviceThrottleResource { major, minor, rate };
self.cgroup.resources.blkio.update_values = true;
let throttle = BlkIoDeviceThrottleResource {
major,
minor,
rate,
};
if self.throttling_iops { if self.throttling_iops {
self.cgroup.resources.blkio.throttle_write_iops_device.push(throttle); self.cgroup
.resources
.blkio
.throttle_write_iops_device
.push(throttle);
} else { } else {
self.cgroup.resources.blkio.throttle_write_bps_device.push(throttle); self.cgroup
.resources
.blkio
.throttle_write_bps_device
.push(throttle);
} }
self self
} }
/// Finish the construction of the blkio resources of a control group. /// Finish the construction of the blkio resources of a control group.
pub fn done(self) -> CgroupBuilder<'a> { pub fn done(self) -> CgroupBuilder {
self.cgroup self.cgroup
} }
} }

View File

@@ -1,3 +1,9 @@
// 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. //! This module contains the implementation of the `cpu` cgroup subsystem.
//! //!
//! See the Kernel's documentation for more information about this subsystem, found at: //! See the Kernel's documentation for more information about this subsystem, found at:
@@ -7,11 +13,13 @@ use std::fs::File;
use std::io::{Read, Write}; use std::io::{Read, Write};
use std::path::PathBuf; use std::path::PathBuf;
use error::*; use crate::error::ErrorKind::*;
use error::ErrorKind::*; use crate::error::*;
use crate::{parse_max_value, read_i64_from, read_u64_from};
use { use crate::{
ControllIdentifier, ControllerInternal, Controllers, CpuResources, Resources, Subsystem, ControllIdentifier, ControllerInternal, Controllers, CpuResources, CustomizedAttribute,
MaxValue, Resources, Subsystem,
}; };
/// A controller that allows controlling the `cpu` subsystem of a Cgroup. /// A controller that allows controlling the `cpu` subsystem of a Cgroup.
@@ -23,6 +31,7 @@ use {
pub struct CpuController { pub struct CpuController {
base: PathBuf, base: PathBuf,
path: PathBuf, path: PathBuf,
v2: bool,
} }
/// The current state of the control group and its processes. /// The current state of the control group and its processes.
@@ -34,6 +43,13 @@ pub struct Cpu {
pub stat: String, pub stat: String,
} }
/// The current state of the control group and its processes.
#[derive(Debug)]
struct CFSQuotaAndPeriod {
quota: MaxValue,
period: u64,
}
impl ControllerInternal for CpuController { impl ControllerInternal for CpuController {
fn control_type(&self) -> Controllers { fn control_type(&self) -> Controllers {
Controllers::Cpu Controllers::Cpu
@@ -51,29 +67,23 @@ impl ControllerInternal for CpuController {
&self.base &self.base
} }
fn is_v2(&self) -> bool {
self.v2
}
fn apply(&self, res: &Resources) -> Result<()> { fn apply(&self, res: &Resources) -> Result<()> {
// get the resources that apply to this controller // get the resources that apply to this controller
let res: &CpuResources = &res.cpu; let res: &CpuResources = &res.cpu;
if res.update_values { update_and_test!(self, set_shares, res.shares, shares);
// apply pid_max update_and_test!(self, set_cfs_period, res.period, cfs_period);
let _ = self.set_shares(res.shares); update_and_test!(self, set_cfs_quota, res.quota, cfs_quota);
if self.shares()? != res.shares as u64 {
return Err(Error::new(ErrorKind::Other));
}
let _ = self.set_cfs_period(res.period); res.attrs.iter().for_each(|(k, v)| {
if self.cfs_period()? != res.period as u64 { let _ = self.set(k, v);
return Err(Error::new(ErrorKind::Other)); });
}
let _ = self.set_cfs_quota(res.quota as u64); // TODO: rt properties (CONFIG_RT_GROUP_SCHED) are not yet supported
if self.cfs_quota()? != res.quota as u64 {
return Err(Error::new(ErrorKind::Other));
}
// TODO: rt properties (CONFIG_RT_GROUP_SCHED) are not yet supported
}
Ok(()) Ok(())
} }
@@ -92,29 +102,21 @@ impl<'a> From<&'a Subsystem> for &'a CpuController {
Subsystem::Cpu(c) => c, Subsystem::Cpu(c) => c,
_ => { _ => {
assert_eq!(1, 0); assert_eq!(1, 0);
::std::mem::uninitialized() let v = std::mem::MaybeUninit::uninit();
v.assume_init()
} }
} }
} }
} }
} }
fn read_u64_from(mut file: File) -> Result<u64> {
let mut string = String::new();
match file.read_to_string(&mut string) {
Ok(_) => string.trim().parse().map_err(|e| Error::with_cause(ParseError, e)),
Err(e) => Err(Error::with_cause(ReadFailed, e)),
}
}
impl CpuController { impl CpuController {
/// Contructs a new `CpuController` with `oroot` serving as the root of the control group. /// Contructs a new `CpuController` with `root` serving as the root of the control group.
pub fn new(oroot: PathBuf) -> Self { pub fn new(root: PathBuf, v2: bool) -> Self {
let mut root = oroot;
root.push(Self::controller_type().to_string());
Self { Self {
base: root.clone(), base: root.clone(),
path: root, path: root,
v2: v2,
} }
} }
@@ -130,7 +132,8 @@ impl CpuController {
Ok(_) => Ok(s), Ok(_) => Ok(s),
Err(e) => Err(Error::with_cause(ReadFailed, e)), Err(e) => Err(Error::with_cause(ReadFailed, e)),
} }
}).unwrap_or("".to_string()), })
.unwrap_or("".to_string()),
} }
} }
@@ -141,7 +144,12 @@ impl CpuController {
/// `shares` to `200` ensures that control group `B` receives twice as much as CPU bandwidth. /// `shares` to `200` ensures that control group `B` receives twice as much as CPU bandwidth.
/// (Assuming both `A` and `B` are of the same parent) /// (Assuming both `A` and `B` are of the same parent)
pub fn set_shares(&self, shares: u64) -> Result<()> { pub fn set_shares(&self, shares: u64) -> Result<()> {
self.open_path("cpu.shares", true).and_then(|mut file| { 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()) file.write_all(shares.to_string().as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e)) .map_err(|e| Error::with_cause(WriteFailed, e))
}) })
@@ -150,12 +158,19 @@ impl CpuController {
/// Retrieve the CPU bandwidth that this control group (relative to other control groups and /// Retrieve the CPU bandwidth that this control group (relative to other control groups and
/// this control group's parent) can use. /// this control group's parent) can use.
pub fn shares(&self) -> Result<u64> { pub fn shares(&self) -> Result<u64> {
self.open_path("cpu.shares", false).and_then(read_u64_from) 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 /// 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. /// control group's access to the CPU should be reallocated.
pub fn set_cfs_period(&self, us: u64) -> Result<()> { 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) self.open_path("cpu.cfs_period_us", true)
.and_then(|mut file| { .and_then(|mut file| {
file.write_all(us.to_string().as_ref()) file.write_all(us.to_string().as_ref())
@@ -166,13 +181,22 @@ impl CpuController {
/// Retrieve the period of time of how often this cgroup's access to the CPU should be /// Retrieve the period of time of how often this cgroup's access to the CPU should be
/// reallocated in microseconds. /// reallocated in microseconds.
pub fn cfs_period(&self) -> Result<u64> { 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) self.open_path("cpu.cfs_period_us", false)
.and_then(read_u64_from) .and_then(read_u64_from)
} }
/// Specify a quota (when using the CFS scheduler) of time in microseconds for which all tasks /// 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()`). /// in this control group can run during one period (see: `set_cfs_period()`).
pub fn set_cfs_quota(&self, us: u64) -> Result<()> { 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) self.open_path("cpu.cfs_quota_us", true)
.and_then(|mut file| { .and_then(|mut file| {
file.write_all(us.to_string().as_ref()) file.write_all(us.to_string().as_ref())
@@ -182,8 +206,101 @@ impl CpuController {
/// Retrieve the quota of time for which all tasks in this cgroup can run during one period, in /// Retrieve the quota of time for which all tasks in this cgroup can run during one period, in
/// microseconds. /// microseconds.
pub fn cfs_quota(&self) -> Result<u64> { 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) self.open_path("cpu.cfs_quota_us", false)
.and_then(read_u64_from) .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,15 +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. //! This module contains the implementation of the `cpuacct` cgroup subsystem.
//! //!
//! See the Kernel's documentation for more information about this subsystem, found at: //! See the Kernel's documentation for more information about this subsystem, found at:
//! [Documentation/cgroup-v1/cpuacct.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/cpuacct.txt) //! [Documentation/cgroup-v1/cpuacct.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/cpuacct.txt)
use std::fs::File; use std::io::Write;
use std::io::{Read, Write};
use std::path::PathBuf; use std::path::PathBuf;
use error::*; use crate::error::ErrorKind::*;
use error::ErrorKind::*; use crate::error::*;
use {ControllIdentifier, ControllerInternal, Controllers, Resources, Subsystem}; 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. /// A controller that allows controlling the `cpuacct` subsystem of a Cgroup.
/// ///
@@ -84,38 +89,17 @@ impl<'a> From<&'a Subsystem> for &'a CpuAcctController {
Subsystem::CpuAcct(c) => c, Subsystem::CpuAcct(c) => c,
_ => { _ => {
assert_eq!(1, 0); assert_eq!(1, 0);
::std::mem::uninitialized() let v = std::mem::MaybeUninit::uninit();
v.assume_init()
} }
} }
} }
} }
} }
fn read_u64_from(mut file: File) -> Result<u64> {
let mut string = String::new();
let res = file.read_to_string(&mut string);
match res {
Ok(_) => match string.trim().parse() {
Ok(e) => Ok(e),
Err(e) => Err(Error::with_cause(ParseError, e)),
},
Err(e) => Err(Error::with_cause(ReadFailed, e)),
}
}
fn read_string_from(mut file: File) -> Result<String> {
let mut string = String::new();
match file.read_to_string(&mut string) {
Ok(_) => Ok(string.trim().to_string()),
Err(e) => Err(Error::with_cause(ReadFailed, e)),
}
}
impl CpuAcctController { impl CpuAcctController {
/// Contructs a new `CpuAcctController` with `oroot` serving as the root of the control group. /// Contructs a new `CpuAcctController` with `root` serving as the root of the control group.
pub fn new(oroot: PathBuf) -> Self { pub fn new(root: PathBuf) -> Self {
let mut root = oroot;
root.push(Self::controller_type().to_string());
Self { Self {
base: root.clone(), base: root.clone(),
path: root, path: root,
@@ -162,7 +146,9 @@ impl CpuAcctController {
/// Reset the statistics the kernel has gathered about the control group. /// Reset the statistics the kernel has gathered about the control group.
pub fn reset(&self) -> Result<()> { pub fn reset(&self) -> Result<()> {
self.open_path("cpuacct.usage", true) self.open_path("cpuacct.usage", true).and_then(|mut file| {
.and_then(|mut file| file.write_all(b"0").map_err(|e| Error::with_cause(WriteFailed, e))) file.write_all(b"0")
.map_err(|e| Error::with_cause(WriteFailed, e))
})
} }
} }

View File

@@ -1,15 +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 `cpuset` cgroup subsystem. //! This module contains the implementation of the `cpuset` cgroup subsystem.
//! //!
//! See the Kernel's documentation for more information about this subsystem, found at: //! See the Kernel's documentation for more information about this subsystem, found at:
//! [Documentation/cgroup-v1/cpusets.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/cpusets.txt) //! [Documentation/cgroup-v1/cpusets.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/cpusets.txt)
use std::fs::File;
use std::io::{Read, Write}; use log::*;
use std::io::Write;
use std::path::PathBuf; use std::path::PathBuf;
use error::*; use crate::error::ErrorKind::*;
use error::ErrorKind::*; use crate::error::*;
use { use crate::{read_string_from, read_u64_from};
use crate::{
ControllIdentifier, ControllerInternal, Controllers, CpuResources, Resources, Subsystem, ControllIdentifier, ControllerInternal, Controllers, CpuResources, Resources, Subsystem,
}; };
@@ -21,6 +29,7 @@ use {
pub struct CpuSetController { pub struct CpuSetController {
base: PathBuf, base: PathBuf,
path: PathBuf, path: PathBuf,
v2: bool,
} }
/// The current state of the `cpuset` controller for this control group. /// The current state of the `cpuset` controller for this control group.
@@ -93,17 +102,85 @@ impl ControllerInternal for CpuSetController {
&self.base &self.base
} }
fn is_v2(&self) -> bool {
self.v2
}
fn apply(&self, res: &Resources) -> Result<()> { fn apply(&self, res: &Resources) -> Result<()> {
// get the resources that apply to this controller // get the resources that apply to this controller
let res: &CpuResources = &res.cpu; let res: &CpuResources = &res.cpu;
if res.update_values { update!(self, set_cpus, res.cpus.as_ref());
let _ = self.set_cpus(&res.cpus); update!(self, set_mems, res.mems.as_ref());
let _ = self.set_mems(&res.mems);
}
Ok(()) 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 { impl ControllIdentifier for CpuSetController {
@@ -119,29 +196,14 @@ impl<'a> From<&'a Subsystem> for &'a CpuSetController {
Subsystem::CpuSet(c) => c, Subsystem::CpuSet(c) => c,
_ => { _ => {
assert_eq!(1, 0); assert_eq!(1, 0);
::std::mem::uninitialized() let v = std::mem::MaybeUninit::uninit();
v.assume_init()
} }
} }
} }
} }
} }
fn read_string_from(mut file: File) -> Result<String> {
let mut string = String::new();
match file.read_to_string(&mut string) {
Ok(_) => Ok(string.trim().to_string()),
Err(e) => Err(Error::with_cause(ReadFailed, e)),
}
}
fn read_u64_from(mut file: File) -> Result<u64> {
let mut string = String::new();
match file.read_to_string(&mut string) {
Ok(_) => string.trim().parse().map_err(|e| Error::with_cause(ParseError, e)),
Err(e) => Err(Error::with_cause(ReadFailed, e)),
}
}
/// Parse a string like "1,2,4-5,8" into a list of (start, end) tuples. /// Parse a string like "1,2,4-5,8" into a list of (start, end) tuples.
fn parse_range(s: String) -> Result<Vec<(u64, u64)>> { fn parse_range(s: String) -> Result<Vec<(u64, u64)>> {
let mut fin = Vec::new(); let mut fin = Vec::new();
@@ -180,13 +242,12 @@ fn parse_range(s: String) -> Result<Vec<(u64, u64)>> {
} }
impl CpuSetController { impl CpuSetController {
/// Contructs a new `CpuSetController` with `oroot` serving as the root of the control group. /// Contructs a new `CpuSetController` with `root` serving as the root of the control group.
pub fn new(oroot: PathBuf) -> Self { pub fn new(root: PathBuf, v2: bool) -> Self {
let mut root = oroot;
root.push(Self::controller_type().to_string());
Self { Self {
base: root.clone(), base: root.clone(),
path: root, path: root,
v2: v2,
} }
} }
@@ -285,9 +346,11 @@ impl CpuSetController {
self.open_path("cpuset.cpu_exclusive", true) self.open_path("cpuset.cpu_exclusive", true)
.and_then(|mut file| { .and_then(|mut file| {
if b { if b {
file.write_all(b"1").map_err(|e| Error::with_cause(WriteFailed, e)) file.write_all(b"1")
.map_err(|e| Error::with_cause(WriteFailed, e))
} else { } else {
file.write_all(b"0").map_err(|e| Error::with_cause(WriteFailed, e)) file.write_all(b"0")
.map_err(|e| Error::with_cause(WriteFailed, e))
} }
}) })
} }
@@ -298,9 +361,11 @@ impl CpuSetController {
self.open_path("cpuset.mem_exclusive", true) self.open_path("cpuset.mem_exclusive", true)
.and_then(|mut file| { .and_then(|mut file| {
if b { if b {
file.write_all(b"1").map_err(|e| Error::with_cause(WriteFailed, e)) file.write_all(b"1")
.map_err(|e| Error::with_cause(WriteFailed, e))
} else { } else {
file.write_all(b"0").map_err(|e| Error::with_cause(WriteFailed, e)) file.write_all(b"0")
.map_err(|e| Error::with_cause(WriteFailed, e))
} }
}) })
} }
@@ -335,9 +400,11 @@ impl CpuSetController {
self.open_path("cpuset.mem_hardwall", true) self.open_path("cpuset.mem_hardwall", true)
.and_then(|mut file| { .and_then(|mut file| {
if b { if b {
file.write_all(b"1").map_err(|e| Error::with_cause(WriteFailed, e)) file.write_all(b"1")
.map_err(|e| Error::with_cause(WriteFailed, e))
} else { } else {
file.write_all(b"0").map_err(|e| Error::with_cause(WriteFailed, e)) file.write_all(b"0")
.map_err(|e| Error::with_cause(WriteFailed, e))
} }
}) })
} }
@@ -348,9 +415,11 @@ impl CpuSetController {
self.open_path("cpuset.sched_load_balance", true) self.open_path("cpuset.sched_load_balance", true)
.and_then(|mut file| { .and_then(|mut file| {
if b { if b {
file.write_all(b"1").map_err(|e| Error::with_cause(WriteFailed, e)) file.write_all(b"1")
.map_err(|e| Error::with_cause(WriteFailed, e))
} else { } else {
file.write_all(b"0").map_err(|e| Error::with_cause(WriteFailed, e)) file.write_all(b"0")
.map_err(|e| Error::with_cause(WriteFailed, e))
} }
}) })
} }
@@ -372,9 +441,11 @@ impl CpuSetController {
self.open_path("cpuset.memory_migrate", true) self.open_path("cpuset.memory_migrate", true)
.and_then(|mut file| { .and_then(|mut file| {
if b { if b {
file.write_all(b"1").map_err(|e| Error::with_cause(WriteFailed, e)) file.write_all(b"1")
.map_err(|e| Error::with_cause(WriteFailed, e))
} else { } else {
file.write_all(b"0").map_err(|e| Error::with_cause(WriteFailed, e)) file.write_all(b"0")
.map_err(|e| Error::with_cause(WriteFailed, e))
} }
}) })
} }
@@ -385,9 +456,11 @@ impl CpuSetController {
self.open_path("cpuset.memory_spread_page", true) self.open_path("cpuset.memory_spread_page", true)
.and_then(|mut file| { .and_then(|mut file| {
if b { if b {
file.write_all(b"1").map_err(|e| Error::with_cause(WriteFailed, e)) file.write_all(b"1")
.map_err(|e| Error::with_cause(WriteFailed, e))
} else { } else {
file.write_all(b"0").map_err(|e| Error::with_cause(WriteFailed, e)) file.write_all(b"0")
.map_err(|e| Error::with_cause(WriteFailed, e))
} }
}) })
} }
@@ -398,9 +471,11 @@ impl CpuSetController {
self.open_path("cpuset.memory_spread_slab", true) self.open_path("cpuset.memory_spread_slab", true)
.and_then(|mut file| { .and_then(|mut file| {
if b { if b {
file.write_all(b"1").map_err(|e| Error::with_cause(WriteFailed, e)) file.write_all(b"1")
.map_err(|e| Error::with_cause(WriteFailed, e))
} else { } else {
file.write_all(b"0").map_err(|e| Error::with_cause(WriteFailed, e)) file.write_all(b"0")
.map_err(|e| Error::with_cause(WriteFailed, e))
} }
}) })
} }
@@ -417,9 +492,11 @@ impl CpuSetController {
self.open_path("cpuset.memory_pressure_enabled", true) self.open_path("cpuset.memory_pressure_enabled", true)
.and_then(|mut file| { .and_then(|mut file| {
if b { if b {
file.write_all(b"1").map_err(|e| Error::with_cause(WriteFailed, e)) file.write_all(b"1")
.map_err(|e| Error::with_cause(WriteFailed, e))
} else { } else {
file.write_all(b"0").map_err(|e| Error::with_cause(WriteFailed, e)) file.write_all(b"0")
.map_err(|e| Error::with_cause(WriteFailed, e))
} }
}) })
} }
@@ -427,7 +504,7 @@ impl CpuSetController {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use cpuset; use crate::cpuset;
#[test] #[test]
fn test_parse_range() { fn test_parse_range() {
let test_cases = vec![ let test_cases = vec![

View File

@@ -1,3 +1,8 @@
// Copyright (c) 2018 Levente Kurusa
//
// SPDX-License-Identifier: Apache-2.0 or MIT
//
//! This module contains the implementation of the `devices` cgroup subsystem. //! This module contains the implementation of the `devices` cgroup subsystem.
//! //!
//! See the Kernel's documentation for more information about this subsystem, found at: //! See the Kernel's documentation for more information about this subsystem, found at:
@@ -5,10 +10,12 @@
use std::io::{Read, Write}; use std::io::{Read, Write};
use std::path::PathBuf; use std::path::PathBuf;
use error::*; use log::*;
use error::ErrorKind::*;
use { use crate::error::ErrorKind::*;
use crate::error::*;
use crate::{
ControllIdentifier, ControllerInternal, Controllers, DeviceResource, DeviceResources, ControllIdentifier, ControllerInternal, Controllers, DeviceResource, DeviceResources,
Resources, Subsystem, Resources, Subsystem,
}; };
@@ -122,8 +129,7 @@ impl DevicePermissions {
return Ok(v); return Ok(v);
} }
for e in s.chars() { for e in s.chars() {
let perm = DevicePermissions::from_char(e) let perm = DevicePermissions::from_char(e).ok_or_else(|| Error::new(ParseError))?;
.ok_or_else(|| Error::new(ParseError))?;
v.push(perm); v.push(perm);
} }
@@ -149,13 +155,11 @@ impl ControllerInternal for DevicesController {
// get the resources that apply to this controller // get the resources that apply to this controller
let res: &DeviceResources = &res.devices; let res: &DeviceResources = &res.devices;
if res.update_values { for i in &res.devices {
for i in &res.devices { if i.allow {
if i.allow { let _ = self.allow_device(i.devtype, i.major, i.minor, &i.access);
let _ = self.allow_device(i.devtype, i.major, i.minor, &i.access); } else {
} else { let _ = self.deny_device(i.devtype, i.major, i.minor, &i.access);
let _ = self.deny_device(i.devtype, i.major, i.minor, &i.access);
}
} }
} }
@@ -176,7 +180,8 @@ impl<'a> From<&'a Subsystem> for &'a DevicesController {
Subsystem::Devices(c) => c, Subsystem::Devices(c) => c,
_ => { _ => {
assert_eq!(1, 0); assert_eq!(1, 0);
::std::mem::uninitialized() let v = std::mem::MaybeUninit::uninit();
v.assume_init()
} }
} }
} }
@@ -184,10 +189,8 @@ impl<'a> From<&'a Subsystem> for &'a DevicesController {
} }
impl DevicesController { impl DevicesController {
/// Constructs a new `DevicesController` with `oroot` serving as the root of the control group. /// Constructs a new `DevicesController` with `root` serving as the root of the control group.
pub fn new(oroot: PathBuf) -> Self { pub fn new(root: PathBuf) -> Self {
let mut root = oroot;
root.push(Self::controller_type().to_string());
Self { Self {
base: root.clone(), base: root.clone(),
path: root, path: root,

View File

@@ -1,15 +1,27 @@
// 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::error::Error as StdError;
use std::fmt; use std::fmt;
/// The different types of errors that can occur while manipulating control groups. /// The different types of errors that can occur while manipulating control groups.
#[derive(Debug, Eq, PartialEq)] #[derive(Debug, Eq, PartialEq)]
pub enum ErrorKind { pub enum ErrorKind {
FsError,
Common(String),
/// An error occured while writing to a control group file. /// An error occured while writing to a control group file.
WriteFailed, WriteFailed,
/// An error occured while trying to read from a control group file. /// An error occured while trying to read from a control group file.
ReadFailed, 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. /// An error occured while trying to parse a value from a control group file.
/// ///
/// In the future, there will be some information attached to this field. /// In the future, there will be some information attached to this field.
@@ -27,6 +39,8 @@ pub enum ErrorKind {
/// This crate checks against this and operations will fail with this error. /// This crate checks against this and operations will fail with this error.
InvalidPath, InvalidPath,
InvalidBytesSize,
/// An unknown error has occured. /// An unknown error has occured.
Other, Other,
} }
@@ -34,26 +48,34 @@ pub enum ErrorKind {
#[derive(Debug)] #[derive(Debug)]
pub struct Error { pub struct Error {
kind: ErrorKind, kind: ErrorKind,
cause: Option<Box<StdError + Send>>, cause: Option<Box<dyn StdError + Send + Sync>>,
} }
impl fmt::Display for Error { impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let msg = match self.kind { let msg = match &self.kind {
ErrorKind::WriteFailed => "unable to write to a control group file", ErrorKind::FsError => "fs error".to_string(),
ErrorKind::ReadFailed => "unable to read a control group file", ErrorKind::Common(s) => s.clone(),
ErrorKind::ParseError => "unable to parse control group file", ErrorKind::WriteFailed => "unable to write to a control group file".to_string(),
ErrorKind::InvalidOperation => "the requested operation is invalid", ErrorKind::ReadFailed => "unable to read a control group file".to_string(),
ErrorKind::InvalidPath => "the given path is invalid", ErrorKind::RemoveFailed => "unable to remove a control group".to_string(),
ErrorKind::Other => "an unknown error", ErrorKind::ParseError => "unable to parse control group file".to_string(),
ErrorKind::InvalidOperation => "the requested operation is invalid".to_string(),
ErrorKind::InvalidPath => "the given path is invalid".to_string(),
ErrorKind::InvalidBytesSize => "invalid bytes size".to_string(),
ErrorKind::Other => "an unknown error".to_string(),
}; };
write!(f, "{}", msg) if let Some(cause) = &self.cause {
write!(f, "{} caused by: {:?}", msg, cause)
} else {
write!(f, "{}", msg)
}
} }
} }
impl StdError for Error { impl StdError for Error {
fn cause(&self) -> Option<&StdError> { fn cause(&self) -> Option<&dyn StdError> {
match self.cause { match self.cause {
Some(ref x) => Some(&**x), Some(ref x) => Some(&**x),
None => None, None => None,
@@ -62,16 +84,19 @@ impl StdError for Error {
} }
impl Error { impl Error {
pub(crate) fn new(kind: ErrorKind) -> Self { pub(crate) fn from_string(s: String) -> Self {
Self { Self {
kind, kind: ErrorKind::Common(s),
cause: None, cause: None,
} }
} }
pub(crate) fn new(kind: ErrorKind) -> Self {
Self { kind, cause: None }
}
pub(crate) fn with_cause<E>(kind: ErrorKind, cause: E) -> Self pub(crate) fn with_cause<E>(kind: ErrorKind, cause: E) -> Self
where where
E: 'static + Send + StdError, E: 'static + Send + Sync + StdError,
{ {
Self { Self {
kind, kind,

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,3 +1,9 @@
// 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. //! This module contains the implementation of the `freezer` cgroup subsystem.
//! //!
//! See the Kernel's documentation for more information about this subsystem, found at: //! See the Kernel's documentation for more information about this subsystem, found at:
@@ -5,10 +11,10 @@
use std::io::{Read, Write}; use std::io::{Read, Write};
use std::path::PathBuf; use std::path::PathBuf;
use error::*; use crate::error::ErrorKind::*;
use error::ErrorKind::*; use crate::error::*;
use {ControllIdentifier, ControllerInternal, Controllers, Resources, Subsystem}; use crate::{ControllIdentifier, ControllerInternal, Controllers, Resources, Subsystem};
/// A controller that allows controlling the `freezer` subsystem of a Cgroup. /// A controller that allows controlling the `freezer` subsystem of a Cgroup.
/// ///
@@ -22,6 +28,7 @@ use {ControllIdentifier, ControllerInternal, Controllers, Resources, Subsystem};
pub struct FreezerController { pub struct FreezerController {
base: PathBuf, base: PathBuf,
path: PathBuf, path: PathBuf,
v2: bool,
} }
/// The current state of the control group /// The current state of the control group
@@ -66,7 +73,8 @@ impl<'a> From<&'a Subsystem> for &'a FreezerController {
Subsystem::Freezer(c) => c, Subsystem::Freezer(c) => c,
_ => { _ => {
assert_eq!(1, 0); assert_eq!(1, 0);
::std::mem::uninitialized() let v = std::mem::MaybeUninit::uninit();
v.assume_init()
} }
} }
} }
@@ -74,41 +82,59 @@ impl<'a> From<&'a Subsystem> for &'a FreezerController {
} }
impl FreezerController { impl FreezerController {
/// Contructs a new `FreezerController` with `oroot` serving as the root of the control group. /// Contructs a new `FreezerController` with `root` serving as the root of the control group.
pub fn new(oroot: PathBuf) -> Self { pub fn new(root: PathBuf, v2: bool) -> Self {
let mut root = oroot;
root.push(Self::controller_type().to_string());
Self { Self {
base: root.clone(), base: root.clone(),
path: root, path: root,
v2: v2,
} }
} }
/// Freezes the processes in the control group. /// Freezes the processes in the control group.
pub fn freeze(&self) -> Result<()> { pub fn freeze(&self) -> Result<()> {
self.open_path("freezer.state", true).and_then(|mut file| { let mut file = "freezer.state";
file.write_all("FROZEN".to_string().as_ref()) 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)) .map_err(|e| Error::with_cause(WriteFailed, e))
}) })
} }
/// Thaws, that is, unfreezes the processes in the control group. /// Thaws, that is, unfreezes the processes in the control group.
pub fn thaw(&self) -> Result<()> { pub fn thaw(&self) -> Result<()> {
self.open_path("freezer.state", true).and_then(|mut file| { let mut file = "freezer.state";
file.write_all("THAWED".to_string().as_ref()) 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)) .map_err(|e| Error::with_cause(WriteFailed, e))
}) })
} }
/// Retrieve the state of processes in the control group. /// Retrieve the state of processes in the control group.
pub fn state(&self) -> Result<FreezerState> { pub fn state(&self) -> Result<FreezerState> {
self.open_path("freezer.state", false).and_then(|mut file| { 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 mut s = String::new();
let res = file.read_to_string(&mut s); let res = file.read_to_string(&mut s);
match res { match res {
Ok(_) => match s.as_ref() { Ok(_) => match s.as_ref() {
"FROZEN" => Ok(FreezerState::Frozen), "FROZEN" => Ok(FreezerState::Frozen),
"THAWED" => Ok(FreezerState::Thawed), "THAWED" => Ok(FreezerState::Thawed),
"1" => Ok(FreezerState::Frozen),
"0" => Ok(FreezerState::Thawed),
"FREEZING" => Ok(FreezerState::Freezing), "FREEZING" => Ok(FreezerState::Freezing),
_ => Err(Error::new(ParseError)), _ => Err(Error::new(ParseError)),
}, },

View File

@@ -1,136 +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. //! 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 //! Currently, we only support the cgroupv1 hierarchy, but in the future we will add support for
//! the Unified Hierarchy. //! the Unified Hierarchy.
use std::fs::File; use procinfo::pid::{mountinfo_self, Mountinfo};
use std::io::BufRead; use std::fs;
use std::io::BufReader; use std::path::PathBuf;
use std::path::{Path, PathBuf};
use blkio::BlkIoController; use crate::blkio::BlkIoController;
use cpu::CpuController; use crate::cpu::CpuController;
use cpuacct::CpuAcctController; use crate::cpuacct::CpuAcctController;
use cpuset::CpuSetController; use crate::cpuset::CpuSetController;
use devices::DevicesController; use crate::devices::DevicesController;
use freezer::FreezerController; use crate::freezer::FreezerController;
use hugetlb::HugeTlbController; use crate::hugetlb::HugeTlbController;
use memory::MemController; use crate::memory::MemController;
use net_cls::NetClsController; use crate::net_cls::NetClsController;
use net_prio::NetPrioController; use crate::net_prio::NetPrioController;
use perf_event::PerfEventController; use crate::perf_event::PerfEventController;
use pid::PidController; use crate::pid::PidController;
use rdma::RdmaController; use crate::rdma::RdmaController;
use {Controllers, Hierarchy, Subsystem}; use crate::systemd::SystemdController;
use crate::{Controllers, Hierarchy, Subsystem};
use cgroup::Cgroup; use crate::cgroup::Cgroup;
/// The standard, original cgroup implementation. Often referred to as "cgroupv1". /// The standard, original cgroup implementation. Often referred to as "cgroupv1".
#[derive(Debug)]
pub struct V1 { pub struct V1 {
mount_point: String, mountinfo: Vec<Mountinfo>,
}
#[derive(Debug)]
pub struct V2 {
root: String,
} }
impl Hierarchy for V1 { impl Hierarchy for V1 {
fn v2(&self) -> bool {
false
}
fn subsystems(&self) -> Vec<Subsystem> { fn subsystems(&self) -> Vec<Subsystem> {
let mut subs = vec![]; let mut subs = vec![];
if self.check_support(Controllers::Pids) {
subs.push(Subsystem::Pid(PidController::new(self.root()))); // The cgroup writeback feature requires cooperation between memcgs and blkcgs
// To avoid exceptions, we should add_task for blkcg before memcg(push BlkIo before Mem)
// For more Information: https://www.alibabacloud.com/help/doc-detail/155509.htm
if let Some(root) = self.get_mount_point(Controllers::BlkIo) {
subs.push(Subsystem::BlkIo(BlkIoController::new(root, false)));
} }
if self.check_support(Controllers::Mem) { if let Some(root) = self.get_mount_point(Controllers::Mem) {
subs.push(Subsystem::Mem(MemController::new(self.root()))); subs.push(Subsystem::Mem(MemController::new(root, false)));
} }
if self.check_support(Controllers::CpuSet) { if let Some(root) = self.get_mount_point(Controllers::Pids) {
subs.push(Subsystem::CpuSet(CpuSetController::new(self.root()))); subs.push(Subsystem::Pid(PidController::new(root, false)));
} }
if self.check_support(Controllers::CpuAcct) { if let Some(root) = self.get_mount_point(Controllers::CpuSet) {
subs.push(Subsystem::CpuAcct(CpuAcctController::new(self.root()))); subs.push(Subsystem::CpuSet(CpuSetController::new(root, false)));
} }
if self.check_support(Controllers::Cpu) { if let Some(root) = self.get_mount_point(Controllers::CpuAcct) {
subs.push(Subsystem::Cpu(CpuController::new(self.root()))); subs.push(Subsystem::CpuAcct(CpuAcctController::new(root)));
} }
if self.check_support(Controllers::Devices) { if let Some(root) = self.get_mount_point(Controllers::Cpu) {
subs.push(Subsystem::Devices(DevicesController::new(self.root()))); subs.push(Subsystem::Cpu(CpuController::new(root, false)));
} }
if self.check_support(Controllers::Freezer) { if let Some(root) = self.get_mount_point(Controllers::Devices) {
subs.push(Subsystem::Freezer(FreezerController::new(self.root()))); subs.push(Subsystem::Devices(DevicesController::new(root)));
} }
if self.check_support(Controllers::NetCls) { if let Some(root) = self.get_mount_point(Controllers::Freezer) {
subs.push(Subsystem::NetCls(NetClsController::new(self.root()))); subs.push(Subsystem::Freezer(FreezerController::new(root, false)));
} }
if self.check_support(Controllers::BlkIo) { if let Some(root) = self.get_mount_point(Controllers::NetCls) {
subs.push(Subsystem::BlkIo(BlkIoController::new(self.root()))); subs.push(Subsystem::NetCls(NetClsController::new(root)));
} }
if self.check_support(Controllers::PerfEvent) { if let Some(root) = self.get_mount_point(Controllers::PerfEvent) {
subs.push(Subsystem::PerfEvent(PerfEventController::new(self.root()))); subs.push(Subsystem::PerfEvent(PerfEventController::new(root)));
} }
if self.check_support(Controllers::NetPrio) { if let Some(root) = self.get_mount_point(Controllers::NetPrio) {
subs.push(Subsystem::NetPrio(NetPrioController::new(self.root()))); subs.push(Subsystem::NetPrio(NetPrioController::new(root)));
} }
if self.check_support(Controllers::HugeTlb) { if let Some(root) = self.get_mount_point(Controllers::HugeTlb) {
subs.push(Subsystem::HugeTlb(HugeTlbController::new(self.root()))); subs.push(Subsystem::HugeTlb(HugeTlbController::new(root, false)));
} }
if self.check_support(Controllers::Rdma) { if let Some(root) = self.get_mount_point(Controllers::Rdma) {
subs.push(Subsystem::Rdma(RdmaController::new(self.root()))); subs.push(Subsystem::Rdma(RdmaController::new(root)));
}
if let Some(root) = self.get_mount_point(Controllers::Systemd) {
subs.push(Subsystem::Systemd(SystemdController::new(root, false)));
} }
subs subs
} }
fn root_control_group(&self) -> Cgroup { fn root_control_group(&self) -> Cgroup {
Cgroup::load(self, "".to_string()) Cgroup::load(auto(), "".to_string())
}
fn check_support(&self, sub: Controllers) -> bool {
let root = self.root().read_dir().unwrap();
for entry in root {
if let Ok(entry) = entry {
if entry.file_name().into_string().unwrap() == sub.to_string() {
return true;
}
}
}
return false;
} }
fn root(&self) -> PathBuf { fn root(&self) -> PathBuf {
PathBuf::from(self.mount_point.clone()) self.mountinfo
.iter()
.find_map(|m| {
if m.fs_type.0 == "cgroup" {
return Some(m.mount_point.parent().unwrap());
}
None
})
.unwrap()
.to_path_buf()
}
}
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 { impl V1 {
/// Finds where control groups are mounted to and returns a hierarchy in which control groups /// Finds where control groups are mounted to and returns a hierarchy in which control groups
/// can be created. /// can be created.
pub fn new() -> Self { pub fn new() -> V1 {
let mount_point = find_v1_mount().unwrap();
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> { pub const UNIFIED_MOUNTPOINT: &'static str = "/sys/fs/cgroup";
// Open mountinfo so we can get a parseable mount list
let mountinfo_path = Path::new("/proc/self/mountinfo");
// If /proc isn't mounted, or something else happens, then bail out #[cfg(all(target_os = "linux", not(target_env = "musl")))]
if mountinfo_path.exists() == false { pub fn is_cgroup2_unified_mode() -> bool {
return None; 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(); // FIXME notwork, nix will not compile CGROUP2_SUPER_MAGIC because not(target_env = "musl")
let mountinfo_reader = BufReader::new(&mountinfo_file); fs_stat.unwrap().filesystem_type() == statfs::CGROUP2_SUPER_MAGIC
for _line in mountinfo_reader.lines() { }
let line = _line.unwrap();
let mut fields = line.split_whitespace(); pub const INIT_CGROUP_PATHS: &'static str = "/proc/1/cgroup";
let index = line.find(" - ").unwrap();
let mut more_fields = line[index + 3..].split_whitespace().collect::<Vec<_>>(); #[cfg(all(target_os = "linux", target_env = "musl"))]
let fstype = more_fields[0]; pub fn is_cgroup2_unified_mode() -> bool {
if fstype == "tmpfs" && more_fields[2].contains("ro") { let lines = fs::read_to_string(INIT_CGROUP_PATHS);
let cgroups_mount = fields.nth(4).unwrap(); if lines.is_err() {
info!("found cgroups at {:?}", cgroups_mount); return false;
return Some(cgroups_mount.to_string()); }
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,17 +1,22 @@
// 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. //! This module contains the implementation of the `hugetlb` cgroup subsystem.
//! //!
//! See the Kernel's documentation for more information about this subsystem, found at: //! See the Kernel's documentation for more information about this subsystem, found at:
//! [Documentation/cgroup-v1/hugetlb.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/hugetlb.txt) //! [Documentation/cgroup-v1/hugetlb.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/hugetlb.txt)
use std::fs::File; use std::io::Write;
use std::io::{Read, Write};
use std::path::PathBuf; use std::path::PathBuf;
use error::*; use crate::error::ErrorKind::*;
use error::ErrorKind::*; use crate::error::*;
use crate::{flat_keyed_to_vec, read_u64_from};
use { use crate::{
ControllIdentifier, ControllerInternal, Controllers, HugePageResources, Resources, ControllIdentifier, ControllerInternal, Controllers, HugePageResources, Resources, Subsystem,
Subsystem,
}; };
/// A controller that allows controlling the `hugetlb` subsystem of a Cgroup. /// A controller that allows controlling the `hugetlb` subsystem of a Cgroup.
@@ -22,6 +27,8 @@ use {
pub struct HugeTlbController { pub struct HugeTlbController {
base: PathBuf, base: PathBuf,
path: PathBuf, path: PathBuf,
sizes: Vec<String>,
v2: bool,
} }
impl ControllerInternal for HugeTlbController { impl ControllerInternal for HugeTlbController {
@@ -38,18 +45,21 @@ impl ControllerInternal for HugeTlbController {
&self.base &self.base
} }
fn is_v2(&self) -> bool {
self.v2
}
fn apply(&self, res: &Resources) -> Result<()> { fn apply(&self, res: &Resources) -> Result<()> {
// get the resources that apply to this controller // get the resources that apply to this controller
let res: &HugePageResources = &res.hugepages; let res: &HugePageResources = &res.hugepages;
if res.update_values { for i in &res.limits {
for i in &res.limits { let _ = self.set_limit_in_bytes(&i.size, i.limit);
let _ = self.set_limit_in_bytes(&i.size, i.limit); if self.limit_in_bytes(&i.size)? != i.limit {
if self.limit_in_bytes(&i.size)? != i.limit { return Err(Error::new(Other));
return Err(Error::new(Other));
}
} }
} }
Ok(()) Ok(())
} }
} }
@@ -67,40 +77,59 @@ impl<'a> From<&'a Subsystem> for &'a HugeTlbController {
Subsystem::HugeTlb(c) => c, Subsystem::HugeTlb(c) => c,
_ => { _ => {
assert_eq!(1, 0); assert_eq!(1, 0);
::std::mem::uninitialized() let v = std::mem::MaybeUninit::uninit();
v.assume_init()
} }
} }
} }
} }
} }
fn read_u64_from(mut file: File) -> Result<u64> {
let mut string = String::new();
match file.read_to_string(&mut string) {
Ok(_) => string.trim().parse().map_err(|e| Error::with_cause(ParseError, e)),
Err(e) => Err(Error::with_cause(ReadFailed, e)),
}
}
impl HugeTlbController { impl HugeTlbController {
/// Constructs a new `HugeTlbController` with `oroot` serving as the root of the control group. /// Constructs a new `HugeTlbController` with `root` serving as the root of the control group.
pub fn new(oroot: PathBuf) -> Self { pub fn new(root: PathBuf, v2: bool) -> Self {
let mut root = oroot; let sizes = get_hugepage_sizes().unwrap();
root.push(Self::controller_type().to_string());
Self { Self {
base: root.clone(), base: root.clone(),
path: root, path: root,
sizes: sizes,
v2: v2,
} }
} }
/// Whether the system supports `hugetlb_size` hugepages. /// Whether the system supports `hugetlb_size` hugepages.
pub fn size_supported(&self, _hugetlb_size: &str) -> bool { pub fn size_supported(&self, hugetlb_size: &str) -> bool {
// TODO for s in &self.sizes {
true 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. /// Check how many times has the limit of `hugetlb_size` hugepages been hit.
pub fn failcnt(&self, hugetlb_size: &str) -> Result<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) self.open_path(&format!("hugetlb.{}.failcnt", hugetlb_size), false)
.and_then(read_u64_from) .and_then(read_u64_from)
} }
@@ -115,8 +144,11 @@ impl HugeTlbController {
/// Get the current usage of memory that is backed by hugepages of a certain size /// Get the current usage of memory that is backed by hugepages of a certain size
/// (`hugetlb_size`). /// (`hugetlb_size`).
pub fn usage_in_bytes(&self, hugetlb_size: &str) -> Result<u64> { pub fn usage_in_bytes(&self, hugetlb_size: &str) -> Result<u64> {
self.open_path(&format!("hugetlb.{}.usage_in_bytes", hugetlb_size), false) let mut file = format!("hugetlb.{}.usage_in_bytes", hugetlb_size);
.and_then(read_u64_from) 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 /// Get the maximum observed usage of memory that is backed by hugepages of a certain size
@@ -125,16 +157,144 @@ impl HugeTlbController {
self.open_path( self.open_path(
&format!("hugetlb.{}.max_usage_in_bytes", hugetlb_size), &format!("hugetlb.{}.max_usage_in_bytes", hugetlb_size),
false, false,
).and_then(read_u64_from) )
.and_then(read_u64_from)
} }
/// Set the limit (in bytes) of how much memory can be backed by hugepages of a certain size /// Set the limit (in bytes) of how much memory can be backed by hugepages of a certain size
/// (`hugetlb_size`). /// (`hugetlb_size`).
pub fn set_limit_in_bytes(&self, hugetlb_size: &str, limit: u64) -> Result<()> { pub fn set_limit_in_bytes(&self, hugetlb_size: &str, limit: u64) -> Result<()> {
self.open_path(&format!("hugetlb.{}.limit_in_bytes", hugetlb_size), true) let mut file = format!("hugetlb.{}.limit_in_bytes", hugetlb_size);
.and_then(|mut file| { if self.v2 {
file.write_all(limit.to_string().as_ref()) file = format!("hugetlb.{}.max", hugetlb_size);
.map_err(|e| Error::with_cause(WriteFailed, e)) }
}) 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,17 +1,45 @@
#[macro_use] // Copyright (c) 2018 Levente Kurusa
extern crate log; // Copyright (c) 2020 Ant Group
//
// SPDX-License-Identifier: Apache-2.0 or MIT
//
use std::fs::File; use log::*;
use std::io::{BufRead, BufReader, Write};
use std::collections::HashMap;
use std::fs::{self, File};
use std::io::{BufRead, BufReader, Read, Write};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::str::FromStr;
macro_rules! update_and_test {
($self: ident, $set_func:ident, $value:expr, $get_func:ident) => {
if let Some(v) = $value {
$self.$set_func(v)?;
if $self.$get_func()? != v {
return Err(Error::new(Other));
}
}
};
}
macro_rules! update {
($self: ident, $set_func:ident, $value:expr) => {
if let Some(v) = $value {
let _ = $self.$set_func(v);
}
};
}
pub mod blkio; pub mod blkio;
pub mod cgroup; pub mod cgroup;
pub mod cgroup_builder;
pub mod cpu; pub mod cpu;
pub mod cpuacct; pub mod cpuacct;
pub mod cpuset; pub mod cpuset;
pub mod devices; pub mod devices;
pub mod error; pub mod error;
pub mod events;
pub mod freezer; pub mod freezer;
pub mod hierarchies; pub mod hierarchies;
pub mod hugetlb; pub mod hugetlb;
@@ -21,27 +49,30 @@ pub mod net_prio;
pub mod perf_event; pub mod perf_event;
pub mod pid; pub mod pid;
pub mod rdma; pub mod rdma;
pub mod cgroup_builder; pub mod systemd;
use blkio::BlkIoController; use crate::blkio::BlkIoController;
use cpu::CpuController; use crate::cpu::CpuController;
use cpuacct::CpuAcctController; use crate::cpuacct::CpuAcctController;
use cpuset::CpuSetController; use crate::cpuset::CpuSetController;
use devices::DevicesController; use crate::devices::DevicesController;
use error::*; use crate::error::ErrorKind::*;
use freezer::FreezerController; use crate::error::*;
use hugetlb::HugeTlbController; use crate::freezer::FreezerController;
use memory::MemController; use crate::hugetlb::HugeTlbController;
use net_cls::NetClsController; use crate::memory::MemController;
use net_prio::NetPrioController; use crate::net_cls::NetClsController;
use perf_event::PerfEventController; use crate::net_prio::NetPrioController;
use pid::PidController; use crate::perf_event::PerfEventController;
use rdma::RdmaController; use crate::pid::PidController;
use crate::rdma::RdmaController;
use crate::systemd::SystemdController;
pub use cgroup::Cgroup; #[doc(inline)]
pub use crate::cgroup::Cgroup;
/// Contains all the subsystems that are available in this crate. /// Contains all the subsystems that are available in this crate.
#[derive(Debug)] #[derive(Debug, Clone)]
pub enum Subsystem { pub enum Subsystem {
/// Controller for the `Pid` subsystem, see `PidController` for more information. /// Controller for the `Pid` subsystem, see `PidController` for more information.
Pid(PidController), Pid(PidController),
@@ -69,10 +100,12 @@ pub enum Subsystem {
HugeTlb(HugeTlbController), HugeTlb(HugeTlbController),
/// Controller for the `Rdma` subsystem, see `RdmaController` for more information. /// Controller for the `Rdma` subsystem, see `RdmaController` for more information.
Rdma(RdmaController), Rdma(RdmaController),
/// Controller for the `Systemd` subsystem, see `SystemdController` for more information.
Systemd(SystemdController),
} }
#[doc(hidden)] #[doc(hidden)]
#[derive(Eq, PartialEq, Debug)] #[derive(Eq, PartialEq, Debug, Clone)]
pub enum Controllers { pub enum Controllers {
Pids, Pids,
Mem, Mem,
@@ -87,6 +120,7 @@ pub enum Controllers {
NetPrio, NetPrio,
HugeTlb, HugeTlb,
Rdma, Rdma,
Systemd,
} }
impl Controllers { impl Controllers {
@@ -105,6 +139,7 @@ impl Controllers {
Controllers::NetPrio => return "net_prio".to_string(), Controllers::NetPrio => return "net_prio".to_string(),
Controllers::HugeTlb => return "hugetlb".to_string(), Controllers::HugeTlb => return "hugetlb".to_string(),
Controllers::Rdma => return "rdma".to_string(), Controllers::Rdma => return "rdma".to_string(),
Controllers::Systemd => return "name=systemd".to_string(),
} }
} }
} }
@@ -121,6 +156,13 @@ mod sealed {
fn get_path_mut(&mut self) -> &mut PathBuf; fn get_path_mut(&mut self) -> &mut PathBuf;
fn get_base(&self) -> &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<()> { fn verify_path(&self) -> Result<()> {
if self.get_path().starts_with(self.get_base()) { if self.get_path().starts_with(self.get_base()) {
Ok(()) Ok(())
@@ -148,6 +190,17 @@ mod sealed {
} }
} }
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)] #[doc(hidden)]
fn path_exists(&self, p: &str) -> bool { fn path_exists(&self, p: &str) -> bool {
if let Err(_) = self.verify_path() { if let Err(_) = self.verify_path() {
@@ -156,11 +209,29 @@ mod sealed {
std::path::Path::new(p).exists() std::path::Path::new(p).exists()
} }
}
pub trait CustomizedAttribute: ControllerInternal {
fn set(&self, key: &str, value: &str) -> Result<()> {
self.open_path(key, true).and_then(|mut file| {
file.write_all(value.as_ref())
.map_err(|e| Error::with_cause(WriteFailed, 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 sealed::ControllerInternal; pub(crate) use crate::sealed::{ControllerInternal, CustomizedAttribute};
/// A Controller is a subsystem attached to the control group. /// A Controller is a subsystem attached to the control group.
/// ///
@@ -182,17 +253,31 @@ pub trait Controller {
/// Does this controller already exist? /// Does this controller already exist?
fn exists(&self) -> bool; fn exists(&self) -> bool;
/// Set notify_on_release
fn set_notify_on_release(&self, enable: bool) -> Result<()>;
/// Set release_agent
fn set_release_agent(&self, path: &str) -> Result<()>;
/// Delete the controller. /// Delete the controller.
fn delete(&self); fn delete(&self) -> Result<()>;
/// Attach a task to this controller. /// Attach a task to this controller.
fn add_task(&self, pid: &CgroupPid) -> Result<()>; fn add_task(&self, pid: &CgroupPid) -> Result<()>;
/// Attach a task to this controller.
fn add_task_by_tgid(&self, pid: &CgroupPid) -> Result<()>;
/// Get the list of tasks that this controller has. /// Get the list of tasks that this controller has.
fn tasks(&self) -> Vec<CgroupPid>; fn tasks(&self) -> Vec<CgroupPid>;
fn v2(&self) -> bool;
} }
impl<T> Controller for T where T: ControllerInternal { impl<T> Controller for T
where
T: ControllerInternal,
{
fn control_type(&self) -> Controllers { fn control_type(&self) -> Controllers {
ControllerInternal::control_type(self) ControllerInternal::control_type(self)
} }
@@ -209,29 +294,60 @@ impl<T> Controller for T where T: ControllerInternal {
/// Create this controller /// Create this controller
fn create(&self) { fn create(&self) {
self.verify_path().expect("path should be valid"); self.verify_path()
.expect(format!("path should be valid: {:?}", self.path()).as_str());
match ::std::fs::create_dir(self.get_path()) { match ::std::fs::create_dir_all(self.get_path()) {
Ok(_) => (), Ok(_) => self.post_create(),
Err(e) => warn!("error create_dir {:?}", e), Err(e) => warn!("error create_dir: {:?} error: {:?}", self.get_path(), e),
} }
} }
/// Set notify_on_release
fn set_notify_on_release(&self, enable: bool) -> Result<()> {
self.open_path("notify_on_release", true)
.and_then(|mut file| {
write!(file, "{}", enable as i32)
.map_err(|e| Error::with_cause(ErrorKind::WriteFailed, 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? /// Does this controller already exist?
fn exists(&self) -> bool { fn exists(&self) -> bool {
self.get_path().exists() self.get_path().exists()
} }
/// Delete the controller. /// Delete the controller.
fn delete(&self) { fn delete(&self) -> Result<()> {
if self.get_path().exists() { if !self.get_path().exists() {
let _ = ::std::fs::remove_dir(self.get_path()); return Ok(());
} }
fs::remove_dir(self.get_path()).map_err(|e| Error::with_cause(ErrorKind::RemoveFailed, e))
} }
/// Attach a task to this controller. /// Attach a task to this controller.
fn add_task(&self, pid: &CgroupPid) -> Result<()> { fn add_task(&self, pid: &CgroupPid) -> Result<()> {
self.open_path("tasks", true).and_then(|mut file| { 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()) file.write_all(pid.pid.to_string().as_ref())
.map_err(|e| Error::with_cause(ErrorKind::WriteFailed, e)) .map_err(|e| Error::with_cause(ErrorKind::WriteFailed, e))
}) })
@@ -239,7 +355,11 @@ impl<T> Controller for T where T: ControllerInternal {
/// Get the list of tasks that this controller has. /// Get the list of tasks that this controller has.
fn tasks(&self) -> Vec<CgroupPid> { fn tasks(&self) -> Vec<CgroupPid> {
self.open_path("tasks", false) let mut file = "tasks";
if self.is_v2() {
file = "cgroup.procs";
}
self.open_path(file, false)
.and_then(|file| { .and_then(|file| {
let bf = BufReader::new(file); let bf = BufReader::new(file);
let mut v = Vec::new(); let mut v = Vec::new();
@@ -250,7 +370,12 @@ impl<T> Controller for T where T: ControllerInternal {
} }
} }
Ok(v.into_iter().map(CgroupPid::from).collect()) Ok(v.into_iter().map(CgroupPid::from).collect())
}).unwrap_or(vec![]) })
.unwrap_or(vec![])
}
fn v2(&self) -> bool {
self.is_v2()
} }
} }
@@ -261,7 +386,7 @@ pub trait ControllIdentifier {
/// Control group hierarchy (right now, only V1 is supported, but in the future Unified will be /// Control group hierarchy (right now, only V1 is supported, but in the future Unified will be
/// implemented as well). /// implemented as well).
pub trait Hierarchy { pub trait Hierarchy: std::fmt::Debug + Send + Sync {
/// Returns what subsystems are supported by the hierarchy. /// Returns what subsystems are supported by the hierarchy.
fn subsystems(&self) -> Vec<Subsystem>; fn subsystems(&self) -> Vec<Subsystem>;
@@ -271,74 +396,80 @@ pub trait Hierarchy {
/// Return a handle to the root control group in the hierarchy. /// Return a handle to the root control group in the hierarchy.
fn root_control_group(&self) -> Cgroup; fn root_control_group(&self) -> Cgroup;
/// Checks whether a certain subsystem is supported in the hierarchy. fn v2(&self) -> bool;
///
/// This is an internal function and should not be used.
#[doc(hidden)]
fn check_support(&self, sub: Controllers) -> bool;
} }
/// Resource limits for the memory subsystem. /// Resource limits for the memory subsystem.
#[derive(Debug, Clone, Eq, PartialEq, Default)] #[derive(Debug, Clone, Eq, PartialEq, Default)]
pub struct MemoryResources { pub struct MemoryResources {
/// Whether values should be applied to the controller.
pub update_values: bool,
/// How much memory (in bytes) can the kernel consume. /// How much memory (in bytes) can the kernel consume.
pub kernel_memory_limit: u64, pub kernel_memory_limit: Option<i64>,
/// Upper limit of memory usage of the control group's tasks. /// Upper limit of memory usage of the control group's tasks.
pub memory_hard_limit: u64, pub memory_hard_limit: Option<i64>,
/// How much memory the tasks in the control group can use when the system is under memory /// How much memory the tasks in the control group can use when the system is under memory
/// pressure. /// pressure.
pub memory_soft_limit: u64, pub memory_soft_limit: Option<i64>,
/// How much of the kernel's memory (in bytes) can be used for TCP-related buffers. /// How much of the kernel's memory (in bytes) can be used for TCP-related buffers.
pub kernel_tcp_memory_limit: u64, pub kernel_tcp_memory_limit: Option<i64>,
/// How much memory and swap together can the tasks in the control group use. /// How much memory and swap together can the tasks in the control group use.
pub memory_swap_limit: 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 /// Controls the tendency of the kernel to swap out parts of the address space of the tasks to
/// disk. Lower value implies less likely. /// disk. Lower value implies less likely.
/// ///
/// Note, however, that a value of zero does not mean the process is never swapped out. Use the /// Note, however, that a value of zero does not mean the process is never swapped out. Use the
/// traditional `mlock(2)` system call for that purpose. /// traditional `mlock(2)` system call for that purpose.
pub swappiness: u64, pub swappiness: Option<u64>,
/// Customized key-value attributes
///
/// # Usage:
/// ```
/// let resource = &mut cgroups_rs::Resources::default();
/// resource.memory.attrs.insert("memory.numa_balancing", "true".to_string());
/// // apply here
pub attrs: std::collections::HashMap<&'static str, String>,
} }
/// Resources limits on the number of processes. /// Resources limits on the number of processes.
#[derive(Debug, Clone, Eq, PartialEq, Default)] #[derive(Debug, Clone, Eq, PartialEq, Default)]
pub struct PidResources { pub struct PidResources {
/// Whether values should be applied to the controller.
pub update_values: bool,
/// The maximum number of processes that can exist in the control group. /// The maximum number of processes that can exist in the control group.
/// ///
/// Note that attaching processes to the control group will still succeed _even_ if the limit /// Note that attaching processes to the control group will still succeed _even_ if the limit
/// would be violated, however forks/clones inside the control group will have with `EAGAIN` if /// would be violated, however forks/clones inside the control group will have with `EAGAIN` if
/// they would violate the limit set here. /// they would violate the limit set here.
pub maximum_number_of_processes: pid::PidMax, pub maximum_number_of_processes: Option<MaxValue>,
} }
/// Resources limits about how the tasks can use the CPU. /// Resources limits about how the tasks can use the CPU.
#[derive(Debug, Clone, Eq, PartialEq, Default)] #[derive(Debug, Clone, Eq, PartialEq, Default)]
pub struct CpuResources { pub struct CpuResources {
/// Whether values should be applied to the controller.
pub update_values: bool,
// cpuset // cpuset
/// A comma-separated list of CPU IDs where the task in the control group can run. Dashes /// A comma-separated list of CPU IDs where the task in the control group can run. Dashes
/// between numbers indicate ranges. /// between numbers indicate ranges.
pub cpus: String, pub cpus: Option<String>,
/// Same syntax as the `cpus` field of this structure, but applies to memory nodes instead of /// Same syntax as the `cpus` field of this structure, but applies to memory nodes instead of
/// processors. /// processors.
pub mems: String, pub mems: Option<String>,
// cpu // cpu
/// Weight of how much of the total CPU time should this control group get. Note that this is /// Weight of how much of the total CPU time should this control group get. Note that this is
/// hierarchical, so this is weighted against the siblings of this control group. /// hierarchical, so this is weighted against the siblings of this control group.
pub shares: u64, pub shares: Option<u64>,
/// In one `period`, how much can the tasks run in nanoseconds. /// In one `period`, how much can the tasks run in nanoseconds.
pub quota: i64, pub quota: Option<i64>,
/// Period of time in nanoseconds. /// Period of time in nanoseconds.
pub period: u64, pub period: Option<u64>,
/// This is currently a no-operation. /// This is currently a no-operation.
pub realtime_runtime: i64, pub realtime_runtime: Option<i64>,
/// This is currently a no-operation. /// This is currently a no-operation.
pub realtime_period: u64, pub realtime_period: Option<u64>,
/// Customized key-value attributes
/// # Usage:
/// ```
/// let resource = &mut cgroups_rs::Resources::default();
/// resource.cpu.attrs.insert("cpu.cfs_init_buffer_us", "10".to_string());
/// // apply here
/// ```
pub attrs: std::collections::HashMap<&'static str, String>,
} }
/// A device resource that can be allowed or denied access to. /// A device resource that can be allowed or denied access to.
@@ -347,20 +478,18 @@ pub struct DeviceResource {
/// If true, access to the device is allowed, otherwise it's denied. /// If true, access to the device is allowed, otherwise it's denied.
pub allow: bool, pub allow: bool,
/// `'c'` for character device, `'b'` for block device; or `'a'` for all devices. /// `'c'` for character device, `'b'` for block device; or `'a'` for all devices.
pub devtype: ::devices::DeviceType, pub devtype: crate::devices::DeviceType,
/// The major number of the device. /// The major number of the device.
pub major: i64, pub major: i64,
/// The minor number of the device. /// The minor number of the device.
pub minor: i64, pub minor: i64,
/// Sequence of `'r'`, `'w'` or `'m'`, each denoting read, write or mknod permissions. /// Sequence of `'r'`, `'w'` or `'m'`, each denoting read, write or mknod permissions.
pub access: Vec<::devices::DevicePermissions>, pub access: Vec<crate::devices::DevicePermissions>,
} }
/// Limit the usage of devices for the control group's tasks. /// Limit the usage of devices for the control group's tasks.
#[derive(Debug, Clone, Eq, PartialEq, Default)] #[derive(Debug, Clone, Eq, PartialEq, Default)]
pub struct DeviceResources { pub struct DeviceResources {
/// Whether values should be applied to the controller.
pub update_values: bool,
/// For each device in the list, the limits in the structure are applied. /// For each device in the list, the limits in the structure are applied.
pub devices: Vec<DeviceResource>, pub devices: Vec<DeviceResource>,
} }
@@ -378,12 +507,10 @@ pub struct NetworkPriority {
/// control group. /// control group.
#[derive(Debug, Clone, Eq, PartialEq, Default)] #[derive(Debug, Clone, Eq, PartialEq, Default)]
pub struct NetworkResources { pub struct NetworkResources {
/// Whether values should be applied to the controller.
pub update_values: bool,
/// The networking class identifier to attach to the packets. /// The networking class identifier to attach to the packets.
/// ///
/// This can then later be used in iptables and such to have special rules. /// This can then later be used in iptables and such to have special rules.
pub class_id: u64, pub class_id: Option<u64>,
/// Priority of the egress traffic for each interface. /// Priority of the egress traffic for each interface.
pub priorities: Vec<NetworkPriority>, pub priorities: Vec<NetworkPriority>,
} }
@@ -401,8 +528,6 @@ pub struct HugePageResource {
/// Provides the ability to set consumption limit on each type of hugepages. /// Provides the ability to set consumption limit on each type of hugepages.
#[derive(Debug, Clone, Eq, PartialEq, Default)] #[derive(Debug, Clone, Eq, PartialEq, Default)]
pub struct HugePageResources { pub struct HugePageResources {
/// Whether values should be applied to the controller.
pub update_values: bool,
/// Set a limit of consumption for each hugepages type. /// Set a limit of consumption for each hugepages type.
pub limits: Vec<HugePageResource>, pub limits: Vec<HugePageResource>,
} }
@@ -415,9 +540,9 @@ pub struct BlkIoDeviceResource {
/// The minor number of the device. /// The minor number of the device.
pub minor: u64, pub minor: u64,
/// The weight of the device against the descendant nodes. /// 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. /// 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) /// Provides the ability to throttle a device (both byte/sec, and IO op/s)
@@ -434,12 +559,10 @@ pub struct BlkIoDeviceThrottleResource {
/// General block I/O resource limits. /// General block I/O resource limits.
#[derive(Debug, Clone, Eq, PartialEq, Default)] #[derive(Debug, Clone, Eq, PartialEq, Default)]
pub struct BlkIoResources { pub struct BlkIoResources {
/// Whether values should be applied to the controller.
pub update_values: bool,
/// The weight of the control group against descendant nodes. /// The weight of the control group against descendant nodes.
pub weight: u16, pub weight: Option<u16>,
/// The weight of the control group against sibling nodes. /// 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. /// For each device, a separate weight (both normal and leaf) can be provided.
pub weight_device: Vec<BlkIoDeviceResource>, pub weight_device: Vec<BlkIoDeviceResource>,
/// Throttled read bytes/second can be provided for each device. /// Throttled read bytes/second can be provided for each device.
@@ -559,10 +682,15 @@ impl Subsystem {
c.get_path_mut().push(path); c.get_path_mut().push(path);
c c
}), }),
Subsystem::Systemd(cont) => Subsystem::Systemd({
let mut c = cont.clone();
c.get_path_mut().push(path);
c
}),
} }
} }
fn to_controller(&self) -> &dyn Controller { pub fn to_controller(&self) -> &dyn Controller {
match self { match self {
Subsystem::Pid(cont) => cont, Subsystem::Pid(cont) => cont,
Subsystem::Mem(cont) => cont, Subsystem::Mem(cont) => cont,
@@ -577,6 +705,163 @@ impl Subsystem {
Subsystem::NetPrio(cont) => cont, Subsystem::NetPrio(cont) => cont,
Subsystem::HugeTlb(cont) => cont, Subsystem::HugeTlb(cont) => cont,
Subsystem::Rdma(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(),
} }
} }
} }
pub fn parse_max_value(s: &String) -> Result<MaxValue> {
if s.trim() == "max" {
return Ok(MaxValue::Max);
}
match s.trim().parse() {
Ok(val) => Ok(MaxValue::Value(val)),
Err(e) => Err(Error::with_cause(ParseError, e)),
}
}
// Flat keyed
// KEY0 VAL0\n
// KEY1 VAL1\n
pub fn flat_keyed_to_vec(mut file: File) -> Result<Vec<(String, i64)>> {
let mut content = String::new();
file.read_to_string(&mut content)
.map_err(|e| Error::with_cause(ReadFailed, e))?;
let mut v = Vec::new();
for line in content.lines() {
let parts: Vec<&str> = line.split(' ').collect();
if parts.len() == 2 {
match parts[1].parse::<i64>() {
Ok(i) => {
v.push((parts[0].to_string(), i));
}
Err(_) => {}
}
}
}
Ok(v)
}
// Flat keyed
// KEY0 VAL0\n
// KEY1 VAL1\n
pub fn flat_keyed_to_hashmap(mut file: File) -> Result<HashMap<String, i64>> {
let mut content = String::new();
file.read_to_string(&mut content)
.map_err(|e| Error::with_cause(ReadFailed, e))?;
let mut h = HashMap::new();
for line in content.lines() {
let parts: Vec<&str> = line.split(' ').collect();
if parts.len() == 2 {
match parts[1].parse::<i64>() {
Ok(i) => {
h.insert(parts[0].to_string(), i);
}
Err(_) => {}
}
}
}
Ok(h)
}
// Nested keyed
// KEY0 SUB_KEY0=VAL00 SUB_KEY1=VAL01...
// KEY1 SUB_KEY0=VAL10 SUB_KEY1=VAL11...
pub fn nested_keyed_to_hashmap(mut file: File) -> Result<HashMap<String, HashMap<String, i64>>> {
let mut content = String::new();
file.read_to_string(&mut content)
.map_err(|e| Error::with_cause(ReadFailed, e))?;
let mut h = HashMap::new();
for line in content.lines() {
let parts: Vec<&str> = line.split(' ').collect();
if parts.len() == 0 {
continue;
}
let mut th = HashMap::new();
for item in parts[1..].into_iter() {
let fields: Vec<&str> = item.split('=').collect();
if fields.len() == 2 {
match fields[1].parse::<i64>() {
Ok(i) => {
th.insert(fields[0].to_string(), i);
}
Err(_) => {}
}
}
}
h.insert(parts[0].to_string(), th);
}
Ok(h)
}
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)
}

View File

@@ -1,16 +1,28 @@
// 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 `memory` cgroup subsystem. //! This module contains the implementation of the `memory` cgroup subsystem.
//! //!
//! See the Kernel's documentation for more information about this subsystem, found at: //! See the Kernel's documentation for more information about this subsystem, found at:
//! [Documentation/cgroup-v1/memory.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/memory.txt) //! [Documentation/cgroup-v1/memory.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/memory.txt)
use std::fs::File; use std::collections::HashMap;
use std::io::{Read, Write}; use std::io::Write;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::mpsc::Receiver;
use error::*; use crate::error::ErrorKind::*;
use error::ErrorKind::*; use crate::error::*;
use crate::events;
use crate::{read_i64_from, read_string_from, read_u64_from};
use { use crate::flat_keyed_to_hashmap;
ControllIdentifier, ControllerInternal, Controllers, MemoryResources, Resources, Subsystem,
use crate::{
ControllIdentifier, ControllerInternal, Controllers, CustomizedAttribute, MaxValue,
MemoryResources, Resources, Subsystem,
}; };
/// A controller that allows controlling the `memory` subsystem of a Cgroup. /// A controller that allows controlling the `memory` subsystem of a Cgroup.
@@ -22,6 +34,15 @@ use {
pub struct MemController { pub struct MemController {
base: PathBuf, base: PathBuf,
path: PathBuf, path: PathBuf,
v2: bool,
}
#[derive(Default, Debug, PartialEq, Eq)]
pub struct SetMemory {
pub low: Option<MaxValue>,
pub high: Option<MaxValue>,
pub min: Option<MaxValue>,
pub max: Option<MaxValue>,
} }
/// Controls statistics and controls about the OOM killer operating in this control group. /// Controls statistics and controls about the OOM killer operating in this control group.
@@ -85,7 +106,7 @@ pub struct NumaStat {
fn parse_numa_stat(s: String) -> Result<NumaStat> { fn parse_numa_stat(s: String) -> Result<NumaStat> {
// Parse the number of nodes // Parse the number of nodes
let nodes = (s.split_whitespace().collect::<Vec<_>>().len() - 8) / 8; let _nodes = (s.split_whitespace().collect::<Vec<_>>().len() - 8) / 8;
let mut ls = s.lines(); let mut ls = s.lines();
let total_line = ls.next().unwrap(); let total_line = ls.next().unwrap();
let file_line = ls.next().unwrap(); let file_line = ls.next().unwrap();
@@ -109,7 +130,8 @@ fn parse_numa_stat(s: String) -> Result<NumaStat> {
x.split("=").collect::<Vec<_>>()[1] x.split("=").collect::<Vec<_>>()[1]
.parse::<u64>() .parse::<u64>()
.unwrap_or(0) .unwrap_or(0)
}).collect() })
.collect()
}, },
file_pages: file_line file_pages: file_line
.split(|x| x == ' ' || x == '=') .split(|x| x == ' ' || x == '=')
@@ -123,7 +145,8 @@ fn parse_numa_stat(s: String) -> Result<NumaStat> {
x.split("=").collect::<Vec<_>>()[1] x.split("=").collect::<Vec<_>>()[1]
.parse::<u64>() .parse::<u64>()
.unwrap_or(0) .unwrap_or(0)
}).collect() })
.collect()
}, },
anon_pages: anon_line anon_pages: anon_line
.split(|x| x == ' ' || x == '=') .split(|x| x == ' ' || x == '=')
@@ -137,7 +160,8 @@ fn parse_numa_stat(s: String) -> Result<NumaStat> {
x.split("=").collect::<Vec<_>>()[1] x.split("=").collect::<Vec<_>>()[1]
.parse::<u64>() .parse::<u64>()
.unwrap_or(0) .unwrap_or(0)
}).collect() })
.collect()
}, },
unevictable_pages: unevict_line unevictable_pages: unevict_line
.split(|x| x == ' ' || x == '=') .split(|x| x == ' ' || x == '=')
@@ -151,7 +175,8 @@ fn parse_numa_stat(s: String) -> Result<NumaStat> {
x.split("=").collect::<Vec<_>>()[1] x.split("=").collect::<Vec<_>>()[1]
.parse::<u64>() .parse::<u64>()
.unwrap_or(0) .unwrap_or(0)
}).collect() })
.collect()
}, },
hierarchical_total_pages: hier_total_line hierarchical_total_pages: hier_total_line
.split(|x| x == ' ' || x == '=') .split(|x| x == ' ' || x == '=')
@@ -165,7 +190,8 @@ fn parse_numa_stat(s: String) -> Result<NumaStat> {
x.split("=").collect::<Vec<_>>()[1] x.split("=").collect::<Vec<_>>()[1]
.parse::<u64>() .parse::<u64>()
.unwrap_or(0) .unwrap_or(0)
}).collect() })
.collect()
}, },
hierarchical_file_pages: hier_file_line hierarchical_file_pages: hier_file_line
.split(|x| x == ' ' || x == '=') .split(|x| x == ' ' || x == '=')
@@ -179,7 +205,8 @@ fn parse_numa_stat(s: String) -> Result<NumaStat> {
x.split("=").collect::<Vec<_>>()[1] x.split("=").collect::<Vec<_>>()[1]
.parse::<u64>() .parse::<u64>()
.unwrap_or(0) .unwrap_or(0)
}).collect() })
.collect()
}, },
hierarchical_anon_pages: hier_anon_line hierarchical_anon_pages: hier_anon_line
.split(|x| x == ' ' || x == '=') .split(|x| x == ' ' || x == '=')
@@ -193,7 +220,8 @@ fn parse_numa_stat(s: String) -> Result<NumaStat> {
x.split("=").collect::<Vec<_>>()[1] x.split("=").collect::<Vec<_>>()[1]
.parse::<u64>() .parse::<u64>()
.unwrap_or(0) .unwrap_or(0)
}).collect() })
.collect()
}, },
hierarchical_unevictable_pages: hier_unevict_line hierarchical_unevictable_pages: hier_unevict_line
.split(|x| x == ' ' || x == '=') .split(|x| x == ' ' || x == '=')
@@ -207,7 +235,8 @@ fn parse_numa_stat(s: String) -> Result<NumaStat> {
x.split("=").collect::<Vec<_>>()[1] x.split("=").collect::<Vec<_>>()[1]
.parse::<u64>() .parse::<u64>()
.unwrap_or(0) .unwrap_or(0)
}).collect() })
.collect()
}, },
}) })
} }
@@ -231,8 +260,8 @@ pub struct MemoryStat {
pub inactive_file: u64, pub inactive_file: u64,
pub active_file: u64, pub active_file: u64,
pub unevictable: u64, pub unevictable: u64,
pub hierarchical_memory_limit: u64, pub hierarchical_memory_limit: i64,
pub hierarchical_memsw_limit: u64, pub hierarchical_memsw_limit: i64,
pub total_cache: u64, pub total_cache: u64,
pub total_rss: u64, pub total_rss: u64,
pub total_rss_huge: u64, pub total_rss_huge: u64,
@@ -250,52 +279,63 @@ pub struct MemoryStat {
pub total_inactive_file: u64, pub total_inactive_file: u64,
pub total_active_file: u64, pub total_active_file: u64,
pub total_unevictable: u64, pub total_unevictable: u64,
pub raw: HashMap<String, u64>,
} }
fn parse_memory_stat(s: String) -> Result<MemoryStat> { fn parse_memory_stat(s: String) -> Result<MemoryStat> {
let sp: Vec<&str> = s let mut raw = HashMap::new();
.split_whitespace()
.filter(|x| x.parse::<u64>().is_ok()) for l in s.lines() {
.collect(); let t: Vec<&str> = l.split(' ').collect();
if t.len() != 2 {
continue;
}
let n = t[1].trim().parse::<u64>();
if n.is_err() {
continue;
}
raw.insert(t[0].to_string(), n.unwrap());
}
let mut spl = sp.iter();
Ok(MemoryStat { Ok(MemoryStat {
cache: spl.next().unwrap().parse::<u64>().unwrap(), cache: *raw.get("cache").unwrap_or(&0),
rss: spl.next().unwrap().parse::<u64>().unwrap(), rss: *raw.get("rss").unwrap_or(&0),
rss_huge: spl.next().unwrap().parse::<u64>().unwrap(), rss_huge: *raw.get("rss_huge").unwrap_or(&0),
shmem: spl.next().unwrap().parse::<u64>().unwrap(), shmem: *raw.get("shmem").unwrap_or(&0),
mapped_file: spl.next().unwrap().parse::<u64>().unwrap(), mapped_file: *raw.get("mapped_file").unwrap_or(&0),
dirty: spl.next().unwrap().parse::<u64>().unwrap(), dirty: *raw.get("dirty").unwrap_or(&0),
writeback: spl.next().unwrap().parse::<u64>().unwrap(), writeback: *raw.get("writeback").unwrap_or(&0),
swap: spl.next().unwrap().parse::<u64>().unwrap(), swap: *raw.get("swap").unwrap_or(&0),
pgpgin: spl.next().unwrap().parse::<u64>().unwrap(), pgpgin: *raw.get("pgpgin").unwrap_or(&0),
pgpgout: spl.next().unwrap().parse::<u64>().unwrap(), pgpgout: *raw.get("pgpgout").unwrap_or(&0),
pgfault: spl.next().unwrap().parse::<u64>().unwrap(), pgfault: *raw.get("pgfault").unwrap_or(&0),
pgmajfault: spl.next().unwrap().parse::<u64>().unwrap(), pgmajfault: *raw.get("pgmajfault").unwrap_or(&0),
inactive_anon: spl.next().unwrap().parse::<u64>().unwrap(), inactive_anon: *raw.get("inactive_anon").unwrap_or(&0),
active_anon: spl.next().unwrap().parse::<u64>().unwrap(), active_anon: *raw.get("active_anon").unwrap_or(&0),
inactive_file: spl.next().unwrap().parse::<u64>().unwrap(), inactive_file: *raw.get("inactive_file").unwrap_or(&0),
active_file: spl.next().unwrap().parse::<u64>().unwrap(), active_file: *raw.get("active_file").unwrap_or(&0),
unevictable: spl.next().unwrap().parse::<u64>().unwrap(), unevictable: *raw.get("unevictable").unwrap_or(&0),
hierarchical_memory_limit: spl.next().unwrap().parse::<u64>().unwrap(), hierarchical_memory_limit: *raw.get("hierarchical_memory_limit").unwrap_or(&0) as i64,
hierarchical_memsw_limit: spl.next().unwrap().parse::<u64>().unwrap(), hierarchical_memsw_limit: *raw.get("hierarchical_memsw_limit").unwrap_or(&0) as i64,
total_cache: spl.next().unwrap().parse::<u64>().unwrap(), total_cache: *raw.get("total_cache").unwrap_or(&0),
total_rss: spl.next().unwrap().parse::<u64>().unwrap(), total_rss: *raw.get("total_rss").unwrap_or(&0),
total_rss_huge: spl.next().unwrap().parse::<u64>().unwrap(), total_rss_huge: *raw.get("total_rss_huge").unwrap_or(&0),
total_shmem: spl.next().unwrap().parse::<u64>().unwrap(), total_shmem: *raw.get("total_shmem").unwrap_or(&0),
total_mapped_file: spl.next().unwrap().parse::<u64>().unwrap(), total_mapped_file: *raw.get("total_mapped_file").unwrap_or(&0),
total_dirty: spl.next().unwrap().parse::<u64>().unwrap(), total_dirty: *raw.get("total_dirty").unwrap_or(&0),
total_writeback: spl.next().unwrap().parse::<u64>().unwrap(), total_writeback: *raw.get("total_writeback").unwrap_or(&0),
total_swap: spl.next().unwrap().parse::<u64>().unwrap(), total_swap: *raw.get("total_swap").unwrap_or(&0),
total_pgpgin: spl.next().unwrap().parse::<u64>().unwrap(), total_pgpgin: *raw.get("total_pgpgin").unwrap_or(&0),
total_pgpgout: spl.next().unwrap().parse::<u64>().unwrap(), total_pgpgout: *raw.get("total_pgpgout").unwrap_or(&0),
total_pgfault: spl.next().unwrap().parse::<u64>().unwrap(), total_pgfault: *raw.get("total_pgfault").unwrap_or(&0),
total_pgmajfault: spl.next().unwrap().parse::<u64>().unwrap(), total_pgmajfault: *raw.get("total_pgmajfault").unwrap_or(&0),
total_inactive_anon: spl.next().unwrap().parse::<u64>().unwrap(), total_inactive_anon: *raw.get("total_inactive_anon").unwrap_or(&0),
total_active_anon: spl.next().unwrap().parse::<u64>().unwrap(), total_active_anon: *raw.get("total_active_anon").unwrap_or(&0),
total_inactive_file: spl.next().unwrap().parse::<u64>().unwrap(), total_inactive_file: *raw.get("total_inactive_file").unwrap_or(&0),
total_active_file: spl.next().unwrap().parse::<u64>().unwrap(), total_active_file: *raw.get("total_active_file").unwrap_or(&0),
total_unevictable: spl.next().unwrap().parse::<u64>().unwrap(), total_unevictable: *raw.get("total_unevictable").unwrap_or(&0),
raw: raw,
}) })
} }
@@ -306,7 +346,7 @@ pub struct MemSwap {
/// How many times the limit has been hit. /// How many times the limit has been hit.
pub fail_cnt: u64, pub fail_cnt: u64,
/// Memory and swap usage limit in bytes. /// Memory and swap usage limit in bytes.
pub limit_in_bytes: u64, pub limit_in_bytes: i64,
/// Current usage of memory and swap in bytes. /// Current usage of memory and swap in bytes.
pub usage_in_bytes: u64, pub usage_in_bytes: u64,
/// The maximum observed usage of memory and swap in bytes. /// The maximum observed usage of memory and swap in bytes.
@@ -320,7 +360,7 @@ pub struct Memory {
/// How many times the limit has been hit. /// How many times the limit has been hit.
pub fail_cnt: u64, pub fail_cnt: u64,
/// The limit in bytes of the memory usage of the control group's tasks. /// The limit in bytes of the memory usage of the control group's tasks.
pub limit_in_bytes: u64, pub limit_in_bytes: i64,
/// The current usage of memory by the control group's tasks. /// The current usage of memory by the control group's tasks.
pub usage_in_bytes: u64, pub usage_in_bytes: u64,
/// The maximum observed usage of memory by the control group's tasks. /// The maximum observed usage of memory by the control group's tasks.
@@ -342,7 +382,7 @@ pub struct Memory {
pub oom_control: OomControl, pub oom_control: OomControl,
/// Allows setting a limit to memory usage which is enforced when the system (note, _not_ the /// Allows setting a limit to memory usage which is enforced when the system (note, _not_ the
/// control group) detects memory pressure. /// control group) detects memory pressure.
pub soft_limit_in_bytes: u64, pub soft_limit_in_bytes: i64,
/// Contains a wide array of statistics about the memory usage of the tasks in the control /// Contains a wide array of statistics about the memory usage of the tasks in the control
/// group. /// group.
pub stat: MemoryStat, pub stat: MemoryStat,
@@ -365,7 +405,7 @@ pub struct Tcp {
pub fail_cnt: u64, pub fail_cnt: u64,
/// The limit in bytes of the memory usage of the kernel's TCP buffers by control group's /// The limit in bytes of the memory usage of the kernel's TCP buffers by control group's
/// tasks. /// tasks.
pub limit_in_bytes: u64, pub limit_in_bytes: i64,
/// The current memory used by the kernel's TCP buffers related to these tasks. /// The current memory used by the kernel's TCP buffers related to these tasks.
pub usage_in_bytes: u64, pub usage_in_bytes: u64,
/// The observed maximum usage of memory by the kernel's TCP buffers (that originated from /// The observed maximum usage of memory by the kernel's TCP buffers (that originated from
@@ -382,7 +422,7 @@ pub struct Kmem {
/// How many times the limit has been hit. /// How many times the limit has been hit.
pub fail_cnt: u64, pub fail_cnt: u64,
/// The limit in bytes of the kernel memory used by the control group's tasks. /// The limit in bytes of the kernel memory used by the control group's tasks.
pub limit_in_bytes: u64, pub limit_in_bytes: i64,
/// The current usage of kernel memory used by the control group's tasks, in bytes. /// The current usage of kernel memory used by the control group's tasks, in bytes.
pub usage_in_bytes: u64, pub usage_in_bytes: u64,
/// The maximum observed usage of kernel memory used by the control group's tasks, in bytes. /// The maximum observed usage of kernel memory used by the control group's tasks, in bytes.
@@ -405,31 +445,98 @@ impl ControllerInternal for MemController {
&self.base &self.base
} }
fn is_v2(&self) -> bool {
self.v2
}
fn apply(&self, res: &Resources) -> Result<()> { fn apply(&self, res: &Resources) -> Result<()> {
// get the resources that apply to this controller // get the resources that apply to this controller
let memres: &MemoryResources = &res.memory; let memres: &MemoryResources = &res.memory;
if memres.update_values { update!(self, set_limit, memres.memory_hard_limit);
let _ = self.set_limit(memres.memory_hard_limit); update!(self, set_soft_limit, memres.memory_soft_limit);
let _ = self.set_soft_limit(memres.memory_soft_limit); update!(self, set_kmem_limit, memres.kernel_memory_limit);
let _ = self.set_kmem_limit(memres.kernel_memory_limit); update!(self, set_memswap_limit, memres.memory_swap_limit);
let _ = self.set_memswap_limit(memres.memory_swap_limit); update!(self, set_tcp_limit, memres.kernel_tcp_memory_limit);
let _ = self.set_tcp_limit(memres.kernel_tcp_memory_limit); update!(self, set_swappiness, memres.swappiness);
let _ = self.set_swappiness(memres.swappiness);
} memres.attrs.iter().for_each(|(k, v)| {
let _ = self.set(k, v);
});
Ok(()) Ok(())
} }
} }
impl MemController { impl MemController {
/// Contructs a new `MemController` with `oroot` serving as the root of the control group. /// Contructs a new `MemController` with `root` serving as the root of the control group.
pub fn new(oroot: PathBuf) -> Self { pub fn new(root: PathBuf, v2: bool) -> Self {
let mut root = oroot;
root.push(Self::controller_type().to_string());
Self { Self {
base: root.clone(), base: root.clone(),
path: root, path: root,
v2: v2,
}
}
// for v2
pub fn set_mem(&self, m: SetMemory) -> Result<()> {
let values = vec![
(m.high, "memory.high"),
(m.low, "memory.low"),
(m.max, "memory.max"),
(m.min, "memory.min"),
];
for value in values {
let v = value.0;
let f = value.1;
if v.is_some() {
let v = v.unwrap().to_string();
self.open_path(f, true).and_then(|mut file| {
file.write_all(v.as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
})?;
}
}
Ok(())
}
// for v2
pub fn get_mem(&self) -> Result<SetMemory> {
let mut m: SetMemory = Default::default();
self.get_max_value("memory.high")
.map(|x| m.high = Some(x))?;
self.get_max_value("memory.low").map(|x| m.low = Some(x))?;
self.get_max_value("memory.max").map(|x| m.max = Some(x))?;
self.get_max_value("memory.min").map(|x| m.min = Some(x))?;
Ok(m)
}
fn memory_stat_v2(&self) -> Memory {
let set = self.get_mem().unwrap();
Memory {
fail_cnt: 0,
limit_in_bytes: set.max.unwrap().to_i64(),
usage_in_bytes: self
.open_path("memory.current", false)
.and_then(read_u64_from)
.unwrap_or(0),
max_usage_in_bytes: 0,
move_charge_at_immigrate: 0,
numa_stat: NumaStat::default(),
oom_control: OomControl::default(),
soft_limit_in_bytes: set.low.unwrap().to_i64(),
stat: self
.open_path("memory.stat", false)
.and_then(read_string_from)
.and_then(parse_memory_stat)
.unwrap_or(MemoryStat::default()),
swappiness: self
.open_path("memory.swap.current", false)
.and_then(read_u64_from)
.unwrap_or(0),
use_hierarchy: 0,
} }
} }
@@ -439,6 +546,10 @@ impl MemController {
/// See the individual fields for more explanation, and as always, remember to consult the /// See the individual fields for more explanation, and as always, remember to consult the
/// kernel Documentation and/or sources. /// kernel Documentation and/or sources.
pub fn memory_stat(&self) -> Memory { pub fn memory_stat(&self) -> Memory {
if self.v2 {
return self.memory_stat_v2();
}
Memory { Memory {
fail_cnt: self fail_cnt: self
.open_path("memory.failcnt", false) .open_path("memory.failcnt", false)
@@ -446,7 +557,7 @@ impl MemController {
.unwrap_or(0), .unwrap_or(0),
limit_in_bytes: self limit_in_bytes: self
.open_path("memory.limit_in_bytes", false) .open_path("memory.limit_in_bytes", false)
.and_then(read_u64_from) .and_then(read_i64_from)
.unwrap_or(0), .unwrap_or(0),
usage_in_bytes: self usage_in_bytes: self
.open_path("memory.usage_in_bytes", false) .open_path("memory.usage_in_bytes", false)
@@ -472,7 +583,7 @@ impl MemController {
.unwrap_or(OomControl::default()), .unwrap_or(OomControl::default()),
soft_limit_in_bytes: self soft_limit_in_bytes: self
.open_path("memory.soft_limit_in_bytes", false) .open_path("memory.soft_limit_in_bytes", false)
.and_then(read_u64_from) .and_then(read_i64_from)
.unwrap_or(0), .unwrap_or(0),
stat: self stat: self
.open_path("memory.stat", false) .open_path("memory.stat", false)
@@ -499,8 +610,8 @@ impl MemController {
.unwrap_or(0), .unwrap_or(0),
limit_in_bytes: self limit_in_bytes: self
.open_path("memory.kmem.limit_in_bytes", false) .open_path("memory.kmem.limit_in_bytes", false)
.and_then(read_u64_from) .and_then(read_i64_from)
.unwrap_or(0), .unwrap_or(-1),
usage_in_bytes: self usage_in_bytes: self
.open_path("memory.kmem.usage_in_bytes", false) .open_path("memory.kmem.usage_in_bytes", false)
.and_then(read_u64_from) .and_then(read_u64_from)
@@ -526,7 +637,7 @@ impl MemController {
.unwrap_or(0), .unwrap_or(0),
limit_in_bytes: self limit_in_bytes: self
.open_path("memory.kmem.tcp.limit_in_bytes", false) .open_path("memory.kmem.tcp.limit_in_bytes", false)
.and_then(read_u64_from) .and_then(read_i64_from)
.unwrap_or(0), .unwrap_or(0),
usage_in_bytes: self usage_in_bytes: self
.open_path("memory.kmem.tcp.usage_in_bytes", false) .open_path("memory.kmem.tcp.usage_in_bytes", false)
@@ -539,9 +650,32 @@ impl MemController {
} }
} }
pub fn memswap_v2(&self) -> MemSwap {
MemSwap {
fail_cnt: self
.open_path("memory.swap.events", false)
.and_then(flat_keyed_to_hashmap)
.and_then(|x| Ok(*x.get("fail").unwrap_or(&0) as u64))
.unwrap(),
limit_in_bytes: self
.open_path("memory.swap.max", false)
.and_then(read_i64_from)
.unwrap_or(0),
usage_in_bytes: self
.open_path("memory.swap.current", false)
.and_then(read_u64_from)
.unwrap_or(0),
max_usage_in_bytes: 0,
}
}
/// Gathers information about the memory usage of the control group including the swap usage /// Gathers information about the memory usage of the control group including the swap usage
/// (if any). /// (if any).
pub fn memswap(&self) -> MemSwap { pub fn memswap(&self) -> MemSwap {
if self.v2 {
return self.memswap_v2();
}
MemSwap { MemSwap {
fail_cnt: self fail_cnt: self
.open_path("memory.memsw.failcnt", false) .open_path("memory.memsw.failcnt", false)
@@ -549,7 +683,7 @@ impl MemController {
.unwrap_or(0), .unwrap_or(0),
limit_in_bytes: self limit_in_bytes: self
.open_path("memory.memsw.limit_in_bytes", false) .open_path("memory.memsw.limit_in_bytes", false)
.and_then(read_u64_from) .and_then(read_i64_from)
.unwrap_or(0), .unwrap_or(0),
usage_in_bytes: self usage_in_bytes: self
.open_path("memory.memsw.usage_in_bytes", false) .open_path("memory.memsw.usage_in_bytes", false)
@@ -564,15 +698,19 @@ impl MemController {
/// Reset the fail counter /// Reset the fail counter
pub fn reset_fail_count(&self) -> Result<()> { pub fn reset_fail_count(&self) -> Result<()> {
self.open_path("memory.failcnt", true) self.open_path("memory.failcnt", true).and_then(|mut file| {
.and_then(|mut file| { file.write_all("0".to_string().as_ref())
file.write_all("0".to_string().as_ref()) .map_err(|e| Error::with_cause(WriteFailed, e))
.map_err(|e| Error::with_cause(WriteFailed, e)) })
})
} }
/// Reset the kernel memory fail counter /// Reset the kernel memory fail counter
pub fn reset_kmem_fail_count(&self) -> Result<()> { pub fn reset_kmem_fail_count(&self) -> Result<()> {
// Ignore kmem because there is no kmem in cgroup v2
if self.v2 {
return Ok(());
}
self.open_path("memory.kmem.failcnt", true) self.open_path("memory.kmem.failcnt", true)
.and_then(|mut file| { .and_then(|mut file| {
file.write_all("0".to_string().as_ref()) file.write_all("0".to_string().as_ref())
@@ -582,6 +720,11 @@ impl MemController {
/// Reset the TCP related fail counter /// Reset the TCP related fail counter
pub fn reset_tcp_fail_count(&self) -> Result<()> { pub fn reset_tcp_fail_count(&self) -> Result<()> {
// Ignore kmem because there is no kmem in cgroup v2
if self.v2 {
return Ok(());
}
self.open_path("memory.kmem.tcp.failcnt", true) self.open_path("memory.kmem.tcp.failcnt", true)
.and_then(|mut file| { .and_then(|mut file| {
file.write_all("0".to_string().as_ref()) file.write_all("0".to_string().as_ref())
@@ -598,17 +741,34 @@ impl MemController {
}) })
} }
/// Set the memory usage limit of the control group, in bytes. /// Reset the max memory usage recorded
pub fn set_limit(&self, limit: u64) -> Result<()> { pub fn reset_max_usage(&self) -> Result<()> {
self.open_path("memory.limit_in_bytes", true) self.open_path("memory.max_usage_in_bytes", true)
.and_then(|mut file| { .and_then(|mut file| {
file.write_all(limit.to_string().as_ref()) file.write_all("0".to_string().as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e)) .map_err(|e| Error::with_cause(WriteFailed, e))
}) })
} }
/// Set the memory usage limit of the control group, in bytes.
pub fn set_limit(&self, limit: i64) -> Result<()> {
let mut file = "memory.limit_in_bytes";
if self.v2 {
file = "memory.max";
}
self.open_path(file, true).and_then(|mut file| {
file.write_all(limit.to_string().as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
})
}
/// Set the kernel memory limit of the control group, in bytes. /// Set the kernel memory limit of the control group, in bytes.
pub fn set_kmem_limit(&self, limit: u64) -> Result<()> { pub fn set_kmem_limit(&self, limit: i64) -> Result<()> {
// Ignore kmem because there is no kmem in cgroup v2
if self.v2 {
return Ok(());
}
self.open_path("memory.kmem.limit_in_bytes", true) self.open_path("memory.kmem.limit_in_bytes", true)
.and_then(|mut file| { .and_then(|mut file| {
file.write_all(limit.to_string().as_ref()) file.write_all(limit.to_string().as_ref())
@@ -617,16 +777,24 @@ impl MemController {
} }
/// Set the memory+swap limit of the control group, in bytes. /// Set the memory+swap limit of the control group, in bytes.
pub fn set_memswap_limit(&self, limit: u64) -> Result<()> { pub fn set_memswap_limit(&self, limit: i64) -> Result<()> {
self.open_path("memory.memsw.limit_in_bytes", true) let mut file = "memory.memsw.limit_in_bytes";
.and_then(|mut file| { if self.v2 {
file.write_all(limit.to_string().as_ref()) file = "memory.swap.max";
.map_err(|e| Error::with_cause(WriteFailed, e)) }
}) 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))
})
} }
/// Set how much kernel memory can be used for TCP-related buffers by the control group. /// Set how much kernel memory can be used for TCP-related buffers by the control group.
pub fn set_tcp_limit(&self, limit: u64) -> Result<()> { pub fn set_tcp_limit(&self, limit: i64) -> Result<()> {
// Ignore kmem because there is no kmem in cgroup v2
if self.v2 {
return Ok(());
}
self.open_path("memory.kmem.tcp.limit_in_bytes", true) self.open_path("memory.kmem.tcp.limit_in_bytes", true)
.and_then(|mut file| { .and_then(|mut file| {
file.write_all(limit.to_string().as_ref()) file.write_all(limit.to_string().as_ref())
@@ -638,12 +806,15 @@ impl MemController {
/// ///
/// This limit is enforced when the system is nearing OOM conditions. Contrast this with the /// This limit is enforced when the system is nearing OOM conditions. Contrast this with the
/// hard limit, which is _always_ enforced. /// hard limit, which is _always_ enforced.
pub fn set_soft_limit(&self, limit: u64) -> Result<()> { pub fn set_soft_limit(&self, limit: i64) -> Result<()> {
self.open_path("memory.soft_limit_in_bytes", true) let mut file = "memory.soft_limit_in_bytes";
.and_then(|mut file| { if self.v2 {
file.write_all(limit.to_string().as_ref()) file = "memory.low"
.map_err(|e| Error::with_cause(WriteFailed, e)) }
}) 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))
})
} }
/// Set how likely the kernel is to swap out parts of the address space used by the control /// Set how likely the kernel is to swap out parts of the address space used by the control
@@ -651,12 +822,32 @@ impl MemController {
/// ///
/// Note that a value of zero does not imply that the process will not be swapped out. /// Note that a value of zero does not imply that the process will not be swapped out.
pub fn set_swappiness(&self, swp: u64) -> Result<()> { pub fn set_swappiness(&self, swp: u64) -> Result<()> {
self.open_path("memory.swappiness", true) let mut file = "memory.swappiness";
if self.v2 {
file = "memory.swap.max"
}
self.open_path(file, true).and_then(|mut file| {
file.write_all(swp.to_string().as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
})
}
pub fn disable_oom_killer(&self) -> Result<()> {
self.open_path("memory.oom_control", true)
.and_then(|mut file| { .and_then(|mut file| {
file.write_all(swp.to_string().as_ref()) file.write_all("1".to_string().as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e)) .map_err(|e| Error::with_cause(WriteFailed, e))
}) })
} }
pub fn register_oom_event(&self, key: &str) -> Result<Receiver<String>> {
if self.v2 {
events::notify_on_oom_v2(key, self.get_path())
} else {
events::notify_on_oom_v1(key, self.get_path())
}
}
} }
impl ControllIdentifier for MemController { impl ControllIdentifier for MemController {
@@ -665,6 +856,8 @@ impl ControllIdentifier for MemController {
} }
} }
impl CustomizedAttribute for MemController {}
impl<'a> From<&'a Subsystem> for &'a MemController { impl<'a> From<&'a Subsystem> for &'a MemController {
fn from(sub: &'a Subsystem) -> &'a MemController { fn from(sub: &'a Subsystem) -> &'a MemController {
unsafe { unsafe {
@@ -672,35 +865,19 @@ impl<'a> From<&'a Subsystem> for &'a MemController {
Subsystem::Mem(c) => c, Subsystem::Mem(c) => c,
_ => { _ => {
assert_eq!(1, 0); assert_eq!(1, 0);
::std::mem::uninitialized() let v = std::mem::MaybeUninit::uninit();
v.assume_init()
} }
} }
} }
} }
} }
fn read_u64_from(mut file: File) -> Result<u64> {
let mut string = String::new();
match file.read_to_string(&mut string) {
Ok(_) => string.trim().parse().map_err(|e| Error::with_cause(ParseError, e)),
Err(e) => Err(Error::with_cause(ReadFailed, e)),
}
}
fn read_string_from(mut file: File) -> Result<String> {
let mut string = String::new();
match file.read_to_string(&mut string) {
Ok(_) => Ok(string.trim().to_string()),
Err(e) => Err(Error::with_cause(ReadFailed, e)),
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use memory::{ use crate::memory::{
parse_memory_stat, parse_numa_stat, parse_oom_control, MemoryStat, NumaStat, OomControl, parse_memory_stat, parse_numa_stat, parse_oom_control, MemoryStat, NumaStat, OomControl,
}; };
static GOOD_VALUE: &str = "\ static GOOD_VALUE: &str = "\
total=51189 N0=51189 N1=123 total=51189 N0=51189 N1=123
file=50175 N0=50175 N1=123 file=50175 N0=50175 N1=123
@@ -800,6 +977,7 @@ total_unevictable 81920
#[test] #[test]
fn test_parse_memory_stat() { fn test_parse_memory_stat() {
let ok = parse_memory_stat(GOOD_MEMORYSTAT_VAL.to_string()).unwrap(); let ok = parse_memory_stat(GOOD_MEMORYSTAT_VAL.to_string()).unwrap();
let raw = ok.raw.clone();
assert_eq!( assert_eq!(
ok, ok,
MemoryStat { MemoryStat {
@@ -839,6 +1017,7 @@ total_unevictable 81920
total_inactive_file: 1272135680, total_inactive_file: 1272135680,
total_active_file: 2338816000, total_active_file: 2338816000,
total_unevictable: 81920, total_unevictable: 81920,
raw: raw,
} }
); );
} }

View File

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

View File

@@ -1,18 +1,22 @@
// Copyright (c) 2018 Levente Kurusa
//
// SPDX-License-Identifier: Apache-2.0 or MIT
//
//! This module contains the implementation of the `net_prio` cgroup subsystem. //! This module contains the implementation of the `net_prio` cgroup subsystem.
//! //!
//! See the Kernel's documentation for more information about this subsystem, found at: //! See the Kernel's documentation for more information about this subsystem, found at:
//! [Documentation/cgroup-v1/net_prio.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/net_prio.txt) //! [Documentation/cgroup-v1/net_prio.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/net_prio.txt)
use std::collections::HashMap; use std::collections::HashMap;
use std::fs::File; use std::io::{BufRead, BufReader, Write};
use std::io::{BufRead, BufReader, Read, Write};
use std::path::PathBuf; use std::path::PathBuf;
use error::*; use crate::error::ErrorKind::*;
use error::ErrorKind::*; use crate::error::*;
use { use crate::read_u64_from;
ControllIdentifier, ControllerInternal, Controllers, NetworkResources, Resources, use crate::{
Subsystem, ControllIdentifier, ControllerInternal, Controllers, NetworkResources, Resources, Subsystem,
}; };
/// A controller that allows controlling the `net_prio` subsystem of a Cgroup. /// A controller that allows controlling the `net_prio` subsystem of a Cgroup.
@@ -44,10 +48,8 @@ impl ControllerInternal for NetPrioController {
// get the resources that apply to this controller // get the resources that apply to this controller
let res: &NetworkResources = &res.network; let res: &NetworkResources = &res.network;
if res.update_values { for i in &res.priorities {
for i in &res.priorities { let _ = self.set_if_prio(&i.name, i.priority);
let _ = self.set_if_prio(&i.name, i.priority);
}
} }
Ok(()) Ok(())
@@ -67,26 +69,17 @@ impl<'a> From<&'a Subsystem> for &'a NetPrioController {
Subsystem::NetPrio(c) => c, Subsystem::NetPrio(c) => c,
_ => { _ => {
assert_eq!(1, 0); assert_eq!(1, 0);
::std::mem::uninitialized() let v = std::mem::MaybeUninit::uninit();
v.assume_init()
} }
} }
} }
} }
} }
fn read_u64_from(mut file: File) -> Result<u64> {
let mut string = String::new();
match file.read_to_string(&mut string) {
Ok(_) => string.trim().parse().map_err(|e| Error::with_cause(ParseError, e)),
Err(e) => Err(Error::with_cause(ReadFailed, e)),
}
}
impl NetPrioController { impl NetPrioController {
/// Constructs a new `NetPrioController` with `oroot` serving as the root of the control group. /// Constructs a new `NetPrioController` with `root` serving as the root of the control group.
pub fn new(oroot: PathBuf) -> Self { pub fn new(root: PathBuf) -> Self {
let mut root = oroot;
root.push(Self::controller_type().to_string());
Self { Self {
base: root.clone(), base: root.clone(),
path: root, path: root,

View File

@@ -1,12 +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. //! This module contains the implementation of the `perf_event` cgroup subsystem.
//! //!
//! See the Kernel's documentation for more information about this subsystem, found at: //! See the Kernel's documentation for more information about this subsystem, found at:
//! [tools/perf/Documentation/perf-record.txt](https://raw.githubusercontent.com/torvalds/linux/master/tools/perf/Documentation/perf-record.txt) //! [tools/perf/Documentation/perf-record.txt](https://raw.githubusercontent.com/torvalds/linux/master/tools/perf/Documentation/perf-record.txt)
use std::path::PathBuf; use std::path::PathBuf;
use error::*; use crate::error::*;
use {ControllIdentifier, ControllerInternal, Controllers, Resources, Subsystem}; use crate::{ControllIdentifier, ControllerInternal, Controllers, Resources, Subsystem};
/// A controller that allows controlling the `perf_event` subsystem of a Cgroup. /// A controller that allows controlling the `perf_event` subsystem of a Cgroup.
/// ///
@@ -50,7 +55,8 @@ impl<'a> From<&'a Subsystem> for &'a PerfEventController {
Subsystem::PerfEvent(c) => c, Subsystem::PerfEvent(c) => c,
_ => { _ => {
assert_eq!(1, 0); assert_eq!(1, 0);
::std::mem::uninitialized() let v = std::mem::MaybeUninit::uninit();
v.assume_init()
} }
} }
} }
@@ -58,10 +64,8 @@ impl<'a> From<&'a Subsystem> for &'a PerfEventController {
} }
impl PerfEventController { impl PerfEventController {
/// Constructs a new `PerfEventController` with `oroot` serving as the root of the control group. /// Constructs a new `PerfEventController` with `root` serving as the root of the control group.
pub fn new(oroot: PathBuf) -> Self { pub fn new(root: PathBuf) -> Self {
let mut root = oroot;
root.push(Self::controller_type().to_string());
Self { Self {
base: root.clone(), base: root.clone(),
path: root, path: root,

View File

@@ -1,16 +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 `pids` cgroup subsystem. //! This module contains the implementation of the `pids` cgroup subsystem.
//! //!
//! See the Kernel's documentation for more information about this subsystem, found at: //! See the Kernel's documentation for more information about this subsystem, found at:
//! [Documentation/cgroups-v1/pids.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/pids.txt) //! [Documentation/cgroups-v1/pids.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/pids.txt)
use std::fs::File;
use std::io::{Read, Write}; use std::io::{Read, Write};
use std::path::PathBuf; use std::path::PathBuf;
use error::*; use crate::error::ErrorKind::*;
use error::ErrorKind::*; use crate::error::*;
use { use crate::read_u64_from;
ControllIdentifier, ControllerInternal, Controllers, PidResources, Resources, Subsystem, use crate::{
parse_max_value, ControllIdentifier, ControllerInternal, Controllers, MaxValue, PidResources,
Resources, Subsystem,
}; };
/// A controller that allows controlling the `pids` subsystem of a Cgroup. /// A controller that allows controlling the `pids` subsystem of a Cgroup.
@@ -18,22 +25,7 @@ use {
pub struct PidController { pub struct PidController {
base: PathBuf, base: PathBuf,
path: PathBuf, path: PathBuf,
} v2: bool,
/// The values found in the `pids.max` file in a Cgroup's `pids` subsystem.
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
pub enum PidMax {
/// This value is returned when the text found `pids.max` is `"max"`.
Max,
/// When the value in `pids.max` is a numerical value, they are returned via this enum field.
Value(i64),
}
impl Default for PidMax {
/// By default, (as per the kernel) `pids.max` should contain `"max"`.
fn default() -> Self {
PidMax::Max
}
} }
impl ControllerInternal for PidController { impl ControllerInternal for PidController {
@@ -50,21 +42,21 @@ impl ControllerInternal for PidController {
&self.base &self.base
} }
fn is_v2(&self) -> bool {
self.v2
}
fn apply(&self, res: &Resources) -> Result<()> { fn apply(&self, res: &Resources) -> Result<()> {
// get the resources that apply to this controller // get the resources that apply to this controller
let pidres: &PidResources = &res.pid; let pidres: &PidResources = &res.pid;
if pidres.update_values { // apply pid_max
// apply pid_max update_and_test!(
let _ = self.set_pid_max(pidres.maximum_number_of_processes); self,
set_pid_max,
// now, verify pidres.maximum_number_of_processes,
if self.get_pid_max()? == pidres.maximum_number_of_processes { get_pid_max
return Ok(()); );
} else {
return Err(Error::new(Other));
}
}
Ok(()) Ok(())
} }
@@ -89,30 +81,22 @@ impl<'a> From<&'a Subsystem> for &'a PidController {
Subsystem::Pid(c) => c, Subsystem::Pid(c) => c,
_ => { _ => {
assert_eq!(1, 0); assert_eq!(1, 0);
::std::mem::uninitialized() let v = std::mem::MaybeUninit::uninit();
v.assume_init()
} }
} }
} }
} }
} }
fn read_u64_from(mut file: File) -> Result<u64> {
let mut string = String::new();
match file.read_to_string(&mut string) {
Ok(_) => string.trim().parse().map_err(|e| Error::with_cause(ParseError, e)),
Err(e) => Err(Error::with_cause(ReadFailed, e)),
}
}
impl PidController { impl PidController {
/// Constructors a new `PidController` instance, with `oroot` serving as the controller's root /// Constructors a new `PidController` instance, with `root` serving as the controller's root
/// directory. /// directory.
pub fn new(oroot: PathBuf) -> Self { pub fn new(root: PathBuf, v2: bool) -> Self {
let mut root = oroot;
root.push(Self::controller_type().to_string());
Self { Self {
base: root.clone(), base: root.clone(),
path: root, path: root,
v2: v2,
} }
} }
@@ -140,19 +124,12 @@ impl PidController {
} }
/// The maximum number of processes that can exist at one time in the control group. /// The maximum number of processes that can exist at one time in the control group.
pub fn get_pid_max(&self) -> Result<PidMax> { pub fn get_pid_max(&self) -> Result<MaxValue> {
self.open_path("pids.max", false).and_then(|mut file| { self.open_path("pids.max", false).and_then(|mut file| {
let mut string = String::new(); let mut string = String::new();
let res = file.read_to_string(&mut string); let res = file.read_to_string(&mut string);
match res { match res {
Ok(_) => if string.trim() == "max" { Ok(_) => parse_max_value(&string),
Ok(PidMax::Max)
} else {
match string.trim().parse() {
Ok(val) => Ok(PidMax::Value(val)),
Err(e) => Err(Error::with_cause(ParseError, e)),
}
},
Err(e) => Err(Error::with_cause(ReadFailed, e)), Err(e) => Err(Error::with_cause(ReadFailed, e)),
} }
}) })
@@ -163,12 +140,9 @@ impl PidController {
/// Note that if `get_pid_current()` returns a higher number than what you /// 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 /// are about to set (`max_pid`), then no processess will be killed. Additonally, attaching
/// extra processes to a control group disregards the limit. /// extra processes to a control group disregards the limit.
pub fn set_pid_max(&self, max_pid: PidMax) -> Result<()> { pub fn set_pid_max(&self, max_pid: MaxValue) -> Result<()> {
self.open_path("pids.max", true).and_then(|mut file| { self.open_path("pids.max", true).and_then(|mut file| {
let string_to_write = match max_pid { let string_to_write = max_pid.to_string();
PidMax::Max => "max".to_string(),
PidMax::Value(num) => num.to_string(),
};
match file.write_all(string_to_write.as_ref()) { match file.write_all(string_to_write.as_ref()) {
Ok(_) => Ok(()), Ok(_) => Ok(()),
Err(e) => Err(Error::with_cause(WriteFailed, e)), Err(e) => Err(Error::with_cause(WriteFailed, e)),

View File

@@ -1,15 +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. //! This module contains the implementation of the `rdma` cgroup subsystem.
//! //!
//! See the Kernel's documentation for more information about this subsystem, found at: //! See the Kernel's documentation for more information about this subsystem, found at:
//! [Documentation/cgroup-v1/rdma.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/rdma.txt) //! [Documentation/cgroup-v1/rdma.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/rdma.txt)
use std::fs::File; use std::io::Write;
use std::io::{Read, Write};
use std::path::PathBuf; use std::path::PathBuf;
use error::*; use crate::error::ErrorKind::*;
use error::ErrorKind::*; use crate::error::*;
use {ControllIdentifier, ControllerInternal, Controllers, Resources, Subsystem}; use crate::read_string_from;
use crate::{ControllIdentifier, ControllerInternal, Controllers, Resources, Subsystem};
/// A controller that allows controlling the `rdma` subsystem of a Cgroup. /// A controller that allows controlling the `rdma` subsystem of a Cgroup.
/// ///
@@ -53,26 +58,17 @@ impl<'a> From<&'a Subsystem> for &'a RdmaController {
Subsystem::Rdma(c) => c, Subsystem::Rdma(c) => c,
_ => { _ => {
assert_eq!(1, 0); assert_eq!(1, 0);
::std::mem::uninitialized() let v = std::mem::MaybeUninit::uninit();
v.assume_init()
} }
} }
} }
} }
} }
fn read_string_from(mut file: File) -> Result<String> {
let mut string = String::new();
match file.read_to_string(&mut string) {
Ok(_) => Ok(string.trim().to_string()),
Err(e) => Err(Error::with_cause(ReadFailed, e)),
}
}
impl RdmaController { impl RdmaController {
/// Constructs a new `RdmaController` with `oroot` serving as the root of the control group. /// Constructs a new `RdmaController` with `root` serving as the root of the control group.
pub fn new(oroot: PathBuf) -> Self { pub fn new(root: PathBuf) -> Self {
let mut root = oroot;
root.push(Self::controller_type().to_string());
Self { Self {
base: root.clone(), base: root.clone(),
path: root, path: root,

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,
}
}
}

View File

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

View File

@@ -1,18 +1,26 @@
//! Simple unit tests about the control groups system. // Copyright (c) 2018 Levente Kurusa
extern crate cgroups; // Copyright (c) 2020 And Group
use cgroups::{Cgroup, CgroupPid}; //
// SPDX-License-Identifier: Apache-2.0 or MIT
//
extern crate libc; //! Simple unit tests about the control groups system.
extern crate nix; use cgroups_rs::memory::MemController;
use cgroups_rs::Controller;
use cgroups_rs::{Cgroup, CgroupPid, Subsystem};
#[test] #[test]
fn test_tasks_iterator() { fn test_tasks_iterator() {
let hier = cgroups::hierarchies::V1::new(); let h = cgroups_rs::hierarchies::auto();
let pid = libc::pid_t::from(nix::unistd::getpid()) as u64; let pid = libc::pid_t::from(nix::unistd::getpid()) as u64;
let cg = Cgroup::new(&hier, String::from("test_tasks_iterator")); let cg = Cgroup::new(h, String::from("test_tasks_iterator"));
{ {
// Add a task to the control group. // Add a task to the control group.
cg.add_task(CgroupPid::from(pid)); 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(); let mut tasks = cg.tasks().into_iter();
// Verify that the task is indeed in the control group // Verify that the task is indeed in the control group
assert_eq!(tasks.next(), Some(CgroupPid::from(pid))); assert_eq!(tasks.next(), Some(CgroupPid::from(pid)));
@@ -25,5 +33,73 @@ fn test_tasks_iterator() {
// Verify that it was indeed removed. // Verify that it was indeed removed.
assert_eq!(tasks.next(), None); assert_eq!(tasks.next(), None);
} }
cg.delete(); cg.delete().unwrap();
}
#[test]
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();
}

View File

@@ -1,13 +1,19 @@
extern crate cgroups; // Copyright (c) 2018 Levente Kurusa
// Copyright (c) 2020 And Group
//
// SPDX-License-Identifier: Apache-2.0 or MIT
//
use cgroups::cpuset::CpuSetController; use cgroups_rs::cpuset::CpuSetController;
use cgroups::error::ErrorKind; use cgroups_rs::error::ErrorKind;
use cgroups::Cgroup; use cgroups_rs::{Cgroup, CgroupPid};
use std::fs;
#[test] #[test]
fn test_cpuset_memory_pressure_root_cg() { fn test_cpuset_memory_pressure_root_cg() {
let hier = cgroups::hierarchies::V1::new(); let h = cgroups_rs::hierarchies::auto();
let cg = Cgroup::new(&hier, String::from("test_cpuset_memory_pressure_root_cg")); let cg = Cgroup::new(h, String::from("test_cpuset_memory_pressure_root_cg"));
{ {
let cpuset: &CpuSetController = cg.controller_of().unwrap(); let cpuset: &CpuSetController = cg.controller_of().unwrap();
@@ -15,5 +21,73 @@ fn test_cpuset_memory_pressure_root_cg() {
let res = cpuset.set_enable_memory_pressure(true); let res = cpuset.set_enable_memory_pressure(true);
assert_eq!(res.unwrap_err().kind(), &ErrorKind::InvalidOperation); assert_eq!(res.unwrap_err().kind(), &ErrorKind::InvalidOperation);
} }
cg.delete(); cg.delete().unwrap();
}
#[test]
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();
} }

View File

@@ -1,27 +1,39 @@
// Copyright (c) 2018 Levente Kurusa
// Copyright (c) 2020 And Group
//
// SPDX-License-Identifier: Apache-2.0 or MIT
//
//! Integration tests about the devices subsystem //! Integration tests about the devices subsystem
extern crate cgroups; use cgroups_rs::devices::{DevicePermissions, DeviceType, DevicesController};
use cgroups::devices::{DevicePermissions, DeviceType, DevicesController}; use cgroups_rs::{Cgroup, DeviceResource};
use cgroups::{Cgroup, DeviceResource};
#[test] #[test]
fn test_devices_parsing() { fn test_devices_parsing() {
let hier = cgroups::hierarchies::V1::new(); // now only v2
let cg = Cgroup::new(&hier, String::from("test_devices_parsing")); 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(); let devices: &DevicesController = cg.controller_of().unwrap();
// Deny access to all devices first // Deny access to all devices first
devices.deny_device( devices
DeviceType::All, .deny_device(
-1, DeviceType::All,
-1, -1,
&vec![ -1,
DevicePermissions::Read, &vec![
DevicePermissions::Write, DevicePermissions::Read,
DevicePermissions::MkNod, DevicePermissions::Write,
], DevicePermissions::MkNod,
); ],
)
.unwrap();
// Acquire the list of allowed devices after we denied all // Acquire the list of allowed devices after we denied all
let allowed_devices = devices.allowed_devices(); let allowed_devices = devices.allowed_devices();
// Verify that there are no devices that we can access. // Verify that there are no devices that we can access.
@@ -29,7 +41,9 @@ fn test_devices_parsing() {
assert_eq!(allowed_devices.unwrap(), Vec::new()); assert_eq!(allowed_devices.unwrap(), Vec::new());
// Now add mknod access to /dev/null device // Now add mknod access to /dev/null device
devices.allow_device(DeviceType::Char, 1, 3, &vec![DevicePermissions::MkNod]); devices
.allow_device(DeviceType::Char, 1, 3, &vec![DevicePermissions::MkNod])
.unwrap();
let allowed_devices = devices.allowed_devices(); let allowed_devices = devices.allowed_devices();
assert!(allowed_devices.is_ok()); assert!(allowed_devices.is_ok());
let allowed_devices = allowed_devices.unwrap(); let allowed_devices = allowed_devices.unwrap();
@@ -46,12 +60,14 @@ fn test_devices_parsing() {
); );
// Now deny, this device explicitly. // Now deny, this device explicitly.
devices.deny_device(DeviceType::Char, 1, 3, &DevicePermissions::all()); devices
.deny_device(DeviceType::Char, 1, 3, &DevicePermissions::all())
.unwrap();
// Finally, check that. // Finally, check that.
let allowed_devices = devices.allowed_devices(); let allowed_devices = devices.allowed_devices();
// Verify that there are no devices that we can access. // Verify that there are no devices that we can access.
assert!(allowed_devices.is_ok()); assert!(allowed_devices.is_ok());
assert_eq!(allowed_devices.unwrap(), Vec::new()); assert_eq!(allowed_devices.unwrap(), Vec::new());
} }
cg.delete(); cg.delete().unwrap();
} }

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();
}

View File

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

View File

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

View File

@@ -1,4 +1,9 @@
#!/bin/sh #!/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` CONTROL_GROUPS=`cargo test -- --list 2>/dev/null | egrep 'test$' | egrep -v '^src' | cut -d':' -f1`

View File

@@ -1,4 +1,9 @@
#!/bin/sh #!/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` CONTROL_GROUPS=`cargo test -- --list 2>/dev/null | egrep 'test$' | egrep -v '^src' | cut -d':' -f1`