Compare commits

...

49 Commits

Author SHA1 Message Date
Fupan Li
de9625ff57 Merge pull request #146 from fidencio/topic/release-v0.3.5
release: v0.3.5
2025-05-29 16:33:54 +08:00
Fupan Li
b6b65f79d1 Merge pull request #141 from michelhe/remove-outdated-docs
hierarchies: Remove outdated documentation
2025-05-29 16:31:55 +08:00
Fupan Li
547fb08c03 Merge pull request #140 from jokemanfire/dev
fix bug: if cgroup path has ":"
2025-05-29 16:31:14 +08:00
Fupan Li
ec9f3547ed Merge pull request #135 from zzzzzzzzzy9/pull-branch
rdma: read rdma.max file
2025-05-29 16:30:09 +08:00
Michel Heily
82a6aa491a hierarchies: Remove outdated documentation
The documentation in the hierarchies module is outdated, this project
does support cgroups2 and the unified hierarchy :)

Signed-off-by: Michel Heily <michelheily@gmail.com>
2025-05-29 10:21:27 +02:00
zzzzzzzzzy9
65c36214b7 rdma: read rdma.max file
This is a simple one, just support rdma.max.

Signed-off-by: zzzzzzzzzy9 <zhang.yu58@zte.com.cn>
Signed-off-by: Fabiano Fidêncio <fidencio@northflank.com>
2025-05-29 10:12:10 +02:00
Fabiano Fidêncio
e0d0b8f4bc release: v0.3.5
There are several fixes since the last release, and itś been a few years
waiting for a new one.

Signed-off-by: Fabiano Fidêncio <fidencio@northflank.com>
2025-05-29 08:53:03 +02:00
Fabiano Fidêncio
db822470e5 Merge pull request #116 from xueruini/fix-mem-panic
Use default memory stats instead of panic.
2025-05-29 08:14:51 +02:00
Fabiano Fidêncio
362373b3ec memory: Fix test_procs_iterator_cgroup test
Otherwise it simply breaks.

Signed-off-by: Alex Man <alexman@stripe.com>
Signed-off-by: Fabiano Fidêncio <fidencio@northflank.com>
2025-05-28 23:02:04 +02:00
Ruini Xue
eadbf53140 memory: Use default memory stats instead of panic.
When containers are terminated, cgroup v2 memory metrics under
/sys/fs/cgroup may disappear.  Previously, kata-agent assumed these
metrics always exist, leading to panics as reported in
kata-containers#138.

This commit returns default value (0) when memory metric files are
missing.  This behaviour aligns with cgroup v1, which also defaults to 0
memory metric files are missing:
- Memory.limit_in_bytes which maps to m.max
  https://github.com/kata-containers/cgroups-rs/blob/main/src/memory.rs#L635
- Memory.soft_limit_in_bytes which maps to m.low
  https://github.com/kata-containers/cgroups-rs/blob/main/src/memory.rs#L661
- MemSwap.fail_cnt:
  https://github.com/kata-containers/cgroups-rs/blob/main/src/memory.rs#L631

Signed-off-by: Ruini Xue <ruini.xue@gmail.com>
Signed-off-by: Alex Man <alexman@stripe.com>
Signed-off-by: Fabiano Fidêncio <fidencio@northflank.com>
2025-05-28 23:02:04 +02:00
Fabiano Fidêncio
b3c57840ee lib: Ignore dead_code warning
I'm not fmiliar with this crate, so let's just ignore the warning for
now instead of removing the code.

Signed-off-by: Fabiano Fidêncio <fidencio@northflank.com>
2025-05-28 23:02:04 +02:00
Fabiano Fidêncio
b10e52d85f Merge pull request #133 from nxfs/controllers-parsing
controllers parsing is resilient to trailing newline
2025-05-28 22:44:03 +02:00
jokemanfire
7d4d4579a3 fix bug: if cgroup path has ":"
This bug is occur in cgroup path "which has :"

Signed-off-by: jokemanfire <hu.dingyang@zte.com.cn>
2024-11-02 20:23:48 +08:00
Tim Zhang
eb3e37a4bc Merge pull request #132 from aa624545345/collect-cgroup-path
Correct path of cgroup when running a container in a container
2024-05-13 17:38:52 +08:00
乔琛 10307740
4005ad844d cgroup: collect path of cgroup when running a container in a container
Path of cgroup is wrong when running a container in a container. Use
the root path of mountinfo fetched from /proc/$(shim_pid)/mountinfo
to trim the path obtained from /proc/self/mountinfo.

Fixes: #131

Signed-off-by: 乔琛 10307740 <qiao.chen@zte.com.cn>
2024-04-16 16:40:05 +08:00
Fernand Sieber
ef3497646f controllers parsing is resilient to trailing newline
In older kernel versions (tested with 5.10.208), the cgroup.controllers
files ends with a newline. This newline is not trimmed and as such the
application later can incorrectly consider that the last controller is not
supported
2024-03-20 13:55:56 +02:00
Peng Tao
69ef63a0ef Merge pull request #130 from Tim-Zhang/release-0.3.4
release: v0.3.4
2023-10-25 15:35:45 +08:00
Tim Zhang
346844ca72 release: v0.3.4
To include #126, #127, #129

Signed-off-by: Tim Zhang <tim@hyper.sh>
2023-10-25 14:48:38 +08:00
Tim Zhang
4f1fe13d91 Merge pull request #127 from nrxus/cgroup2-identify-musl
make musl cgroupv2 identifying same as other target_envs
2023-10-25 14:42:03 +08:00
Andrés Medina
17a6c6b842 fix misidentification of cgroup v2 in musl targets
When compiling for a musl target, use the same CGROUP2_SUPER_MAGIC
constant that we use for other linux targets

Signed-off-by: Andrés Medina <andresnrx@gmail.com>
2023-10-24 15:52:35 -07:00
Tim Zhang
3c4b724433 Merge pull request #129 from mzweilz/mzweilz/main
Support setting max memory limit in cgroup v2
2023-10-23 20:00:55 +08:00
Tim Zhang
01885adb99 Merge pull request #126 from HeRaNO/use-peak
memory: use `peak` in `max_usage_in_bytes`
2023-10-23 20:00:25 +08:00
mengze
ce5f5f638e Support setting max memory limit in cgroup v2
When setting memory.max/memory.swap.max in cgroup v2, Linux uses "max"
instead of "-1" to indicate no restriction on memory usage. The
set_limit and set_memswap_limit functions of the memory controller
accept i64 as a parameter. In cgroupv2, if the parameter is -1, "max"
should be written into the cgroup file.

Fixes #128

Signed-off-by: mengze <mengze@linux.alibaba.com>
2023-10-23 19:14:16 +08:00
HeRaNO
be837166e9 memory: use peak in max_usage_in_bytes
Kernel 5.19 introduces `memory.peak` and kernel 6.5 introduces
`memory.swap.peak` to expose the memory and swap usage watermark.
This change use these values to fill `max_usage_in_bytes`.

Signed-off-by: HeRaNO <heran55@126.com>
2023-09-12 12:14:11 +08:00
Tim Zhang
8d29c194e3 Merge pull request #122 from justxuewei/release-033
release: v0.3.3
2023-08-03 15:18:13 +08:00
Xuewei Niu
8a82ad0ac2 release: v0.3.3
The included patches are

- 89edba0f85: gha: Bump Rust version to 1.69.0
- 66a93b1c3d: devices: Throw an error if device resources are invalid
- 55505e0b3e: Minor changes for cgroup and devices subsystem
- 0b6b229a38: add .path() method

Signed-off-by: Xuewei Niu <niuxuewei.nxw@antgroup.com>
2023-08-03 15:11:31 +08:00
Bin Liu
369f3bebed Merge pull request #120 from fprasx/main
add .path() method
2023-08-03 15:10:08 +08:00
Felix Prasanna
0b6b229a38 add .path() method
Allows the user to re-retrieve the path to the cgroup.

Signed-off-by: Felix Prasanna <felix@neon.tech>
2023-08-01 10:14:45 -04:00
Tim Zhang
f55bdb1775 Merge pull request #121 from justxuewei/devices
Minor changes for cgroup and devices subsystem
2023-08-01 19:46:06 +08:00
Xuewei Niu
55505e0b3e Minor changes for cgroup and devices subsystem
The changes include:

- Expose `create()` and add `exists()` for `Cgroup`: The changes
are allowed to load cgroup and test if the cgroup exists. If not exists,
performing the `create()` directly to avoid performing `new()`.
- Make path of devices cgroup error more details: The origin path is
either `devices.allow` or `devices.deny`. It not shows which cgroup it
belongs to.

