Compare commits

...

33 Commits

Author SHA1 Message Date
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
Fupan Li
a45ecf0884 Merge pull request #102 from Tim-Zhang/0.3.0
release: v0.3.0
2023-01-11 20:37:19 +08:00
Tim Zhang
2f60f213cc release: v0.3.0
Bump the major version for API changed in #91.

Signed-off-by: Tim Zhang <tim@hyper.sh>
2023-01-11 20:08:37 +08:00
Tim Zhang
91146f0ea3 Merge pull request #101 from Tim-Zhang/release-0.2.12
release: v0.2.12
2023-01-11 11:30:48 +08:00
Tim Zhang
e845665b3a release: v0.2.12
To include patches #91, #94, #98, #99

Signed-off-by: Tim Zhang <tim@hyper.sh>
2023-01-11 11:22:27 +08:00
Tim Zhang
55034f5b05 Merge pull request #97 from dcantah/cg2-kill
cgroup2: Add Kill method
2023-01-11 11:18:48 +08:00
Bin Liu
e2c2618707 Merge pull request #100 from dcantah/fix-clippy
Lint: Fix clippy errors
2023-01-10 14:36:47 +08:00
Danny Canter
07878325c3 cgroup2: Add Kill method
Fixes: #98

Add a Kill method for cgroups v2. This uses the new functionality in
kernels 5.14+ where writing to a cgroup.kill file will send a SIGKILL
to every process running in the cgroup. This would be useful for
kata to avoid freezing+manually sending SIGKILL+thawing process it
does currently to emulate runc's behavior.

Signed-off-by: Danny Canter <danny@dcantah.dev>
2023-01-09 21:56:47 -08:00
Danny Canter
88fb33113d Lint: Fix clippy errors
Fixes: #99

Signed-off-by: Danny Canter <danny@dcantah.dev>
2023-01-08 02:31:56 -08:00
Bin Liu
1211754b62 Merge pull request #91 from yaoyinnan/90/ftr/threaded-mode
cgroup: support to set threaded mode in cgroup v2
2023-01-05 11:57:07 +08:00
yaoyinnan
c9d02afe33 cgroup: support to set threaded mode in cgroup v2
Support to set threaded mode in cgroup v2. The premise of switching to threaded mode is that only the cgroup of cpuset, cpu and pids is supported.

Fixes: #90

Signed-off-by: yaoyinnan <yaoyinnan@foxmail.com>
2022-12-30 12:46:50 +08:00
Tim Zhang
0348f0a95e Merge pull request #94 from liubin/fix/93-include-path-in-error-message
error: introduce thiserror to add path to error message
2022-11-28 10:42:47 +08:00
bin liu
d387c6edc7 error: introduce thiserror to add path to error message
For some cgroup file operations, when failed, add the path
and the value (for write operation) to the error message.

Fixes: #93

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

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

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

Signed-off-by: quanwei.zqw <quanwei.zqw@alibaba-inc.com>
2022-09-23 10:26:03 +08:00
28 changed files with 1132 additions and 345 deletions

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.2.10" version = "0.3.2"
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"
@@ -14,9 +14,10 @@ readme = "README.md"
[dependencies] [dependencies]
log = "0.4" log = "0.4"
regex = "1.1" regex = "1.1"
nix = { version = "0.24", default-features = false, features = ["event", "fs", "process"] } nix = { version = "0.25.0", default-features = false, features = ["event", "fs", "process"] }
libc = "0.2" libc = "0.2"
serde = { version = "1.0", features = ["derive"], optional = true } serde = { version = "1.0", features = ["derive"], optional = true }
thiserror = "1"
[dev-dependencies] [dev-dependencies]
libc = "0.2.76" libc = "0.2.76"

View File

