Compare commits

...

11 Commits

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

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

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

11
.travis.yml Normal file
View File

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

View File

@@ -4,8 +4,12 @@ description = "Native Rust crate for managing control groups on Linux"
repository = "https://github.com/levex/cgroups-rs"
keywords = ["linux", "cgroup", "containers", "isolation"]
categories = ["os", "api-bindings", "os::unix-apis"]
license = "BSD-2-Clause OR Apache-2.0"
version = "0.0.1"
license = "MIT OR Apache-2.0"
version = "0.0.2"
authors = ["Levente Kurusa <lkurusa@acm.org>"]
[dependencies]
[dev-dependencies]
nix = "0.11.0"
libc = "0.2.43"

23
LICENSE
View File

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

13
LICENSE-Apache-2.0 Normal file
View File

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

21
LICENSE-MIT Normal file
View File

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

View File

@@ -1,2 +1,38 @@
# cgroups-rs
# cgroups-rs ![Build](https://travis-ci.org/levex/cgroups-rs.svg?branch=master)
Native Rust library for managing control groups under Linux
# Example
## Create a control group, and limit the pid resource
``` rust
// Acquire a handle for the V1 cgroup hierarchy.
let hier = ::hierarchies::V1::new();
// Create a control group named "example" in the hierarchy.
let cg = Cgroup::new(&hier, String::from("example"), 0);
{
// Get a handle to the pids controller of the control group.
let pids: &PidController = cg.controller_of().expect("No pids controller in V1 hierarchy!");
// Set the maximum amount of processes in the cgroup.
pids.set_pid_max(PidMax::Value(10));
// Check that this has had the desired effect by reading the value back from the kernel.
assert_eq!(pids.get_pid_max(), Some(PidMax::Value(10)));
}
// Once done, delete the control group (and its associated controllers).
cg.delete();
```
# Disclaimer
This crate is licensed under:
- MIT License (see LICENSE-MIT); or
- Apache 2.0 LIcense (see LICENSE-Apache-2.0),
at your option.
Please note that this crate is under heavy development, we will use sematic
versioning, but during the `0.0.*` phase, no guarantees are made about
backwards compatibility.
Regardless, check back often and thanks for taking a look!

View File

@@ -6,7 +6,8 @@ use std::path::PathBuf;
use std::io::{Read, Write};
use std::fs::File;
use {BlkIoResources, Controllers, Controller, Resources, ControllIdentifier, Subsystem};
use {CgroupError, BlkIoResources, Controllers, Controller, Resources, ControllIdentifier, Subsystem};
use CgroupError::*;
/// A controller that allows controlling the `blkio` subsystem of a Cgroup.
///
@@ -105,28 +106,28 @@ impl Controller for BlkIoController {
let res: &BlkIoResources = &res.blkio;
if res.update_values {
self.set_weight(res.weight as u64);
self.set_leaf_weight(res.leaf_weight as u64);
let _ = self.set_weight(res.weight as u64);
let _ = self.set_leaf_weight(res.leaf_weight as u64);
for dev in &res.weight_device {
self.set_weight_for_device(format!("{}:{} {}",
let _ = self.set_weight_for_device(format!("{}:{} {}",
dev.major, dev.minor, dev.weight));
}
for dev in &res.throttle_read_bps_device {
self.throttle_read_bps_for_device(dev.major, dev.minor, dev.rate);
let _ = self.throttle_read_bps_for_device(dev.major, dev.minor, dev.rate);
}
for dev in &res.throttle_write_bps_device {
self.throttle_write_bps_for_device(dev.major, dev.minor, dev.rate);
let _ = self.throttle_write_bps_for_device(dev.major, dev.minor, dev.rate);
}
for dev in &res.throttle_read_iops_device {
self.throttle_read_iops_for_device(dev.major, dev.minor, dev.rate);
let _ = self.throttle_read_iops_for_device(dev.major, dev.minor, dev.rate);
}
for dev in &res.throttle_write_iops_device {
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);
}
}
}
@@ -152,16 +153,20 @@ impl<'a> From<&'a Subsystem> for &'a BlkIoController {
}
}
fn read_string_from(mut file: File) -> Option<String> {
fn read_string_from(mut file: File) -> Result<String, CgroupError> {
let mut string = String::new();
let _ = file.read_to_string(&mut string);
Some(string.trim().to_string())
match file.read_to_string(&mut string) {
Ok(_) => Ok(string.trim().to_string()),
Err(e) => Err(CgroupError::ReadError(e)),
}
}
fn read_u64_from(mut file: File) -> Option<u64> {
fn read_u64_from(mut file: File) -> Result<u64, CgroupError> {
let mut string = String::new();
let _ = file.read_to_string(&mut string);
string.trim().parse().ok()
match file.read_to_string(&mut string) {
Ok(_) => string.trim().parse().map_err(|_| ParseError),
Err(e) => Err(CgroupError::ReadError(e)),
}
}
impl BlkIoController {
@@ -270,68 +275,68 @@ impl BlkIoController {
/// Set the leaf weight on the control group's tasks, i.e., how are they weighted against the
/// descendant control groups' tasks.
pub fn set_leaf_weight(self: &Self, w: u64) {
pub fn set_leaf_weight(self: &Self, w: u64) -> Result<(), CgroupError> {
self.open_path("blkio.leaf_weight", true).and_then(|mut file| {
file.write_all(w.to_string().as_ref()).ok()
});
file.write_all(w.to_string().as_ref()).map_err(CgroupError::WriteError)
})
}
/// Same as `set_leaf_weight()`, but settable per each block device.
pub fn set_leaf_weight_for_device(self: &Self, d: String) {
pub fn set_leaf_weight_for_device(self: &Self, d: String) -> Result<(), CgroupError> {
self.open_path("blkio.leaf_weight_device", true).and_then(|mut file| {
file.write_all(d.as_ref()).ok()
});
file.write_all(d.as_ref()).map_err(CgroupError::WriteError)
})
}
/// Reset the statistics the kernel has gathered so far and start fresh.
pub fn reset_stats(self: &Self) {
pub fn reset_stats(self: &Self) -> Result<(), CgroupError> {
self.open_path("blkio.leaf_weight_device", true).and_then(|mut file| {
file.write_all("1".to_string().as_ref()).ok()
});
file.write_all("1".to_string().as_ref()).map_err(CgroupError::WriteError)
})
}
/// Throttle the bytes per second rate of read operation affecting the block device
/// `major:minor` to `bps`.
pub fn throttle_read_bps_for_device(self: &Self, major: u64, minor: u64, bps: u64) {
pub fn throttle_read_bps_for_device(self: &Self, major: u64, minor: u64, bps: u64) -> Result<(), CgroupError> {
self.open_path("blkio.throttle.read_bps_device", true).and_then(|mut file| {
file.write_all(format!("{}:{} {}", major, minor, bps).to_string().as_ref()).ok()
});
file.write_all(format!("{}:{} {}", major, minor, bps).to_string().as_ref()).map_err(CgroupError::WriteError)
})
}
/// Throttle the I/O operations per second rate of read operation affecting the block device
/// `major:minor` to `bps`.
pub fn throttle_read_iops_for_device(self: &Self, major: u64, minor: u64, iops: u64) {
pub fn throttle_read_iops_for_device(self: &Self, major: u64, minor: u64, iops: u64) -> Result<(), CgroupError> {
self.open_path("blkio.throttle.read_iops_device", true).and_then(|mut file| {
file.write_all(format!("{}:{} {}", major, minor, iops).to_string().as_ref()).ok()
});
file.write_all(format!("{}:{} {}", major, minor, iops).to_string().as_ref()).map_err(CgroupError::WriteError)
})
}
/// Throttle the bytes per second rate of write operation affecting the block device
/// `major:minor` to `bps`.
pub fn throttle_write_bps_for_device(self: &Self, major: u64, minor: u64, bps: u64) {
pub fn throttle_write_bps_for_device(self: &Self, major: u64, minor: u64, bps: u64) -> Result<(), CgroupError> {
self.open_path("blkio.throttle.write_bps_device", true).and_then(|mut file| {
file.write_all(format!("{}:{} {}", major, minor, bps).to_string().as_ref()).ok()
});
file.write_all(format!("{}:{} {}", major, minor, bps).to_string().as_ref()).map_err(CgroupError::WriteError)
})
}
/// Throttle the I/O operations per second rate of write operation affecting the block device
/// `major:minor` to `bps`.
pub fn throttle_write_iops_for_device(self: &Self, major: u64, minor: u64, iops: u64) {
pub fn throttle_write_iops_for_device(self: &Self, major: u64, minor: u64, iops: u64) -> Result<(), CgroupError> {
self.open_path("blkio.throttle.write_iops_device", true).and_then(|mut file| {
file.write_all(format!("{}:{} {}", major, minor, iops).to_string().as_ref()).ok()
});
file.write_all(format!("{}:{} {}", major, minor, iops).to_string().as_ref()).map_err(CgroupError::WriteError)
})
}
/// Set the weight of the control group's tasks.
pub fn set_weight(self: &Self, w: u64) {
pub fn set_weight(self: &Self, w: u64) -> Result<(), CgroupError> {
self.open_path("blkio.leaf_weight", true).and_then(|mut file| {
file.write_all(w.to_string().as_ref()).ok()
});
file.write_all(w.to_string().as_ref()).map_err(CgroupError::WriteError)
})
}
/// Same as `set_weight()`, but settable per each block device.
pub fn set_weight_for_device(self: &Self, d: String) {
pub fn set_weight_for_device(self: &Self, d: String) -> Result<(), CgroupError> {
self.open_path("blkio.weight_device", true).and_then(|mut file| {
file.write_all(d.as_ref()).ok()
});
file.write_all(d.as_ref()).map_err(CgroupError::WriteError)
})
}
}