Signed-off-by: Xuewei Niu <niuxuewei.nxw@antgroup.com>
2023-08-01 19:36:55 +08:00
Fupan Li
df347c1db8 Merge pull request #118 from justxuewei/devices
devices: Throw an error if device resources are invalid
2023-08-01 09:51:34 +08:00
Xuewei Niu
66a93b1c3d devices: Throw an error if device resources are invalid
The cgroup-rs should throw errors while setting devices cgroup if the rule
is invalid. For example, if a cgroup has permissions of some devices. Then
we set a `a *:* rwm` to its parent's `devices.deny`. An error should be
thrown to make users realize that it is a invalid rule.

Signed-off-by: Xuewei Niu <niuxuewei.nxw@antgroup.com>
2023-07-31 15:18:19 +08:00
Tim Zhang
ca66292f5f Merge pull request #119 from justxuewei/ga/rust1690
gha: Bump Rust version to 1.69.0
2023-07-31 15:17:24 +08:00
Xuewei Niu
89edba0f85 gha: Bump Rust version to 1.69.0
Keep Rust version the same as kata-containers repo 's version.

Signed-off-by: Xuewei Niu <niuxuewei.nxw@antgroup.com>
2023-07-31 15:01:17 +08:00
Tim Zhang
1b61c07b69 Merge pull request #114 from Tim-Zhang/release-0.3.2
release: v0.3.2
2023-03-15 15:53:10 +08:00
Tim Zhang
45e1f0c274 release: v0.3.2
To include patches #104, #113.

Signed-off-by: Tim Zhang <tim@hyper.sh>
2023-03-15 10:50:12 +08:00
Bin Liu
41b5f9c25c Merge pull request #113 from gkurz/fix-hugetlb-limit-in-bytes
Fix HugeTlbController::limit_in_bytes() for v2
2023-03-14 13:32:38 +08:00
Greg Kurz
93a59571e3 Fix HugeTlbController::limit_in_bytes() for v2
With Cgroups v2, the file to use is "max", not "limit_in_bytes".

Fixes #112

Signed-off-by: Greg Kurz <groug@kaod.org>
2023-03-10 13:37:41 +01:00
Tim Zhang
257012f2bb Merge pull request #104 from yaoyinnan/103/fix/add-task
Determine cgroup mode in add_task()
2023-02-09 11:39:29 +08:00
yaoyinnan
3dd0735324 Add UT for add_task().
Add UT for add_task() for cgroup v1 and v2.

Fixes: #103

Signed-off-by: yaoyinnan <yaoyinnan@foxmail.com>
2023-02-07 22:40:33 +08:00
yaoyinnan
6b338cf997 Determine cgroup mode in add_task()
Determine the cgroup mode in add_task() to avoid the wrong operation of the caller writing threads to cgroup.threads in non-thread mode.

Fixes: #103

Signed-off-by: yaoyinnan <yaoyinnan@foxmail.com>
2023-02-07 22:40:06 +08:00
Fupan Li
225be2cdbb Merge pull request #111 from Tim-Zhang/release-0.3.1
release: v0.3.1
2023-02-07 16:34:03 +08:00
Tim Zhang
51779d6915 release: v0.3.1
To include patches #105, #108, #110.

Signed-off-by: Tim Zhang <tim@hyper.sh>
2023-02-07 16:22:48 +08:00
Bin Liu
5ea28f076c Merge pull request #105 from amitlevy/patch-1
Fixed documentation for cpu quota and period
2023-02-07 15:10:48 +08:00
Fupan Li
aa74f34a91 Merge pull request #110 from liubin/fix/add-discard-to-blkio
blkio: add discard field to IoService
2023-02-06 14:59:08 +08:00
Amit Levy
328428ace4 Fixed documentation for cpu quota and period
nanoseconds -> microseonds

Noticed while debugging, also fits Red Hat documentation
https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/resource_management_guide/sec-cpu
, and I read the implementation to confirm

Signed-off-by: Amit Levy <amitlevy49@gmail.com>
2023-02-06 11:40:42 +08:00
Chao Wu
c8bb7e1c7e Merge pull request #108 from jongwu/error_cause
error: replace cause method with source
2023-02-05 23:05:04 +08:00
bin liu
25a1340123 blkio: add discard field to IoService
Some system has the `Discard` field in io service data, current the
library can't handle it correctly.

With this commit the blkio can get metrics whether it has the discard field.

Fixes: #109

Signed-off-by: bin liu <liubin0329@gmail.com>
2023-02-02 22:38:46 +08:00
Jianyong Wu
4203075f19 error: replace cause method with source
cause method for std::error::Error is depricated by rust, source method
is recommended.

Fixes: #107
Signed-off-by: Jianyong Wu <jianyong.wu@arm.com>
2023-02-02 14:00:11 +08:00
22 changed files with 483 additions and 272 deletions

View File

@@ -1,7 +1,7 @@
name: BVT name: BVT
on: [pull_request] on: [pull_request]
env: env:
RUST_VERSION: 1.52 RUST_VERSION: 1.69.0
jobs: jobs:
build: build:
name: Build name: Build

4
.gitignore vendored
View File