@@ -16,7 +16,8 @@ use crate::error::*;
use crate::{read_string_from, read_u64_from}; use crate::{read_string_from, read_u64_from};
use crate::{ use crate::{
BlkIoResources, ControllIdentifier, ControllerInternal, Controllers, Resources, Subsystem, BlkIoResources, ControllIdentifier, ControllerInternal, Controllers, CustomizedAttribute,
Resources, Subsystem,
}; };
/// A controller that allows controlling the `blkio` subsystem of a Cgroup. /// A controller that allows controlling the `blkio` subsystem of a Cgroup.
@@ -42,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 {
@@ -58,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,
} }
@@ -86,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 {
@@ -378,6 +399,10 @@ impl ControllerInternal for BlkIoController {
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);
} }
res.attrs.iter().for_each(|(k, v)| {
let _ = self.set(k, v);
});
Ok(()) Ok(())
} }
} }
@@ -660,8 +685,12 @@ impl BlkIoController {
pub fn set_leaf_weight(&self, w: u64) -> Result<()> { pub fn set_leaf_weight(&self, w: u64) -> Result<()> {
self.open_path("blkio.leaf_weight", true) self.open_path("blkio.leaf_weight", true)
.and_then(|mut file| { .and_then(|mut file| {
file.write_all(w.to_string().as_ref()) file.write_all(w.to_string().as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(
WriteFailed("blkio.leaf_weight".to_string(), w.to_string()),
e,
)
})
}) })
} }
@@ -670,7 +699,15 @@ impl BlkIoController {
self.open_path("blkio.leaf_weight_device", true) self.open_path("blkio.leaf_weight_device", true)
.and_then(|mut file| { .and_then(|mut file| {
file.write_all(format!("{}:{} {}", major, minor, weight).as_ref()) file.write_all(format!("{}:{} {}", major, minor, weight).as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e)) .map_err(|e| {
Error::with_cause(
WriteFailed(
"blkio.leaf_weight_device".to_string(),
format!("{}:{} {}", major, minor, weight),
),
e,
)
})
}) })
} }
@@ -678,99 +715,117 @@ impl BlkIoController {
pub fn reset_stats(&self) -> Result<()> { pub fn reset_stats(&self) -> Result<()> {
self.open_path("blkio.reset_stats", true) self.open_path("blkio.reset_stats", true)
.and_then(|mut file| { .and_then(|mut file| {
file.write_all("1".to_string().as_ref()) file.write_all("1".to_string().as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(
WriteFailed("blkio.reset_stats".to_string(), "1".to_string()),
e,
)
})
}) })
} }
/// Throttle the bytes per second rate of read operation affecting the block device /// Throttle the bytes per second rate of read operation affecting the block device
/// `major:minor` to `bps`. /// `major:minor` to `bps`.
pub fn throttle_read_bps_for_device(&self, major: u64, minor: u64, bps: u64) -> Result<()> { pub fn throttle_read_bps_for_device(&self, major: u64, minor: u64, bps: u64) -> Result<()> {
let mut file = "blkio.throttle.read_bps_device"; let mut file_name = "blkio.throttle.read_bps_device";
let mut content = format!("{}:{} {}", major, minor, bps); let mut content = format!("{}:{} {}", major, minor, bps);
if self.v2 { if self.v2 {
file = "io.max"; file_name = "io.max";
content = format!("{}:{} rbps={}", major, minor, bps); content = format!("{}:{} rbps={}", major, minor, bps);
} }
self.open_path(file, true).and_then(|mut file| { self.open_path(file_name, true).and_then(|mut file| {
file.write_all(content.as_ref()) file.write_all(content.as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(WriteFailed(file_name.to_string(), content.to_string()), e)
})
}) })
} }
/// Throttle the I/O operations per second rate of read operation affecting the block device /// Throttle the I/O operations per second rate of read operation affecting the block device
/// `major:minor` to `bps`. /// `major:minor` to `bps`.
pub fn throttle_read_iops_for_device(&self, major: u64, minor: u64, iops: u64) -> Result<()> { pub fn throttle_read_iops_for_device(&self, major: u64, minor: u64, iops: u64) -> Result<()> {
let mut file = "blkio.throttle.read_iops_device"; let mut file_name = "blkio.throttle.read_iops_device";
let mut content = format!("{}:{} {}", major, minor, iops); let mut content = format!("{}:{} {}", major, minor, iops);
if self.v2 { if self.v2 {
file = "io.max"; file_name = "io.max";
content = format!("{}:{} riops={}", major, minor, iops); content = format!("{}:{} riops={}", major, minor, iops);
} }
self.open_path(file, true).and_then(|mut file| { self.open_path(file_name, true).and_then(|mut file| {
file.write_all(content.as_ref()) file.write_all(content.as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(WriteFailed(file_name.to_string(), content.to_string()), e)
})
}) })
} }
/// Throttle the bytes per second rate of write operation affecting the block device /// Throttle the bytes per second rate of write operation affecting the block device
/// `major:minor` to `bps`. /// `major:minor` to `bps`.
pub fn throttle_write_bps_for_device(&self, major: u64, minor: u64, bps: u64) -> Result<()> { pub fn throttle_write_bps_for_device(&self, major: u64, minor: u64, bps: u64) -> Result<()> {
let mut file = "blkio.throttle.write_bps_device"; let mut file_name = "blkio.throttle.write_bps_device";
let mut content = format!("{}:{} {}", major, minor, bps); let mut content = format!("{}:{} {}", major, minor, bps);
if self.v2 { if self.v2 {
file = "io.max"; file_name = "io.max";
content = format!("{}:{} wbps={}", major, minor, bps); content = format!("{}:{} wbps={}", major, minor, bps);
} }
self.open_path(file, true).and_then(|mut file| { self.open_path(file_name, true).and_then(|mut file| {
file.write_all(content.as_ref()) file.write_all(content.as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(WriteFailed(file_name.to_string(), content.to_string()), e)
})
}) })
} }
/// Throttle the I/O operations per second rate of write operation affecting the block device /// Throttle the I/O operations per second rate of write operation affecting the block device
/// `major:minor` to `bps`. /// `major:minor` to `bps`.
pub fn throttle_write_iops_for_device(&self, major: u64, minor: u64, iops: u64) -> Result<()> { pub fn throttle_write_iops_for_device(&self, major: u64, minor: u64, iops: u64) -> Result<()> {
let mut file = "blkio.throttle.write_iops_device"; let mut file_name = "blkio.throttle.write_iops_device";
let mut content = format!("{}:{} {}", major, minor, iops); let mut content = format!("{}:{} {}", major, minor, iops);
if self.v2 { if self.v2 {
file = "io.max"; file_name = "io.max";
content = format!("{}:{} wiops={}", major, minor, iops); content = format!("{}:{} wiops={}", major, minor, iops);
} }
self.open_path(file, true).and_then(|mut file| { self.open_path(file_name, true).and_then(|mut file| {
file.write_all(content.as_ref()) file.write_all(content.as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(WriteFailed(file_name.to_string(), content.to_string()), e)
})
}) })
} }
/// Set the weight of the control group's tasks. /// Set the weight of the control group's tasks.
pub fn set_weight(&self, w: u64) -> Result<()> { pub fn set_weight(&self, w: u64) -> Result<()> {
// Attation: may not find in high kernel version. // Attation: may not find in high kernel version.
let mut file = "blkio.weight"; let mut file_name = "blkio.weight";
if self.v2 { if self.v2 {
file = "io.bfq.weight"; file_name = "io.bfq.weight";
} }
self.open_path(file, true).and_then(|mut file| { self.open_path(file_name, true).and_then(|mut file| {
file.write_all(w.to_string().as_ref()) file.write_all(w.to_string().as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(WriteFailed(file_name.to_string(), w.to_string()), e)
})
}) })
} }
/// Same as `set_weight()`, but settable per each block device. /// Same as `set_weight()`, but settable per each block device.
pub fn set_weight_for_device(&self, major: u64, minor: u64, weight: u64) -> Result<()> { pub fn set_weight_for_device(&self, major: u64, minor: u64, weight: u64) -> Result<()> {
let mut file = "blkio.weight_device"; let mut file_name = "blkio.weight_device";
if self.v2 { if self.v2 {
// Attation: there is no weight for device in runc // Attation: there is no weight for device in runc
// https://github.com/opencontainers/runc/blob/46be7b612e2533c494e6a251111de46d8e286ed5/libcontainer/cgroups/fs2/io.go#L30 // https://github.com/opencontainers/runc/blob/46be7b612e2533c494e6a251111de46d8e286ed5/libcontainer/cgroups/fs2/io.go#L30
// may depends on IO schedulers https://wiki.ubuntu.com/Kernel/Reference/IOSchedulers // may depends on IO schedulers https://wiki.ubuntu.com/Kernel/Reference/IOSchedulers
file = "io.bfq.weight"; file_name = "io.bfq.weight";
} }
self.open_path(file, true).and_then(|mut file| { self.open_path(file_name, true).and_then(|mut file| {
file.write_all(format!("{}:{} {}", major, minor, weight).as_ref()) file.write_all(format!("{}:{} {}", major, minor, weight).as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e)) .map_err(|e| {
Error::with_cause(
WriteFailed(
file_name.to_string(),
format!("{}:{} {}", major, minor, weight),
),
e,
)
})
}) })
} }
} }
impl CustomizedAttribute for BlkIoController {}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use crate::blkio::{parse_blkio_data, BlkIoData}; use crate::blkio::{parse_blkio_data, BlkIoData};
@@ -782,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
@@ -798,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
"; ";
@@ -849,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 {
@@ -858,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 {
@@ -867,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 {
@@ -876,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

@@ -16,6 +16,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.
/// ///
/// ///
@@ -36,14 +41,18 @@ pub struct Cgroup {
/// The hierarchy. /// The hierarchy.
hier: Box<dyn Hierarchy>, hier: Box<dyn Hierarchy>,
path: String, path: String,
/// List of controllers specifically enabled in the control group.
specified_controllers: Option<Vec<String>>,
} }
impl Clone for Cgroup { impl Clone for Cgroup {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Cgroup { Cgroup {
subsystems: self.subsystems.clone(), subsystems: self.subsystems.clone(),
path: self.path.clone(),
hier: crate::hierarchies::auto(), hier: crate::hierarchies::auto(),
path: self.path.clone(),
specified_controllers: None,
} }
} }
} }
@@ -54,48 +63,67 @@ impl Default for Cgroup {
subsystems: Vec::new(), subsystems: Vec::new(),
hier: crate::hierarchies::auto(), hier: crate::hierarchies::auto(),
path: "".to_string(), path: "".to_string(),
specified_controllers: None,
} }
} }
} }
impl Cgroup { impl Cgroup {
pub fn v2(&self) -> bool {
self.hier.v2()
}
/// Create this control group. /// Create this control group.
fn create(&self) { fn create(&self) -> Result<()> {
if self.hier.v2() { if self.hier.v2() {
let _ret = create_v2_cgroup(self.hier.root(), &self.path); create_v2_cgroup(self.hier.root(), &self.path, &self.specified_controllers)
} else { } else {
for subsystem in &self.subsystems { for subsystem in &self.subsystems {
subsystem.to_controller().create(); subsystem.to_controller().create();
} }
Ok(())
} }
} }
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 { pub fn new<P: AsRef<Path>>(hier: Box<dyn Hierarchy>, path: P) -> Result<Cgroup> {
let cg = Cgroup::load(hier, path); let cg = Cgroup::load(hier, path);
cg.create(); cg.create()?;
cg Ok(cg)
}
/// 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.
pub fn new_with_specified_controllers<P: AsRef<Path>>(
hier: Box<dyn Hierarchy>,
path: P,
specified_controllers: Option<Vec<String>>,
) -> Result<Cgroup> {
let cg = if let Some(sc) = specified_controllers {
Cgroup::load_with_specified_controllers(hier, path, sc)
} else {
Cgroup::load(hier, path)
};
cg.create()?;
Ok(cg)
} }
/// Create a new control group in the hierarchy `hier`, with name `path` and `relative_paths` /// 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. /// 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 /// 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>>( pub fn new_with_relative_paths<P: AsRef<Path>>(
hier: Box<dyn Hierarchy>, hier: Box<dyn Hierarchy>,
path: P, path: P,
relative_paths: HashMap<String, String>, relative_paths: HashMap<String, String>,
) -> Cgroup { ) -> Result<Cgroup> {
let cg = Cgroup::load_with_relative_paths(hier, path, relative_paths); let cg = Cgroup::load_with_relative_paths(hier, path, relative_paths);
cg.create(); cg.create()?;
cg Ok(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`.
@@ -116,6 +144,34 @@ impl Cgroup {
path: path.to_str().unwrap().to_string(), path: path.to_str().unwrap().to_string(),
subsystems, subsystems,
hier, hier,
specified_controllers: None,
}
}
/// Create a handle for a specified control group in the hierarchy `hier`, with name `path`.
///
/// Returns a handle to the control group (that possibly does not exist until `create()` has
/// been called on the cgroup.
pub fn load_with_specified_controllers<P: AsRef<Path>>(
hier: Box<dyn Hierarchy>,
path: P,
specified_controllers: Vec<String>,
) -> Cgroup {
let path = path.as_ref();
let mut subsystems = hier.subsystems();
if path.as_os_str() != "" {
subsystems = subsystems
.into_iter()
.filter(|x| specified_controllers.contains(&x.controller_name()))
.map(|x| x.enter(path))
.collect::<Vec<_>>();
}
Cgroup {
path: path.to_str().unwrap().to_string(),
subsystems,
hier,
specified_controllers: Some(specified_controllers),
} }
} }
@@ -159,6 +215,7 @@ impl Cgroup {
subsystems, subsystems,
hier, hier,
path: path.to_str().unwrap().to_string(), path: path.to_str().unwrap().to_string(),
specified_controllers: None,
} }
} }
@@ -233,36 +290,137 @@ impl Cgroup {
None None
} }
/// Removes tasks from the control group by thread group id.
///
/// Note that this means that the task will be moved back to the root control group in the
/// hierarchy and any rules applied to that control group will _still_ apply to the proc.
pub fn remove_task_by_tgid(&self, tgid: CgroupPid) -> Result<()> {
self.hier.root_control_group().add_task_by_tgid(tgid)
}
/// Removes a task from the control group. /// Removes a task from the control group.
/// ///
/// Note that this means that the task will be moved back to the root control group in the /// Note that this means that the task will be moved back to the root control group in the
/// hierarchy and any rules applied to that control group will _still_ apply to the task. /// hierarchy and any rules applied to that control group will _still_ apply to the task.
pub fn remove_task(&self, pid: CgroupPid) { pub fn remove_task(&self, tid: CgroupPid) -> Result<()> {
let _ = self.hier.root_control_group().add_task(pid); self.hier.root_control_group().add_task(tid)
}
/// Moves tasks to the parent control group by thread group id.
pub fn move_task_to_parent_by_tgid(&self, tgid: CgroupPid) -> Result<()> {
self.hier
.parent_control_group(&self.path)
.add_task_by_tgid(tgid)
}
/// Moves a task to the parent control group.
pub fn move_task_to_parent(&self, tid: CgroupPid) -> Result<()> {
self.hier.parent_control_group(&self.path).add_task(tid)
}
/// Return a handle to the parent control group in the hierarchy.
pub fn parent_control_group(&self) -> Cgroup {
self.hier.parent_control_group(&self.path)
}
/// Kill every process in the control group. Only supported for v2 cgroups and on
/// kernels 5.14+. This will fail with InvalidOperation if the 'cgroup.kill' file does
/// not exist.
pub fn kill(&self) -> Result<()> {
if !self.v2() {
return Err(Error::new(CgroupVersion));
}
let val = "1";
let file_name = "cgroup.kill";
let p = self.hier.root().join(self.path.clone()).join(file_name);
// If cgroup.kill doesn't exist they're not on 5.14+ so lets
// surface some error the caller can check against.
if !p.exists() {
return Err(Error::new(InvalidOperation));
}
fs::write(p, val)
.map_err(|e| Error::with_cause(WriteFailed(file_name.to_string(), val.to_string()), e))
} }
/// 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, tid: CgroupPid) -> Result<()> {
if self.v2() { if self.v2() {
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(&pid) 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 {
Ok(()) Err(Error::new(SubsystemsEmpty))
} }
} else { } else {
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(&tid))
} }
} }
/// Attach a task to the control group by thread group id. /// Attach tasks to the control group by thread group id.
pub fn add_task_by_tgid(&self, pid: CgroupPid) -> Result<()> { pub fn add_task_by_tgid(&self, tgid: CgroupPid) -> Result<()> {
self.subsystems() if self.v2() {
.iter() let subsystems = self.subsystems();
.try_for_each(|sub| sub.to_controller().add_task_by_tgid(&pid)) if !subsystems.is_empty() {
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)
} else {
Err(Error::new(SubsystemsEmpty))
}
} else {
self.subsystems()
.iter()
.try_for_each(|sub| sub.to_controller().add_task_by_tgid(&tgid))
}
}
/// set cgroup.type
pub fn set_cgroup_type(&self, cgroup_type: &str) -> Result<()> {
if self.v2() {
let subsystems = self.subsystems();
if !subsystems.is_empty() {
let c = subsystems[0].to_controller();
c.set_cgroup_type(cgroup_type)
} else {
Err(Error::new(SubsystemsEmpty))
}
} else {
Err(Error::new(CgroupVersion))
}
}
/// get cgroup.type
pub fn get_cgroup_type(&self) -> Result<String> {
if self.v2() {
let subsystems = self.subsystems();
if !subsystems.is_empty() {
let c = subsystems[0].to_controller();
let cgroup_type = c.get_cgroup_type()?;
Ok(cgroup_type)
} else {
Err(Error::new(SubsystemsEmpty))
}
} else {
Err(Error::new(CgroupVersion))
}
} }
/// Set notify_on_release to the control group. /// Set notify_on_release to the control group.
@@ -281,6 +439,33 @@ impl Cgroup {
.try_for_each(|sub| sub.to_controller().set_release_agent(path)) .try_for_each(|sub| sub.to_controller().set_release_agent(path))
} }
/// Returns an Iterator that can be used to iterate over the procs that are currently in the
/// control group.
pub fn procs(&self) -> Vec<CgroupPid> {
// Collect the procs from all subsystems
let mut v = if self.v2() {
let subsystems = self.subsystems();
if !subsystems.is_empty() {
let c = subsystems[0].to_controller();
c.procs()
} else {
vec![]
}
} else {
self.subsystems()
.iter()
.map(|x| x.to_controller().procs())
.fold(vec![], |mut acc, mut x| {
acc.append(&mut x);
acc
})
};
v.sort();
v.dedup();
v
}
/// 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> {
@@ -328,9 +513,22 @@ fn supported_controllers() -> Vec<String> {
.collect::<Vec<String>>() .collect::<Vec<String>>()
} }
fn create_v2_cgroup(root: PathBuf, path: &str) -> Result<()> { fn create_v2_cgroup(
root: PathBuf,
path: &str,
specified_controllers: &Option<Vec<String>>,
) -> Result<()> {
// controler list ["memory", "cpu"] // controler list ["memory", "cpu"]
let controllers = supported_controllers(); let controllers = if let Some(s_controllers) = specified_controllers.clone() {
if verify_supported_controllers(s_controllers.as_ref()) {
s_controllers
} else {
return Err(Error::new(ErrorKind::SpecifiedControllers));
}
} else {
supported_controllers()
};
let mut fp = root; let mut fp = root;
// enable for root // enable for root
@@ -358,6 +556,16 @@ fn create_v2_cgroup(root: PathBuf, path: &str) -> Result<()> {
Ok(()) Ok(())
} }
pub fn verify_supported_controllers(controllers: &[String]) -> bool {
let sc = supported_controllers();
for controller in controllers.iter() {
if !sc.contains(controller) {
return false;
}
}
true
}
pub fn get_cgroups_relative_paths() -> Result<HashMap<String, String>> { pub fn get_cgroups_relative_paths() -> Result<HashMap<String, String>> {
let path = "/proc/self/cgroup".to_string(); let path = "/proc/self/cgroup".to_string();
get_cgroups_relative_paths_by_path(path) get_cgroups_relative_paths_by_path(path)
@@ -370,7 +578,8 @@ pub fn get_cgroups_relative_paths_by_pid(pid: u32) -> Result<HashMap<String, Str
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 = fs::read_to_string(path).map_err(|e| Error::with_cause(ReadFailed, e))?; let content =
fs::read_to_string(path.clone()).map_err(|e| Error::with_cause(ReadFailed(path), e))?;
for l in content.lines() { for l in content.lines() {
let fl: Vec<&str> = l.split(':').collect(); let fl: Vec<&str> = l.split(':').collect();
if fl.len() != 3 { if fl.len() != 3 {

View File

@@ -57,11 +57,11 @@
//! .read(6, 1, 10) //! .read(6, 1, 10)
//! .write(11, 1, 100) //! .write(11, 1, 100)
//! .done() //! .done()
//! .build(h); //! .build(h).unwrap();
//! ``` //! ```
use crate::{ use crate::{
BlkIoDeviceResource, BlkIoDeviceThrottleResource, Cgroup, DeviceResource, Hierarchy, BlkIoDeviceResource, BlkIoDeviceThrottleResource, Cgroup, DeviceResource, Error, Hierarchy,
HugePageResource, MaxValue, NetworkPriority, Resources, HugePageResource, MaxValue, NetworkPriority, Resources,
}; };
@@ -80,6 +80,8 @@ pub struct CgroupBuilder {
name: String, name: String,
/// Internal, unsupported field: use the associated builders instead. /// Internal, unsupported field: use the associated builders instead.
resources: Resources, resources: Resources,
/// List of controllers specifically enabled in the control group.
specified_controllers: Option<Vec<String>>,
} }
impl CgroupBuilder { impl CgroupBuilder {
@@ -90,6 +92,7 @@ impl CgroupBuilder {
CgroupBuilder { CgroupBuilder {
name: name.to_owned(), name: name.to_owned(),
resources: Resources::default(), resources: Resources::default(),
specified_controllers: None,
} }
} }
@@ -134,10 +137,22 @@ impl CgroupBuilder {
} }
/// 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, hier: Box<dyn Hierarchy>) -> Cgroup { pub fn build(self, hier: Box<dyn Hierarchy>) -> Result<Cgroup, Error> {
let cg = Cgroup::new(hier, self.name); if let Some(controllers) = self.specified_controllers {
let _ret = cg.apply(&self.resources); let cg = Cgroup::new_with_specified_controllers(hier, self.name, Some(controllers))?;
cg cg.apply(&self.resources)?;
Ok(cg)
} else {
let cg = Cgroup::new(hier, self.name)?;
cg.apply(&self.resources)?;
Ok(cg)
}
}
/// Specifically enable some controllers in the control group.
pub fn set_specified_controllers(mut self, specified_controllers: Vec<String>) -> Self {
self.specified_controllers = Some(specified_controllers);
self
} }
} }