View File

@@ -1,6 +1,6 @@
//! This module handles cgroup operations. Start here!
use {CgroupPid, Resources, ControllIdentifier, Controller, Hierarchy, Subsystem};
use {CgroupError, CgroupPid, Resources, ControllIdentifier, Controller, Hierarchy, Subsystem};
use std::convert::From;
@@ -17,12 +17,15 @@ use std::convert::From;
/// > specialized behaviour.
///
/// This crate is an attempt at providing a Rust-native way of managing these cgroups.
pub struct Cgroup {
pub struct Cgroup<'b> {
/// The list of subsystems that control this cgroup
subsystems: Vec<Subsystem>,
/// The hierarchy.
hier: &'b Hierarchy,
}
impl Cgroup {
impl<'b> Cgroup<'b> {
/// Create this control group.
fn create(self: &Self) {
@@ -38,15 +41,29 @@ impl Cgroup {
/// Note that if the handle goes out of scope and is dropped, the control group is _not_
/// destroyed.
pub fn new(hier: &Hierarchy, path: String) -> Cgroup {
let cg = Cgroup::load(hier, path);
cg.create();
cg
}
/// Create a handle for a control group in the hierarchy `hier`, with name `path`.
///
/// Returns a handle to the control group (that possibly does not exist until `create()` has
/// been called on the cgroup.
///
/// Note that if the handle goes out of scope and is dropped, the control group is _not_
/// destroyed.
pub fn load(hier: &Hierarchy, path: String) -> Cgroup {
let mut subsystems = hier.subsystems();
subsystems = subsystems.into_iter().map(|x| x.enter(&path)).collect::<Vec<_>>();
if path != "" {
subsystems = subsystems.into_iter().map(|x| x.enter(&path)).collect::<Vec<_>>();
}
let cg = Cgroup {
//name: path,
subsystems: subsystems,
hier: hier,
};
cg.create();
cg
}
@@ -92,7 +109,7 @@ impl Cgroup {
///
/// ## Example:
///
/// ```
/// ```text
/// let pids: &PidController = control_group.controller_of()
/// .expect("No pids controller attached!");
/// let cpu: &CpuController = control_group.controller_of()
@@ -114,8 +131,29 @@ impl Cgroup {
None
}
/// Attach a task to the control group.
pub fn add_task(self: &Self, pid: CgroupPid) {
self.subsystems().iter().for_each(|sub| sub.to_controller().add_task(&pid));
/// Removes a task from the control group.
///
/// Note that this means that the task will be moved back to the root control group in the
/// hierarchy and any rules applied to that control group will _still_ apply to the task.
pub fn remove_task(self: &Self, pid: CgroupPid) {
let _ = self.hier.root_control_group().add_task(pid);
}
/// Attach a task to the control group.
pub fn add_task(self: &Self, pid: CgroupPid) -> Result<(), CgroupError> {
self.subsystems().iter().try_for_each(|sub| sub.to_controller().add_task(&pid))
}
/// Returns an Iterator that can be used to iterate over the tasks that are currently in the
/// control group.
pub fn tasks(self: &Self) -> Vec<CgroupPid> {
/* Collect the tasks from all subsystems */
let mut v = self.subsystems().iter()
.map(|x| x.to_controller().tasks())
.fold(vec![], |mut acc, mut x| { acc.append(&mut x); acc });
v.sort();
v.dedup();
v
}
}

View File

@@ -6,7 +6,7 @@
use std::path::PathBuf;
use std::io::{Read, Write};
use {CpuResources, Controllers, Controller, Resources, ControllIdentifier, Subsystem};
use {CgroupError, CpuResources, Controllers, Controller, Resources, ControllIdentifier, Subsystem};
/// A controller that allows controlling the `cpu` subsystem of a Cgroup.
///
@@ -40,9 +40,9 @@ impl Controller for CpuController {
if res.update_values {
/* apply pid_max */
self.set_shares(res.shares);
self.set_cfs_period(res.period);
self.set_cfs_quota(res.quota as u64);
let _ = self.set_shares(res.shares);
let _ = self.set_cfs_period(res.period);
let _ = self.set_cfs_quota(res.quota as u64);
/* TODO: rt properties (CONFIG_RT_GROUP_SCHED) are not yet supported */
}
}
@@ -84,8 +84,11 @@ impl CpuController {
Cpu {
stat: self.open_path("cpu.stat", false).and_then(|mut file| {
let mut s = String::new();
let _ = file.read_to_string(&mut s);
Some(s)
let res = file.read_to_string(&mut s);
match res {
Ok(_) => Ok(s),
Err(e) => Err(CgroupError::ReadError(e)),
}
}).unwrap_or("".to_string()),
}
}
@@ -96,25 +99,25 @@ impl CpuController {
/// For example, setting control group `A`'s `shares` to `100`, and control group `B`'s
/// `shares` to `200` ensures that control group `B` receives twice as much as CPU bandwidth.
/// (Assuming both `A` and `B` are of the same parent)
pub fn set_shares(self: &Self, shares: u64) {
pub fn set_shares(self: &Self, shares: u64) -> Result<(), CgroupError> {
self.open_path("cpu.shares", true).and_then(|mut file| {
file.write_all(shares.to_string().as_ref()).ok()
});
file.write_all(shares.to_string().as_ref()).map_err(CgroupError::WriteError)
})
}
/// Specify a period (when using the CFS scheduler) of time in microseconds for how often this
/// control group's access to the CPU should be reallocated.
pub fn set_cfs_period(self: &Self, us: u64) {
pub fn set_cfs_period(self: &Self, us: u64) -> Result<(), CgroupError> {
self.open_path("cpu.cfs_period_us", true).and_then(|mut file| {
file.write_all(us.to_string().as_ref()).ok()
});
file.write_all(us.to_string().as_ref()).map_err(CgroupError::WriteError)
})
}
/// Specify a quota (when using the CFS scheduler) of time in microseconds for which all tasks
/// in this control group can run during one period (see: `set_cfs_period()`).
pub fn set_cfs_quota(self: &Self, us: u64) {
pub fn set_cfs_quota(self: &Self, us: u64) -> Result<(), CgroupError> {
self.open_path("cpu.cfs_quota_us", true).and_then(|mut file| {
file.write_all(us.to_string().as_ref()).ok()
});
file.write_all(us.to_string().as_ref()).map_err(CgroupError::WriteError)
})
}
}

View File

@@ -6,7 +6,7 @@ use std::path::PathBuf;
use std::io::{Read, Write};
use std::fs::File;
use {Controllers, Resources, Subsystem, ControllIdentifier, Controller};
use {CgroupError, Controllers, Resources, Subsystem, ControllIdentifier, Controller};
/// A controller that allows controlling the `cpuacct` subsystem of a Cgroup.
///
@@ -28,7 +28,7 @@ pub struct CpuAcct {
/// time spent is `user` time or `system` time.
///
/// An example is as follows:
/// ```
/// ```text
/// cpu user system
/// 0 8348363768 0
/// 1 8324369100 0
@@ -79,10 +79,24 @@ impl<'a> From<&'a Subsystem> for &'a CpuAcctController {
}
}
fn read_u64_from(mut file: File) -> Option<u64> {
fn read_u64_from(mut file: File) -> Result<u64, CgroupError> {
let mut string = String::new();
let _ = file.read_to_string(&mut string);
string.trim().parse().ok()
let res = file.read_to_string(&mut string);
match res {
Ok(_) => match string.trim().parse() {
Ok(e) => Ok(e),
Err(_) => Err(CgroupError::ParseError),
},
Err(e) => Err(CgroupError::ReadError(e)),
}
}
fn read_string_from(mut file: File) -> Result<String, CgroupError> {
let mut string = String::new();
match file.read_to_string(&mut string) {
Ok(_) => Ok(string.trim().to_string()),
Err(e) => Err(CgroupError::ReadError(e)),
}
}
impl CpuAcctController {
@@ -101,38 +115,18 @@ impl CpuAcctController {
pub fn cpuacct(self: &Self) -> CpuAcct {
CpuAcct {
stat: self.open_path("cpuacct.stat", false)
.and_then(|mut file| {
let mut string = String::new();
let _ = file.read_to_string(&mut string);
Some(string.trim().to_string())
}).unwrap_or("".to_string()),
.and_then(|file| read_string_from(file)).unwrap_or("".to_string()),
usage: self.open_path("cpuacct.usage", false)
.and_then(|file| read_u64_from(file))
.unwrap_or(0),
usage_all: self.open_path("cpuacct.usage_all", false)
.and_then(|mut file| {
let mut string = String::new();
let _ = file.read_to_string(&mut string);
Some(string.trim().to_string())
}).unwrap_or("".to_string()),
.and_then(|file| read_string_from(file)).unwrap_or("".to_string()),
usage_percpu: self.open_path("cpuacct.usage_percpu", false)
.and_then(|mut file| {
let mut string = String::new();
let _ = file.read_to_string(&mut string);
Some(string.trim().to_string())
}).unwrap_or("".to_string()),
.and_then(|file| read_string_from(file)).unwrap_or("".to_string()),
usage_percpu_sys: self.open_path("cpuacct.usage_percpu_sys", false)
.and_then(|mut file| {
let mut string = String::new();
let _ = file.read_to_string(&mut string);
Some(string.trim().to_string())
}).unwrap_or("".to_string()),
.and_then(|file| read_string_from(file)).unwrap_or("".to_string()),
usage_percpu_user: self.open_path("cpuacct.usage_percpu_user", false)
.and_then(|mut file| {
let mut string = String::new();
let _ = file.read_to_string(&mut string);
Some(string.trim().to_string())
}).unwrap_or("".to_string()),
.and_then(|file| read_string_from(file)).unwrap_or("".to_string()),
usage_sys: self.open_path("cpuacct.usage_sys", false)
.and_then(|file| read_u64_from(file))
.unwrap_or(0),
@@ -143,9 +137,9 @@ impl CpuAcctController {
}
/// Reset the statistics the kernel has gathered about the control group.
pub fn reset(self: &Self) {
pub fn reset(self: &Self) -> Result<(), CgroupError> {
self.open_path("cpuacct.usage", true).and_then(|mut file| {
file.write_all(b"0").ok()
});
file.write_all(b"0").map_err(CgroupError::WriteError)
})
}
}

View File

@@ -6,7 +6,8 @@ use std::path::PathBuf;
use std::io::{Read, Write};
use std::fs::File;
use {CpuResources, Resources, Controller, ControllIdentifier, Subsystem, Controllers};
use {CgroupError, CpuResources, Resources, Controller, ControllIdentifier, Subsystem, Controllers};
use CgroupError::*;
/// A controller that allows controlling the `cpuset` subsystem of a Cgroup.
///
@@ -84,8 +85,8 @@ impl Controller for CpuSetController {
if res.update_values {
/* apply pid_max */
self.set_cpus(&res.cpus);
self.set_mems(&res.mems);
let _ = self.set_cpus(&res.cpus);
let _ = self.set_mems(&res.mems);
}
}
}
@@ -110,10 +111,20 @@ impl<'a> From<&'a Subsystem> for &'a CpuSetController {
}
}
fn read_u64_from(mut file: File) -> Option<u64> {
fn read_string_from(mut file: File) -> Result<String, CgroupError> {
let mut string = String::new();
let _ = file.read_to_string(&mut string);
string.trim().parse().ok()
match file.read_to_string(&mut string) {
Ok(_) => Ok(string.trim().to_string()),
Err(e) => Err(CgroupError::ReadError(e)),
}
}
fn read_u64_from(mut file: File) -> Result<u64, CgroupError> {
let mut string = String::new();
match file.read_to_string(&mut string) {
Ok(_) => string.trim().parse().map_err(|_| ParseError),
Err(e) => Err(CgroupError::ReadError(e)),
}
}
impl CpuSetController {
@@ -137,122 +148,96 @@ impl CpuSetController {
}).map(|x| x == 1).unwrap_or(false)
},
cpus: {
self.open_path("cpuset.cpus", false).and_then(|mut file| {
let mut string = String::new();
let _ = file.read_to_string(&mut string);
Some(string.trim().to_string())
}).unwrap()
self.open_path("cpuset.cpus", false).and_then(read_string_from).unwrap_or("".to_string())
},
effective_cpus: {
self.open_path("cpuset.effective_cpus", false).and_then(|mut file| {
let mut string = String::new();
let _ = file.read_to_string(&mut string);
Some(string.trim().to_string())
}).unwrap()
self.open_path("cpuset.effective_cpus", false).and_then(read_string_from).unwrap_or("".to_string())
},
effective_mems: {
self.open_path("cpuset.effective_mems", false).and_then(|mut file| {
let mut string = String::new();
let _ = file.read_to_string(&mut string);
Some(string.trim().to_string())
}).unwrap()
self.open_path("cpuset.effective_mems", false).and_then(read_string_from).unwrap_or("".to_string())
},
mem_exclusive: {
self.open_path("cpuset.mem_exclusive", false).and_then(|file| {
read_u64_from(file)
}).map(|x| x == 1).unwrap_or(false)
self.open_path("cpuset.mem_exclusive", false).and_then(read_u64_from)
.map(|x| x == 1).unwrap_or(false)
},
mem_hardwall: {
self.open_path("cpuset.mem_hardwall", false).and_then(|file| {
read_u64_from(file)
}).map(|x| x == 1).unwrap_or(false)
self.open_path("cpuset.mem_hardwall", false).and_then(read_u64_from)
.map(|x| x == 1).unwrap_or(false)
},
memory_migrate: {
self.open_path("cpuset.memory_migrate", false).and_then(|file| {
read_u64_from(file)
}).map(|x| x == 1).unwrap_or(false)
self.open_path("cpuset.memory_migrate", false).and_then(read_u64_from)
.map(|x| x == 1).unwrap_or(false)
},
memory_pressure: {
self.open_path("cpuset.memory_pressure", false).and_then(|file| {
read_u64_from(file)
}).unwrap_or(0)
self.open_path("cpuset.memory_pressure", false).and_then(read_u64_from).unwrap_or(0)
},
memory_pressure_enabled: {
self.open_path("cpuset.memory_pressure_enabled", false).and_then(|file| {
read_u64_from(file)
}).map(|x| x == 1)
self.open_path("cpuset.memory_pressure_enabled", false).and_then(read_u64_from)
.map(|x| x == 1).ok()
},
memory_spread_page: {
self.open_path("cpuset.memory_spread_page", false).and_then(|file| {
read_u64_from(file)
}).map(|x| x == 1).unwrap_or(false)
self.open_path("cpuset.memory_spread_page", false).and_then(read_u64_from)
.map(|x| x == 1).unwrap_or(false)
},
memory_spread_slab: {
self.open_path("cpuset.memory_spread_slab", false).and_then(|file| {
read_u64_from(file)
}).map(|x| x == 1).unwrap_or(false)
self.open_path("cpuset.memory_spread_slab", false).and_then(read_u64_from)
.map(|x| x == 1).unwrap_or(false)
},
mems: {
self.open_path("cpuset.mems", false).and_then(|mut file| {
let mut string = String::new();
let _ = file.read_to_string(&mut string);
Some(string.trim().to_string())
}).unwrap()
self.open_path("cpuset.mems", false).and_then(read_string_from).unwrap_or("".to_string())
},
sched_load_balance: {
self.open_path("cpuset.sched_load_balance", false).and_then(|file| {
read_u64_from(file)
}).map(|x| x == 1).unwrap_or(false)
self.open_path("cpuset.sched_load_balance", false).and_then(read_u64_from)
.map(|x| x == 1).unwrap_or(false)
},
sched_relax_domain_level: {
self.open_path("cpuset.sched_relax_domain_level", false).and_then(|file| {
read_u64_from(file)
}).unwrap_or(0)
self.open_path("cpuset.sched_relax_domain_level", false).and_then(read_u64_from)
.unwrap_or(0)
},
}
}
/// Control whether the CPUs selected via `set_cpus()` should be exclusive to this control
/// group or not.
pub fn set_cpu_exclusive(self: &Self, b: bool) {
pub fn set_cpu_exclusive(self: &Self, b: bool) -> Result<(), CgroupError> {
self.open_path("cpuset.cpu_exclusive", true).and_then(|mut file| {
if b {
file.write_all(b"1").ok()
file.write_all(b"1").map_err(CgroupError::WriteError)
} else {
file.write_all(b"0").ok()
file.write_all(b"0").map_err(CgroupError::WriteError)
}
});
})
}
/// Control whether the memory nodes selected via `set_memss()` should be exclusive to this control
/// group or not.
pub fn set_mem_exclusive(self: &Self, b: bool) {
pub fn set_mem_exclusive(self: &Self, b: bool) -> Result<(), CgroupError> {
self.open_path("cpuset.mem_exclusive", true).and_then(|mut file| {
if b {
file.write_all(b"1").ok()
file.write_all(b"1").map_err(CgroupError::WriteError)
} else {
file.write_all(b"0").ok()
file.write_all(b"0").map_err(CgroupError::WriteError)
}
});
})
}
/// Set the CPUs that the tasks in this control group can run on.
///
/// Syntax is a comma separated list of CPUs, with an additional extension that ranges can
/// be represented via dashes.
pub fn set_cpus(self: &Self, cpus: &String) {
pub fn set_cpus(self: &Self, cpus: &String) -> Result<(), CgroupError> {
self.open_path("cpuset.cpus", true).and_then(|mut file| {
file.write_all(cpus.as_ref()).ok()
});
file.write_all(cpus.as_ref()).map_err(CgroupError::WriteError)
})
}
/// Set the memory nodes that the tasks in this control group can use.
///
/// Syntax is the same as with `set_cpus()`.
pub fn set_mems(self: &Self, mems: &String) {
pub fn set_mems(self: &Self, mems: &String) -> Result<(), CgroupError> {
self.open_path("cpuset.mems", true).and_then(|mut file| {
file.write_all(mems.as_ref()).ok()
});
file.write_all(mems.as_ref()).map_err(CgroupError::WriteError)
})
}
/// Controls whether the control group should be "hardwalled", i.e., whether kernel allocations
@@ -260,71 +245,71 @@ impl CpuSetController {
///
/// Note that some kernel allocations, most notably those that are made in interrupt handlers
/// may disregard this.
pub fn set_hardwall(self: &Self, b: bool) {
pub fn set_hardwall(self: &Self, b: bool) -> Result<(), CgroupError> {
self.open_path("cpuset.mem_hardwall", true).and_then(|mut file| {
if b {
file.write_all(b"1").ok()
file.write_all(b"1").map_err(CgroupError::WriteError)
} else {
file.write_all(b"0").ok()
file.write_all(b"0").map_err(CgroupError::WriteError)
}
});
})
}
/// Controls whether the kernel should attempt to rebalance the load between the CPUs specified in the
/// `cpus` field of this control group.
pub fn set_load_balancing(self: &Self, b: bool) {
pub fn set_load_balancing(self: &Self, b: bool) -> Result<(), CgroupError> {
self.open_path("cpuset.sched_load_balance", true).and_then(|mut file| {
if b {
file.write_all(b"1").ok()
file.write_all(b"1").map_err(CgroupError::WriteError)
} else {
file.write_all(b"0").ok()
file.write_all(b"0").map_err(CgroupError::WriteError)
}
});
})
}
/// Contorl how much effort the kernel should invest in rebalacing the control group.
///
/// See @CpuSet 's similar field for more information.
pub fn set_rebalance_relax_domain_level(self: &Self, i: i64) {
pub fn set_rebalance_relax_domain_level(self: &Self, i: i64) -> Result<(), CgroupError> {
self.open_path("cpuset.sched_relax_domain_level", true).and_then(|mut file| {
file.write_all(i.to_string().as_ref()).ok()
});
file.write_all(i.to_string().as_ref()).map_err(CgroupError::WriteError)
})
}
/// Control whether when using `set_mems()` the existing memory used by the tasks should be
/// migrated over to the now-selected nodes.
pub fn set_memory_migration(self: &Self, b: bool) {
pub fn set_memory_migration(self: &Self, b: bool) -> Result<(), CgroupError> {
self.open_path("cpuset.memory_migrate", true).and_then(|mut file| {
if b {
file.write_all(b"1").ok()
file.write_all(b"1").map_err(CgroupError::WriteError)
} else {
file.write_all(b"0").ok()
file.write_all(b"0").map_err(CgroupError::WriteError)
}
});
})
}
/// Control whether filesystem buffers should be evenly split across the nodes selected via
/// `set_mems()`.
pub fn set_memory_spread_page(self: &Self, b: bool) {
pub fn set_memory_spread_page(self: &Self, b: bool) -> Result<(), CgroupError> {
self.open_path("cpuset.memory_spread_page", true).and_then(|mut file| {
if b {
file.write_all(b"1").ok()
file.write_all(b"1").map_err(CgroupError::WriteError)
} else {
file.write_all(b"0").ok()
file.write_all(b"0").map_err(CgroupError::WriteError)
}
});
})
}
/// Control whether the kernel's slab cache for file I/O should be evenly split across the
/// nodes selected via `set_mems()`.
pub fn set_memory_spread_slab(self: &Self, b: bool) {
pub fn set_memory_spread_slab(self: &Self, b: bool) -> Result<(), CgroupError> {
self.open_path("cpuset.memory_spread_slab", true).and_then(|mut file| {
if b {
file.write_all(b"1").ok()
file.write_all(b"1").map_err(CgroupError::WriteError)
} else {
file.write_all(b"0").ok()
file.write_all(b"0").map_err(CgroupError::WriteError)
}
});
})
}
/// Control whether the kernel should collect information to calculate memory pressure for
@@ -332,14 +317,14 @@ impl CpuSetController {
///
/// Note: This is a no-operation if the control group referred by `self` is not the root
/// control group.
pub fn set_enable_memory_pressure(self: &Self, b: bool) {
pub fn set_enable_memory_pressure(self: &Self, b: bool) -> Result<(), CgroupError> {
/* XXX: this file should only be present in the root cpuset cg */
self.open_path("cpuset.memory_pressure_enabled", true).and_then(|mut file| {
if b {
file.write_all(b"1").ok()
file.write_all(b"1").map_err(CgroupError::WriteError)
} else {
file.write_all(b"0").ok()
file.write_all(b"0").map_err(CgroupError::WriteError)
}
});
})
}
}