@@ -8,7 +8,3 @@ Cargo.lock
# These are backup files generated by rustfmt # These are backup files generated by rustfmt
**/*.rs.bk **/*.rs.bk
/target
**/*.rs.bk
Cargo.lock

View File

@@ -5,7 +5,7 @@ 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.3.0" version = "0.3.5"
authors = ["The Kata Containers community <kata-dev@lists.katacontainers.io>", "Levente Kurusa <lkurusa@acm.org>", "Sam Wilson <tecywiz121@hotmail.com>"] authors = ["The Kata Containers community <kata-dev@lists.katacontainers.io>", "Levente Kurusa <lkurusa@acm.org>", "Sam Wilson <tecywiz121@hotmail.com>"]
edition = "2018" edition = "2018"
homepage = "https://github.com/kata-containers/cgroups-rs" homepage = "https://github.com/kata-containers/cgroups-rs"

View File

@@ -43,7 +43,7 @@ pub struct BlkIoData {
pub data: u64, pub data: u64,
} }
#[derive(Eq, PartialEq, Debug)] #[derive(Eq, PartialEq, Debug, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
/// Per-device activity from the control group. /// Per-device activity from the control group.
pub struct IoService { pub struct IoService {
@@ -59,6 +59,8 @@ pub struct IoService {
pub sync: u64, pub sync: u64,
/// How many items were asynchronously transferred. /// How many items were asynchronously transferred.
pub r#async: u64, pub r#async: u64,
/// How many items were discarded.
pub discard: u64,
/// Total number of items transferred. /// Total number of items transferred.
pub total: u64, pub total: u64,
} }
@@ -87,44 +89,62 @@ pub struct IoStat {
} }
fn parse_io_service(s: String) -> Result<Vec<IoService>> { fn parse_io_service(s: String) -> Result<Vec<IoService>> {
s.lines() let mut io_services = Vec::<IoService>::new();
let mut io_service = IoService::default();
let lines = s
.lines()
.filter(|x| x.split_whitespace().count() == 3) .filter(|x| x.split_whitespace().count() == 3)
.map(|x| { .map(|x| {
let mut spl = x.split_whitespace(); let mut spl = x.split_whitespace();
(spl.next().unwrap(), spl.next().unwrap(), spl.next().unwrap()) (
spl.next().unwrap(),
spl.next().unwrap(),
spl.next().unwrap(),
)
}) })
.map(|(a, b, c)| { .map(|(a, b, c)| {
let mut spl = a.split(':'); let mut spl = a.split(':');
(spl.next().unwrap(), spl.next().unwrap(), b, c) (
}) spl.next().unwrap().parse::<i16>(),
.collect::<Vec<_>>() spl.next().unwrap().parse::<i16>(),
.chunks(5) b,
.map(|x| { c,
match x { )
[(major, minor, "Read", read_val), (_, _, "Write", write_val),
(_, _, "Sync", sync_val), (_, _, "Async", async_val),
(_, _, "Total", total_val)] =>
Some(IoService {
major: major.parse::<i16>().unwrap(),
minor: minor.parse::<i16>().unwrap(),
read: read_val.parse::<u64>().unwrap(),
write: write_val.parse::<u64>().unwrap(),
sync: sync_val.parse::<u64>().unwrap(),
r#async: async_val.parse::<u64>().unwrap(),
total: total_val.parse::<u64>().unwrap(),
}),
_ => None,
}
})
.fold(Ok(Vec::new()), |acc, x| {
if acc.is_err() || x.is_none() {
Err(Error::new(ParseError))
} else {
let mut acc = acc.unwrap();
acc.push(x.unwrap());
Ok(acc)
}
}) })
.collect::<Vec<_>>();
for (major_num, minor_num, op, val) in lines.iter() {
let major = *major_num.as_ref().map_err(|_| Error::new(ParseError))?;
let minor = *minor_num.as_ref().map_err(|_| Error::new(ParseError))?;
if (major != io_service.major || minor != io_service.minor) && io_service.major != 0 {
// new block device
io_services.push(io_service);
io_service = IoService::default();
}
io_service.major = major;
io_service.minor = minor;
let val = val.parse::<u64>().map_err(|_| Error::new(ParseError))?;
match *op {
"Read" => io_service.read = val,
"Write" => io_service.write = val,
"Sync" => io_service.sync = val,
"Async" => io_service.r#async = val,
"Discard" => io_service.discard = val,
"Total" => io_service.total = val,
_ => {}
}
}
if io_service.major != 0 {
io_services.push(io_service);
}
Ok(io_services)
} }
fn get_value(s: &str) -> String { fn get_value(s: &str) -> String {
@@ -410,10 +430,10 @@ impl<'a> From<&'a Subsystem> for &'a BlkIoController {
impl BlkIoController { impl BlkIoController {
/// Constructs a new `BlkIoController` with `root` 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(root: PathBuf, v2: bool) -> Self { pub fn new(point: PathBuf, root: PathBuf, v2: bool) -> Self {
Self { Self {
base: root.clone(), base: root,
path: root, path: point,
v2, v2,
} }
} }
@@ -817,6 +837,7 @@ mod test {
8:32 Write 0 8:32 Write 0
8:32 Sync 4280320 8:32 Sync 4280320
8:32 Async 0 8:32 Async 0
8:32 Discard 1
8:32 Total 4280320 8:32 Total 4280320
8:48 Read 5705479168 8:48 Read 5705479168
8:48 Write 56096055296 8:48 Write 56096055296
@@ -833,28 +854,6 @@ mod test {
8:0 Sync 7192576 8:0 Sync 7192576
8:0 Async 0 8:0 Async 0
8:0 Total 7192576 8:0 Total 7192576
Total 61823067136
";
static TEST_WRONG_VALUE: &str = "\
8:32 Read 4280320
8:32 Write 0
8:32 Async 0
8:32 Total 4280320 8:48 Read 5705479168
8:48 Write 56096055296
8:48 Sync 11213923328
8:48 Async 50587611136
8:48 Total 61801534464
8:16 Read 10059776
8:16 Write 0
8:16 Sync 10059776
8:16 Async 0
8:16 Total 10059776
8:0 Read 7192576
8:0 Write 0
8:0 Sync 7192576
8:0 Async 0
8:0 Total 7192576
Total 61823067136 Total 61823067136
"; ";
@@ -884,6 +883,7 @@ Total 61823067136
write: 0, write: 0,
sync: 4280320, sync: 4280320,
r#async: 0, r#async: 0,
discard: 1,
total: 4280320, total: 4280320,
}, },
IoService { IoService {
@@ -893,6 +893,7 @@ Total 61823067136
write: 56096055296, write: 56096055296,
sync: 11213923328, sync: 11213923328,
r#async: 50587611136, r#async: 50587611136,
discard: 0,
total: 61801534464, total: 61801534464,
}, },
IoService { IoService {
@@ -902,6 +903,7 @@ Total 61823067136
write: 0, write: 0,
sync: 10059776, sync: 10059776,
r#async: 0, r#async: 0,
discard: 0,
total: 10059776, total: 10059776,
}, },
IoService { IoService {
@@ -911,12 +913,34 @@ Total 61823067136
write: 0, write: 0,
sync: 7192576, sync: 7192576,
r#async: 0, r#async: 0,
discard: 0,
total: 7192576, total: 7192576,
} }
] ]
); );
let err = parse_io_service(TEST_WRONG_VALUE.to_string()).unwrap_err();
assert_eq!(err.kind(), &ErrorKind::ParseError,); let invalid_values = vec![
"\
8:32 Read 4280320
8:32 Write a
8:32 Async 1
",
"\
8:32 Read 4280320
b:32 Write 1
8:32 Async 1
",
"\
8:32 Read 4280320
8:32 Write 1
8:c Async 1
",
];
for value in invalid_values {
let err = parse_io_service(value.to_string()).unwrap_err();
assert_eq!(err.kind(), &ErrorKind::ParseError,);
}
} }
#[test] #[test]

View File

@@ -9,6 +9,7 @@
use crate::error::ErrorKind::*; use crate::error::ErrorKind::*;
use crate::error::*; use crate::error::*;
use crate::hierarchies::V1;
use crate::{CgroupPid, ControllIdentifier, Controller, Hierarchy, Resources, Subsystem}; use crate::{CgroupPid, ControllIdentifier, Controller, Hierarchy, Resources, Subsystem};
use std::collections::HashMap; use std::collections::HashMap;
@@ -16,6 +17,11 @@ use std::convert::From;
use std::fs; use std::fs;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
pub const CGROUP_MODE_DOMAIN: &str = "domain";
pub const CGROUP_MODE_DOMAIN_THREADED: &str = "domain threaded";
pub const CGROUP_MODE_DOMAIN_INVALID: &str = "domain invalid";
pub const CGROUP_MODE_THREADED: &str = "threaded";
/// A control group is the central structure to this crate. /// A control group is the central structure to this crate.
/// ///
/// ///
@@ -68,8 +74,13 @@ impl Cgroup {
self.hier.v2() self.hier.v2()
} }
/// Return the path the cgroup is located at.
pub fn path(&self) -> &str {
&self.path
}
/// Create this control group. /// Create this control group.
fn create(&self) -> Result<()> { pub fn create(&self) -> Result<()> {
if self.hier.v2() { if self.hier.v2() {
create_v2_cgroup(self.hier.root(), &self.path, &self.specified_controllers) create_v2_cgroup(self.hier.root(), &self.path, &self.specified_controllers)
} else { } else {
@@ -346,7 +357,18 @@ impl Cgroup {
let subsystems = self.subsystems(); let subsystems = self.subsystems();
if !subsystems.is_empty() { if !subsystems.is_empty() {
let c = subsystems[0].to_controller(); let c = subsystems[0].to_controller();
c.add_task(&tid) let cgroup_type = self.get_cgroup_type()?;
// In cgroup v2, writing to the cgroup.threads file is only supported in thread mode.
if cgroup_type == *CGROUP_MODE_DOMAIN_THREADED
|| cgroup_type == *CGROUP_MODE_THREADED
{
// It is used to move the threads of a process into a cgroup in thread mode.
c.add_task(&tid)
} else {
// When the cgroup type is domain or domain invalid,
// cgroup.threads cannot be written.
Err(Error::new(CgroupMode))
}
} else { } else {
Err(Error::new(SubsystemsEmpty)) Err(Error::new(SubsystemsEmpty))
} }
@@ -363,6 +385,8 @@ impl Cgroup {
let subsystems = self.subsystems(); let subsystems = self.subsystems();
if !subsystems.is_empty() { if !subsystems.is_empty() {
let c = subsystems[0].to_controller(); let c = subsystems[0].to_controller();
// It is used to move a thread of the process to a cgroup,
// and other threads of the process will also move together.
c.add_task_by_tgid(&tgid) c.add_task_by_tgid(&tgid)
} else { } else {
Err(Error::new(SubsystemsEmpty)) Err(Error::new(SubsystemsEmpty))
@@ -474,6 +498,13 @@ impl Cgroup {
v.dedup(); v.dedup();
v v
} }
/// Checks if the cgroup exists.
///
/// Returns true if at least one subsystem exists.
pub fn exists(&self) -> bool {
self.subsystems().iter().any(|e| e.to_controller().exists())
}
} }
pub const UNIFIED_MOUNTPOINT: &str = "/sys/fs/cgroup"; pub const UNIFIED_MOUNTPOINT: &str = "/sys/fs/cgroup";
@@ -491,7 +522,7 @@ fn supported_controllers() -> Vec<String> {
let ret = fs::read_to_string(p.as_str()); let ret = fs::read_to_string(p.as_str());
ret.unwrap_or_default() ret.unwrap_or_default()
.split(' ') .split(' ')
.map(|x| x.to_string()) .map(|x| x.trim().to_string())
.collect::<Vec<String>>() .collect::<Vec<String>>()
} }
@@ -558,19 +589,47 @@ pub fn get_cgroups_relative_paths_by_pid(pid: u32) -> Result<HashMap<String, Str
get_cgroups_relative_paths_by_path(path) get_cgroups_relative_paths_by_path(path)
} }
fn get_cgroup_destination(mut mount_root: String, pidpath: String) -> String {
if mount_root == "/" {
mount_root = String::from("");
}
pidpath.trim_start_matches(&mount_root).to_string()
}
pub fn existing_path(paths: HashMap<String, String>) -> Result<HashMap<String, String>> {
let mount_roots_v1 = V1::new();
let mut mount_roots_subsystems_map = HashMap::new();
for s in mount_roots_v1.subsystems().iter() {
let controller_name = s.controller_name();
let path_from_cgroup = paths
.get(&controller_name)
.ok_or(Error::new(Common(format!(
"controller {} found in mountinfo, but not found in cgroup.",
controller_name
))))?;
let path_from_mountinfo = s.to_controller().base().to_string_lossy().to_string();
let des_path = get_cgroup_destination(path_from_mountinfo, path_from_cgroup.to_owned());
mount_roots_subsystems_map.insert(controller_name, des_path);
}
Ok(mount_roots_subsystems_map)
}
fn get_cgroups_relative_paths_by_path(path: String) -> Result<HashMap<String, String>> { fn get_cgroups_relative_paths_by_path(path: String) -> Result<HashMap<String, String>> {
let mut m = HashMap::new(); let mut m = HashMap::new();
let content = let content =
fs::read_to_string(path.clone()).map_err(|e| Error::with_cause(ReadFailed(path), e))?; fs::read_to_string(path.clone()).map_err(|e| Error::with_cause(ReadFailed(path), e))?;
for l in content.lines() { // cgroup path may have ":" , likes
let fl: Vec<&str> = l.split(':').collect(); // "2:cpu,cpuacct:/system.slice/containerd.service/test.slice:cri-containerd:96b37a2edf84351487f42039e137427f1812f678850675fac214caf597ee5e4a"
if fl.len() != 3 { for line in content.lines() {
continue; if let Some((first_value_part, remaining_path)) =
} line.split_once(':').unwrap_or_default().1.split_once(':')
{
let keys: Vec<&str> = fl[1].split(',').collect(); let keys: Vec<&str> = first_value_part.split(',').collect();
for key in &keys { keys.iter().for_each(|key| {
m.insert(key.to_string(), fl[2].to_string()); m.insert(key.to_string(), remaining_path.to_string());
});
} }
} }
Ok(m) Ok(m)

View File

@@ -59,7 +59,6 @@ impl ControllerInternal for CpuController {
fn get_path(&self) -> &PathBuf { fn get_path(&self) -> &PathBuf {
&self.path &self.path
} }
fn get_path_mut(&mut self) -> &mut PathBuf { fn get_path_mut(&mut self) -> &mut PathBuf {
&mut self.path &mut self.path
} }
@@ -113,10 +112,10 @@ impl<'a> From<&'a Subsystem> for &'a CpuController {
impl CpuController { impl CpuController {
/// Contructs a new `CpuController` with `root` 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(root: PathBuf, v2: bool) -> Self { pub fn new(point: PathBuf, root: PathBuf, v2: bool) -> Self {
Self { Self {
base: root.clone(), base: root,
path: root, path: point,
v2, v2,
} }
} }

View File

@@ -100,10 +100,10 @@ impl<'a> From<&'a Subsystem> for &'a CpuAcctController {
impl CpuAcctController { impl CpuAcctController {
/// Contructs a new `CpuAcctController` with `root` 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(root: PathBuf) -> Self { pub fn new(point: PathBuf, root: PathBuf) -> Self {
Self { Self {
base: root.clone(), base: root,
path: root, path: point,
} }
} }

View File

@@ -254,10 +254,10 @@ fn parse_range(s: String) -> Result<Vec<(u64, u64)>> {
impl CpuSetController { impl CpuSetController {
/// Contructs a new `CpuSetController` with `root` 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(root: PathBuf, v2: bool) -> Self { pub fn new(point: PathBuf, root: PathBuf, v2: bool) -> Self {
Self { Self {
base: root.clone(), base: root,
path: root, path: point,
v2, v2,
} }
} }

View File

@@ -46,6 +46,7 @@ pub enum DeviceType {
Block, Block,
} }
#[allow(clippy::derivable_impls)]
impl Default for DeviceType { impl Default for DeviceType {
fn default() -> Self { fn default() -> Self {
DeviceType::All DeviceType::All
@@ -170,9 +171,9 @@ impl ControllerInternal for DevicesController {
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); 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); self.deny_device(i.devtype, i.major, i.minor, &i.access)?;
} }
} }
@@ -203,10 +204,10 @@ impl<'a> From<&'a Subsystem> for &'a DevicesController {
impl DevicesController { impl DevicesController {
/// Constructs a new `DevicesController` with `root` 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(root: PathBuf) -> Self { pub fn new(point: PathBuf, root: PathBuf) -> Self {
Self { Self {
base: root.clone(), base: root,
path: root, path: point,
} }
} }
@@ -238,7 +239,13 @@ impl DevicesController {
let final_str = format!("{} {}:{} {}", devtype.to_char(), major, minor, perms); let final_str = format!("{} {}:{} {}", devtype.to_char(), major, minor, perms);
self.open_path("devices.allow", true).and_then(|mut file| { self.open_path("devices.allow", true).and_then(|mut file| {
file.write_all(final_str.as_ref()).map_err(|e| { file.write_all(final_str.as_ref()).map_err(|e| {
Error::with_cause(WriteFailed("devices.allow".to_string(), final_str), e) Error::with_cause(
WriteFailed(
self.get_path().join("devices.allow").display().to_string(),
final_str,
),
e,
)
}) })
}) })
} }
@@ -271,7 +278,13 @@ impl DevicesController {
let final_str = format!("{} {}:{} {}", devtype.to_char(), major, minor, perms); let final_str = format!("{} {}:{} {}", devtype.to_char(), major, minor, perms);
self.open_path("devices.deny", true).and_then(|mut file| { self.open_path("devices.deny", true).and_then(|mut file| {
file.write_all(final_str.as_ref()).map_err(|e| { file.write_all(final_str.as_ref()).map_err(|e| {
Error::with_cause(WriteFailed("devices.deny".to_string(), final_str), e) Error::with_cause(
WriteFailed(
self.get_path().join("devices.deny").display().to_string(),
final_str,
),
e,
)
}) })
}) })
} }
@@ -284,7 +297,7 @@ impl DevicesController {
match res { match res {
Ok(_) => { Ok(_) => {
s.lines().fold(Ok(Vec::new()), |acc, line| { s.lines().fold(Ok(Vec::new()), |acc, line| {
let ls = line.to_string().split(|c| c == ' ' || c == ':').map(|x| x.to_string()).collect::<Vec<String>>(); let ls = line.split(|c| c == ' ' || c == ':').map(|x| x.to_string()).collect::<Vec<String>>();
if acc.is_err() || ls.len() != 4 { if acc.is_err() || ls.len() != 4 {
error!("allowed_devices: acc: {:?}, ls: {:?}", acc, ls); error!("allowed_devices: acc: {:?}, ls: {:?}", acc, ls);
Err(Error::new(ParseError)) Err(Error::new(ParseError))

View File

@@ -59,6 +59,10 @@ pub enum ErrorKind {
#[error("using method in wrong cgroup version")] #[error("using method in wrong cgroup version")]
CgroupVersion, CgroupVersion,
/// Using method in wrong cgroup mode.
#[error("using method in wrong cgroup mode.")]
CgroupMode,
/// Subsystems is empty. /// Subsystems is empty.
#[error("subsystems is empty")] #[error("subsystems is empty")]
SubsystemsEmpty, SubsystemsEmpty,
@@ -85,7 +89,7 @@ impl fmt::Display for Error {
} }
impl StdError for Error { impl StdError for Error {
fn cause(&self) -> Option<&dyn StdError> { fn source(&self) -> Option<&(dyn StdError + 'static)> {
#[allow(clippy::manual_map)] #[allow(clippy::manual_map)]
match self.cause { match self.cause {
Some(ref x) => Some(&**x), Some(ref x) => Some(&**x),

View File

@@ -84,14 +84,13 @@ impl<'a> From<&'a Subsystem> for &'a FreezerController {
impl FreezerController { impl FreezerController {
/// Contructs a new `FreezerController` with `root` 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(root: PathBuf, v2: bool) -> Self { pub fn new(point: PathBuf, root: PathBuf, v2: bool) -> Self {
Self { Self {
base: root.clone(), base: root,
path: root, path: point,
v2, v2,
} }
} }
/// Freezes the processes in the control group. /// Freezes the processes in the control group.
pub fn freeze(&self) -> Result<()> { pub fn freeze(&self) -> Result<()> {
let mut file_name = "freezer.state"; let mut file_name = "freezer.state";

View File

@@ -5,9 +5,6 @@
// //
//! 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
//! the Unified Hierarchy.
use std::fs; use std::fs;
use std::fs::File; use std::fs::File;
@@ -37,6 +34,8 @@ use crate::cgroup::Cgroup;
/// See `proc(5)` for format details. /// See `proc(5)` for format details.
#[derive(Debug, PartialEq, Eq, Hash, Clone)] #[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct Mountinfo { pub struct Mountinfo {
/// Mount root directory of the file system.
pub mount_root: PathBuf,
/// Mount pathname relative to the process's root. /// Mount pathname relative to the process's root.
pub mount_point: PathBuf, pub mount_point: PathBuf,
/// Filesystem type (main type with optional sub-type). /// Filesystem type (main type with optional sub-type).
@@ -57,6 +56,7 @@ pub(crate) fn parse_mountinfo_for_line(line: &str) -> Option<Mountinfo> {
return None; return None;
} }
let mount_point = PathBuf::from(s0_values[4]); let mount_point = PathBuf::from(s0_values[4]);
let mount_root = PathBuf::from(s0_values[3]);
let fs_type_values: Vec<_> = s1_values[0].trim().split('.').collect(); let fs_type_values: Vec<_> = s1_values[0].trim().split('.').collect();
let fs_type = match fs_type_values.len() { let fs_type = match fs_type_values.len() {
1 => (fs_type_values[0].to_string(), None), 1 => (fs_type_values[0].to_string(), None),
@@ -69,6 +69,7 @@ pub(crate) fn parse_mountinfo_for_line(line: &str) -> Option<Mountinfo> {
let super_opts: Vec<String> = s1_values[2].trim().split(',').map(String::from).collect(); let super_opts: Vec<String> = s1_values[2].trim().split(',').map(String::from).collect();
Some(Mountinfo { Some(Mountinfo {
mount_root,
mount_point, mount_point,
fs_type, fs_type,
super_opts, super_opts,
@@ -123,47 +124,53 @@ impl Hierarchy for V1 {
// The cgroup writeback feature requires cooperation between memcgs and blkcgs // 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) // 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 // For more Information: https://www.alibabacloud.com/help/doc-detail/155509.htm
if let Some(root) = self.get_mount_point(Controllers::BlkIo) { if let Some((point, root)) = self.get_mount_point(Controllers::BlkIo) {
subs.push(Subsystem::BlkIo(BlkIoController::new(root, false))); subs.push(Subsystem::BlkIo(BlkIoController::new(point, root, false)));
} }
if let Some(root) = self.get_mount_point(Controllers::Mem) { if let Some((point, root)) = self.get_mount_point(Controllers::Mem) {
subs.push(Subsystem::Mem(MemController::new(root, false))); subs.push(Subsystem::Mem(MemController::new(point, root, false)));
} }
if let Some(root) = self.get_mount_point(Controllers::Pids) { if let Some((point, root)) = self.get_mount_point(Controllers::Pids) {
subs.push(Subsystem::Pid(PidController::new(root, false))); subs.push(Subsystem::Pid(PidController::new(point, root, false)));
} }
if let Some(root) = self.get_mount_point(Controllers::CpuSet) { if let Some((point, root)) = self.get_mount_point(Controllers::CpuSet) {
subs.push(Subsystem::CpuSet(CpuSetController::new(root, false))); subs.push(Subsystem::CpuSet(CpuSetController::new(point, root, false)));
} }
if let Some(root) = self.get_mount_point(Controllers::CpuAcct) { if let Some((point, root)) = self.get_mount_point(Controllers::CpuAcct) {
subs.push(Subsystem::CpuAcct(CpuAcctController::new(root))); subs.push(Subsystem::CpuAcct(CpuAcctController::new(point, root)));
} }
if let Some(root) = self.get_mount_point(Controllers::Cpu) { if let Some((point, root)) = self.get_mount_point(Controllers::Cpu) {
subs.push(Subsystem::Cpu(CpuController::new(root, false))); subs.push(Subsystem::Cpu(CpuController::new(point, root, false)));
} }
if let Some(root) = self.get_mount_point(Controllers::Devices) { if let Some((point, root)) = self.get_mount_point(Controllers::Devices) {
subs.push(Subsystem::Devices(DevicesController::new(root))); subs.push(Subsystem::Devices(DevicesController::new(point, root)));
} }
if let Some(root) = self.get_mount_point(Controllers::Freezer) { if let Some((point, root)) = self.get_mount_point(Controllers::Freezer) {
subs.push(Subsystem::Freezer(FreezerController::new(root, false))); subs.push(Subsystem::Freezer(FreezerController::new(
point, root, false,
)));
} }
if let Some(root) = self.get_mount_point(Controllers::NetCls) { if let Some((point, root)) = self.get_mount_point(Controllers::NetCls) {
subs.push(Subsystem::NetCls(NetClsController::new(root))); subs.push(Subsystem::NetCls(NetClsController::new(point, root)));
} }
if let Some(root) = self.get_mount_point(Controllers::PerfEvent) { if let Some((point, root)) = self.get_mount_point(Controllers::PerfEvent) {
subs.push(Subsystem::PerfEvent(PerfEventController::new(root))); subs.push(Subsystem::PerfEvent(PerfEventController::new(point, root)));
} }
if let Some(root) = self.get_mount_point(Controllers::NetPrio) { if let Some((point, root)) = self.get_mount_point(Controllers::NetPrio) {
subs.push(Subsystem::NetPrio(NetPrioController::new(root))); subs.push(Subsystem::NetPrio(NetPrioController::new(point, root)));
} }
if let Some(root) = self.get_mount_point(Controllers::HugeTlb) { if let Some((point, root)) = self.get_mount_point(Controllers::HugeTlb) {
subs.push(Subsystem::HugeTlb(HugeTlbController::new(root, false))); subs.push(Subsystem::HugeTlb(HugeTlbController::new(
point, root, false,
)));
} }
if let Some(root) = self.get_mount_point(Controllers::Rdma) { if let Some((point, root)) = self.get_mount_point(Controllers::Rdma) {
subs.push(Subsystem::Rdma(RdmaController::new(root))); subs.push(Subsystem::Rdma(RdmaController::new(point, root)));
} }
if let Some(root) = self.get_mount_point(Controllers::Systemd) { if let Some((point, root)) = self.get_mount_point(Controllers::Systemd) {
subs.push(Subsystem::Systemd(SystemdController::new(root, false))); subs.push(Subsystem::Systemd(SystemdController::new(
point, root, false,
)));
} }
subs subs
@@ -218,29 +225,51 @@ impl Hierarchy for V2 {
for s in controller_list { for s in controller_list {
match s { match s {
"cpu" => { "cpu" => {
subs.push(Subsystem::Cpu(CpuController::new(self.root(), true))); subs.push(Subsystem::Cpu(CpuController::new(
self.root(),
PathBuf::from(""),
true,
)));
} }
"io" => { "io" => {
subs.push(Subsystem::BlkIo(BlkIoController::new(self.root(), true))); subs.push(Subsystem::BlkIo(BlkIoController::new(
self.root(),
PathBuf::from(""),
true,
)));
} }
"cpuset" => { "cpuset" => {
subs.push(Subsystem::CpuSet(CpuSetController::new(self.root(), true))); subs.push(Subsystem::CpuSet(CpuSetController::new(
self.root(),
PathBuf::from(""),
true,
)));
} }
"memory" => { "memory" => {
subs.push(Subsystem::Mem(MemController::new(self.root(), true))); subs.push(Subsystem::Mem(MemController::new(
self.root(),
PathBuf::from(""),
true,
)));
} }
"pids" => { "pids" => {
subs.push(Subsystem::Pid(PidController::new(self.root(), true))); subs.push(Subsystem::Pid(PidController::new(
self.root(),
PathBuf::from(""),
true,
)));
} }
"freezer" => { "freezer" => {
subs.push(Subsystem::Freezer(FreezerController::new( subs.push(Subsystem::Freezer(FreezerController::new(
self.root(), self.root(),
PathBuf::from(""),
true, true,
))); )));
} }
"hugetlb" => { "hugetlb" => {
subs.push(Subsystem::HugeTlb(HugeTlbController::new( subs.push(Subsystem::HugeTlb(HugeTlbController::new(
self.root(), self.root(),
PathBuf::from(""),
true, true,
))); )));
} }
@@ -275,10 +304,10 @@ impl V1 {
} }
} }
pub fn get_mount_point(&self, controller: Controllers) -> Option<PathBuf> { pub fn get_mount_point(&self, controller: Controllers) -> Option<(PathBuf, PathBuf)> {
self.mountinfo.iter().find_map(|m| { self.mountinfo.iter().find_map(|m| {
if m.fs_type.0 == "cgroup" && m.super_opts.contains(&controller.to_string()) { if m.fs_type.0 == "cgroup" && m.super_opts.contains(&controller.to_string()) {
return Some(m.mount_point.clone()); return Some((m.mount_point.to_owned(), m.mount_root.to_owned()));
} }
None None
}) })
@@ -309,43 +338,16 @@ impl Default for V2 {
pub const UNIFIED_MOUNTPOINT: &str = "/sys/fs/cgroup"; pub const UNIFIED_MOUNTPOINT: &str = "/sys/fs/cgroup";
#[cfg(any(
all(target_os = "linux", not(target_env = "musl")),
target_os = "android"
))]
pub fn is_cgroup2_unified_mode() -> bool { pub fn is_cgroup2_unified_mode() -> bool {
use nix::sys::statfs; use nix::sys::statfs;
let path = std::path::Path::new(UNIFIED_MOUNTPOINT); let path = std::path::Path::new(UNIFIED_MOUNTPOINT);
let fs_stat = statfs::statfs(path); let fs_stat = match statfs::statfs(path) {
if fs_stat.is_err() { Ok(fs_stat) => fs_stat,
return false; Err(_) => return false,
} };
// FIXME notwork, nix will not compile CGROUP2_SUPER_MAGIC because not(target_env = "musl") fs_stat.filesystem_type() == statfs::CGROUP2_SUPER_MAGIC
fs_stat.unwrap().filesystem_type() == statfs::CGROUP2_SUPER_MAGIC
}
pub const INIT_CGROUP_PATHS: &str = "/proc/1/cgroup";
#[cfg(all(target_os = "linux", target_env = "musl"))]
pub fn is_cgroup2_unified_mode() -> bool {
let lines = fs::read_to_string(INIT_CGROUP_PATHS);
if lines.is_err() {
return false;
}
for line in lines.unwrap().lines() {
let fields: Vec<&str> = line.split(':').collect();
if fields.len() != 3 {
continue;
}
if fields[0] != "0" {
return false;
}
}
true
} }
pub fn auto() -> Box<dyn Hierarchy> { pub fn auto() -> Box<dyn Hierarchy> {
@@ -364,19 +366,19 @@ mod tests {
fn test_parse_mount() { fn test_parse_mount() {
let mountinfo = vec![ let mountinfo = vec![
("29 26 0:26 / /sys/fs/cgroup/cpuset,cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:10 - cgroup cgroup rw,cpuset,cpu,cpuacct", ("29 26 0:26 / /sys/fs/cgroup/cpuset,cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:10 - cgroup cgroup rw,cpuset,cpu,cpuacct",
Mountinfo{mount_point: PathBuf::from("/sys/fs/cgroup/cpuset,cpu,cpuacct"), fs_type: ("cgroup".to_string(), None), super_opts: vec![ Mountinfo{mount_root: PathBuf::from("/"), mount_point: PathBuf::from("/sys/fs/cgroup/cpuset,cpu,cpuacct"), fs_type: ("cgroup".to_string(), None), super_opts: vec![
"rw".to_string(), "rw".to_string(),
"cpuset".to_string(), "cpuset".to_string(),
"cpu".to_string(), "cpu".to_string(),
"cpuacct".to_string(), "cpuacct".to_string(),
]}), ]}),
("121 1731 0:42 / /shm rw,nosuid,nodev,noexec,relatime shared:68 master:66 - tmpfs shm rw,size=65536k", ("121 1731 0:42 / /shm rw,nosuid,nodev,noexec,relatime shared:68 master:66 - tmpfs shm rw,size=65536k",
Mountinfo{mount_point: PathBuf::from("/shm"), fs_type: ("tmpfs".to_string(), None), super_opts: vec![ Mountinfo{mount_root: PathBuf::from("/"), mount_point: PathBuf::from("/shm"), fs_type: ("tmpfs".to_string(), None), super_opts: vec![
"rw".to_string(), "rw".to_string(),
"size=65536k".to_string(), "size=65536k".to_string(),
]}), ]}),
("121 1731 0:42 / /shm rw,nosuid,nodev,noexec,relatime shared:68 master:66 - tmpfs.123 shm rw,size=65536k", ("121 1731 0:42 / /shm rw,nosuid,nodev,noexec,relatime shared:68 master:66 - tmpfs.123 shm rw,size=65536k",
Mountinfo{mount_point: PathBuf::from("/shm"), fs_type: ("tmpfs".to_string(), Some("123".to_string())), super_opts: vec![ Mountinfo{mount_root: PathBuf::from("/"), mount_point: PathBuf::from("/shm"), fs_type: ("tmpfs".to_string(), Some("123".to_string())), super_opts: vec![
"rw".to_string(), "rw".to_string(),
"size=65536k".to_string(), "size=65536k".to_string(),
]}), ]}),

View File

@@ -88,11 +88,11 @@ impl<'a> From<&'a Subsystem> for &'a HugeTlbController {
impl HugeTlbController { impl HugeTlbController {
/// Constructs a new `HugeTlbController` with `root` 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(root: PathBuf, v2: bool) -> Self { pub fn new(point: PathBuf, root: PathBuf, v2: bool) -> Self {
let sizes = get_hugepage_sizes(); let sizes = get_hugepage_sizes();
Self { Self {
base: root.clone(), base: root,
path: root, path: point,
sizes, sizes,
v2, v2,
} }
@@ -138,8 +138,11 @@ impl HugeTlbController {
/// Get the limit (in bytes) of how much memory can be backed by hugepages of a certain size /// Get the limit (in bytes) of how much memory can be backed by hugepages of a certain size
/// (`hugetlb_size`). /// (`hugetlb_size`).
pub fn limit_in_bytes(&self, hugetlb_size: &str) -> Result<u64> { pub fn limit_in_bytes(&self, hugetlb_size: &str) -> Result<u64> {
self.open_path(&format!("hugetlb.{}.limit_in_bytes", hugetlb_size), false) let mut file_name = format!("hugetlb.{}.limit_in_bytes", hugetlb_size);
.and_then(read_u64_from) if self.v2 {
file_name = format!("hugetlb.{}.max", hugetlb_size);
}
self.open_path(&file_name, false).and_then(read_u64_from)
} }
/// 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

View File

@@ -231,6 +231,7 @@ mod sealed {
}) })
} }
#[allow(dead_code)]
fn get(&self, key: &str) -> Result<String> { fn get(&self, key: &str) -> Result<String> {
self.open_path(key, false).and_then(|mut file: File| { self.open_path(key, false).and_then(|mut file: File| {
let mut string = String::new(); let mut string = String::new();
@@ -255,6 +256,9 @@ pub trait Controller {
/// The file system path to the controller. /// The file system path to the controller.
fn path(&self) -> &Path; fn path(&self) -> &Path;
/// Root path of the file system to the controller.
fn base(&self) -> &Path;
/// Apply a set of resources to the Controller, invoking its internal functions to pass the /// Apply a set of resources to the Controller, invoking its internal functions to pass the
/// kernel the information. /// kernel the information.
fn apply(&self, res: &Resources) -> Result<()>; fn apply(&self, res: &Resources) -> Result<()>;
@@ -307,6 +311,10 @@ where
self.get_path() self.get_path()
} }
fn base(&self) -> &Path {
self.get_base()
}
/// Apply a set of resources to the Controller, invoking its internal functions to pass the /// Apply a set of resources to the Controller, invoking its internal functions to pass the
/// kernel the information. /// kernel the information.
fn apply(&self, res: &Resources) -> Result<()> { fn apply(&self, res: &Resources) -> Result<()> {
@@ -604,9 +612,9 @@ pub struct CpuResources {
/// 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: Option<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 microseconds.
pub quota: Option<i64>, pub quota: Option<i64>,
/// Period of time in nanoseconds. /// Period of time in microseconds.
pub period: Option<u64>, pub period: Option<u64>,
/// This is currently a no-operation. /// This is currently a no-operation.
pub realtime_runtime: Option<i64>, pub realtime_runtime: Option<i64>,
@@ -879,6 +887,7 @@ pub enum MaxValue {
Value(i64), Value(i64),
} }
#[allow(clippy::derivable_impls)]
impl Default for MaxValue { impl Default for MaxValue {
fn default() -> Self { fn default() -> Self {
MaxValue::Max MaxValue::Max

View File

@@ -540,10 +540,10 @@ impl ControllerInternal for MemController {
impl MemController { impl MemController {
/// Contructs a new `MemController` with `root` 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(root: PathBuf, v2: bool) -> Self { pub fn new(point: PathBuf, root: PathBuf, v2: bool) -> Self {
Self { Self {
base: root.clone(), base: root,
path: root, path: point,
v2, v2,
} }
} }
@@ -573,18 +573,33 @@ impl MemController {
// for v2 // for v2
pub fn get_mem(&self) -> Result<SetMemory> { pub fn get_mem(&self) -> Result<SetMemory> {
let mut m: SetMemory = Default::default(); let m = SetMemory {
self.get_max_value("memory.high") high: self
.map(|x| m.high = Some(x))?; .get_max_value("memory.high")
self.get_max_value("memory.low").map(|x| m.low = Some(x))?; .map_or(Some(MaxValue::default()), Some),
self.get_max_value("memory.max").map(|x| m.max = Some(x))?; low: self
self.get_max_value("memory.min").map(|x| m.min = Some(x))?; .get_max_value("memory.low")
.map_or(Some(MaxValue::Value(0)), Some),
max: self
.get_max_value("memory.max")
.map_or(Some(MaxValue::default()), Some),
min: self
.get_max_value("memory.min")
.map_or(Some(MaxValue::Value(0)), Some),
};
Ok(m) Ok(m)
} }
fn memory_stat_v2(&self) -> Memory { fn memory_stat_v2(&self) -> Memory {
let set = self.get_mem().unwrap(); // NOTE: get_mem() always returns T, but let's
// still do `unwrap_or` for safety.
let set = self.get_mem().unwrap_or(SetMemory {
low: Some(MaxValue::Value(0)),
high: Some(MaxValue::default()),
max: Some(MaxValue::default()),
min: Some(MaxValue::Value(0)),
});
Memory { Memory {
fail_cnt: 0, fail_cnt: 0,
@@ -593,7 +608,10 @@ impl MemController {
.open_path("memory.current", false) .open_path("memory.current", false)
.and_then(read_u64_from) .and_then(read_u64_from)
.unwrap_or(0), .unwrap_or(0),
max_usage_in_bytes: 0, max_usage_in_bytes: self
.open_path("memory.peak", false)
.and_then(read_u64_from)
.unwrap_or(0),
move_charge_at_immigrate: 0, move_charge_at_immigrate: 0,
numa_stat: NumaStat::default(), numa_stat: NumaStat::default(),
oom_control: OomControl::default(), oom_control: OomControl::default(),
@@ -727,7 +745,7 @@ impl MemController {
.open_path("memory.swap.events", false) .open_path("memory.swap.events", false)
.and_then(flat_keyed_to_hashmap) .and_then(flat_keyed_to_hashmap)
.map(|x| *x.get("fail").unwrap_or(&0) as u64) .map(|x| *x.get("fail").unwrap_or(&0) as u64)
.unwrap(), .unwrap_or(0),
limit_in_bytes: self limit_in_bytes: self
.open_path("memory.swap.max", false) .open_path("memory.swap.max", false)
.and_then(read_i64_from) .and_then(read_i64_from)
@@ -736,7 +754,10 @@ impl MemController {
.open_path("memory.swap.current", false) .open_path("memory.swap.current", false)
.and_then(read_u64_from) .and_then(read_u64_from)
.unwrap_or(0), .unwrap_or(0),
max_usage_in_bytes: 0, max_usage_in_bytes: self
.open_path("memory.swap.peak", false)
.and_then(read_u64_from)
.unwrap_or(0),
} }
} }
@@ -844,13 +865,16 @@ impl MemController {
/// Set the memory usage limit of the control group, in bytes. /// Set the memory usage limit of the control group, in bytes.
pub fn set_limit(&self, limit: i64) -> Result<()> { pub fn set_limit(&self, limit: i64) -> Result<()> {
let mut file_name = "memory.limit_in_bytes"; let mut file_name = "memory.limit_in_bytes";
let mut limit_str = limit.to_string();
if self.v2 { if self.v2 {
file_name = "memory.max"; file_name = "memory.max";
if limit == -1 {
limit_str = "max".to_string();
}
} }
self.open_path(file_name, true).and_then(|mut file| { self.open_path(file_name, true).and_then(|mut file| {
file.write_all(limit.to_string().as_ref()).map_err(|e| { file.write_all(limit_str.as_ref())
Error::with_cause(WriteFailed(file_name.to_string(), limit.to_string()), e) .map_err(|e| Error::with_cause(WriteFailed(file_name.to_string(), limit_str), e))
})
}) })
} }
@@ -881,13 +905,16 @@ 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: i64) -> Result<()> { pub fn set_memswap_limit(&self, limit: i64) -> Result<()> {
let mut file_name = "memory.memsw.limit_in_bytes"; let mut file_name = "memory.memsw.limit_in_bytes";
let mut limit_str = limit.to_string();
if self.v2 { if self.v2 {
file_name = "memory.swap.max"; file_name = "memory.swap.max";
if limit == -1 {
limit_str = "max".to_string();
}
} }
self.open_path(file_name, true).and_then(|mut file| { self.open_path(file_name, true).and_then(|mut file| {
file.write_all(limit.to_string().as_ref()).map_err(|e| { file.write_all(limit_str.as_ref())
Error::with_cause(WriteFailed(file_name.to_string(), limit.to_string()), e) .map_err(|e| Error::with_cause(WriteFailed(file_name.to_string(), limit_str), e))
})
}) })
} }

View File

@@ -76,10 +76,10 @@ impl<'a> From<&'a Subsystem> for &'a NetClsController {
impl NetClsController { impl NetClsController {
/// Constructs a new `NetClsController` with `root` 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(root: PathBuf) -> Self { pub fn new(point: PathBuf, root: PathBuf) -> Self {
Self { Self {
base: root.clone(), base: root,
path: root, path: point,
} }
} }

View File

@@ -79,10 +79,10 @@ impl<'a> From<&'a Subsystem> for &'a NetPrioController {
impl NetPrioController { impl NetPrioController {
/// Constructs a new `NetPrioController` with `root` 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(root: PathBuf) -> Self { pub fn new(point: PathBuf, root: PathBuf) -> Self {
Self { Self {
base: root.clone(), base: root,
path: root, path: point,
} }
} }

View File

@@ -65,10 +65,10 @@ impl<'a> From<&'a Subsystem> for &'a PerfEventController {
impl PerfEventController { impl PerfEventController {
/// Constructs a new `PerfEventController` with `root` 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(root: PathBuf) -> Self { pub fn new(point: PathBuf, root: PathBuf) -> Self {
Self { Self {
base: root.clone(), base: root,
path: root, path: point,
} }
} }
} }

View File

@@ -92,10 +92,10 @@ impl<'a> From<&'a Subsystem> for &'a PidController {
impl PidController { impl PidController {
/// Constructors a new `PidController` instance, with `root` serving as the controller's root /// Constructors a new `PidController` instance, with `root` serving as the controller's root
/// directory. /// directory.
pub fn new(root: PathBuf, v2: bool) -> Self { pub fn new(point: PathBuf, root: PathBuf, v2: bool) -> Self {
Self { Self {
base: root.clone(), base: root,
path: root, path: point,
v2, v2,
} }
} }

View File

@@ -68,10 +68,10 @@ impl<'a> From<&'a Subsystem> for &'a RdmaController {
impl RdmaController { impl RdmaController {
/// Constructs a new `RdmaController` with `root` 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(root: PathBuf) -> Self { pub fn new(point: PathBuf, root: PathBuf) -> Self {
Self { Self {
base: root.clone(), base: root,
path: root, path: point,
} }
} }
@@ -81,6 +81,11 @@ impl RdmaController {
.and_then(read_string_from) .and_then(read_string_from)
} }
/// Returns the max usage of RDMA/IB specific resources.
pub fn max(&self) -> Result<String> {
self.open_path("rdma.max", false).and_then(read_string_from)
}
/// Set a maximum usage for each RDMA/IB resource. /// Set a maximum usage for each RDMA/IB resource.
pub fn set_max(&self, max: &str) -> Result<()> { pub fn set_max(&self, max: &str) -> Result<()> {
self.open_path("rdma.max", true).and_then(|mut file| { self.open_path("rdma.max", true).and_then(|mut file| {

View File

@@ -62,10 +62,10 @@ impl<'a> From<&'a Subsystem> for &'a SystemdController {
impl SystemdController { impl SystemdController {
/// Constructs a new `SystemdController` with `root` serving as the root of the control group. /// Constructs a new `SystemdController` with `root` serving as the root of the control group.
pub fn new(root: PathBuf, v2: bool) -> Self { pub fn new(point: PathBuf, root: PathBuf, v2: bool) -> Self {
Self { Self {
base: root.clone(), base: root,
path: root, path: point,
_v2: v2, _v2: v2,
} }
} }

View File

@@ -5,6 +5,10 @@
// //
//! Simple unit tests about the control groups system. //! Simple unit tests about the control groups system.
use cgroups_rs::cgroup::{
CGROUP_MODE_DOMAIN, CGROUP_MODE_DOMAIN_INVALID, CGROUP_MODE_DOMAIN_THREADED,
CGROUP_MODE_THREADED,
};
use cgroups_rs::memory::MemController; use cgroups_rs::memory::MemController;
use cgroups_rs::Controller; use cgroups_rs::Controller;
use cgroups_rs::{Cgroup, CgroupPid, Subsystem}; use cgroups_rs::{Cgroup, CgroupPid, Subsystem};
@@ -36,6 +40,127 @@ fn test_procs_iterator_cgroup() {
cg.delete().unwrap(); cg.delete().unwrap();
} }
#[test]
fn test_tasks_iterator_cgroup_v1() {
if cgroups_rs::hierarchies::is_cgroup2_unified_mode() {
return;
}
let h = cgroups_rs::hierarchies::auto();
let pid = libc::pid_t::from(nix::unistd::getpid()) as u64;
let cg = Cgroup::new(h, String::from("test_tasks_iterator_cgroup_v1")).unwrap();
{
// Add a task to the control group.
cg.add_task(CgroupPid::from(pid)).unwrap();
let mut tasks = cg.tasks().into_iter();
// Verify that the task is indeed in the xcontrol group
assert_eq!(tasks.next(), Some(CgroupPid::from(pid)));
assert_eq!(tasks.next(), None);
// Now, try removing it.
cg.remove_task(CgroupPid::from(pid)).unwrap();
tasks = cg.tasks().into_iter();
// Verify that it was indeed removed.
assert_eq!(tasks.next(), None);
}
cg.delete().unwrap();
}
#[test]
fn test_tasks_iterator_cgroup_threaded_mode() {
if !cgroups_rs::hierarchies::is_cgroup2_unified_mode() {
return;
}
let pid = libc::pid_t::from(nix::unistd::getpid()) as u64;
let cg = Cgroup::new(
cgroups_rs::hierarchies::auto(),
String::from("test_tasks_iterator_cgroup_threaded_mode"),
)
.unwrap();
let cg_threaded_sub1 = Cgroup::new_with_specified_controllers(
cgroups_rs::hierarchies::auto(),
String::from("test_tasks_iterator_cgroup_threaded_mode/threaded_sub1"),
Some(vec![String::from("cpuset"), String::from("cpu")]),
)
.unwrap();
let cg_threaded_sub2 = Cgroup::new_with_specified_controllers(
cgroups_rs::hierarchies::auto(),
String::from("test_tasks_iterator_cgroup_threaded_mode/threaded_sub2"),
Some(vec![String::from("cpuset"), String::from("cpu")]),
)
.unwrap();
{
// Verify that cgroup type of the control group is domain mode.
assert_eq!(cg.get_cgroup_type().unwrap(), CGROUP_MODE_DOMAIN);
// Set cgroup type of the sub-control group is thread mode.
cg_threaded_sub1
.set_cgroup_type(CGROUP_MODE_THREADED)
.unwrap();
// Verify that cgroup type of the sub-control group is thread mode.
assert_eq!(
cg_threaded_sub1.get_cgroup_type().unwrap(),
CGROUP_MODE_THREADED
);
// Verify that the cgroup type of the sub-control group that does
// not set the cgroup type is domain invalid mode.
assert_eq!(
cg_threaded_sub2.get_cgroup_type().unwrap(),
CGROUP_MODE_DOMAIN_INVALID
);
// Verify whether the cgroup type of the parent control group of
// the control group whose cgroup type is set to thread mode is
// domain thread mode.
assert_eq!(cg.get_cgroup_type().unwrap(), CGROUP_MODE_DOMAIN_THREADED);
// Set cgroup type of the sub-control group is thread mode.
cg_threaded_sub2
.set_cgroup_type(CGROUP_MODE_THREADED)
.unwrap();
// Verify that cgroup type of the sub-control group is thread mode.
assert_eq!(
cg_threaded_sub2.get_cgroup_type().unwrap(),
CGROUP_MODE_THREADED
);
// Add a proc to the control group.
cg.add_task_by_tgid(CgroupPid::from(pid)).unwrap();
let mut procs = cg.procs().into_iter();
// Verify that the task is indeed in the x control group
assert_eq!(procs.next(), Some(CgroupPid::from(pid)));
assert_eq!(procs.next(), None);
// Add a task to the sub control group.
cg_threaded_sub1.add_task(CgroupPid::from(pid)).unwrap();
let mut tasks = cg_threaded_sub1.tasks().into_iter();
// Verify that the task is indeed in the xcontrol group
assert_eq!(tasks.next(), Some(CgroupPid::from(pid)));
assert_eq!(tasks.next(), None);
// Now, try move it to parent.
cg_threaded_sub1
.move_task_to_parent(CgroupPid::from(pid))
.unwrap();
tasks = cg_threaded_sub1.tasks().into_iter();
// Verify that it was indeed removed.
assert_eq!(tasks.next(), None);
// Now, try removing it.
cg.remove_task_by_tgid(CgroupPid::from(pid)).unwrap();
procs = cg.procs().into_iter();
// Verify that it was indeed removed.
assert_eq!(procs.next(), None);
}
cg_threaded_sub1.delete().unwrap();
cg_threaded_sub2.delete().unwrap();
cg.delete().unwrap();
}
#[test] #[test]
fn test_kill_cgroup() { fn test_kill_cgroup() {
if !cgroups_rs::hierarchies::is_cgroup2_unified_mode() { if !cgroups_rs::hierarchies::is_cgroup2_unified_mode() {
@@ -74,7 +199,7 @@ fn test_kill_cgroup() {
} }
} }
}; };
assert!(!status.is_none()); assert!(status.is_some());
} }
cg.delete().unwrap(); cg.delete().unwrap();
} }
@@ -146,57 +271,3 @@ fn test_cgroup_v2() {
cg.delete().unwrap(); cg.delete().unwrap();
} }
#[test]
fn test_tasks_iterator_cgroup_threaded_mode() {
if !cgroups_rs::hierarchies::is_cgroup2_unified_mode() {
return;
}
let h = cgroups_rs::hierarchies::auto();
let pid = libc::pid_t::from(nix::unistd::getpid()) as u64;
let cg = Cgroup::new(h, String::from("test_tasks_iterator_cgroup_threaded_mode")).unwrap();
let h = cgroups_rs::hierarchies::auto();
let specified_controllers = vec![String::from("cpuset"), String::from("cpu")];
let cg_threaded = Cgroup::new_with_specified_controllers(
h,
String::from("test_tasks_iterator_cgroup_threaded_mode/threaded"),
Some(specified_controllers),
)
.unwrap();
cg_threaded.set_cgroup_type("threaded").unwrap();
{
// Add a task to the control group.
cg.add_task_by_tgid(CgroupPid::from(pid)).unwrap();
let mut procs = cg.procs().into_iter();
// Verify that the task is indeed in the xcontrol group
assert_eq!(procs.next(), Some(CgroupPid::from(pid)));
assert_eq!(procs.next(), None);
// Add a task to the sub control group.
cg_threaded.add_task(CgroupPid::from(pid)).unwrap();
let mut tasks = cg_threaded.tasks().into_iter();
// Verify that the task is indeed in the xcontrol group
assert_eq!(tasks.next(), Some(CgroupPid::from(pid)));
assert_eq!(tasks.next(), None);
// Now, try move it to parent.
cg_threaded
.move_task_to_parent(CgroupPid::from(pid))
.unwrap();
tasks = cg_threaded.tasks().into_iter();
// Verify that it was indeed removed.
assert_eq!(tasks.next(), None);
// Now, try removing it.
cg.remove_task_by_tgid(CgroupPid::from(pid)).unwrap();
procs = cg.procs().into_iter();
// Verify that it was indeed removed.
assert_eq!(procs.next(), None);
}
cg_threaded.delete().unwrap();
cg.delete().unwrap();
}