View File

@@ -131,7 +131,7 @@ impl CpuController {
let res = file.read_to_string(&mut s); let res = file.read_to_string(&mut s);
match res { match res {
Ok(_) => Ok(s), Ok(_) => Ok(s),
Err(e) => Err(Error::with_cause(ReadFailed, e)), Err(e) => Err(Error::with_cause(ReadFailed("cpu.stat".to_string()), e)),
} }
}) })
.unwrap_or_default(), .unwrap_or_default(),
@@ -145,14 +145,15 @@ impl CpuController {
/// `shares` to `200` ensures that control group `B` receives twice as much as CPU bandwidth. /// `shares` to `200` ensures that control group `B` receives twice as much as CPU bandwidth.
/// (Assuming both `A` and `B` are of the same parent) /// (Assuming both `A` and `B` are of the same parent)
pub fn set_shares(&self, shares: u64) -> Result<()> { pub fn set_shares(&self, shares: u64) -> Result<()> {
let mut file = "cpu.shares"; let mut file_name = "cpu.shares";
if self.v2 { if self.v2 {
file = "cpu.weight"; file_name = "cpu.weight";
} }
// NOTE: .CpuShares is not used here. Conversion is the caller's responsibility. // NOTE: .CpuShares is not used here. Conversion is the caller's responsibility.
self.open_path(file, true).and_then(|mut file| { self.open_path(file_name, true).and_then(|mut file| {
file.write_all(shares.to_string().as_ref()) file.write_all(shares.to_string().as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(WriteFailed(file_name.to_string(), shares.to_string()), e)
})
}) })
} }
@@ -174,8 +175,12 @@ impl CpuController {
} }
self.open_path("cpu.cfs_period_us", true) self.open_path("cpu.cfs_period_us", true)
.and_then(|mut file| { .and_then(|mut file| {
file.write_all(us.to_string().as_ref()) file.write_all(us.to_string().as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(
WriteFailed("cpu.cfs_period_us".to_string(), us.to_string()),
e,
)
})
}) })
} }
@@ -200,8 +205,12 @@ impl CpuController {
} }
self.open_path("cpu.cfs_quota_us", true) self.open_path("cpu.cfs_quota_us", true)
.and_then(|mut file| { .and_then(|mut file| {
file.write_all(us.to_string().as_ref()) file.write_all(us.to_string().as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(
WriteFailed("cpu.cfs_quota_us".to_string(), us.to_string()),
e,
)
})
}) })
} }
@@ -262,23 +271,31 @@ impl CpuController {
let line = format!("{} {}", new_quota, new_period); let line = format!("{} {}", new_quota, new_period);
self.open_path("cpu.max", true).and_then(|mut file| { self.open_path("cpu.max", true).and_then(|mut file| {
file.write_all(line.as_ref()) file.write_all(line.as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e)) .map_err(|e| Error::with_cause(WriteFailed("cpu.max".to_string(), line), e))
}) })
} }
pub fn set_rt_runtime(&self, us: i64) -> Result<()> { pub fn set_rt_runtime(&self, us: i64) -> Result<()> {
self.open_path("cpu.rt_runtime_us", true) self.open_path("cpu.rt_runtime_us", true)
.and_then(|mut file| { .and_then(|mut file| {
file.write_all(us.to_string().as_ref()) file.write_all(us.to_string().as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(
WriteFailed("cpu.rt_runtime_us".to_string(), us.to_string()),
e,
)
})
}) })
} }
pub fn set_rt_period_us(&self, us: u64) -> Result<()> { pub fn set_rt_period_us(&self, us: u64) -> Result<()> {
self.open_path("cpu.rt_period_us", true) self.open_path("cpu.rt_period_us", true)
.and_then(|mut file| { .and_then(|mut file| {
file.write_all(us.to_string().as_ref()) file.write_all(us.to_string().as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(
WriteFailed("cpu.rt_period_us".to_string(), us.to_string()),
e,
)
})
}) })
} }
} }
@@ -288,7 +305,7 @@ impl CustomizedAttribute for CpuController {}
fn parse_cfs_quota_and_period(mut file: File) -> Result<CfsQuotaAndPeriod> { fn parse_cfs_quota_and_period(mut file: File) -> Result<CfsQuotaAndPeriod> {
let mut content = String::new(); let mut content = String::new();
file.read_to_string(&mut content) file.read_to_string(&mut content)
.map_err(|e| Error::with_cause(ReadFailed, e))?; .map_err(|e| Error::with_cause(ReadFailed("cpu.max".to_string()), e))?;
let fields = content.trim().split(' ').collect::<Vec<&str>>(); let fields = content.trim().split(' ').collect::<Vec<&str>>();
if fields.len() != 2 { if fields.len() != 2 {

View File

@@ -148,8 +148,9 @@ impl CpuAcctController {
/// Reset the statistics the kernel has gathered about the control group. /// Reset the statistics the kernel has gathered about the control group.
pub fn reset(&self) -> Result<()> { pub fn reset(&self) -> Result<()> {
self.open_path("cpuacct.usage", true).and_then(|mut file| { self.open_path("cpuacct.usage", true).and_then(|mut file| {
file.write_all(b"0") file.write_all(b"0").map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(WriteFailed("cpuacct.usage".to_string(), "0".to_string()), e)
})
}) })
} }
} }

View File

@@ -144,7 +144,12 @@ fn find_no_empty_parent(from: &str, file: &str) -> Result<(String, Vec<PathBuf>)
let current_value = let current_value =
match ::std::fs::read_to_string(current_path.clone().join(file).to_str().unwrap()) { match ::std::fs::read_to_string(current_path.clone().join(file).to_str().unwrap()) {
Ok(cpus) => String::from(cpus.trim()), Ok(cpus) => String::from(cpus.trim()),
Err(e) => return Err(Error::with_cause(ReadFailed, e)), Err(e) => {
return Err(Error::with_cause(
ReadFailed(current_path.display().to_string()),
e,
))
}
}; };
if !current_value.is_empty() { if !current_value.is_empty() {
@@ -177,7 +182,12 @@ fn copy_from_parent(current: &str, file: &str) -> Result<()> {
pb.push(file); pb.push(file);
match ::std::fs::write(pb.to_str().unwrap(), value.as_bytes()) { match ::std::fs::write(pb.to_str().unwrap(), value.as_bytes()) {
Ok(_) => (), Ok(_) => (),
Err(e) => return Err(Error::with_cause(WriteFailed, e)), Err(e) => {
return Err(Error::with_cause(
WriteFailed(pb.display().to_string(), pb.display().to_string()),
e,
))
}
} }
} }
@@ -347,11 +357,19 @@ impl CpuSetController {
self.open_path("cpuset.cpu_exclusive", true) self.open_path("cpuset.cpu_exclusive", true)
.and_then(|mut file| { .and_then(|mut file| {
if b { if b {
file.write_all(b"1") file.write_all(b"1").map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(
WriteFailed("cpuset.cpu_exclusive".to_string(), "1".to_string()),
e,
)
})
} else { } else {
file.write_all(b"0") file.write_all(b"0").map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(
WriteFailed("cpuset.cpu_exclusive".to_string(), "0".to_string()),
e,
)
})
} }
}) })
} }
@@ -362,11 +380,19 @@ impl CpuSetController {
self.open_path("cpuset.mem_exclusive", true) self.open_path("cpuset.mem_exclusive", true)
.and_then(|mut file| { .and_then(|mut file| {
if b { if b {
file.write_all(b"1") file.write_all(b"1").map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(
WriteFailed("cpuset.mem_exclusive".to_string(), "1".to_string()),
e,
)
})
} else { } else {
file.write_all(b"0") file.write_all(b"0").map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(
WriteFailed("cpuset.mem_exclusive".to_string(), "0".to_string()),
e,
)
})
} }
}) })
} }
@@ -377,8 +403,9 @@ impl CpuSetController {
/// be represented via dashes. /// be represented via dashes.
pub fn set_cpus(&self, cpus: &str) -> Result<()> { pub fn set_cpus(&self, cpus: &str) -> Result<()> {
self.open_path("cpuset.cpus", true).and_then(|mut file| { self.open_path("cpuset.cpus", true).and_then(|mut file| {
file.write_all(cpus.as_ref()) file.write_all(cpus.as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(WriteFailed("cpuset.cpus".to_string(), cpus.to_string()), e)
})
}) })
} }
@@ -387,8 +414,9 @@ impl CpuSetController {
/// Syntax is the same as with `set_cpus()`. /// Syntax is the same as with `set_cpus()`.
pub fn set_mems(&self, mems: &str) -> Result<()> { pub fn set_mems(&self, mems: &str) -> Result<()> {
self.open_path("cpuset.mems", true).and_then(|mut file| { self.open_path("cpuset.mems", true).and_then(|mut file| {
file.write_all(mems.as_ref()) file.write_all(mems.as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(WriteFailed("cpuset.mems".to_string(), mems.to_string()), e)
})
}) })
} }
@@ -401,11 +429,19 @@ impl CpuSetController {
self.open_path("cpuset.mem_hardwall", true) self.open_path("cpuset.mem_hardwall", true)
.and_then(|mut file| { .and_then(|mut file| {
if b { if b {
file.write_all(b"1") file.write_all(b"1").map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(
WriteFailed("cpuset.mem_hardwall".to_string(), "1".to_string()),
e,
)
})
} else { } else {
file.write_all(b"0") file.write_all(b"0").map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(
WriteFailed("cpuset.mem_hardwall".to_string(), "0".to_string()),
e,
)
})
} }
}) })
} }
@@ -416,11 +452,19 @@ impl CpuSetController {
self.open_path("cpuset.sched_load_balance", true) self.open_path("cpuset.sched_load_balance", true)
.and_then(|mut file| { .and_then(|mut file| {
if b { if b {
file.write_all(b"1") file.write_all(b"1").map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(
WriteFailed("cpuset.sched_load_balance".to_string(), "1".to_string()),
e,
)
})
} else { } else {
file.write_all(b"0") file.write_all(b"0").map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(
WriteFailed("cpuset.sched_load_balance".to_string(), "0".to_string()),
e,
)
})
} }
}) })
} }
@@ -431,8 +475,12 @@ impl CpuSetController {
pub fn set_rebalance_relax_domain_level(&self, i: i64) -> Result<()> { pub fn set_rebalance_relax_domain_level(&self, i: i64) -> Result<()> {
self.open_path("cpuset.sched_relax_domain_level", true) self.open_path("cpuset.sched_relax_domain_level", true)
.and_then(|mut file| { .and_then(|mut file| {
file.write_all(i.to_string().as_ref()) file.write_all(i.to_string().as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(
WriteFailed("cpuset.sched_relax_domain_level".to_string(), i.to_string()),
e,
)
})
}) })
} }
@@ -442,11 +490,19 @@ impl CpuSetController {
self.open_path("cpuset.memory_migrate", true) self.open_path("cpuset.memory_migrate", true)
.and_then(|mut file| { .and_then(|mut file| {
if b { if b {
file.write_all(b"1") file.write_all(b"1").map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(
WriteFailed("cpuset.memory_migrate".to_string(), "1".to_string()),
e,
)
})
} else { } else {
file.write_all(b"0") file.write_all(b"0").map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(
WriteFailed("cpuset.memory_migrate".to_string(), "0".to_string()),
e,
)
})
} }
}) })
} }
@@ -457,11 +513,19 @@ impl CpuSetController {
self.open_path("cpuset.memory_spread_page", true) self.open_path("cpuset.memory_spread_page", true)
.and_then(|mut file| { .and_then(|mut file| {
if b { if b {
file.write_all(b"1") file.write_all(b"1").map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(
WriteFailed("cpuset.memory_spread_page".to_string(), "1".to_string()),
e,
)
})
} else { } else {
file.write_all(b"0") file.write_all(b"0").map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(
WriteFailed("cpuset.memory_spread_page".to_string(), "0".to_string()),
e,
)
})
} }
}) })
} }
@@ -472,11 +536,19 @@ impl CpuSetController {
self.open_path("cpuset.memory_spread_slab", true) self.open_path("cpuset.memory_spread_slab", true)
.and_then(|mut file| { .and_then(|mut file| {
if b { if b {
file.write_all(b"1") file.write_all(b"1").map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(
WriteFailed("cpuset.memory_spread_slab".to_string(), "1".to_string()),
e,
)
})
} else { } else {
file.write_all(b"0") file.write_all(b"0").map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(
WriteFailed("cpuset.memory_spread_slab".to_string(), "0".to_string()),
e,
)
})
} }
}) })
} }
@@ -493,11 +565,25 @@ impl CpuSetController {
self.open_path("cpuset.memory_pressure_enabled", true) self.open_path("cpuset.memory_pressure_enabled", true)
.and_then(|mut file| { .and_then(|mut file| {
if b { if b {
file.write_all(b"1") file.write_all(b"1").map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(
WriteFailed(
"cpuset.memory_pressure_enabled".to_string(),
"1".to_string(),
),
e,
)
})
} else { } else {
file.write_all(b"0") file.write_all(b"0").map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(
WriteFailed(
"cpuset.memory_pressure_enabled".to_string(),
"0".to_string(),
),
e,
)
})
} }
}) })
} }