View File

@@ -5,7 +5,7 @@
use std::path::PathBuf;
use std::io::{Read, Write};
use {DeviceResources, Controllers, Controller, Resources, ControllIdentifier, Subsystem};
use {CgroupError, DeviceResources, Controllers, Controller, Resources, ControllIdentifier, Subsystem};
/// A controller that allows controlling the `devices` subsystem of a Cgroup.
///
@@ -32,9 +32,9 @@ impl Controller for DevicesController {
let wstr = format!("{} {}:{} {}",
i.devtype, i.major, i.minor, i.access);
if i.allow {
self.allow_device(&wstr);
let _ = self.allow_device(&wstr);
} else {
self.deny_device(&wstr);
let _ = self.deny_device(&wstr);
}
}
}
@@ -81,10 +81,10 @@ impl DevicesController {
///
/// Note that `dev` can be "regex"-like: both `$major` and `$minor` can be `*` which implies
/// that their value does not matter.
pub fn allow_device(self: &Self, dev: &String) {
pub fn allow_device(self: &Self, dev: &String) -> Result<(), CgroupError> {
self.open_path("devices.allow", true).and_then(|mut file| {
file.write_all(dev.as_ref()).ok()
});
file.write_all(dev.as_ref()).map_err(CgroupError::WriteError)
})
}
/// Deny the control group's tasks access to the devices covered by `dev`.
@@ -96,18 +96,21 @@ impl DevicesController {
///
/// Note that `dev` can be "regex"-like: both `$major` and `$minor` can be `*` which implies
/// that their value does not matter.
pub fn deny_device(self: &Self, dev: &String) {
pub fn deny_device(self: &Self, dev: &String) -> Result<(), CgroupError> {
self.open_path("devices.deny", true).and_then(|mut file| {
file.write_all(dev.as_ref()).ok()
});
file.write_all(dev.as_ref()).map_err(CgroupError::WriteError)
})
}
/// Get the current list of allowed devices.
pub fn allowed_devices(self: &Self) -> String {
pub fn allowed_devices(self: &Self) -> Result<String, CgroupError> {
self.open_path("devices.list", false).and_then(|mut file| {
let mut s = String::new();
let _ = file.read_to_string(&mut s);
Some(s)
}).unwrap_or("".to_string())
let res = file.read_to_string(&mut s);
match res {
Ok(_) => Ok(s),
Err(e) => Err(CgroupError::ReadError(e)),
}
})
}
}