View File

@@ -237,8 +237,9 @@ impl DevicesController {
}; };
let final_str = format!("{} {}:{} {}", devtype.to_char(), major, minor, perms); let final_str = format!("{} {}:{} {}", devtype.to_char(), major, minor, perms);
self.open_path("devices.allow", true).and_then(|mut file| { self.open_path("devices.allow", true).and_then(|mut file| {
file.write_all(final_str.as_ref()) file.write_all(final_str.as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(WriteFailed("devices.allow".to_string(), final_str), e)
})
}) })
} }
@@ -269,8 +270,9 @@ impl DevicesController {
}; };
let final_str = format!("{} {}:{} {}", devtype.to_char(), major, minor, perms); let final_str = format!("{} {}:{} {}", devtype.to_char(), major, minor, perms);
self.open_path("devices.deny", true).and_then(|mut file| { self.open_path("devices.deny", true).and_then(|mut file| {
file.write_all(final_str.as_ref()) file.write_all(final_str.as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(WriteFailed("devices.deny".to_string(), final_str), e)
})
}) })
} }
@@ -315,7 +317,7 @@ impl DevicesController {
} }
}) })
}, },
Err(e) => Err(Error::with_cause(ReadFailed, e)), Err(e) => Err(Error::with_cause(ReadFailed("devices.list".to_string()), e)),
} }
}) })
} }

View File

@@ -8,40 +8,67 @@ use std::error::Error as StdError;
use std::fmt; use std::fmt;
/// The different types of errors that can occur while manipulating control groups. /// The different types of errors that can occur while manipulating control groups.
#[derive(Debug, Eq, PartialEq)] #[derive(thiserror::Error, Debug, Eq, PartialEq)]
pub enum ErrorKind { pub enum ErrorKind {
#[error("fs error")]
FsError, FsError,
#[error("common error: {0}")]
Common(String), Common(String),
/// An error occured while writing to a control group file. /// An error occured while writing to a control group file.
WriteFailed, #[error("unable to write to a control group file {0}, value {1}")]
WriteFailed(String, String),
/// An error occured while trying to read from a control group file. /// An error occured while trying to read from a control group file.
ReadFailed, #[error("unable to read a control group file {0}")]
ReadFailed(String),
/// An error occured while trying to remove a control group. /// An error occured while trying to remove a control group.
#[error("unable to remove a control group")]
RemoveFailed, RemoveFailed,
/// An error occured while trying to parse a value from a control group file. /// An error occured while trying to parse a value from a control group file.
/// ///
/// In the future, there will be some information attached to this field. /// In the future, there will be some information attached to this field.
#[error("unable to parse control group file")]
ParseError, ParseError,
/// You tried to do something invalid. /// You tried to do something invalid.
/// ///
/// This could be because you tried to set a value in a control group that is not a root /// This could be because you tried to set a value in a control group that is not a root
/// control group. Or, when using unified hierarchy, you tried to add a task in a leaf node. /// control group. Or, when using unified hierarchy, you tried to add a task in a leaf node.
#[error("the requested operation is invalid")]
InvalidOperation, InvalidOperation,
/// The path of the control group was invalid. /// The path of the control group was invalid.
/// ///
/// This could be caused by trying to escape the control group filesystem via a string of "..". /// This could be caused by trying to escape the control group filesystem via a string of "..".
/// This crate checks against this and operations will fail with this error. /// This crate checks against this and operations will fail with this error.
#[error("the given path is invalid")]
InvalidPath, InvalidPath,
#[error("invalid bytes size")]
InvalidBytesSize, InvalidBytesSize,
/// The specified controller is not in the list of supported controllers.
#[error("specified controller is not in the list of supported controllers")]
SpecifiedControllers,
/// Using method in wrong cgroup version.
#[error("using method in wrong cgroup version")]
CgroupVersion,
/// Using method in wrong cgroup mode.
#[error("using method in wrong cgroup mode.")]
CgroupMode,
/// Subsystems is empty.
#[error("subsystems is empty")]
SubsystemsEmpty,
/// An unknown error has occured. /// An unknown error has occured.
#[error("an unknown error")]
Other, Other,
} }
@@ -53,29 +80,16 @@ pub struct Error {
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 {
ErrorKind::FsError => "fs error".to_string(),
ErrorKind::Common(s) => s.clone(),
ErrorKind::WriteFailed => "unable to write to a control group file".to_string(),
ErrorKind::ReadFailed => "unable to read a control group file".to_string(),
ErrorKind::RemoveFailed => "unable to remove a control group".to_string(),
ErrorKind::ParseError => "unable to parse control group file".to_string(),
ErrorKind::InvalidOperation => "the requested operation is invalid".to_string(),
ErrorKind::InvalidPath => "the given path is invalid".to_string(),
ErrorKind::InvalidBytesSize => "invalid bytes size".to_string(),
ErrorKind::Other => "an unknown error".to_string(),
};
if let Some(cause) = &self.cause { if let Some(cause) = &self.cause {
write!(f, "{} caused by: {:?}", msg, cause) write!(f, "{} caused by: {:?}", &self.kind, cause)
} else { } else {
write!(f, "{}", msg) write!(f, "{}", &self.kind)
} }
} }
} }
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