View File

@@ -5,7 +5,7 @@
use std::path::PathBuf;
use std::io::{Read, Write};
use {Controllers, Controller, Resources, ControllIdentifier, Subsystem};
use {CgroupError, Controllers, Controller, Resources, ControllIdentifier, Subsystem};
/// A controller that allows controlling the `freezer` subsystem of a Cgroup.
///
@@ -73,30 +73,33 @@ impl FreezerController {
}
/// Freezes the processes in the control group.
pub fn freeze(self: &Self) {
pub fn freeze(self: &Self) -> Result<(), CgroupError> {
self.open_path("freezer.state", true).and_then(|mut file| {
file.write_all("FROZEN".to_string().as_ref()).ok()
});
file.write_all("FROZEN".to_string().as_ref()).map_err(CgroupError::WriteError)
})
}
/// Thaws, that is, unfreezes the processes in the control group.
pub fn thaw(self: &Self) {
pub fn thaw(self: &Self) -> Result<(), CgroupError> {
self.open_path("freezer.state", true).and_then(|mut file| {
file.write_all("THAWED".to_string().as_ref()).ok()
});
file.write_all("THAWED".to_string().as_ref()).map_err(CgroupError::WriteError)
})
}
/// Retrieve the state of processes in the control group.
pub fn state(self: &Self) -> FreezerState {
pub fn state(self: &Self) -> Result<FreezerState, CgroupError> {
self.open_path("freezer.state", false).and_then(|mut file| {
let mut s = String::new();
let _ = file.read_to_string(&mut s);
match s.as_ref() {
"FROZEN" => Some(FreezerState::Frozen),
"THAWED" => Some(FreezerState::Thawed),
"FREEZING" => Some(FreezerState::Freezing),
_ => None,
let res = file.read_to_string(&mut s);
match res {
Ok(_) => match s.as_ref() {
"FROZEN" => Ok(FreezerState::Frozen),
"THAWED" => Ok(FreezerState::Thawed),
"FREEZING" => Ok(FreezerState::Freezing),
_ => Err(CgroupError::ParseError),
},
Err(e) => Err(CgroupError::ReadError(e)),
}
}).unwrap_or(FreezerState::Thawed)
})
}
}

View File