@@ -46,10 +46,11 @@ fn register_memory_event(
arg: &str, arg: &str,
) -> Result<Receiver<String>> { ) -> Result<Receiver<String>> {
let path = cg_dir.join(event_name); let path = cg_dir.join(event_name);
let event_file = File::open(path).map_err(|e| Error::with_cause(ReadFailed, e))?; let event_file = File::open(path.clone())
.map_err(|e| Error::with_cause(ReadFailed(path.display().to_string()), e))?;
let eventfd = let eventfd = eventfd(0, EfdFlags::EFD_CLOEXEC)
eventfd(0, EfdFlags::EFD_CLOEXEC).map_err(|e| Error::with_cause(ReadFailed, e))?; .map_err(|e| Error::with_cause(ReadFailed("eventfd".to_string()), e))?;
let event_control_path = cg_dir.join("cgroup.event_control"); let event_control_path = cg_dir.join("cgroup.event_control");
let data = if arg.is_empty() { let data = if arg.is_empty() {
@@ -59,7 +60,12 @@ fn register_memory_event(
}; };
// write to file and set mode to 0700(FIXME) // write to file and set mode to 0700(FIXME)
fs::write(&event_control_path, data).map_err(|e| Error::with_cause(WriteFailed, e))?; fs::write(&event_control_path, data.clone()).map_err(|e| {
Error::with_cause(
WriteFailed(event_control_path.display().to_string(), data),
e,
)
})?;
let mut eventfd_file = unsafe { File::from_raw_fd(eventfd) }; let mut eventfd_file = unsafe { File::from_raw_fd(eventfd) };

View File

@@ -94,40 +94,40 @@ impl FreezerController {
/// Freezes the processes in the control group. /// Freezes the processes in the control group.
pub fn freeze(&self) -> Result<()> { pub fn freeze(&self) -> Result<()> {
let mut file = "freezer.state"; let mut file_name = "freezer.state";
let mut content = "FROZEN".to_string(); let mut content = "FROZEN".to_string();
if self.v2 { if self.v2 {
file = "cgroup.freeze"; file_name = "cgroup.freeze";
content = "1".to_string(); content = "1".to_string();
} }
self.open_path(file, true).and_then(|mut file| { self.open_path(file_name, true).and_then(|mut file| {
file.write_all(content.as_ref()) file.write_all(content.as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e)) .map_err(|e| Error::with_cause(WriteFailed(file_name.to_string(), content), e))
}) })
} }
/// Thaws, that is, unfreezes the processes in the control group. /// Thaws, that is, unfreezes the processes in the control group.
pub fn thaw(&self) -> Result<()> { pub fn thaw(&self) -> Result<()> {
let mut file = "freezer.state"; let mut file_name = "freezer.state";
let mut content = "THAWED".to_string(); let mut content = "THAWED".to_string();
if self.v2 { if self.v2 {
file = "cgroup.freeze"; file_name = "cgroup.freeze";
content = "0".to_string(); content = "0".to_string();
} }
self.open_path(file, true).and_then(|mut file| { self.open_path(file_name, true).and_then(|mut file| {
file.write_all(content.as_ref()) file.write_all(content.as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e)) .map_err(|e| Error::with_cause(WriteFailed(file_name.to_string(), content), e))
}) })
} }
/// Retrieve the state of processes in the control group. /// Retrieve the state of processes in the control group.
pub fn state(&self) -> Result<FreezerState> { pub fn state(&self) -> Result<FreezerState> {
let mut file = "freezer.state"; let mut file_name = "freezer.state";
if self.v2 { if self.v2 {
file = "cgroup.freeze"; file_name = "cgroup.freeze";
} }
self.open_path(file, false).and_then(|mut file| { self.open_path(file_name, false).and_then(|mut file| {
let mut s = String::new(); let mut s = String::new();
let res = file.read_to_string(&mut s); let res = file.read_to_string(&mut s);
match res { match res {
@@ -139,7 +139,7 @@ impl FreezerController {
"FREEZING" => Ok(FreezerState::Freezing), "FREEZING" => Ok(FreezerState::Freezing),
_ => Err(Error::new(ParseError)), _ => Err(Error::new(ParseError)),
}, },
Err(e) => Err(Error::with_cause(ReadFailed, e)), Err(e) => Err(Error::with_cause(ReadFailed(file_name.to_string()), e)),
} }
}) })
} }

View File

@@ -12,7 +12,7 @@
use std::fs; use std::fs;
use std::fs::File; use std::fs::File;
use std::io::{BufRead, BufReader}; use std::io::{BufRead, BufReader};
use std::path::PathBuf; use std::path::{Path, PathBuf};
use crate::blkio::BlkIoController; use crate::blkio::BlkIoController;
use crate::cpu::CpuController; use crate::cpu::CpuController;
@@ -173,6 +173,12 @@ impl Hierarchy for V1 {
Cgroup::load(auto(), "") Cgroup::load(auto(), "")
} }
fn parent_control_group(&self, path: &str) -> Cgroup {
let path = Path::new(path);
let parent_path = path.parent().unwrap().to_string_lossy().to_string();
Cgroup::load(auto(), parent_path)
}
fn root(&self) -> PathBuf { fn root(&self) -> PathBuf {
self.mountinfo self.mountinfo
.iter() .iter()
@@ -249,6 +255,12 @@ impl Hierarchy for V2 {
Cgroup::load(auto(), "") Cgroup::load(auto(), "")
} }
fn parent_control_group(&self, path: &str) -> Cgroup {
let path = Path::new(path);
let parent_path = path.parent().unwrap().to_string_lossy().to_string();
Cgroup::load(auto(), parent_path)
}
fn root(&self) -> PathBuf { fn root(&self) -> PathBuf {
PathBuf::from(self.root.clone()) PathBuf::from(self.root.clone())
} }

View File

@@ -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
@@ -165,13 +168,14 @@ impl HugeTlbController {
/// Set the limit (in bytes) of how much memory can be backed by hugepages of a certain size /// Set the limit (in bytes) of how much memory can be backed by hugepages of a certain size
/// (`hugetlb_size`). /// (`hugetlb_size`).
pub fn set_limit_in_bytes(&self, hugetlb_size: &str, limit: u64) -> Result<()> { pub fn set_limit_in_bytes(&self, hugetlb_size: &str, limit: u64) -> Result<()> {
let mut file = format!("hugetlb.{}.limit_in_bytes", hugetlb_size); let mut file_name = format!("hugetlb.{}.limit_in_bytes", hugetlb_size);
if self.v2 { if self.v2 {
file = format!("hugetlb.{}.max", hugetlb_size); file_name = format!("hugetlb.{}.max", hugetlb_size);
} }
self.open_path(&file, true).and_then(|mut file| { self.open_path(&file_name, true).and_then(|mut file| {
file.write_all(limit.to_string().as_ref()) file.write_all(limit.to_string().as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(WriteFailed(file_name.to_string(), limit.to_string()), e)
})
}) })
} }
} }

View File

@@ -181,12 +181,21 @@ mod sealed {
if w { if w {
match File::create(&path) { match File::create(&path) {
Err(e) => Err(Error::with_cause(ErrorKind::WriteFailed, e)), Err(e) => Err(Error::with_cause(
ErrorKind::WriteFailed(
path.display().to_string(),
"[CREATE FILE]".to_string(),
),
e,
)),
Ok(file) => Ok(file), Ok(file) => Ok(file),
} }
} else { } else {
match File::open(&path) { match File::open(&path) {
Err(e) => Err(Error::with_cause(ErrorKind::ReadFailed, e)), Err(e) => Err(Error::with_cause(
ErrorKind::ReadFailed(path.display().to_string()),
e,
)),
Ok(file) => Ok(file), Ok(file) => Ok(file),
} }
} }
@@ -198,7 +207,7 @@ mod sealed {
let res = file.read_to_string(&mut string); let res = file.read_to_string(&mut string);
match res { match res {
Ok(_) => parse_max_value(&string), Ok(_) => parse_max_value(&string),
Err(e) => Err(Error::with_cause(ReadFailed, e)), Err(e) => Err(Error::with_cause(ReadFailed(f.to_string()), e)),
} }
}) })
} }
@@ -216,8 +225,9 @@ mod sealed {
pub trait CustomizedAttribute: ControllerInternal { pub trait CustomizedAttribute: ControllerInternal {
fn set(&self, key: &str, value: &str) -> Result<()> { fn set(&self, key: &str, value: &str) -> Result<()> {
self.open_path(key, true).and_then(|mut file| { self.open_path(key, true).and_then(|mut file| {
file.write_all(value.as_ref()) file.write_all(value.as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(WriteFailed(key.to_string(), value.to_string()), e)
})
}) })
} }
@@ -226,7 +236,7 @@ mod sealed {
let mut string = String::new(); let mut string = String::new();
match file.read_to_string(&mut string) { match file.read_to_string(&mut string) {
Ok(_) => Ok(string.trim().to_owned()), Ok(_) => Ok(string.trim().to_owned()),
Err(e) => Err(Error::with_cause(ReadFailed, e)), Err(e) => Err(Error::with_cause(ReadFailed(key.to_string()), e)),
} }
}) })
} }
@@ -270,9 +280,18 @@ pub trait Controller {
/// Attach a task to this controller. /// Attach a task to this controller.
fn add_task_by_tgid(&self, pid: &CgroupPid) -> Result<()>; fn add_task_by_tgid(&self, pid: &CgroupPid) -> Result<()>;
/// set cgroup type.
fn set_cgroup_type(&self, cgroup_type: &str) -> Result<()>;
/// get cgroup type.
fn get_cgroup_type(&self) -> Result<String>;
/// 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>;
/// Get the list of procs that this controller has.
fn procs(&self) -> Vec<CgroupPid>;
fn v2(&self) -> bool; fn v2(&self) -> bool;
} }
@@ -307,18 +326,32 @@ where
/// Set notify_on_release /// Set notify_on_release
fn set_notify_on_release(&self, enable: bool) -> Result<()> { fn set_notify_on_release(&self, enable: bool) -> Result<()> {
if self.is_v2() {
return Err(Error::new(ErrorKind::CgroupVersion));
}
self.open_path("notify_on_release", true) self.open_path("notify_on_release", true)
.and_then(|mut file| { .and_then(|mut file| {
write!(file, "{}", enable as i32) write!(file, "{}", enable as i32).map_err(|e| {
.map_err(|e| Error::with_cause(ErrorKind::WriteFailed, e)) Error::with_cause(
ErrorKind::WriteFailed("notify_on_release".to_string(), enable.to_string()),
e,
)
})
}) })
} }
/// Set release_agent /// Set release_agent
fn set_release_agent(&self, path: &str) -> Result<()> { fn set_release_agent(&self, path: &str) -> Result<()> {
if self.is_v2() {
return Err(Error::new(ErrorKind::CgroupVersion));
}
self.open_path("release_agent", true).and_then(|mut file| { self.open_path("release_agent", true).and_then(|mut file| {
file.write_all(path.as_bytes()) file.write_all(path.as_bytes()).map_err(|e| {
.map_err(|e| Error::with_cause(ErrorKind::WriteFailed, e)) Error::with_cause(
ErrorKind::WriteFailed("release_agent".to_string(), path.to_string()),
e,
)
})
}) })
} }
/// Does this controller already exist? /// Does this controller already exist?
@@ -332,36 +365,58 @@ where
return Ok(()); return Ok(());
} }
remove_dir(self.get_path()) // Compatible with runC for remove dir operation
// https://github.com/opencontainers/runc/blob/main/libcontainer/cgroups/utils.go#L272
//
// We trying to remove all paths five times with increasing delay between tries.
// If after all there are not removed cgroups - appropriate error will be
// returned.
let mut delay = std::time::Duration::from_millis(10);
let cgroup_path = self.get_path();
for _i in 0..4 {
if let Ok(()) = remove_dir(cgroup_path) {
return Ok(());
}
std::thread::sleep(delay);
delay *= 2;
}
remove_dir(cgroup_path)
} }
/// Attach a task to this controller. /// Attach a task to this controller.
fn add_task(&self, pid: &CgroupPid) -> Result<()> { fn add_task(&self, pid: &CgroupPid) -> Result<()> {
let mut file = "tasks"; let mut file_name = "tasks";
if self.is_v2() { if self.is_v2() {
file = "cgroup.procs"; file_name = "cgroup.threads";
} }
self.open_path(file, true).and_then(|mut file| { self.open_path(file_name, true).and_then(|mut file| {
file.write_all(pid.pid.to_string().as_ref()) file.write_all(pid.pid.to_string().as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(ErrorKind::WriteFailed, e)) Error::with_cause(
ErrorKind::WriteFailed(file_name.to_string(), pid.pid.to_string()),
e,
)
})
}) })
} }
/// Attach a task to this controller by thread group id. /// Attach a task to this controller by thread group id.
fn add_task_by_tgid(&self, pid: &CgroupPid) -> Result<()> { fn add_task_by_tgid(&self, pid: &CgroupPid) -> Result<()> {
self.open_path("cgroup.procs", true).and_then(|mut file| { let file_name = "cgroup.procs";
file.write_all(pid.pid.to_string().as_ref()) self.open_path(file_name, true).and_then(|mut file| {
.map_err(|e| Error::with_cause(ErrorKind::WriteFailed, e)) file.write_all(pid.pid.to_string().as_ref()).map_err(|e| {
Error::with_cause(
ErrorKind::WriteFailed(file_name.to_string(), pid.pid.to_string()),
e,
)
})
}) })
} }
/// Get the list of tasks that this controller has. /// Get the list of procs that this controller has.
fn tasks(&self) -> Vec<CgroupPid> { fn procs(&self) -> Vec<CgroupPid> {
let mut file = "tasks"; let file_name = "cgroup.procs";
if self.is_v2() { self.open_path(file_name, false)
file = "cgroup.procs";
}
self.open_path(file, false)
.map(|file| { .map(|file| {
let bf = BufReader::new(file); let bf = BufReader::new(file);
let mut v = Vec::new(); let mut v = Vec::new();
@@ -379,6 +434,64 @@ where
.unwrap_or_default() .unwrap_or_default()
} }
/// Get the list of tasks that this controller has.
fn tasks(&self) -> Vec<CgroupPid> {
let mut file_name = "tasks";
if self.is_v2() {
file_name = "cgroup.threads";
}
self.open_path(file_name, false)
.map(|file| {
let bf = BufReader::new(file);
let mut v = Vec::new();
for line in bf.lines() {
match line {
Ok(line) => {
let n = line.trim().parse().unwrap_or(0u64);
v.push(n);
}
Err(_) => break,
}
}
v.into_iter().map(CgroupPid::from).collect()
})
.unwrap_or_default()
}
/// set cgroup.type
fn set_cgroup_type(&self, cgroup_type: &str) -> Result<()> {
if !self.is_v2() {
return Err(Error::new(ErrorKind::CgroupVersion));
}
let file_name = "cgroup.type";
self.open_path(file_name, true).and_then(|mut file| {
file.write_all(cgroup_type.as_bytes()).map_err(|e| {
Error::with_cause(
ErrorKind::WriteFailed(file_name.to_string(), cgroup_type.to_string()),
e,
)
})
})
}
/// get cgroup.type
fn get_cgroup_type(&self) -> Result<String> {
if !self.is_v2() {
return Err(Error::new(ErrorKind::CgroupVersion));
}
let file_name = "cgroup.type";
self.open_path(file_name, 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(
ErrorKind::ReadFailed(file_name.to_string()),
e,
)),
}
})
}
fn v2(&self) -> bool { fn v2(&self) -> bool {
self.is_v2() self.is_v2()
} }
@@ -393,8 +506,11 @@ fn remove_dir(dir: &Path) -> Result<()> {
} }
if dir.exists() && dir.is_dir() { if dir.exists() && dir.is_dir() {
for entry in fs::read_dir(dir).map_err(|e| Error::with_cause(ReadFailed, e))? { for entry in fs::read_dir(dir)
let entry = entry.map_err(|e| Error::with_cause(ReadFailed, e))?; .map_err(|e| Error::with_cause(ReadFailed(dir.display().to_string()), e))?
{
let entry =
entry.map_err(|e| Error::with_cause(ReadFailed(dir.display().to_string()), e))?;
let path = entry.path(); let path = entry.path();
if path.is_dir() { if path.is_dir() {
remove_dir(&path)?; remove_dir(&path)?;
@@ -423,6 +539,9 @@ pub trait Hierarchy: std::fmt::Debug + Send + Sync {
/// 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;
/// Return a handle to the parent control group in the hierarchy.
fn parent_control_group(&self, path: &str) -> Cgroup;
fn v2(&self) -> bool; fn v2(&self) -> bool;
} }
@@ -485,9 +604,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>,
@@ -613,6 +732,15 @@ pub struct BlkIoResources {
pub throttle_write_bps_device: Vec<BlkIoDeviceThrottleResource>, pub throttle_write_bps_device: Vec<BlkIoDeviceThrottleResource>,
/// Throttled write IO operations per second can be provided for each device. /// Throttled write IO operations per second can be provided for each device.
pub throttle_write_iops_device: Vec<BlkIoDeviceThrottleResource>, pub throttle_write_iops_device: Vec<BlkIoDeviceThrottleResource>,
/// Customized key-value attributes
/// # Usage:
/// ```
/// let resource = &mut cgroups_rs::Resources::default();
/// resource.blkio.attrs.insert("io.cost.weight".to_string(), "10".to_string());
/// // apply here
/// ```
pub attrs: HashMap<String, String>,
} }
/// The resource limits and constraints that will be set on the control group. /// The resource limits and constraints that will be set on the control group.
@@ -792,7 +920,7 @@ pub fn parse_max_value(s: &str) -> Result<MaxValue> {
pub fn flat_keyed_to_vec(mut file: File) -> Result<Vec<(String, i64)>> { pub fn flat_keyed_to_vec(mut file: File) -> Result<Vec<(String, i64)>> {
let mut content = String::new(); let mut content = String::new();
file.read_to_string(&mut content) file.read_to_string(&mut content)
.map_err(|e| Error::with_cause(ReadFailed, e))?; .map_err(|e| Error::with_cause(ReadFailed("FIXME: read_string_from".to_string()), e))?;
let mut v = Vec::new(); let mut v = Vec::new();
for line in content.lines() { for line in content.lines() {
@@ -812,7 +940,7 @@ pub fn flat_keyed_to_vec(mut file: File) -> Result<Vec<(String, i64)>> {
pub fn flat_keyed_to_hashmap(mut file: File) -> Result<HashMap<String, i64>> { pub fn flat_keyed_to_hashmap(mut file: File) -> Result<HashMap<String, i64>> {
let mut content = String::new(); let mut content = String::new();
file.read_to_string(&mut content) file.read_to_string(&mut content)
.map_err(|e| Error::with_cause(ReadFailed, e))?; .map_err(|e| Error::with_cause(ReadFailed("FIXME: read_string_from".to_string()), e))?;
let mut h = HashMap::new(); let mut h = HashMap::new();
for line in content.lines() { for line in content.lines() {
@@ -832,7 +960,7 @@ pub fn flat_keyed_to_hashmap(mut file: File) -> Result<HashMap<String, i64>> {
pub fn nested_keyed_to_hashmap(mut file: File) -> Result<HashMap<String, HashMap<String, i64>>> { pub fn nested_keyed_to_hashmap(mut file: File) -> Result<HashMap<String, HashMap<String, i64>>> {
let mut content = String::new(); let mut content = String::new();
file.read_to_string(&mut content) file.read_to_string(&mut content)
.map_err(|e| Error::with_cause(ReadFailed, e))?; .map_err(|e| Error::with_cause(ReadFailed("FIXME: read_string_from".to_string()), e))?;
let mut h = HashMap::new(); let mut h = HashMap::new();
for line in content.lines() { for line in content.lines() {
@@ -866,7 +994,10 @@ where
.trim() .trim()
.parse::<T>() .parse::<T>()
.map_err(|e| Error::with_cause(ParseError, e)), .map_err(|e| Error::with_cause(ParseError, e)),
Err(e) => Err(Error::with_cause(ReadFailed, e)), Err(e) => Err(Error::with_cause(
ReadFailed("FIXME: can't get path in fn read_from".to_string()),
e,
)),
} }
} }
@@ -874,7 +1005,10 @@ fn read_string_from(mut file: File) -> Result<String> {
let mut string = String::new(); let mut string = String::new();
match file.read_to_string(&mut string) { match file.read_to_string(&mut string) {
Ok(_) => Ok(string.trim().to_string()), Ok(_) => Ok(string.trim().to_string()),
Err(e) => Err(Error::with_cause(ReadFailed, e)), Err(e) => Err(Error::with_cause(
ReadFailed("FIXME: can't get path in fn read_string_from".to_string()),
e,
)),
} }
} }

View File

@@ -562,8 +562,9 @@ impl MemController {
if let Some(v) = v { if let Some(v) = v {
let v = v.to_string(); let v = v.to_string();
self.open_path(f, true).and_then(|mut file| { self.open_path(f, true).and_then(|mut file| {
file.write_all(v.as_ref()) file.write_all(v.as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(WriteFailed(f.to_string(), format!("{:?}", v)), e)
})
})?; })?;
} }
} }
@@ -769,8 +770,12 @@ impl MemController {
/// Reset the fail counter /// Reset the fail counter
pub fn reset_fail_count(&self) -> Result<()> { pub fn reset_fail_count(&self) -> Result<()> {
self.open_path("memory.failcnt", true).and_then(|mut file| { self.open_path("memory.failcnt", true).and_then(|mut file| {
file.write_all("0".to_string().as_ref()) file.write_all("0".to_string().as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(
WriteFailed("memory.failcnt".to_string(), "0".to_string()),
e,
)
})
}) })
} }
@@ -783,8 +788,12 @@ impl MemController {
self.open_path("memory.kmem.failcnt", true) self.open_path("memory.kmem.failcnt", true)
.and_then(|mut file| { .and_then(|mut file| {
file.write_all("0".to_string().as_ref()) file.write_all("0".to_string().as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(
WriteFailed("memory.kmem.failcnt".to_string(), "0".to_string()),
e,
)
})
}) })
} }
@@ -797,8 +806,12 @@ impl MemController {
self.open_path("memory.kmem.tcp.failcnt", true) self.open_path("memory.kmem.tcp.failcnt", true)
.and_then(|mut file| { .and_then(|mut file| {
file.write_all("0".to_string().as_ref()) file.write_all("0".to_string().as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(
WriteFailed("memory.kmem.tcp.failcnt".to_string(), "0".to_string()),
e,
)
})
}) })
} }
@@ -806,8 +819,12 @@ impl MemController {
pub fn reset_memswap_fail_count(&self) -> Result<()> { pub fn reset_memswap_fail_count(&self) -> Result<()> {
self.open_path("memory.memsw.failcnt", true) self.open_path("memory.memsw.failcnt", true)
.and_then(|mut file| { .and_then(|mut file| {
file.write_all("0".to_string().as_ref()) file.write_all("0".to_string().as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(
WriteFailed("memory.memsw.failcnt".to_string(), "0".to_string()),
e,
)
})
}) })
} }
@@ -815,20 +832,25 @@ impl MemController {
pub fn reset_max_usage(&self) -> Result<()> { pub fn reset_max_usage(&self) -> Result<()> {
self.open_path("memory.max_usage_in_bytes", true) self.open_path("memory.max_usage_in_bytes", true)
.and_then(|mut file| { .and_then(|mut file| {
file.write_all("0".to_string().as_ref()) file.write_all("0".to_string().as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(
WriteFailed("memory.max_usage_in_bytes".to_string(), "0".to_string()),
e,
)
})
}) })
} }
/// Set the memory usage limit of the control group, in bytes. /// Set the memory usage limit of the control group, in bytes.
pub fn set_limit(&self, limit: i64) -> Result<()> { pub fn set_limit(&self, limit: i64) -> Result<()> {
let mut file = "memory.limit_in_bytes"; let mut file_name = "memory.limit_in_bytes";
if self.v2 { if self.v2 {
file = "memory.max"; file_name = "memory.max";
} }
self.open_path(file, true).and_then(|mut file| { self.open_path(file_name, true).and_then(|mut file| {
file.write_all(limit.to_string().as_ref()) file.write_all(limit.to_string().as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(WriteFailed(file_name.to_string(), limit.to_string()), e)
})
}) })
} }
@@ -848,20 +870,24 @@ impl MemController {
warn!("memory.kmem.limit_in_bytes is unsupported by the kernel"); warn!("memory.kmem.limit_in_bytes is unsupported by the kernel");
Ok(()) Ok(())
} }
Err(e) => Err(Error::with_cause(WriteFailed, e)), Err(e) => Err(Error::with_cause(
WriteFailed("memory.kmem.limit_in_bytes".to_string(), limit.to_string()),
e,
)),
} }
}) })
} }
/// Set the memory+swap limit of the control group, in bytes. /// Set the memory+swap limit of the control group, in bytes.
pub fn set_memswap_limit(&self, limit: i64) -> Result<()> { pub fn set_memswap_limit(&self, limit: i64) -> Result<()> {
let mut file = "memory.memsw.limit_in_bytes"; let mut file_name = "memory.memsw.limit_in_bytes";
if self.v2 { if self.v2 {
file = "memory.swap.max"; file_name = "memory.swap.max";
} }
self.open_path(file, true).and_then(|mut file| { self.open_path(file_name, true).and_then(|mut file| {
file.write_all(limit.to_string().as_ref()) file.write_all(limit.to_string().as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(WriteFailed(file_name.to_string(), limit.to_string()), e)
})
}) })
} }
@@ -874,8 +900,15 @@ impl MemController {
self.open_path("memory.kmem.tcp.limit_in_bytes", true) self.open_path("memory.kmem.tcp.limit_in_bytes", true)
.and_then(|mut file| { .and_then(|mut file| {
file.write_all(limit.to_string().as_ref()) file.write_all(limit.to_string().as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(
WriteFailed(
"memory.kmem.tcp.limit_in_bytes".to_string(),
limit.to_string(),
),
e,
)
})
}) })
} }
@@ -884,13 +917,14 @@ impl MemController {
/// This limit is enforced when the system is nearing OOM conditions. Contrast this with the /// This limit is enforced when the system is nearing OOM conditions. Contrast this with the
/// hard limit, which is _always_ enforced. /// hard limit, which is _always_ enforced.
pub fn set_soft_limit(&self, limit: i64) -> Result<()> { pub fn set_soft_limit(&self, limit: i64) -> Result<()> {
let mut file = "memory.soft_limit_in_bytes"; let mut file_name = "memory.soft_limit_in_bytes";
if self.v2 { if self.v2 {
file = "memory.low" file_name = "memory.low"
} }
self.open_path(file, true).and_then(|mut file| { self.open_path(file_name, true).and_then(|mut file| {
file.write_all(limit.to_string().as_ref()) file.write_all(limit.to_string().as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(WriteFailed(file_name.to_string(), limit.to_string()), e)
})
}) })
} }
@@ -899,22 +933,27 @@ impl MemController {
/// ///
/// Note that a value of zero does not imply that the process will not be swapped out. /// Note that a value of zero does not imply that the process will not be swapped out.
pub fn set_swappiness(&self, swp: u64) -> Result<()> { pub fn set_swappiness(&self, swp: u64) -> Result<()> {
let mut file = "memory.swappiness"; let mut file_name = "memory.swappiness";
if self.v2 { if self.v2 {
file = "memory.swap.max" file_name = "memory.swap.max"
} }
self.open_path(file, true).and_then(|mut file| { self.open_path(file_name, true).and_then(|mut file| {
file.write_all(swp.to_string().as_ref()) file.write_all(swp.to_string().as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(WriteFailed(file_name.to_string(), swp.to_string()), e)
})
}) })
} }
pub fn disable_oom_killer(&self) -> Result<()> { pub fn disable_oom_killer(&self) -> Result<()> {
self.open_path("memory.oom_control", true) self.open_path("memory.oom_control", true)
.and_then(|mut file| { .and_then(|mut file| {
file.write_all("1".to_string().as_ref()) file.write_all("1".to_string().as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(
WriteFailed("memory.oom_control".to_string(), "1".to_string()),
e,
)
})
}) })
} }