@@ -23,6 +23,8 @@ use ::net_prio::NetPrioController;
use ::hugetlb::HugeTlbController;
use ::rdma::RdmaController;
use ::cgroup::Cgroup;
/// The standard, original cgroup implementation. Often referred to as "cgroupv1".
pub struct V1 {
@@ -75,6 +77,10 @@ impl Hierarchy for V1 {
subs
}
fn root_control_group(self: &Self) -> Cgroup {
Cgroup::load(self, "".to_string())
}
fn check_support(self: &Self, sub: Controllers) -> bool {
let root = self.root().read_dir().unwrap();
for entry in root {

View File

@@ -6,7 +6,8 @@ use std::path::PathBuf;
use std::fs::File;
use std::io::{Write, Read};
use {HugePageResources, Controllers, Controller, Resources, ControllIdentifier, Subsystem};
use {CgroupError, HugePageResources, Controllers, Controller, Resources, ControllIdentifier, Subsystem};
use CgroupError::*;
/// A controller that allows controlling the `hugetlb` subsystem of a Cgroup.
@@ -31,7 +32,7 @@ impl Controller for HugeTlbController {
if res.update_values {
for i in &res.limits {
self.set_limit_in_bytes(&i.size, i.limit);
let _ = self.set_limit_in_bytes(&i.size, i.limit);
}
}
}
@@ -57,10 +58,12 @@ impl<'a> From<&'a Subsystem> for &'a HugeTlbController {
}
}
fn read_u64_from(mut file: File) -> Option<u64> {
fn read_u64_from(mut file: File) -> Result<u64, CgroupError> {
let mut string = String::new();
let _ = file.read_to_string(&mut string);
string.trim().parse().ok()
match file.read_to_string(&mut string) {
Ok(_) => string.trim().parse().map_err(|_| ParseError),
Err(e) => Err(CgroupError::ReadError(e)),
}
}
impl HugeTlbController {
@@ -81,38 +84,38 @@ impl HugeTlbController {
}
/// Check how many times has the limit of `hugetlb_size` hugepages been hit.
pub fn failcnt(self: &Self, hugetlb_size: &String) -> Option<u64> {
pub fn failcnt(self: &Self, hugetlb_size: &String) -> Result<u64, CgroupError> {
self.open_path(&format!("hugetlb.{}.failcnt", hugetlb_size), false)
.and_then(read_u64_from)
}
/// Get the limit (in bytes) of how much memory can be backed by hugepages of a certain size
/// (`hugetlb_size`).
pub fn limit_in_bytes(self: &Self, hugetlb_size: &String) -> Option<u64> {
pub fn limit_in_bytes(self: &Self, hugetlb_size: &String) -> Result<u64, CgroupError> {
self.open_path(&format!("hugetlb.{}.limit_in_bytes", hugetlb_size), false)
.and_then(read_u64_from)
}
/// Get the current usage of memory that is backed by hugepages of a certain size
/// (`hugetlb_size`).
pub fn usage_in_bytes(self: &Self, hugetlb_size: &String) -> Option<u64> {
pub fn usage_in_bytes(self: &Self, hugetlb_size: &String) -> Result<u64, CgroupError> {
self.open_path(&format!("hugetlb.{}.usage_in_bytes", hugetlb_size), false)
.and_then(read_u64_from)
}
/// Get the maximum observed usage of memory that is backed by hugepages of a certain size
/// (`hugetlb_size`).
pub fn max_usage_in_bytes(self: &Self, hugetlb_size: &String) -> Option<u64> {
pub fn max_usage_in_bytes(self: &Self, hugetlb_size: &String) -> Result<u64, CgroupError> {
self.open_path(&format!("hugetlb.{}.max_usage_in_bytes", hugetlb_size), false)
.and_then(read_u64_from)
}
/// Set the limit (in bytes) of how much memory can be backed by hugepages of a certain size
/// (`hugetlb_size`).
pub fn set_limit_in_bytes(self: &Self, hugetlb_size: &String, limit: u64) {
pub fn set_limit_in_bytes(self: &Self, hugetlb_size: &String, limit: u64) -> Result<(), CgroupError> {
self.open_path(&format!("hugetlb.{}.limit_in_bytes", hugetlb_size), false)
.and_then(|mut file| {
file.write_all(limit.to_string().as_ref()).ok()
});
file.write_all(limit.to_string().as_ref()).map_err(CgroupError::WriteError)
})
}
}

View File

@@ -1,6 +1,6 @@
use std::path::PathBuf;
use std::fs::File;
use std::io::Write;
use std::io::{BufRead, BufReader, Write};
pub mod hierarchies;
pub mod pid;
@@ -32,6 +32,8 @@ use net_prio::NetPrioController;
use hugetlb::HugeTlbController;
use rdma::RdmaController;
pub use cgroup::Cgroup;
/// Contains all the subsystems that are available in this crate.
#[derive(Debug)]
pub enum Subsystem {
@@ -63,6 +65,29 @@ pub enum Subsystem {
Rdma(RdmaController),
}
/// The different types of errors that can occur while manipulating control groups.
#[derive(Debug)]
pub enum CgroupError {
/// An error occured while writing to a control group file.
WriteError(std::io::Error),
/// An error occured while trying to read from a control group file.
ReadError(std::io::Error),
/// An error occured while trying to parse a value from a control group file.
///
/// In the future, there will be some information attached to this field.
ParseError,
/// You tried to do something invalid.
///
/// This could be because you tried to set a value in a control group that is not a root
/// control group. Or, when using unified hierarchy, you tried to add a task in a leaf node.
InvalidOperation,
/// The path of the control group was invalid.
///
/// This could be caused by trying to escape the control group filesystem via a string of "..".
/// This crate checks against this and operations will fail with this error.
InvalidPath,
}
#[doc(hidden)]
#[derive(Eq, PartialEq, Debug)]
pub enum Controllers {
@@ -147,32 +172,47 @@ pub trait Controller {
}
#[doc(hidden)]
fn open_path(self: &Self, p: &str, w: bool) -> Option<File> {
fn open_path(self: &Self, p: &str, w: bool) -> Result<File, CgroupError> {
let mut path = self.get_path().clone();
path.push(p);
if !self.verify_path() {
return None;
return Err(CgroupError::InvalidPath);
}
if w {
match File::create(&path) {
Err(_) => return None,
Ok(file) => return Some(file),
Err(e) => return Err(CgroupError::WriteError(e)),
Ok(file) => return Ok(file),
}
} else {
match File::open(&path) {
Err(_) => return None,
Ok(file) => return Some(file),
Err(e) => return Err(CgroupError::ReadError(e)),
Ok(file) => return Ok(file),
}
}
}
/// Attach a task to this controller.
fn add_task(self: &Self, pid: &CgroupPid) {
fn add_task(self: &Self, pid: &CgroupPid) -> Result<(), CgroupError> {
self.open_path("tasks", true).and_then(|mut file| {
file.write_all(pid.pid.to_string().as_ref()).ok()
});
file.write_all(pid.pid.to_string().as_ref()).map_err(CgroupError::WriteError)
})
}
/// Get the list of tasks that this controller has.
fn tasks(self: &Self) -> Vec<CgroupPid> {
self.open_path("tasks", false).and_then(|file| {
let bf = BufReader::new(file);
let mut v = Vec::new();
for line in bf.lines() {
if let Ok(line) = line {
let n = line.trim().parse().unwrap_or(0u64);
v.push(n);
}
}
Ok(v.into_iter().map(CgroupPid::from).collect())
}).unwrap_or(vec![])
}
}
@@ -186,11 +226,17 @@ pub trait ControllIdentifier {
pub trait Hierarchy {
/// Returns what subsystems are supported by the hierarchy.
fn subsystems(self: &Self) -> Vec<Subsystem>;
/// Returns the root directory of the hierarchy.
fn root(self: &Self) -> PathBuf;
/// Return a handle to the root control group in the hierarchy.
fn root_control_group(self: &Self) -> Cgroup;
/// Checks whether a certain subsystem is supported in the hierarchy.
///
/// This is an internal function and should not be used.
#[doc(hidden)]
fn check_support(self: &Self, sub: Controllers) -> bool;
}
@@ -389,6 +435,7 @@ pub struct Resources {
/// A structure representing a `pid`. Currently implementations exist for `u64` and
/// `std::process::Child`.
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct CgroupPid {
/// The process identifier
pub pid: u64,
@@ -500,67 +547,3 @@ impl Subsystem {
}
}
}
#[cfg(test)]
mod tests {
use {Resources, PidResources, Hierarchy, Controller, Controllers, Subsystem};
use pid::{PidMax, PidController};
use cgroup::Cgroup;
#[test]
fn create_and_delete_cgroup() {
let hier = ::hierarchies::V1::new();
let cg = Cgroup::new(&hier, String::from("ltest2"), 0);
{
let pidcontroller: &PidController = cg.controller_of().unwrap();
pidcontroller.set_pid_max(PidMax::Value(1337));
assert_eq!(pidcontroller.get_pid_max(), Some(PidMax::Value(1337)));
}
cg.delete();
}
#[test]
fn test_pid_pids_current_is_zero() {
let hier = ::hierarchies::V1::new();
let cg = Cgroup::new(&hier, String::from("ltest3"), 0);
{
let pidcontroller: &PidController = cg.controller_of().unwrap();
assert_eq!(pidcontroller.get_pid_current(), 0);
}
cg.delete();
}
#[test]
fn test_pid_pids_events_is_zero() {
let hier = ::hierarchies::V1::new();
let cg = Cgroup::new(&hier, String::from("ltest4"), 0);
{
let pidcontroller: &PidController = cg.controller_of().unwrap();
assert_eq!(pidcontroller.get_pid_events(), 0);
}
cg.delete();
}
#[test]
fn test_setting_resources() {
let hier = ::hierarchies::V1::new();
let cg = Cgroup::new(&hier, String::from("ltest5"), 0);
{
let res = Resources {
pid: PidResources {
update_values: true,
maximum_number_of_processes: PidMax::Value(512),
},
..Default::default()
};
cg.apply(&res);
/* verify */
let pidcontroller: &PidController = cg.controller_of().unwrap();
assert_eq!(pidcontroller.get_pid_max(), Some(PidMax::Value(512)));
}
cg.delete();
}
}

View File

@@ -6,7 +6,8 @@ use std::path::PathBuf;
use std::io::{Write, Read};
use std::fs::File;
use {Resources, MemoryResources, Controller, Controllers, Subsystem, ControllIdentifier};
use {CgroupError, Resources, MemoryResources, Controller, Controllers, Subsystem, ControllIdentifier};
use CgroupError::*;
/// A controller that allows controlling the `memory` subsystem of a Cgroup.
///
@@ -51,7 +52,7 @@ pub struct Memory {
/// Contains various statistics about the NUMA locality of the control group's tasks.
///
/// The format of this field (as lifted from the kernel sources):
/// ```
/// ```text
/// total=<total pages> N0=<node 0 pages> N1=<node 1 pages> ...
/// file=<total file pages> N0=<node 0 pages> N1=<node 1 pages> ...
/// anon=<total anon pages> N0=<node 0 pages> N1=<node 1 pages> ...
@@ -125,12 +126,12 @@ impl Controller for MemController {
let memres: &MemoryResources = &res.memory;
if memres.update_values {
self.set_limit(memres.memory_hard_limit);
self.set_soft_limit(memres.memory_soft_limit);
self.set_kmem_limit(memres.kernel_memory_limit);
self.set_memswap_limit(memres.memory_swap_limit);
self.set_tcp_limit(memres.kernel_tcp_memory_limit);
self.set_swappiness(memres.swappiness);
let _ = self.set_limit(memres.memory_hard_limit);
let _ = self.set_soft_limit(memres.memory_soft_limit);
let _ = self.set_kmem_limit(memres.kernel_memory_limit);
let _ = self.set_memswap_limit(memres.memory_swap_limit);
let _ = self.set_tcp_limit(memres.kernel_tcp_memory_limit);
let _ = self.set_swappiness(memres.swappiness);
}
}
}
@@ -154,46 +155,29 @@ impl MemController {
pub fn memory_stat(self: &Self) -> Memory {
Memory {
fail_cnt: self.open_path("memory.failcnt", false)
.and_then(|file| read_u64_from(file))
.unwrap_or(0),
.and_then(read_u64_from).unwrap_or(0),
limit_in_bytes: self.open_path("memory.limit_in_bytes", false)
.and_then(|file| read_u64_from(file))
.unwrap_or(0),
.and_then(read_u64_from).unwrap_or(0),
usage_in_bytes: self.open_path("memory.usage_in_bytes", false)
.and_then(|file| read_u64_from(file))
.unwrap_or(0),
.and_then(read_u64_from).unwrap_or(0),
max_usage_in_bytes: self.open_path("memory.max_usage_in_bytes", false)
.and_then(|file| read_u64_from(file))
.unwrap_or(0),
.and_then(read_u64_from).unwrap_or(0),
move_charge_at_immigrate: self.open_path("memory.move_charge_at_immigrate", false)
.and_then(|file| read_u64_from(file))
.unwrap_or(0),
.and_then(read_u64_from).unwrap_or(0),
numa_stat: self.open_path("memory.numa_stat", false)
.and_then(|mut file| {
let mut string = String::new();
let _ = file.read_to_string(&mut string);
Some(string.trim().to_string())
}).unwrap_or("".to_string()),
.and_then(read_string_from).unwrap_or("".to_string()),
oom_control: self.open_path("memory.oom_control", false)
.and_then(|mut file| {
let mut string = String::new();
let _ = file.read_to_string(&mut string);
Some(string.trim().to_string())
}).unwrap_or("".to_string()),
.and_then(read_string_from).unwrap_or("".to_string()),
soft_limit_in_bytes: self.open_path("memory.soft_limit_in_bytes", false)
.and_then(|file| read_u64_from(file))
.and_then(read_u64_from)
.unwrap_or(0),
stat: self.open_path("memory.stat", false)
.and_then(|mut file| {
let mut string = String::new();
let _ = file.read_to_string(&mut string);
Some(string.trim().to_string())
}).unwrap_or("".to_string()),
.and_then(read_string_from).unwrap_or("".to_string()),
swappiness: self.open_path("memory.swappiness", false)
.and_then(|file| read_u64_from(file))
.and_then(read_u64_from)
.unwrap_or(0),
use_hierarchy: self.open_path("memory.use_hierarchy", false)
.and_then(|file| read_u64_from(file))
.and_then(read_u64_from)
.unwrap_or(0)
}
}
@@ -202,23 +186,15 @@ impl MemController {
pub fn kmem_stat(self: &Self) -> Kmem {
Kmem {
fail_cnt: self.open_path("memory.kmem.failcnt", false)
.and_then(|file| read_u64_from(file))
.unwrap_or(0),
.and_then(read_u64_from).unwrap_or(0),
limit_in_bytes: self.open_path("memory.kmem.limit_in_bytes", false)
.and_then(|file| read_u64_from(file))
.unwrap_or(0),
.and_then(read_u64_from).unwrap_or(0),
usage_in_bytes: self.open_path("memory.kmem.usage_in_bytes", false)
.and_then(|file| read_u64_from(file))
.unwrap_or(0),
.and_then(read_u64_from).unwrap_or(0),
max_usage_in_bytes: self.open_path("memory.kmem.max_usage_in_bytes", false)
.and_then(|file| read_u64_from(file))
.unwrap_or(0),
.and_then(read_u64_from).unwrap_or(0),
slabinfo: self.open_path("memory.kmem.slabinfo", false)
.and_then(|mut file| {
let mut string = String::new();
let _ = file.read_to_string(&mut string);
Some(string.trim().to_string())
}).unwrap_or("".to_string()),
.and_then(read_string_from).unwrap_or("".to_string()),
}
}
@@ -227,17 +203,13 @@ impl MemController {
pub fn kmem_tcp_stat(self: &Self) -> Tcp {
Tcp {
fail_cnt: self.open_path("memory.kmem.tcp.failcnt", false)
.and_then(|file| read_u64_from(file))
.unwrap_or(0),
.and_then(read_u64_from).unwrap_or(0),
limit_in_bytes: self.open_path("memory.kmem.tcp.limit_in_bytes", false)
.and_then(|file| read_u64_from(file))
.unwrap_or(0),
.and_then(read_u64_from).unwrap_or(0),
usage_in_bytes: self.open_path("memory.kmem.tcp.usage_in_bytes", false)
.and_then(|file| read_u64_from(file))
.unwrap_or(0),
.and_then(read_u64_from).unwrap_or(0),
max_usage_in_bytes: self.open_path("memory.kmem.tcp.max_usage_in_bytes", false)
.and_then(|file| read_u64_from(file))
.unwrap_or(0),
.and_then(read_u64_from).unwrap_or(0),
}
}
@@ -246,46 +218,42 @@ impl MemController {
pub fn memswap(self: &Self) -> MemSwap {
MemSwap {
fail_cnt: self.open_path("memory.memsw.failcnt", false)
.and_then(|file| read_u64_from(file))
.unwrap_or(0),
.and_then(read_u64_from).unwrap_or(0),
limit_in_bytes: self.open_path("memory.memsw.limit_in_bytes", false)
.and_then(|file| read_u64_from(file))
.unwrap_or(0),
.and_then(read_u64_from).unwrap_or(0),
usage_in_bytes: self.open_path("memory.memsw.usage_in_bytes", false)
.and_then(|file| read_u64_from(file))
.unwrap_or(0),
.and_then(read_u64_from).unwrap_or(0),
max_usage_in_bytes: self.open_path("memory.memsw.max_usage_in_bytes", false)
.and_then(|file| read_u64_from(file))
.unwrap_or(0),
.and_then(read_u64_from).unwrap_or(0),
}
}
/// Set the memory usage limit of the control group, in bytes.
pub fn set_limit(self: &Self, limit: u64) {
pub fn set_limit(self: &Self, limit: u64) -> Result<(), CgroupError> {
self.open_path("memory.limit_in_bytes", true).and_then(|mut file| {
file.write_all(limit.to_string().as_ref()).ok()
});
file.write_all(limit.to_string().as_ref()).map_err(CgroupError::WriteError)
})
}
/// Set the kernel memory limit of the control group, in bytes.
pub fn set_kmem_limit(self: &Self, limit: u64) {
pub fn set_kmem_limit(self: &Self, limit: u64) -> Result<(), CgroupError> {
self.open_path("memory.kmem.limit_in_bytes", true).and_then(|mut file| {
file.write_all(limit.to_string().as_ref()).ok()
});
file.write_all(limit.to_string().as_ref()).map_err(CgroupError::WriteError)
})
}
/// Set the memory+swap limit of the control group, in bytes.
pub fn set_memswap_limit(self: &Self, limit: u64) {
pub fn set_memswap_limit(self: &Self, limit: u64) -> Result<(), CgroupError> {
self.open_path("memory.memsw.limit_in_bytes", true).and_then(|mut file| {
file.write_all(limit.to_string().as_ref()).ok()
});
file.write_all(limit.to_string().as_ref()).map_err(CgroupError::WriteError)
})
}
/// Set how much kernel memory can be used for TCP-related buffers by the control group.
pub fn set_tcp_limit(self: &Self, limit: u64) {
pub fn set_tcp_limit(self: &Self, limit: u64) -> Result<(), CgroupError> {
self.open_path("memory.kmem.tcp.limit_in_bytes", true).and_then(|mut file| {
file.write_all(limit.to_string().as_ref()).ok()
});
file.write_all(limit.to_string().as_ref()).map_err(CgroupError::WriteError)
})
}
@@ -293,10 +261,10 @@ impl MemController {
///
/// This limit is enforced when the system is nearing OOM conditions. Contrast this with the
/// hard limit, which is _always_ enforced.
pub fn set_soft_limit(self: &Self, limit: u64) {
pub fn set_soft_limit(self: &Self, limit: u64) -> Result<(), CgroupError> {
self.open_path("memory.soft_limit_in_bytes", true).and_then(|mut file| {
file.write_all(limit.to_string().as_ref()).ok()
});
file.write_all(limit.to_string().as_ref()).map_err(CgroupError::WriteError)
})
}
@@ -304,10 +272,10 @@ impl MemController {
/// group.
///
/// Note that a value of zero does not imply that the process will not be swapped out.
pub fn set_swappiness(self: &Self, swp: u64) {
pub fn set_swappiness(self: &Self, swp: u64) -> Result<(), CgroupError> {
self.open_path("memory.swappiness", true).and_then(|mut file| {
file.write_all(swp.to_string().as_ref()).ok()
});
file.write_all(swp.to_string().as_ref()).map_err(CgroupError::WriteError)
})
}
}
@@ -331,8 +299,18 @@ impl<'a> From<&'a Subsystem> for &'a MemController {
}
}
fn read_u64_from(mut file: File) -> Option<u64> {
fn read_u64_from(mut file: File) -> Result<u64, CgroupError> {
let mut string = String::new();
let _ = file.read_to_string(&mut string);
string.trim().parse().ok()
match file.read_to_string(&mut string) {
Ok(_) => string.trim().parse().map_err(|_| ParseError),
Err(e) => Err(CgroupError::ReadError(e)),
}
}
fn read_string_from(mut file: File) -> Result<String, CgroupError> {
let mut string = String::new();
match file.read_to_string(&mut string) {
Ok(_) => Ok(string.trim().to_string()),
Err(e) => Err(CgroupError::ReadError(e)),
}
}

View File

@@ -6,7 +6,8 @@ use std::path::PathBuf;
use std::io::{Read, Write};
use std::fs::File;
use {NetworkResources, Controllers, Controller, Resources, ControllIdentifier, Subsystem};
use {CgroupError, NetworkResources, Controllers, Controller, Resources, ControllIdentifier, Subsystem};
use CgroupError::*;
/// A controller that allows controlling the `net_cls` subsystem of a Cgroup.
///
@@ -30,7 +31,7 @@ impl Controller for NetClsController {
let res: &NetworkResources = &res.network;
if res.update_values {
self.set_class(res.class_id);
let _ = self.set_class(res.class_id);
}
}
}
@@ -55,10 +56,12 @@ impl<'a> From<&'a Subsystem> for &'a NetClsController {
}
}
fn read_u64_from(mut file: File) -> Option<u64> {
fn read_u64_from(mut file: File) -> Result<u64, CgroupError> {
let mut string = String::new();
let _ = file.read_to_string(&mut string);
string.trim().parse().ok()
match file.read_to_string(&mut string) {
Ok(_) => string.trim().parse().map_err(|_| ParseError),
Err(e) => Err(CgroupError::ReadError(e)),
}
}
impl NetClsController {
@@ -73,17 +76,17 @@ impl NetClsController {
}
/// Set the network class id of the outgoing packets of the control group's tasks.
pub fn set_class(self: &Self, class: u64) {
pub fn set_class(self: &Self, class: u64) -> Result<(), CgroupError> {
self.open_path("net_cls.classid", true).and_then(|mut file| {
let s = format!("{:#08X}", class);
file.write_all(s.as_ref()).ok()
});
file.write_all(s.as_ref()).map_err(CgroupError::WriteError)
})
}
/// Get the network class id of the outgoing packets of the control group's tasks.
pub fn get_class(self: &Self) -> u64 {
pub fn get_class(self: &Self) -> Result<u64, CgroupError> {
self.open_path("net_cls.classid", false).and_then(|file| {
read_u64_from(file)
}).unwrap_or(0u64)
})
}
}

View File

@@ -7,7 +7,8 @@ use std::io::{BufReader, BufRead, Write, Read};
use std::fs::File;
use std::collections::HashMap;
use {NetworkResources, Controllers, Controller, Resources, ControllIdentifier, Subsystem};
use {CgroupError, NetworkResources, Controllers, Controller, Resources, ControllIdentifier, Subsystem};
use CgroupError::*;
/// A controller that allows controlling the `net_prio` subsystem of a Cgroup.
///
@@ -32,7 +33,7 @@ impl Controller for NetPrioController {
if res.update_values {
for i in &res.priorities {
self.set_if_prio(&i.name, i.priority);
let _ = self.set_if_prio(&i.name, i.priority);
}
}
}
@@ -58,10 +59,12 @@ impl<'a> From<&'a Subsystem> for &'a NetPrioController {
}
}
fn read_u64_from(mut file: File) -> Option<u64> {
fn read_u64_from(mut file: File) -> Result<u64, CgroupError> {
let mut string = String::new();
let _ = file.read_to_string(&mut string);
string.trim().parse().ok()
match file.read_to_string(&mut string) {
Ok(_) => string.trim().parse().map_err(|_| ParseError),
Err(e) => Err(CgroupError::ReadError(e)),
}
}
impl NetPrioController {
@@ -83,24 +86,39 @@ impl NetPrioController {
}
/// A map of priorities for each network interface.
pub fn ifpriomap(self: &Self) -> HashMap<String, u64> {
self.open_path("net_prio.ifpriomap", false)
.and_then(|file| {
let bf = BufReader::new(file);
Some(bf.lines().map(|line| {
pub fn ifpriomap(self: &Self) -> Result<HashMap<String, u64>, CgroupError> {
self.open_path("net_prio.ifpriomap", false) .and_then(|file| {
let bf = BufReader::new(file);
bf.lines().fold(Ok(HashMap::new()), |acc, line| {
if acc.is_err() {
acc
} else {
let mut acc = acc.unwrap();
let l = line.unwrap();
let mut sp = l.split_whitespace();
(sp.nth(0).unwrap().to_string(),
sp.nth(1).unwrap().trim().parse().unwrap())
}).collect())
}).unwrap_or(HashMap::new())
let ifname = sp.nth(0);
let ifprio = sp.nth(1);
if ifname.is_none() || ifprio.is_none() {
Err(CgroupError::ParseError)
} else {
let ifname = ifname.unwrap();
let ifprio = ifprio.unwrap().trim().parse();
if ifprio.is_err() {
Err(CgroupError::ParseError)
} else {
acc.insert(ifname.to_string(), ifprio.unwrap());
Ok(acc)
}
}
}
})
})
}
/// Set the priority of the network traffic on `eif` to be `prio`.
pub fn set_if_prio(self: &Self, eif: &String, prio: u64) {
self.open_path("net_prio.ifpriomap", true)
.and_then(|mut file| {
Some(file.write_all(format!("{} {}", eif, prio).as_ref()))
});
pub fn set_if_prio(self: &Self, eif: &String, prio: u64) -> Result<(), CgroupError> {
self.open_path("net_prio.ifpriomap", true).and_then(|mut file| {
file.write_all(format!("{} {}", eif, prio).as_ref()).map_err(CgroupError::WriteError)
})
}
}

View File

@@ -4,8 +4,10 @@
//! [Documentation/cgroups-v1/pids.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/pids.txt)
use std::path::PathBuf;
use std::io::{Write, Read};
use std::fs::File;
use {Resources, PidResources, Controller, ControllIdentifier, Subsystem, Controllers};
use {CgroupError, Resources, PidResources, Controller, ControllIdentifier, Subsystem, Controllers};
use CgroupError::*;
/// A controller that allows controlling the `pids` subsystem of a Cgroup.
#[derive(Debug, Clone)]
@@ -42,7 +44,7 @@ impl Controller for PidController {
if pidres.update_values {
/* apply pid_max */
self.set_pid_max(pidres.maximum_number_of_processes);
let _ = self.set_pid_max(pidres.maximum_number_of_processes);
}
}
}
@@ -73,6 +75,14 @@ impl<'a> From<&'a Subsystem> for &'a PidController {
}
}
fn read_u64_from(mut file: File) -> Result<u64, CgroupError> {
let mut string = String::new();
match file.read_to_string(&mut string) {
Ok(_) => string.trim().parse().map_err(|_| ParseError),
Err(e) => Err(CgroupError::ReadError(e)),
}
}
impl PidController {
/// Constructors a new `PidController` instance, with `oroot` serving as the controller's root
/// directory.
@@ -86,32 +96,44 @@ impl PidController {
}
/// The number of times `fork` failed because the limit was hit.
pub fn get_pid_events(self: &Self) -> i64 {
pub fn get_pid_events(self: &Self) -> Result<u64, CgroupError> {
self.open_path("pids.events", false).and_then(|mut file| {
let mut string = String::new();
let _ = file.read_to_string(&mut string);
Some(string.split_whitespace().nth(1).unwrap().parse().unwrap_or(0))
}).unwrap()
match file.read_to_string(&mut string) {
Ok(_) => {
match string.split_whitespace().nth(1) {
Some(elem) => match elem.parse() {
Ok(val) => Ok(val),
Err(_) => Err(CgroupError::ParseError),
},
None => Err(CgroupError::ParseError),
}
},
Err(e) => Err(CgroupError::ReadError(e)),
}
})
}
/// The number of processes currently.
pub fn get_pid_current(self: &Self) -> i64 {
self.open_path("pids.current", false).and_then(|mut file| {
let mut string = String::new();
let _ = file.read_to_string(&mut string);
Some(string.trim().parse().unwrap_or(0))
}).unwrap()
pub fn get_pid_current(self: &Self) -> Result<u64, CgroupError> {
self.open_path("pids.current", false).and_then(read_u64_from)
}
/// The maximum number of processes that can exist at one time in the control group.
pub fn get_pid_max(self: &Self) -> Option<PidMax> {
pub fn get_pid_max(self: &Self) -> Result<PidMax, CgroupError> {
self.open_path("pids.max", false).and_then(|mut file| {
let mut string = String::new();
let _ = file.read_to_string(&mut string);
if string.trim() == "max" {
Some(PidMax::Max)
} else {
Some(PidMax::Value(string.trim().parse().unwrap_or(0)))
let res = file.read_to_string(&mut string);
match res {
Ok(_) => if string.trim() == "max" {
Ok(PidMax::Max)
} else {
match string.trim().parse() {
Ok(val) => Ok(PidMax::Value(val)),
Err(_) => Err(CgroupError::ParseError),
}
},
Err(e) => Err(CgroupError::ReadError(e)),
}
})
}
@@ -121,17 +143,16 @@ impl PidController {
/// Note that if `get_pid_current()` returns a higher number than what you
/// are about to set (`max_pid`), then no processess will be killed. Additonally, attaching
/// extra processes to a control group disregards the limit.
pub fn set_pid_max(self: &Self, max_pid: PidMax) {
pub fn set_pid_max(self: &Self, max_pid: PidMax) -> Result<(), CgroupError> {
self.open_path("pids.max", true).and_then(|mut file| {
let string_to_write = match max_pid {
PidMax::Max => "max".to_string(),
PidMax::Value(num) => num.to_string(),
};
match file.write_all(string_to_write.as_ref()) {
Ok(_) => (),
Err(e) => println!("error {:?}", e),
Ok(_) => Ok(()),
Err(e) => Err(CgroupError::WriteError(e)),
}
Some(0i64)
});
})
}
}

View File

@@ -6,7 +6,7 @@ use std::path::PathBuf;
use std::io::{Write, Read};
use std::fs::File;
use {Controllers, Controller, Resources, ControllIdentifier, Subsystem};
use {CgroupError, Controllers, Controller, Resources, ControllIdentifier, Subsystem};
/// A controller that allows controlling the `rdma` subsystem of a Cgroup.
///
@@ -48,10 +48,12 @@ impl<'a> From<&'a Subsystem> for &'a RdmaController {
}
}
fn read_string_from(mut file: File) -> Option<String> {
fn read_string_from(mut file: File) -> Result<String, CgroupError> {
let mut string = String::new();
let _ = file.read_to_string(&mut string);
Some(string.trim().to_string())
match file.read_to_string(&mut string) {
Ok(_) => Ok(string.trim().to_string()),
Err(e) => Err(CgroupError::ReadError(e)),
}
}
impl RdmaController {
@@ -66,17 +68,15 @@ impl RdmaController {
}
/// Returns the current usage of RDMA/IB specific resources.
pub fn current(self: &Self) -> String {
pub fn current(self: &Self) -> Result<String, CgroupError> {
self.open_path("rdma.current", false)
.and_then(read_string_from)
.unwrap_or("".to_string())
}
/// Set a maximum usage for each RDMA/IB resource.
pub fn set_max(self: &Self, max: &String) {
self.open_path("rdma.max", true)
.and_then(|mut file| {
file.write_all(max.as_ref()).ok()
});
pub fn set_max(self: &Self, max: &String) -> Result<(), CgroupError> {
self.open_path("rdma.max", true).and_then(|mut file| {
file.write_all(max.as_ref()).map_err(CgroupError::WriteError)
})
}
}

29
tests/cgroup.rs Normal file
View File

@@ -0,0 +1,29 @@
//! Simple unit tests about the control groups system.
extern crate cgroups;
use cgroups::{Cgroup, CgroupPid};
extern crate nix;
extern crate libc;
#[test]
fn test_tasks_iterator() {
let hier = cgroups::hierarchies::V1::new();
let pid = libc::pid_t::from(nix::unistd::getpid()) as u64;
let cg = Cgroup::new(&hier, String::from("test_tasks_iterator"));
{
// Add a task to the control group.
cg.add_task(CgroupPid::from(pid));
let mut tasks = cg.tasks().into_iter();
// Verify that the task is indeed in the control group
assert_eq!(tasks.next(), Some(CgroupPid::from(pid)));
assert_eq!(tasks.next(), None);
// Now, try removing it.
cg.remove_task(CgroupPid::from(pid));
tasks = cg.tasks().into_iter();
// Verify that it was indeed removed.
assert_eq!(tasks.next(), None);
}
cg.delete();
}

95
tests/pids.rs Normal file
View File

@@ -0,0 +1,95 @@
//! Integration tests about the pids subsystem
extern crate cgroups;
use cgroups::{CgroupError, CgroupPid, Cgroup, Resources, PidResources};
use cgroups::pid::{PidController, PidMax};
use cgroups::Controller;
extern crate nix;
use nix::unistd::{Pid, fork, ForkResult};
use nix::sys::wait::{waitpid, WaitStatus};
extern crate libc;
use libc::pid_t;
use std::thread;
#[test]
fn create_and_delete_cgroup() {
let hier = cgroups::hierarchies::V1::new();
let cg = Cgroup::new(&hier, String::from("create_and_delete_cgroup"));
{
let pidcontroller: &PidController = cg.controller_of().unwrap();
pidcontroller.set_pid_max(PidMax::Value(1337));
assert_eq!(pidcontroller.get_pid_max(), Some(PidMax::Value(1337)));
}
cg.delete();
}
#[test]
fn test_pids_current_is_zero() {
let hier = cgroups::hierarchies::V1::new();
let cg = Cgroup::new(&hier, String::from("test_pids_current_is_zero"));
{
let pidcontroller: &PidController = cg.controller_of().unwrap();
assert_eq!(pidcontroller.get_pid_current(), 0);
}
cg.delete();
}
#[test]
fn test_pids_events_is_zero() {
let hier = cgroups::hierarchies::V1::new();
let cg = Cgroup::new(&hier, String::from("test_pids_events_is_zero"));
{
let pidcontroller: &PidController = cg.controller_of().unwrap();
assert_eq!(pidcontroller.get_pid_events(), 0);
}
cg.delete();
}
#[test]
fn test_pid_events_is_not_zero() {
let hier = cgroups::hierarchies::V1::new();
let cg = Cgroup::new(&hier, String::from("test_pid_events_is_not_zero"));
{
let pids: &PidController = cg.controller_of().unwrap();
let before = pids.get_pid_events();
match fork() {
Ok(ForkResult::Parent { child, .. }) => {
// move the process into the control group
pids.add_task(&(pid_t::from(child) as u64).into());
println!("added task to cg: {:?}", child);
// Set limit to one
pids.set_pid_max(PidMax::Value(1));
println!("err = {:?}", pids.get_pid_max());
// wait on the child
let res = waitpid(child, None);
if let Ok(WaitStatus::Exited(_, e)) = res {
assert_eq!(e, 0i32);
} else {
panic!("found result: {:?}", res);
}
// Check pids.events
assert_eq!(pids.get_pid_events(), before + 1);
},
Ok(ForkResult::Child) => {
loop {
if pids.get_pid_max() == Some(PidMax::Value(1)) {
if let Err(_) = fork() {
unsafe { libc::exit(0) };
} else {
unsafe { libc::exit(1) };
}
}
}
},
Err(_) => panic!("failed to fork"),
}
}
cg.delete();
}

26
tests/resources.rs Normal file
View File

@@ -0,0 +1,26 @@
//! Integration test about setting resources using `apply()`
extern crate cgroups;
use cgroups::{Cgroup, Resources, PidResources};
use cgroups::pid::{PidController, PidMax};
#[test]
fn pid_resources() {
let hier = cgroups::hierarchies::V1::new();
let cg = Cgroup::new(&hier, String::from("pid_resources"));
{
let res = Resources {
pid: PidResources {
update_values: true,
maximum_number_of_processes: PidMax::Value(512),
},
..Default::default()
};
cg.apply(&res);
/* verify */
let pidcontroller: &PidController = cg.controller_of().unwrap();
assert_eq!(pidcontroller.get_pid_max(), Some(PidMax::Value(512)));
}
cg.delete();
}

19
tools/create_cgroup.sh Executable file
View File

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

14
tools/delete_cgroup.sh Executable file
View File

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