View File

@@ -88,8 +88,9 @@ impl NetClsController {
self.open_path("net_cls.classid", true) self.open_path("net_cls.classid", true)
.and_then(|mut file| { .and_then(|mut file| {
let s = format!("{:#08X}", class); let s = format!("{:#08X}", class);
file.write_all(s.as_ref()) file.write_all(s.as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(WriteFailed("net_cls.classid".to_string(), s), e)
})
}) })
} }

View File

@@ -132,7 +132,15 @@ impl NetPrioController {
self.open_path("net_prio.ifpriomap", true) self.open_path("net_prio.ifpriomap", true)
.and_then(|mut file| { .and_then(|mut file| {
file.write_all(format!("{} {}", eif, prio).as_ref()) file.write_all(format!("{} {}", eif, prio).as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e)) .map_err(|e| {
Error::with_cause(
WriteFailed(
"net_prio.ifpriomap".to_string(),
format!("{} {}", eif, prio),
),
e,
)
})
}) })
} }
} }

View File

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

View File

@@ -84,8 +84,9 @@ impl RdmaController {
/// Set a maximum usage for each RDMA/IB resource. /// Set a maximum usage for each RDMA/IB resource.
pub fn set_max(&self, max: &str) -> Result<()> { pub fn set_max(&self, max: &str) -> Result<()> {
self.open_path("rdma.max", true).and_then(|mut file| { self.open_path("rdma.max", true).and_then(|mut file| {
file.write_all(max.as_ref()) file.write_all(max.as_ref()).map_err(|e| {
.map_err(|e| Error::with_cause(WriteFailed, e)) Error::with_cause(WriteFailed("rdma.max".to_string(), max.to_string()), e)
})
}) })
} }
} }

View File

@@ -22,7 +22,8 @@ pub fn test_cpu_res_build() {
.cpu() .cpu()
.shares(85) .shares(85)
.done() .done()
.build(h); .build(h)
.unwrap();
{ {
let cpu: &CpuController = cg.controller_of().unwrap(); let cpu: &CpuController = cg.controller_of().unwrap();
@@ -42,7 +43,8 @@ pub fn test_memory_res_build() {
.swappiness(70) .swappiness(70)
.memory_hard_limit(1024 * 1024 * 1024) .memory_hard_limit(1024 * 1024 * 1024)
.done() .done()
.build(h); .build(h)
.unwrap();
{ {
let c: &MemController = cg.controller_of().unwrap(); let c: &MemController = cg.controller_of().unwrap();
@@ -64,7 +66,8 @@ pub fn test_pid_res_build() {
.pid() .pid()
.maximum_number_of_processes(MaxValue::Value(123)) .maximum_number_of_processes(MaxValue::Value(123))
.done() .done()
.build(h); .build(h)
.unwrap();
{ {
let c: &PidController = cg.controller_of().unwrap(); let c: &PidController = cg.controller_of().unwrap();
@@ -83,7 +86,8 @@ pub fn test_devices_res_build() {
.devices() .devices()
.device(1, 6, DeviceType::Char, true, vec![DevicePermissions::Read]) .device(1, 6, DeviceType::Char, true, vec![DevicePermissions::Read])
.done() .done()
.build(h); .build(h)
.unwrap();
{ {
let c: &DevicesController = cg.controller_of().unwrap(); let c: &DevicesController = cg.controller_of().unwrap();
@@ -113,7 +117,8 @@ pub fn test_network_res_build() {
.network() .network()
.class_id(1337) .class_id(1337)
.done() .done()
.build(h); .build(h)
.unwrap();
{ {
let c: &NetClsController = cg.controller_of().unwrap(); let c: &NetClsController = cg.controller_of().unwrap();
@@ -134,7 +139,8 @@ pub fn 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(h); .build(h)
.unwrap();
{ {
let c: &HugeTlbController = cg.controller_of().unwrap(); let c: &HugeTlbController = cg.controller_of().unwrap();
@@ -152,7 +158,8 @@ pub fn test_blkio_res_build() {
.blkio() .blkio()
.weight(100) .weight(100)
.done() .done()
.build(h); .build(h)
.unwrap();
{ {
let c: &BlkIoController = cg.controller_of().unwrap(); let c: &BlkIoController = cg.controller_of().unwrap();

View File

@@ -5,29 +5,60 @@
// //
//! 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};
use std::process::Command;
use std::thread::sleep;
use std::time::Duration;
#[test] #[test]
fn test_tasks_iterator() { fn test_procs_iterator_cgroup() {
let h = cgroups_rs::hierarchies::auto(); 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(h, String::from("test_tasks_iterator")); let cg = Cgroup::new(h, String::from("test_procs_iterator_cgroup")).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);
// 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.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. // Add a task to the control group.
cg.add_task(CgroupPid::from(pid)).unwrap(); 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 xcontrol group
assert_eq!(tasks.next(), Some(CgroupPid::from(pid))); assert_eq!(tasks.next(), Some(CgroupPid::from(pid)));
assert_eq!(tasks.next(), None); assert_eq!(tasks.next(), None);
// Now, try removing it. // Now, try removing it.
cg.remove_task(CgroupPid::from(pid)); cg.remove_task(CgroupPid::from(pid)).unwrap();
tasks = cg.tasks().into_iter(); tasks = cg.tasks().into_iter();
// Verify that it was indeed removed. // Verify that it was indeed removed.
@@ -36,6 +67,143 @@ fn test_tasks_iterator() {
cg.delete().unwrap(); 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]
fn test_kill_cgroup() {
if !cgroups_rs::hierarchies::is_cgroup2_unified_mode() {
return;
}
let h = cgroups_rs::hierarchies::auto();
let cg = Cgroup::new(h, String::from("test_kill_cgroup")).unwrap();
{
// Spawn a proc, don't want to getpid(2) here.
let mut child = Command::new("sleep").arg("infinity").spawn().unwrap();
cg.add_task_by_tgid(CgroupPid::from(child.id() as u64))
.unwrap();
let cg_procs = cg.procs();
assert_eq!(cg_procs.len(), 1_usize);
// Now kill and wait on the proc.
cg.kill().unwrap();
let mut tries = 0;
let status: Option<std::process::ExitStatus> = loop {
match child.try_wait() {
Ok(Some(status)) => {
break Some(status);
}
Ok(None) => {
if tries > 3 {
break None;
}
sleep(Duration::from_millis(100));
tries += 1;
}
Err(e) => {
child.kill().unwrap();
panic!("error attempting to wait: {}", e);
}
}
};
assert!(!status.is_none());
}
cg.delete().unwrap();
}
#[test] #[test]
fn test_cgroup_with_relative_paths() { fn test_cgroup_with_relative_paths() {
if cgroups_rs::hierarchies::is_cgroup2_unified_mode() { if cgroups_rs::hierarchies::is_cgroup2_unified_mode() {
@@ -83,7 +251,7 @@ fn test_cgroup_v2() {
return; return;
} }
let h = cgroups_rs::hierarchies::auto(); let h = cgroups_rs::hierarchies::auto();
let cg = Cgroup::new(h, String::from("test_v2")); let cg = Cgroup::new(h, String::from("test_v2")).unwrap();
let mem_controller: &MemController = cg.controller_of().unwrap(); let mem_controller: &MemController = cg.controller_of().unwrap();
let (mem, swp, rev) = (4 * 1024 * 1000, 2 * 1024 * 1000, 1024 * 1000); let (mem, swp, rev) = (4 * 1024 * 1000, 2 * 1024 * 1000, 1024 * 1000);

View File

@@ -10,7 +10,7 @@ use cgroups_rs::Cgroup;
#[test] #[test]
fn test_cfs_quota_and_periods() { fn test_cfs_quota_and_periods() {
let h = cgroups_rs::hierarchies::auto(); let h = cgroups_rs::hierarchies::auto();
let cg = Cgroup::new(h, String::from("test_cfs_quota_and_periods")); let cg = Cgroup::new(h, String::from("test_cfs_quota_and_periods")).unwrap();
let cpu_controller: &CpuController = cg.controller_of().unwrap(); let cpu_controller: &CpuController = cg.controller_of().unwrap();

View File

@@ -13,7 +13,7 @@ use std::fs;
#[test] #[test]
fn test_cpuset_memory_pressure_root_cg() { fn test_cpuset_memory_pressure_root_cg() {
let h = cgroups_rs::hierarchies::auto(); let h = cgroups_rs::hierarchies::auto();
let cg = Cgroup::new(h, String::from("test_cpuset_memory_pressure_root_cg")); let cg = Cgroup::new(h, String::from("test_cpuset_memory_pressure_root_cg")).unwrap();
{ {
let cpuset: &CpuSetController = cg.controller_of().unwrap(); let cpuset: &CpuSetController = cg.controller_of().unwrap();
@@ -27,7 +27,7 @@ fn test_cpuset_memory_pressure_root_cg() {
#[test] #[test]
fn test_cpuset_set_cpus() { fn test_cpuset_set_cpus() {
let h = cgroups_rs::hierarchies::auto(); let h = cgroups_rs::hierarchies::auto();
let cg = Cgroup::new(h, String::from("test_cpuset_set_cpus")); let cg = Cgroup::new(h, String::from("test_cpuset_set_cpus")).unwrap();
{ {
let cpuset: &CpuSetController = cg.controller_of().unwrap(); let cpuset: &CpuSetController = cg.controller_of().unwrap();
@@ -64,7 +64,7 @@ fn test_cpuset_set_cpus() {
#[test] #[test]
fn test_cpuset_set_cpus_add_task() { fn test_cpuset_set_cpus_add_task() {
let h = cgroups_rs::hierarchies::auto(); let h = cgroups_rs::hierarchies::auto();
let cg = Cgroup::new(h, String::from("test_cpuset_set_cpus_add_task/sub-dir")); let cg = Cgroup::new(h, String::from("test_cpuset_set_cpus_add_task/sub-dir")).unwrap();
let cpuset: &CpuSetController = cg.controller_of().unwrap(); let cpuset: &CpuSetController = cg.controller_of().unwrap();
let set = cpuset.cpuset(); let set = cpuset.cpuset();
@@ -77,13 +77,13 @@ fn test_cpuset_set_cpus_add_task() {
// Add a task to the control group. // Add a task to the control group.
let pid_i = libc::pid_t::from(nix::unistd::getpid()) as u64; let pid_i = libc::pid_t::from(nix::unistd::getpid()) as u64;
let _ = cg.add_task(CgroupPid::from(pid_i)); let _ = cg.add_task_by_tgid(CgroupPid::from(pid_i));
let tasks = cg.tasks(); let tasks = cg.tasks();
assert!(!tasks.is_empty()); assert!(!tasks.is_empty());
println!("tasks after added: {:?}", tasks); println!("tasks after added: {:?}", tasks);
// remove task // remove task
let _ = cg.remove_task(CgroupPid::from(pid_i)); cg.remove_task_by_tgid(CgroupPid::from(pid_i)).unwrap();
let tasks = cg.tasks(); let tasks = cg.tasks();
println!("tasks after deleted: {:?}", tasks); println!("tasks after deleted: {:?}", tasks);
assert_eq!(0, tasks.len()); assert_eq!(0, tasks.len());

View File

@@ -17,7 +17,7 @@ fn test_devices_parsing() {
} }
let h = cgroups_rs::hierarchies::auto(); let h = cgroups_rs::hierarchies::auto();
let cg = Cgroup::new(h, String::from("test_devices_parsing")); let cg = Cgroup::new(h, String::from("test_devices_parsing")).unwrap();
{ {
let devices: &DevicesController = cg.controller_of().unwrap(); let devices: &DevicesController = cg.controller_of().unwrap();

View File

@@ -17,7 +17,7 @@ fn test_hugetlb_sizes() {
} }
let h = cgroups_rs::hierarchies::auto(); let h = cgroups_rs::hierarchies::auto();
let cg = Cgroup::new(h, String::from("test_hugetlb_sizes")); let cg = Cgroup::new(h, String::from("test_hugetlb_sizes")).unwrap();
{ {
let hugetlb_controller: &HugeTlbController = cg.controller_of().unwrap(); let hugetlb_controller: &HugeTlbController = cg.controller_of().unwrap();
let _ = hugetlb_controller.get_sizes(); let _ = hugetlb_controller.get_sizes();

View File

@@ -11,7 +11,7 @@ use cgroups_rs::{Cgroup, MaxValue};
#[test] #[test]
fn test_disable_oom_killer() { fn test_disable_oom_killer() {
let h = cgroups_rs::hierarchies::auto(); let h = cgroups_rs::hierarchies::auto();
let cg = Cgroup::new(h, String::from("test_disable_oom_killer")); let cg = Cgroup::new(h, String::from("test_disable_oom_killer")).unwrap();
{ {
let mem_controller: &MemController = cg.controller_of().unwrap(); let mem_controller: &MemController = cg.controller_of().unwrap();
@@ -40,7 +40,7 @@ fn set_kmem_limit_v1() {
return; return;
} }
let cg = Cgroup::new(h, String::from("set_kmem_limit_v1")); let cg = Cgroup::new(h, String::from("set_kmem_limit_v1")).unwrap();
{ {
let mem_controller: &MemController = cg.controller_of().unwrap(); let mem_controller: &MemController = cg.controller_of().unwrap();
mem_controller.set_kmem_limit(1).unwrap(); mem_controller.set_kmem_limit(1).unwrap();
@@ -55,7 +55,7 @@ fn set_mem_v2() {
return; return;
} }
let cg = Cgroup::new(h, String::from("set_mem_v2")); let cg = Cgroup::new(h, String::from("set_mem_v2")).unwrap();
{ {
let mem_controller: &MemController = cg.controller_of().unwrap(); let mem_controller: &MemController = cg.controller_of().unwrap();

View File

@@ -17,7 +17,7 @@ use libc::pid_t;
#[test] #[test]
fn create_and_delete_cgroup() { fn create_and_delete_cgroup() {
let h = cgroups_rs::hierarchies::auto(); let h = cgroups_rs::hierarchies::auto();
let cg = Cgroup::new(h, String::from("create_and_delete_cgroup")); let cg = Cgroup::new(h, String::from("create_and_delete_cgroup")).unwrap();
{ {
let pidcontroller: &PidController = cg.controller_of().unwrap(); let pidcontroller: &PidController = cg.controller_of().unwrap();
pidcontroller.set_pid_max(MaxValue::Value(1337)).unwrap(); pidcontroller.set_pid_max(MaxValue::Value(1337)).unwrap();
@@ -31,7 +31,7 @@ fn create_and_delete_cgroup() {
#[test] #[test]
fn test_pids_current_is_zero() { fn test_pids_current_is_zero() {
let h = cgroups_rs::hierarchies::auto(); let h = cgroups_rs::hierarchies::auto();
let cg = Cgroup::new(h, String::from("test_pids_current_is_zero")); let cg = Cgroup::new(h, String::from("test_pids_current_is_zero")).unwrap();
{ {
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();
@@ -43,7 +43,7 @@ fn test_pids_current_is_zero() {
#[test] #[test]
fn test_pids_events_is_zero() { fn test_pids_events_is_zero() {
let h = cgroups_rs::hierarchies::auto(); let h = cgroups_rs::hierarchies::auto();
let cg = Cgroup::new(h, String::from("test_pids_events_is_zero")); let cg = Cgroup::new(h, String::from("test_pids_events_is_zero")).unwrap();
{ {
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();
@@ -56,7 +56,7 @@ fn test_pids_events_is_zero() {
#[test] #[test]
fn test_pid_events_is_not_zero() { fn test_pid_events_is_not_zero() {
let h = cgroups_rs::hierarchies::auto(); let h = cgroups_rs::hierarchies::auto();
let cg = Cgroup::new(h, String::from("test_pid_events_is_not_zero")); let cg = Cgroup::new(h, String::from("test_pid_events_is_not_zero")).unwrap();
{ {
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();
@@ -65,7 +65,7 @@ fn test_pid_events_is_not_zero() {
match unsafe { fork() } { match unsafe { fork() } {
Ok(ForkResult::Parent { child, .. }) => { Ok(ForkResult::Parent { child, .. }) => {
// move the process into the control group // move the process into the control group
let _ = pids.add_task(&(pid_t::from(child) as u64).into()); let _ = pids.add_task_by_tgid(&(pid_t::from(child) as u64).into());
println!("added task to cg: {:?}", child); println!("added task to cg: {:?}", child);

View File

@@ -11,7 +11,7 @@ use cgroups_rs::{Cgroup, MaxValue, PidResources, Resources};
#[test] #[test]
fn pid_resources() { fn pid_resources() {
let h = cgroups_rs::hierarchies::auto(); let h = cgroups_rs::hierarchies::auto();
let cg = Cgroup::new(h, String::from("pid_resources")); let cg = Cgroup::new(h, String::from("pid_resources")).unwrap();
{ {
let res = Resources { let res = Resources {
pid: PidResources { pid: PidResources {