Compare commits

...

47 Commits

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

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

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

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

2
.cargo/config Normal file
View File

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

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,13 @@ 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"
authors = ["Levente Kurusa <lkurusa@acm.org>"]
license = "MIT OR Apache-2.0"
version = "0.1.1-alpha.0"
authors = ["Levente Kurusa <lkurusa@acm.org>", "Sam Wilson <tecywiz121@hotmail.com>"]
[dependencies]
log = "0.4"
[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,56 @@
# cgroups-rs
# cgroups-rs ![Build](https://travis-ci.org/levex/cgroups-rs.svg?branch=master)
Native Rust library for managing control groups under Linux
Right now the crate only support the original, V1 hierarchy, however support
is planned for the Unified hierarchy.
# Examples
## Create a control group using the builder pattern
``` rust
// Acquire a handle for the V1 cgroup hierarchy.
let hier = ::hierarchies::V1::new();
// Use the builder pattern (see the documentation to create the control group)
//
// This creates a control group named "example" in the V1 hierarchy.
let cg: Cgroup = CgroupBuilder::new("example", &v1)
.cpu()
.shares(85)
.done()
.build();
// Now `cg` is a control group that gets 85% of the CPU time in relative to
// other control groups.
// Get a handle to the CPU controller.
let cpus: &CpuController = cg.controller_of().unwrap();
cpus.add_task(1234u64);
// [...]
// Finally, clean up and delete the control group.
cg.delete();
// Note that `Cgroup` does not implement `Drop` and therefore when the
// structure is dropped, the Cgroup will stay around. This is because, later
// you can then re-create the `Cgroup` using `load()`. We aren't too set on
// this behavior, so it might change in the feature. Rest assured, it will be a
// major version change.
```
# Disclaimer
This crate is licensed under:
- MIT License (see LICENSE-MIT); or
- Apache 2.0 License (see LICENSE-Apache-2.0),
at your option.
Please note that this crate is under heavy development, we will use sematic
versioning, but during the `0.0.*` phase, no guarantees are made about
backwards compatibility.
Regardless, check back often and thanks for taking a look!

View File

@@ -1,12 +1,17 @@
//! This module contains the implementation of the `blkio` cgroup subsystem.
//!
//!
//! See the Kernel's documentation for more information about this subsystem, found at:
//! [Documentation/cgroup-v1/blkio-controller.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/blkio-controller.txt)
use std::path::PathBuf;
use std::io::{Read, Write};
use std::fs::File;
use std::io::{Read, Write};
use std::path::PathBuf;
use {BlkIoResources, Controllers, Controller, Resources, ControllIdentifier, Subsystem};
use error::*;
use error::ErrorKind::*;
use {
BlkIoResources, ControllIdentifier, ControllerInternal, Controllers, Resources, Subsystem,
};
/// A controller that allows controlling the `blkio` subsystem of a Cgroup.
///
@@ -18,117 +23,283 @@ pub struct BlkIoController {
path: PathBuf,
}
#[derive(Eq, PartialEq, Debug)]
/// Per-device information
pub struct BlkIoData {
/// The major number of the device.
pub major: i16,
/// The minor number of the device.
pub minor: i16,
/// The data that is associated with the device.
pub data: u64,
}
#[derive(Eq, PartialEq, Debug)]
/// Per-device activity from the control group.
pub struct IoService {
/// The major number of the device.
pub major: i16,
/// The minor number of the device.
pub minor: i16,
/// How many items were read from the device.
pub read: u64,
/// How many items were written to the device.
pub write: u64,
/// How many items were synchronously transferred.
pub sync: u64,
/// How many items were asynchronously transferred.
pub async: u64,
/// Total number of items transferred.
pub total: u64,
}
fn parse_io_service(s: String) -> Result<Vec<IoService>> {
s.lines()
.filter(|x| x.split_whitespace().collect::<Vec<_>>().len() == 3)
.map(|x| {
let mut spl = x.split_whitespace();
(spl.nth(0).unwrap(), spl.nth(0).unwrap(), spl.nth(0).unwrap())
})
.map(|(a, b, c)| {
let mut spl = a.split(":");
(spl.nth(0).unwrap(), spl.nth(0).unwrap(), b, c)
})
.collect::<Vec<_>>()
.chunks(5)
.map(|x| {
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(),
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)
}
})
}
fn parse_io_service_total(s: String) -> Result<u64> {
s.lines()
.filter(|x| x.split_whitespace().collect::<Vec<_>>().len() == 2)
.fold(Err(Error::new(ParseError)), |_, x| {
match x.split_whitespace().collect::<Vec<_>>().as_slice() {
["Total", val] => val.parse::<u64>().map_err(|_| Error::new(ParseError)),
_ => Err(Error::new(ParseError)),
}
})
}
fn parse_blkio_data(s: String) -> Result<Vec<BlkIoData>> {
let r = s
.chars()
.map(|x| if x == ':' { ' ' } else { x })
.collect::<String>();
let r = r
.lines()
.flat_map(|x| x.split_whitespace())
.collect::<Vec<_>>();
let r = r.chunks(3).collect::<Vec<_>>();
let mut res = Vec::new();
let err = r.iter().try_for_each(|x| match x {
[major, minor, data] => {
res.push(BlkIoData {
major: major.parse::<i16>().unwrap(),
minor: minor.parse::<i16>().unwrap(),
data: data.parse::<u64>().unwrap(),
});
Ok(())
}
_ => Err(Error::new(ParseError)),
});
if err.is_err() {
return Err(Error::new(ParseError));
} else {
return Ok(res);
}
}
/// Current state and statistics about how throttled are the block devices when accessed from the
/// controller's control group.
#[derive(Debug)]
pub struct BlkIoThrottle {
/// Statistics about the bytes transferred between the block devices by the tasks in this
/// control group.
pub io_service_bytes: Vec<IoService>,
/// Total amount of bytes transferred to and from the block devices.
pub io_service_bytes: String,
pub io_service_bytes_total: u64,
/// Same as `io_service_bytes`, but contains all descendant control groups.
pub io_service_bytes_recursive: String,
pub io_service_bytes_recursive: Vec<IoService>,
/// Total amount of bytes transferred to and from the block devices, including all descendant
/// control groups.
pub io_service_bytes_recursive_total: u64,
/// The number of I/O operations performed on the devices as seen by the throttling policy.
pub io_serviced: String,
pub io_serviced: Vec<IoService>,
/// The total number of I/O operations performed on the devices as seen by the throttling
/// policy.
pub io_serviced_total: u64,
/// Same as `io_serviced`, but contains all descendant control groups.
pub io_serviced_recursive: String,
pub io_serviced_recursive: Vec<IoService>,
/// Same as `io_serviced`, but contains all descendant control groups and contains only the
/// total amount.
pub io_serviced_recursive_total: u64,
/// The upper limit of bytes per second rate of read operation on the block devices by the
/// control group's tasks.
pub read_bps_device: String,
pub read_bps_device: Vec<BlkIoData>,
/// The upper limit of I/O operation per second, when said operation is a read operation.
pub read_iops_device: String,
pub read_iops_device: Vec<BlkIoData>,
/// The upper limit of bytes per second rate of write operation on the block devices by the
/// control group's tasks.
pub write_bps_device: String,
pub write_bps_device: Vec<BlkIoData>,
/// The upper limit of I/O operation per second, when said operation is a write operation.
pub write_iops_device: String,
pub write_iops_device: Vec<BlkIoData>,
}
/// Statistics and state of the block devices.
#[derive(Debug)]
pub struct BlkIo {
/// The number of BIOS requests merged into I/O requests by the control group's tasks.
pub io_merged: String,
pub io_merged: Vec<IoService>,
/// Same as `io_merged`, but only reports the total number.
pub io_merged_total: u64,
/// Same as `io_merged`, but contains all descendant control groups.
pub io_merged_recursive: String,
pub io_merged_recursive: Vec<IoService>,
/// Same as `io_merged_recursive`, but only reports the total number.
pub io_merged_recursive_total: u64,
/// The number of requests queued for I/O operations by the tasks of the control group.
pub io_queued: String,
pub io_queued: Vec<IoService>,
/// Same as `io_queued`, but only reports the total number.
pub io_queued_total: u64,
/// Same as `io_queued`, but contains all descendant control groups.
pub io_queued_recursive: String,
/// The number of bytes transferred from and to the block device (as seen by the CFQ I/O
/// scheduler).
pub io_service_bytes: String,
pub io_queued_recursive: Vec<IoService>,
/// Same as `io_queued_recursive`, but contains all descendant control groups.
pub io_queued_recursive_total: u64,
/// The number of bytes transferred from and to the block device (as seen by the CFQ I/O scheduler).
pub io_service_bytes: Vec<IoService>,
/// Same as `io_service_bytes`, but contains all descendant control groups.
pub io_service_bytes_recursive: String,
pub io_service_bytes_total: u64,
/// Same as `io_service_bytes`, but contains all descendant control groups.
pub io_service_bytes_recursive: Vec<IoService>,
/// Total amount of bytes transferred between the tasks and block devices, including the
/// descendant control groups' numbers.
pub io_service_bytes_recursive_total: u64,
/// The number of I/O operations (as seen by the CFQ I/O scheduler) between the devices and the
/// control group's tasks.
pub io_serviced: String,
pub io_serviced: Vec<IoService>,
/// The total number of I/O operations performed on the devices as seen by the throttling
/// policy.
pub io_serviced_total: u64,
/// Same as `io_serviced`, but contains all descendant control groups.
pub io_serviced_recursive: String,
pub io_serviced_recursive: Vec<IoService>,
/// Same as `io_serviced`, but contains all descendant control groups and contains only the
/// total amount.
pub io_serviced_recursive_total: u64,
/// The total time spent between dispatch and request completion for I/O requests (as seen by
/// the CFQ I/O scheduler) by the control group's tasks.
pub io_service_time: String,
pub io_service_time: Vec<IoService>,
/// Same as `io_service_time`, but contains all descendant control groups and contains only the
/// total amount.
pub io_service_time_total: u64,
/// Same as `io_service_time`, but contains all descendant control groups.
pub io_service_time_recursive: String,
pub io_service_time_recursive: Vec<IoService>,
/// Same as `io_service_time_recursive`, but contains all descendant control groups and only
/// the total amount.
pub io_service_time_recursive_total: u64,
/// Total amount of time spent waiting for a free slot in the CFQ I/O scheduler's queue.
pub io_wait_time: String,
pub io_wait_time: Vec<IoService>,
/// Same as `io_wait_time`, but only reports the total amount.
pub io_wait_time_total: u64,
/// Same as `io_wait_time`, but contains all descendant control groups.
pub io_wait_time_recursive: String,
pub io_wait_time_recursive: Vec<IoService>,
/// Same as `io_wait_time_recursive`, but only reports the total amount.
pub io_wait_time_recursive_total: u64,
/// How much weight do the control group's tasks have when competing against the descendant
/// control group's tasks.
pub leaf_weight: u64,
/// Same as `leaf_weight`, but per-block-device.
pub leaf_weight_device: String,
pub leaf_weight_device: Vec<BlkIoData>,
/// Total number of sectors transferred between the block devices and the control group's
/// tasks.
pub sectors: String,
pub sectors: Vec<BlkIoData>,
/// Same as `sectors`, but contains all descendant control groups.
pub sectors_recursive: String,
pub sectors_recursive: Vec<BlkIoData>,
/// Similar statistics, but as seen by the throttle policy.
pub throttle: BlkIoThrottle,
/// The time the control group had access to the I/O devices.
pub time: String,
pub time: Vec<BlkIoData>,
/// Same as `time`, but contains all descendant control groups.
pub time_recursive: String,
pub time_recursive: Vec<BlkIoData>,
/// The weight of this control group.
pub weight: u64,
/// Same as `weight`, but per-block-device.
pub weight_device: String,
pub weight_device: Vec<BlkIoData>,
}
impl Controller for BlkIoController {
fn control_type(self: &Self) -> Controllers { Controllers::BlkIo }
fn get_path<'a>(self: &'a Self) -> &'a PathBuf { &self.path }
fn get_path_mut<'a>(self: &'a mut Self) -> &'a mut PathBuf { &mut self.path }
fn get_base<'a>(self: &'a Self) -> &'a PathBuf { &self.base }
impl ControllerInternal for BlkIoController {
fn control_type(&self) -> Controllers {
Controllers::BlkIo
}
fn get_path(&self) -> &PathBuf {
&self.path
}
fn get_path_mut(&mut self) -> &mut PathBuf {
&mut self.path
}
fn get_base(&self) -> &PathBuf {
&self.base
}
fn apply(self: &Self, res: &Resources) {
/* get the resources that apply to this controller */
fn apply(&self, res: &Resources) -> Result<()> {
// get the resources that apply to this controller
let res: &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!("{}:{} {}",
dev.major, dev.minor, dev.weight));
let _ = self.set_weight_for_device(dev.major, dev.minor, dev.weight as u64);
let _ = self.set_leaf_weight_for_device(dev.major, dev.minor, dev.leaf_weight as u64);
}
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);
}
}
Ok(())
}
}
@@ -146,22 +317,26 @@ impl<'a> From<&'a Subsystem> for &'a BlkIoController {
_ => {
assert_eq!(1, 0);
::std::mem::uninitialized()
},
}
}
}
}
}
fn read_string_from(mut file: File) -> Option<String> {
fn read_string_from(mut file: File) -> Result<String> {
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(Error::with_cause(ReadFailed, e)),
}
}
fn read_u64_from(mut file: File) -> Option<u64> {
fn read_u64_from(mut file: File) -> Result<u64> {
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(|e| Error::with_cause(ParseError, e)),
Err(e) => Err(Error::with_cause(ReadFailed, e)),
}
}
impl BlkIoController {
@@ -177,161 +352,492 @@ impl BlkIoController {
/// Gathers statistics about and reports the state of the block devices used by the control
/// group's tasks.
pub fn blkio(self: &Self) -> BlkIo {
pub fn blkio(&self) -> BlkIo {
BlkIo {
io_merged: self.open_path("blkio.io_merged", false).and_then(|file| {
read_string_from(file)
}).unwrap_or("".to_string()),
io_merged_recursive: self.open_path("blkio.io_merged_recursive", false).and_then(|file| {
read_string_from(file)
}).unwrap_or("".to_string()),
io_queued: self.open_path("blkio.io_queued", false).and_then(|file| {
read_string_from(file)
}).unwrap_or("".to_string()),
io_queued_recursive: self.open_path("blkio.io_queued_recursive", false).and_then(|file| {
read_string_from(file)
}).unwrap_or("".to_string()),
io_service_bytes: self.open_path("blkio.io_service_bytes", false).and_then(|file| {
read_string_from(file)
}).unwrap_or("".to_string()),
io_service_bytes_recursive: self.open_path("blkio.io_service_bytes_recursive", false).and_then(|file| {
read_string_from(file)
}).unwrap_or("".to_string()),
io_serviced: self.open_path("blkio.io_serviced", false).and_then(|file| {
read_string_from(file)
}).unwrap_or("".to_string()),
io_serviced_recursive: self.open_path("blkio.io_serviced_recursive", false).and_then(|file| {
read_string_from(file)
}).unwrap_or("".to_string()),
io_service_time: self.open_path("blkio.io_service_time", false).and_then(|file| {
read_string_from(file)
}).unwrap_or("".to_string()),
io_service_time_recursive: self.open_path("blkio.io_service_time_recursive", false).and_then(|file| {
read_string_from(file)
}).unwrap_or("".to_string()),
io_wait_time: self.open_path("blkio.io_wait_time", false).and_then(|file| {
read_string_from(file)
}).unwrap_or("".to_string()),
io_wait_time_recursive: self.open_path("blkio.io_wait_time_recursive", false).and_then(|file| {
read_string_from(file)
}).unwrap_or("".to_string()),
leaf_weight: self.open_path("blkio.leaf_weight", false).and_then(|file| {
read_u64_from(file)
}).unwrap_or(0u64),
leaf_weight_device: self.open_path("blkio.leaf_weight_device", false).and_then(|file| {
read_string_from(file)
}).unwrap_or("".to_string()),
sectors: self.open_path("blkio.sectors", false).and_then(|file| {
read_string_from(file)
}).unwrap_or("".to_string()),
sectors_recursive: self.open_path("blkio.sectors_recursive", false).and_then(|file| {
read_string_from(file)
}).unwrap_or("".to_string()),
io_merged: self
.open_path("blkio.io_merged", false)
.and_then(read_string_from)
.and_then(parse_io_service)
.unwrap_or(Vec::new()),
io_merged_total: self
.open_path("blkio.io_merged", false)
.and_then(read_string_from)
.and_then(parse_io_service_total)
.unwrap_or(0),
io_merged_recursive: self
.open_path("blkio.io_merged_recursive", false)
.and_then(read_string_from)
.and_then(parse_io_service)
.unwrap_or(Vec::new()),
io_merged_recursive_total: self
.open_path("blkio.io_merged_recursive", false)
.and_then(read_string_from)
.and_then(parse_io_service_total)
.unwrap_or(0),
io_queued: self
.open_path("blkio.io_queued", false)
.and_then(read_string_from)
.and_then(parse_io_service)
.unwrap_or(Vec::new()),
io_queued_total: self
.open_path("blkio.io_queued", false)
.and_then(read_string_from)
.and_then(parse_io_service_total)
.unwrap_or(0),
io_queued_recursive: self
.open_path("blkio.io_queued_recursive", false)
.and_then(read_string_from)
.and_then(parse_io_service)
.unwrap_or(Vec::new()),
io_queued_recursive_total: self
.open_path("blkio.io_queued_recursive", false)
.and_then(read_string_from)
.and_then(parse_io_service_total)
.unwrap_or(0),
io_service_bytes: self
.open_path("blkio.io_service_bytes", false)
.and_then(read_string_from)
.and_then(parse_io_service)
.unwrap_or(Vec::new()),
io_service_bytes_total: self
.open_path("blkio.io_service_bytes", false)
.and_then(read_string_from)
.and_then(parse_io_service_total)
.unwrap_or(0),
io_service_bytes_recursive: self
.open_path("blkio.io_service_bytes_recursive", false)
.and_then(read_string_from)
.and_then(parse_io_service)
.unwrap_or(Vec::new()),
io_service_bytes_recursive_total: self
.open_path("blkio.io_service_bytes_recursive", false)
.and_then(read_string_from)
.and_then(parse_io_service_total)
.unwrap_or(0),
io_serviced: self
.open_path("blkio.io_serviced", false)
.and_then(read_string_from)
.and_then(parse_io_service)
.unwrap_or(Vec::new()),
io_serviced_total: self
.open_path("blkio.io_serviced", false)
.and_then(read_string_from)
.and_then(parse_io_service_total)
.unwrap_or(0),
io_serviced_recursive: self
.open_path("blkio.io_serviced_recursive", false)
.and_then(read_string_from)
.and_then(parse_io_service)
.unwrap_or(Vec::new()),
io_serviced_recursive_total: self
.open_path("blkio.io_serviced_recursive", false)
.and_then(read_string_from)
.and_then(parse_io_service_total)
.unwrap_or(0),
io_service_time: self
.open_path("blkio.io_service_time", false)
.and_then(read_string_from)
.and_then(parse_io_service)
.unwrap_or(Vec::new()),
io_service_time_total: self
.open_path("blkio.io_service_time", false)
.and_then(read_string_from)
.and_then(parse_io_service_total)
.unwrap_or(0),
io_service_time_recursive: self
.open_path("blkio.io_service_time_recursive", false)
.and_then(read_string_from)
.and_then(parse_io_service)
.unwrap_or(Vec::new()),
io_service_time_recursive_total: self
.open_path("blkio.io_service_time_recursive", false)
.and_then(read_string_from)
.and_then(parse_io_service_total)
.unwrap_or(0),
io_wait_time: self
.open_path("blkio.io_wait_time", false)
.and_then(read_string_from)
.and_then(parse_io_service)
.unwrap_or(Vec::new()),
io_wait_time_total: self
.open_path("blkio.io_wait_time", false)
.and_then(read_string_from)
.and_then(parse_io_service_total)
.unwrap_or(0),
io_wait_time_recursive: self
.open_path("blkio.io_wait_time_recursive", false)
.and_then(read_string_from)
.and_then(parse_io_service)
.unwrap_or(Vec::new()),
io_wait_time_recursive_total: self
.open_path("blkio.io_wait_time_recursive", false)
.and_then(read_string_from)
.and_then(parse_io_service_total)
.unwrap_or(0),
leaf_weight: self
.open_path("blkio.leaf_weight", false)
.and_then(|file| read_u64_from(file))
.unwrap_or(0u64),
leaf_weight_device: self
.open_path("blkio.leaf_weight_device", false)
.and_then(read_string_from)
.and_then(parse_blkio_data)
.unwrap_or(Vec::new()),
sectors: self
.open_path("blkio.sectors", false)
.and_then(read_string_from)
.and_then(parse_blkio_data)
.unwrap_or(Vec::new()),
sectors_recursive: self
.open_path("blkio.sectors_recursive", false)
.and_then(read_string_from)
.and_then(parse_blkio_data)
.unwrap_or(Vec::new()),
throttle: BlkIoThrottle {
io_service_bytes: self.open_path("blkio.throttle.io_service_bytes", false).and_then(|file| {
read_string_from(file)
}).unwrap_or("".to_string()),
io_service_bytes_recursive: self.open_path("blkio.throttle.io_service_bytes_recursive", false).and_then(|file| {
read_string_from(file)
}).unwrap_or("".to_string()),
io_serviced: self.open_path("blkio.throttle.io_serviced", false).and_then(|file| {
read_string_from(file)
}).unwrap_or("".to_string()),
io_serviced_recursive: self.open_path("blkio.throttle.io_serviced_recursive", false).and_then(|file| {
read_string_from(file)
}).unwrap_or("".to_string()),
read_bps_device: self.open_path("blkio.throttle.read_bps_device", false).and_then(|file| {
read_string_from(file)
}).unwrap_or("".to_string()),
read_iops_device: self.open_path("blkio.throttle.read_iops_device", false).and_then(|file| {
read_string_from(file)
}).unwrap_or("".to_string()),
write_bps_device: self.open_path("blkio.throttle.write_bps_device", false).and_then(|file| {
read_string_from(file)
}).unwrap_or("".to_string()),
write_iops_device: self.open_path("blkio.throttle.write_iops_device", false).and_then(|file| {
read_string_from(file)
}).unwrap_or("".to_string()),
io_service_bytes: self
.open_path("blkio.throttle.io_service_bytes", false)
.and_then(read_string_from)
.and_then(parse_io_service)
.unwrap_or(Vec::new()),
io_service_bytes_total: self
.open_path("blkio.throttle.io_service_bytes", false)
.and_then(read_string_from)
.and_then(parse_io_service_total)
.unwrap_or(0),
io_service_bytes_recursive: self
.open_path("blkio.throttle.io_service_bytes_recursive", false)
.and_then(read_string_from)
.and_then(parse_io_service)
.unwrap_or(Vec::new()),
io_service_bytes_recursive_total: self
.open_path("blkio.throttle.io_service_bytes_recursive", false)
.and_then(read_string_from)
.and_then(parse_io_service_total)
.unwrap_or(0),
io_serviced: self
.open_path("blkio.throttle.io_serviced", false)
.and_then(read_string_from)
.and_then(parse_io_service)
.unwrap_or(Vec::new()),
io_serviced_total: self
.open_path("blkio.throttle.io_serviced", false)
.and_then(read_string_from)
.and_then(parse_io_service_total)
.unwrap_or(0),
io_serviced_recursive: self
.open_path("blkio.throttle.io_serviced_recursive", false)
.and_then(read_string_from)
.and_then(parse_io_service)
.unwrap_or(Vec::new()),
io_serviced_recursive_total: self
.open_path("blkio.throttle.io_serviced_recursive", false)
.and_then(read_string_from)
.and_then(parse_io_service_total)
.unwrap_or(0),
read_bps_device: self
.open_path("blkio.throttle.read_bps_device", false)
.and_then(read_string_from)
.and_then(parse_blkio_data)
.unwrap_or(Vec::new()),
read_iops_device: self
.open_path("blkio.throttle.read_iops_device", false)
.and_then(read_string_from)
.and_then(parse_blkio_data)
.unwrap_or(Vec::new()),
write_bps_device: self
.open_path("blkio.throttle.write_bps_device", false)
.and_then(read_string_from)
.and_then(parse_blkio_data)
.unwrap_or(Vec::new()),
write_iops_device: self
.open_path("blkio.throttle.write_iops_device", false)
.and_then(read_string_from)
.and_then(parse_blkio_data)
.unwrap_or(Vec::new()),
},
time: self.open_path("blkio.time", false).and_then(|file| {
read_string_from(file)
}).unwrap_or("".to_string()),
time_recursive: self.open_path("blkio.time_recursive", false).and_then(|file| {
read_string_from(file)
}).unwrap_or("".to_string()),
weight: self.open_path("blkio.weight", false).and_then(|file| {
read_u64_from(file)
}).unwrap_or(0u64),
weight_device: self.open_path("blkio.weight_device", false).and_then(|file| {
read_string_from(file)
}).unwrap_or("".to_string()),
time: self
.open_path("blkio.time", false)
.and_then(read_string_from)
.and_then(parse_blkio_data)
.unwrap_or(Vec::new()),
time_recursive: self
.open_path("blkio.time_recursive", false)
.and_then(read_string_from)
.and_then(parse_blkio_data)
.unwrap_or(Vec::new()),
weight: self
.open_path("blkio.weight", false)
.and_then(|file| read_u64_from(file))
.unwrap_or(0u64),
weight_device: self
.open_path("blkio.weight_device", false)
.and_then(read_string_from)
.and_then(parse_blkio_data)
.unwrap_or(Vec::new()),
}
}
/// 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) {
self.open_path("blkio.leaf_weight", true).and_then(|mut file| {
file.write_all(w.to_string().as_ref()).ok()
});
pub fn set_leaf_weight(&self, w: u64) -> Result<()> {
self.open_path("blkio.leaf_weight", true)
.and_then(|mut file| {
file.write_all(w.to_string().as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
})
}
/// Same as `set_leaf_weight()`, but settable per each block device.
pub fn set_leaf_weight_for_device(self: &Self, d: String) {
self.open_path("blkio.leaf_weight_device", true).and_then(|mut file| {
file.write_all(d.as_ref()).ok()
});
pub fn set_leaf_weight_for_device(
&self,
major: u64,
minor: u64,
weight: u64,
) -> Result<()> {
self.open_path("blkio.leaf_weight_device", true)
.and_then(|mut file| {
file.write_all(format!("{}:{} {}", major, minor, weight).as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
})
}
/// Reset the statistics the kernel has gathered so far and start fresh.
pub fn reset_stats(self: &Self) {
self.open_path("blkio.leaf_weight_device", true).and_then(|mut file| {
file.write_all("1".to_string().as_ref()).ok()
});
pub fn reset_stats(&self) -> Result<()> {
self.open_path("blkio.reset_stats", true)
.and_then(|mut file| {
file.write_all("1".to_string().as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
})
}
/// 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) {
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()
});
pub fn throttle_read_bps_for_device(
&self,
major: u64,
minor: u64,
bps: u64,
) -> Result<()> {
self.open_path("blkio.throttle.read_bps_device", true)
.and_then(|mut file| {
file.write_all(format!("{}:{} {}", major, minor, bps).to_string().as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
})
}
/// 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) {
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()
});
pub fn throttle_read_iops_for_device(
&self,
major: u64,
minor: u64,
iops: u64,
) -> Result<()> {
self.open_path("blkio.throttle.read_iops_device", true)
.and_then(|mut file| {
file.write_all(format!("{}:{} {}", major, minor, iops).to_string().as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
})
}
/// 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) {
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()
});
pub fn throttle_write_bps_for_device(
&self,
major: u64,
minor: u64,
bps: u64,
) -> Result<()> {
self.open_path("blkio.throttle.write_bps_device", true)
.and_then(|mut file| {
file.write_all(format!("{}:{} {}", major, minor, bps).to_string().as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
})
}
/// 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) {
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()
});
pub fn throttle_write_iops_for_device(
&self,
major: u64,
minor: u64,
iops: u64,
) -> Result<()> {
self.open_path("blkio.throttle.write_iops_device", true)
.and_then(|mut file| {
file.write_all(format!("{}:{} {}", major, minor, iops).to_string().as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
})
}
/// Set the weight of the control group's tasks.
pub fn set_weight(self: &Self, w: u64) {
self.open_path("blkio.leaf_weight", true).and_then(|mut file| {
file.write_all(w.to_string().as_ref()).ok()
});
pub fn set_weight(&self, w: u64) -> Result<()> {
self.open_path("blkio.weight", true)
.and_then(|mut file| {
file.write_all(w.to_string().as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
})
}
/// Same as `set_weight()`, but settable per each block device.
pub fn set_weight_for_device(self: &Self, d: String) {
self.open_path("blkio.weight_device", true).and_then(|mut file| {
file.write_all(d.as_ref()).ok()
});
pub fn set_weight_for_device(
&self,
major: u64,
minor: u64,
weight: u64,
) -> Result<()> {
self.open_path("blkio.weight_device", true)
.and_then(|mut file| {
file.write_all(format!("{}:{} {}", major, minor, weight).as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
})
}
}
#[cfg(test)]
mod test {
use blkio::{parse_blkio_data, BlkIoData};
use blkio::{parse_io_service, parse_io_service_total, IoService};
use error::*;
static TEST_VALUE: &str = "\
8:32 Read 4280320
8:32 Write 0
8:32 Sync 4280320
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
";
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
";
static TEST_BLKIO_DATA: &str = "\
8:48 454480833999
8:32 228392923193
8:16 772456885
8:0 559583764
";
#[test]
fn test_parse_io_service_total() {
let ok = parse_io_service_total(TEST_VALUE.to_string()).unwrap();
assert_eq!(
ok,
61823067136
);
}
#[test]
fn test_parse_io_service() {
let ok = parse_io_service(TEST_VALUE.to_string()).unwrap();
assert_eq!(
ok,
vec![
IoService {
major: 8,
minor: 32,
read: 4280320,
write: 0,
sync: 4280320,
async: 0,
total: 4280320,
},
IoService {
major: 8,
minor: 48,
read: 5705479168,
write: 56096055296,
sync: 11213923328,
async: 50587611136,
total: 61801534464,
},
IoService {
major: 8,
minor: 16,
read: 10059776,
write: 0,
sync: 10059776,
async: 0,
total: 10059776,
},
IoService {
major: 8,
minor: 0,
read: 7192576,
write: 0,
sync: 7192576,
async: 0,
total: 7192576,
}
]
);
let err = parse_io_service(TEST_WRONG_VALUE.to_string()).unwrap_err();
assert_eq!(
err.kind(),
&ErrorKind::ParseError,
);
}
#[test]
fn test_parse_blkio_data() {
assert_eq!(
parse_blkio_data(TEST_BLKIO_DATA.to_string()).unwrap(),
vec![
BlkIoData {
major: 8,
minor: 48,
data: 454480833999,
},
BlkIoData {
major: 8,
minor: 32,
data: 228392923193,
},
BlkIoData {
major: 8,
minor: 16,
data: 772456885,
},
BlkIoData {
major: 8,
minor: 0,
data: 559583764,
}
]
);
}
}

View File

@@ -1,31 +1,35 @@
//! This module handles cgroup operations. Start here!
use {CgroupPid, Resources, ControllIdentifier, Controller, Hierarchy, Subsystem};
use error::*;
use {CgroupPid, ControllIdentifier, Controller, Hierarchy, Resources, Subsystem};
use std::convert::From;
use std::path::Path;
/// A control group is the central structure to this crate.
///
///
/// # What are control groups?
///
/// Lifting over from the Linux kernel sources:
/// Lifting over from the Linux kernel sources:
///
/// > Control Groups provide a mechanism for aggregating/partitioning sets of
/// > tasks, and all their future children, into hierarchical groups with
/// > specialized behaviour.
///
/// This crate is an attempt at providing a Rust-native way of managing these cgroups.
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) {
fn create(&self) {
for subsystem in &self.subsystems {
subsystem.to_controller().create();
}
@@ -37,21 +41,39 @@ 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 mut subsystems = hier.subsystems();
subsystems = subsystems.into_iter().map(|x| x.enter(&path)).collect::<Vec<_>>();
let cg = Cgroup {
//name: path,
subsystems: subsystems,
};
pub fn new<P: AsRef<Path>>(hier: &Hierarchy, path: P) -> 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<P: AsRef<Path>>(hier: &Hierarchy, path: P) -> Cgroup {
let path = path.as_ref();
let mut subsystems = hier.subsystems();
if path.as_os_str() != "" {
subsystems = subsystems
.into_iter()
.map(|x| x.enter(path))
.collect::<Vec<_>>();
}
let cg = Cgroup {
subsystems: subsystems,
hier: hier,
};
cg
}
/// The list of subsystems that this control group supports.
pub fn subsystems(self: &Self) -> &Vec<Subsystem> {
pub fn subsystems(&self) -> &Vec<Subsystem> {
&self.subsystems
}
@@ -61,61 +83,85 @@ impl Cgroup {
/// system call will fail if there are any descendants. Thus, one should check whether it was
/// actually removed, and remove the descendants first if not. In the future, this behavior
/// will change.
pub fn delete(self: Self) {
self.subsystems.into_iter().for_each(|sub| {
match sub {
Subsystem::Pid(pidc) => pidc.delete(),
Subsystem::Mem(c) => c.delete(),
Subsystem::CpuSet(c) => c.delete(),
Subsystem::CpuAcct(c) => c.delete(),
Subsystem::Cpu(c) => c.delete(),
Subsystem::Devices(c) => c.delete(),
Subsystem::Freezer(c) => c.delete(),
Subsystem::NetCls(c) => c.delete(),
Subsystem::BlkIo(c) => c.delete(),
Subsystem::PerfEvent(c) => c.delete(),
Subsystem::NetPrio(c) => c.delete(),
Subsystem::HugeTlb(c) => c.delete(),
Subsystem::Rdma(c) => c.delete(),
}
pub fn delete(self) {
self.subsystems.into_iter().for_each(|sub| match sub {
Subsystem::Pid(pidc) => pidc.delete(),
Subsystem::Mem(c) => c.delete(),
Subsystem::CpuSet(c) => c.delete(),
Subsystem::CpuAcct(c) => c.delete(),
Subsystem::Cpu(c) => c.delete(),
Subsystem::Devices(c) => c.delete(),
Subsystem::Freezer(c) => c.delete(),
Subsystem::NetCls(c) => c.delete(),
Subsystem::BlkIo(c) => c.delete(),
Subsystem::PerfEvent(c) => c.delete(),
Subsystem::NetPrio(c) => c.delete(),
Subsystem::HugeTlb(c) => c.delete(),
Subsystem::Rdma(c) => c.delete(),
});
}
/// Apply a set of resource limits to the control group.
pub fn apply(self: &Self, res: &Resources) {
for subsystem in &self.subsystems {
subsystem.to_controller().apply(res);
}
pub fn apply(&self, res: &Resources) -> Result<()> {
self.subsystems
.iter()
.try_fold((), |_, e| e.to_controller().apply(res))
}
/// Retrieve a container based on type inference.
///
/// ## Example:
///
/// ```
/// ```text
/// let pids: &PidController = control_group.controller_of()
/// .expect("No pids controller attached!");
/// let cpu: &CpuController = control_group.controller_of()
/// .expect("No cpu controller attached!");
/// ```
pub fn controller_of<'a, T>(self: &'a Self) -> Option<&'a T>
where &'a T: From<&'a Subsystem>,
T: Controller + ControllIdentifier,
where
&'a T: From<&'a Subsystem>,
T: Controller + ControllIdentifier,
{
for i in &self.subsystems {
if i.to_controller().control_type() == T::controller_type() {
/*
* N.B.:
* https://play.rust-lang.org/?gist=978b2846bacebdaa00be62374f4f4334&version=stable&mode=debug&edition=2015
*/
// N.B.:
// https://play.rust-lang.org/?gist=978b2846bacebdaa00be62374f4f4334&version=stable&mode=debug&edition=2015
return Some(i.into());
}
}
None
}
/// Removes a task from the control group.
///
/// Note that this means that the task will be moved back to the root control group in the
/// hierarchy and any rules applied to that control group will _still_ apply to the task.
pub fn remove_task(&self, pid: CgroupPid) {
let _ = self.hier.root_control_group().add_task(pid);
}
/// Attach a task to the control group.
pub fn add_task(self: &Self, pid: CgroupPid) {
self.subsystems().iter().for_each(|sub| sub.to_controller().add_task(&pid));
pub fn add_task(&self, pid: CgroupPid) -> Result<()> {
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) -> 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
}
}

368
src/cgroup_builder.rs Normal file
View File

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

View File

@@ -1,20 +1,26 @@
//! This module contains the implementation of the `cpu` cgroup subsystem.
//!
//!
//! See the Kernel's documentation for more information about this subsystem, found at:
//! [Documentation/scheduler/sched-design-CFS.txt](https://www.kernel.org/doc/Documentation/scheduler/sched-design-CFS.txt)
//! paragraph 7 ("GROUP SCHEDULER EXTENSIONS TO CFS").
use std::path::PathBuf;
use std::fs::File;
use std::io::{Read, Write};
use std::path::PathBuf;
use {CpuResources, Controllers, Controller, Resources, ControllIdentifier, Subsystem};
use error::*;
use error::ErrorKind::*;
use {
ControllIdentifier, ControllerInternal, Controllers, CpuResources, Resources, Subsystem,
};
/// A controller that allows controlling the `cpu` subsystem of a Cgroup.
///
///
/// In essence, it allows gathering information about how much the tasks inside the control group
/// are using the CPU and creating rules that limit their usage. Note that this crate does not yet
/// support managing realtime tasks.
#[derive(Debug, Clone)]
pub struct CpuController{
pub struct CpuController {
base: PathBuf,
path: PathBuf,
}
@@ -28,23 +34,48 @@ pub struct Cpu {
pub stat: String,
}
impl Controller for CpuController {
fn control_type(self: &Self) -> Controllers { Controllers::Cpu}
fn get_path<'a>(self: &'a Self) -> &'a PathBuf { &self.path }
fn get_path_mut<'a>(self: &'a mut Self) -> &'a mut PathBuf { &mut self.path }
fn get_base<'a>(self: &'a Self) -> &'a PathBuf { &self.base }
impl ControllerInternal for CpuController {
fn control_type(&self) -> Controllers {
Controllers::Cpu
}
fn apply(self: &Self, res: &Resources) {
/* get the resources that apply to this controller */
fn get_path(&self) -> &PathBuf {
&self.path
}
fn get_path_mut(&mut self) -> &mut PathBuf {
&mut self.path
}
fn get_base(&self) -> &PathBuf {
&self.base
}
fn apply(&self, res: &Resources) -> Result<()> {
// get the resources that apply to this controller
let res: &CpuResources = &res.cpu;
if res.update_values {
/* apply pid_max */
self.set_shares(res.shares);
self.set_cfs_period(res.period);
self.set_cfs_quota(res.quota as u64);
/* TODO: rt properties (CONFIG_RT_GROUP_SCHED) are not yet supported */
// apply pid_max
let _ = self.set_shares(res.shares);
if self.shares()? != res.shares as u64 {
return Err(Error::new(ErrorKind::Other));
}
let _ = self.set_cfs_period(res.period);
if self.cfs_period()? != res.period as u64 {
return Err(Error::new(ErrorKind::Other));
}
let _ = self.set_cfs_quota(res.quota as u64);
if self.cfs_quota()? != res.quota as u64 {
return Err(Error::new(ErrorKind::Other));
}
// TODO: rt properties (CONFIG_RT_GROUP_SCHED) are not yet supported
}
Ok(())
}
}
@@ -62,12 +93,20 @@ impl<'a> From<&'a Subsystem> for &'a CpuController {
_ => {
assert_eq!(1, 0);
::std::mem::uninitialized()
},
}
}
}
}
}
fn read_u64_from(mut file: File) -> Result<u64> {
let mut string = String::new();
match file.read_to_string(&mut string) {
Ok(_) => string.trim().parse().map_err(|e| Error::with_cause(ParseError, e)),
Err(e) => Err(Error::with_cause(ReadFailed, e)),
}
}
impl CpuController {
/// Contructs a new `CpuController` with `oroot` serving as the root of the control group.
pub fn new(oroot: PathBuf) -> Self {
@@ -80,41 +119,71 @@ impl CpuController {
}
/// Returns CPU time statistics based on the processes in the control group.
pub fn cpu(self: &Self) -> Cpu {
pub fn cpu(&self) -> Cpu {
Cpu {
stat: self.open_path("cpu.stat", false).and_then(|mut file| {
let mut s = String::new();
let _ = file.read_to_string(&mut s);
Some(s)
}).unwrap_or("".to_string()),
stat: self
.open_path("cpu.stat", false)
.and_then(|mut file| {
let mut s = String::new();
let res = file.read_to_string(&mut s);
match res {
Ok(_) => Ok(s),
Err(e) => Err(Error::with_cause(ReadFailed, e)),
}
}).unwrap_or("".to_string()),
}
}
/// Configures the CPU bandwidth (in relative relation to other control groups and this control
/// group's parent).
///
///
/// For example, setting control group `A`'s `shares` to `100`, and control group `B`'s
/// `shares` to `200` ensures that control group `B` receives twice as much as CPU bandwidth.
/// (Assuming both `A` and `B` are of the same parent)
pub fn set_shares(self: &Self, shares: u64) {
pub fn set_shares(&self, shares: u64) -> Result<()> {
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(|e| Error::with_cause(WriteFailed, e))
})
}
/// Retrieve the CPU bandwidth that this control group (relative to other control groups and
/// this control group's parent) can use.
pub fn shares(&self) -> Result<u64> {
self.open_path("cpu.shares", false).and_then(read_u64_from)
}
/// Specify a period (when using the CFS scheduler) of time in microseconds for how often this
/// control group's access to the CPU should be reallocated.
pub fn set_cfs_period(self: &Self, us: u64) {
self.open_path("cpu.cfs_period_us", true).and_then(|mut file| {
file.write_all(us.to_string().as_ref()).ok()
});
pub fn set_cfs_period(&self, us: u64) -> Result<()> {
self.open_path("cpu.cfs_period_us", true)
.and_then(|mut file| {
file.write_all(us.to_string().as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
})
}
/// Retrieve the period of time of how often this cgroup's access to the CPU should be
/// reallocated in microseconds.
pub fn cfs_period(&self) -> Result<u64> {
self.open_path("cpu.cfs_period_us", false)
.and_then(read_u64_from)
}
/// Specify a quota (when using the CFS scheduler) of time in microseconds for which all tasks
/// in this control group can run during one period (see: `set_cfs_period()`).
pub fn set_cfs_quota(self: &Self, us: u64) {
self.open_path("cpu.cfs_quota_us", true).and_then(|mut file| {
file.write_all(us.to_string().as_ref()).ok()
});
pub fn set_cfs_quota(&self, us: u64) -> Result<()> {
self.open_path("cpu.cfs_quota_us", true)
.and_then(|mut file| {
file.write_all(us.to_string().as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
})
}
/// Retrieve the quota of time for which all tasks in this cgroup can run during one period, in
/// microseconds.
pub fn cfs_quota(&self) -> Result<u64> {
self.open_path("cpu.cfs_quota_us", false)
.and_then(read_u64_from)
}
}

View File

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

View File

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

View File

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

87
src/error.rs Normal file
View File

@@ -0,0 +1,87 @@
use std::error::Error as StdError;
use std::fmt;
/// The different types of errors that can occur while manipulating control groups.
#[derive(Debug, Eq, PartialEq)]
pub enum ErrorKind {
/// An error occured while writing to a control group file.
WriteFailed,
/// An error occured while trying to read from a control group file.
ReadFailed,
/// An error occured while trying to 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,
/// An unknown error has occured.
Other,
}
#[derive(Debug)]
pub struct Error {
kind: ErrorKind,
cause: Option<Box<StdError + Send>>,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let msg = match self.kind {
ErrorKind::WriteFailed => "unable to write to a control group file",
ErrorKind::ReadFailed => "unable to read a control group file",
ErrorKind::ParseError => "unable to parse control group file",
ErrorKind::InvalidOperation => "the requested operation is invalid",
ErrorKind::InvalidPath => "the given path is invalid",
ErrorKind::Other => "an unknown error",
};
write!(f, "{}", msg)
}
}
impl StdError for Error {
fn cause(&self) -> Option<&StdError> {
match self.cause {
Some(ref x) => Some(&**x),
None => None,
}
}
}
impl Error {
pub(crate) fn new(kind: ErrorKind) -> Self {
Self {
kind,
cause: None,
}
}
pub(crate) fn with_cause<E>(kind: ErrorKind, cause: E) -> Self
where
E: 'static + Send + StdError,
{
Self {
kind,
cause: Some(Box::new(cause)),
}
}
pub fn kind(&self) -> &ErrorKind {
&self.kind
}
}
pub type Result<T> = ::std::result::Result<T, Error>;

View File

@@ -1,11 +1,14 @@
//! This module contains the implementation of the `freezer` cgroup subsystem.
//!
//!
//! See the Kernel's documentation for more information about this subsystem, found at:
//! [Documentation/cgroup-v1/freezer-subsystem.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/freezer-subsystem.txt)
use std::path::PathBuf;
use std::io::{Read, Write};
use std::path::PathBuf;
use {Controllers, Controller, Resources, ControllIdentifier, Subsystem};
use error::*;
use error::ErrorKind::*;
use {ControllIdentifier, ControllerInternal, Controllers, Resources, Subsystem};
/// A controller that allows controlling the `freezer` subsystem of a Cgroup.
///
@@ -16,7 +19,7 @@ use {Controllers, Controller, Resources, ControllIdentifier, Subsystem};
/// Note that if the control group is currently in the `Frozen` or `Freezing` state, then no
/// processes can be added to it.
#[derive(Debug, Clone)]
pub struct FreezerController{
pub struct FreezerController {
base: PathBuf,
path: PathBuf,
}
@@ -31,13 +34,22 @@ pub enum FreezerState {
Frozen,
}
impl Controller for FreezerController {
fn control_type(self: &Self) -> Controllers { Controllers::Freezer }
fn get_path<'a>(self: &'a Self) -> &'a PathBuf { &self.path }
fn get_path_mut<'a>(self: &'a mut Self) -> &'a mut PathBuf { &mut self.path }
fn get_base<'a>(self: &'a Self) -> &'a PathBuf { &self.base }
impl ControllerInternal for FreezerController {
fn control_type(&self) -> Controllers {
Controllers::Freezer
}
fn get_path(&self) -> &PathBuf {
&self.path
}
fn get_path_mut(&mut self) -> &mut PathBuf {
&mut self.path
}
fn get_base(&self) -> &PathBuf {
&self.base
}
fn apply(self: &Self, _res: &Resources) {
fn apply(&self, _res: &Resources) -> Result<()> {
Ok(())
}
}
@@ -55,7 +67,7 @@ impl<'a> From<&'a Subsystem> for &'a FreezerController {
_ => {
assert_eq!(1, 0);
::std::mem::uninitialized()
},
}
}
}
}
@@ -73,30 +85,35 @@ impl FreezerController {
}
/// Freezes the processes in the control group.
pub fn freeze(self: &Self) {
pub fn freeze(&self) -> Result<()> {
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(|e| Error::with_cause(WriteFailed, e))
})
}
/// Thaws, that is, unfreezes the processes in the control group.
pub fn thaw(self: &Self) {
pub fn thaw(&self) -> Result<()> {
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(|e| Error::with_cause(WriteFailed, e))
})
}
/// Retrieve the state of processes in the control group.
pub fn state(self: &Self) -> FreezerState {
pub fn state(&self) -> Result<FreezerState> {
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(Error::new(ParseError)),
},
Err(e) => Err(Error::with_cause(ReadFailed, e)),
}
}).unwrap_or(FreezerState::Thawed)
})
}
}

View File

@@ -3,26 +3,27 @@
//! Currently, we only support the cgroupv1 hierarchy, but in the future we will add support for
//! the Unified Hierarchy.
use std::fs::File;
use std::io::BufRead;
use std::io::BufReader;
use std::fs::File;
use std::path::{Path, PathBuf};
use blkio::BlkIoController;
use cpu::CpuController;
use cpuacct::CpuAcctController;
use cpuset::CpuSetController;
use devices::DevicesController;
use freezer::FreezerController;
use hugetlb::HugeTlbController;
use memory::MemController;
use net_cls::NetClsController;
use net_prio::NetPrioController;
use perf_event::PerfEventController;
use pid::PidController;
use rdma::RdmaController;
use {Controllers, Hierarchy, Subsystem};
use ::pid::PidController;
use ::memory::MemController;
use ::cpuset::CpuSetController;
use ::cpuacct::CpuAcctController;
use ::cpu::CpuController;
use ::freezer::FreezerController;
use ::devices::DevicesController;
use ::net_cls::NetClsController;
use ::blkio::BlkIoController;
use ::perf_event::PerfEventController;
use ::net_prio::NetPrioController;
use ::hugetlb::HugeTlbController;
use ::rdma::RdmaController;
use cgroup::Cgroup;
/// The standard, original cgroup implementation. Often referred to as "cgroupv1".
pub struct V1 {
@@ -30,7 +31,7 @@ pub struct V1 {
}
impl Hierarchy for V1 {
fn subsystems(self: &Self) -> Vec<Subsystem> {
fn subsystems(&self) -> Vec<Subsystem> {
let mut subs = vec![];
if self.check_support(Controllers::Pids) {
subs.push(Subsystem::Pid(PidController::new(self.root())));
@@ -75,7 +76,11 @@ impl Hierarchy for V1 {
subs
}
fn check_support(self: &Self, sub: Controllers) -> bool {
fn root_control_group(&self) -> Cgroup {
Cgroup::load(self, "".to_string())
}
fn check_support(&self, sub: Controllers) -> bool {
let root = self.root().read_dir().unwrap();
for entry in root {
if let Ok(entry) = entry {
@@ -87,7 +92,7 @@ impl Hierarchy for V1 {
return false;
}
fn root(self: &Self) -> PathBuf {
fn root(&self) -> PathBuf {
PathBuf::from(self.mount_point.clone())
}
}
@@ -104,10 +109,10 @@ impl V1 {
}
fn find_v1_mount() -> Option<String> {
/* Open mountinfo so we can get a parseable mount list */
// Open mountinfo so we can get a parseable mount list
let mountinfo_path = Path::new("/proc/self/mountinfo");
/* If /proc isn't mounted, or something else happens, then bail out */
// If /proc isn't mounted, or something else happens, then bail out
if mountinfo_path.exists() == false {
return None;
}
@@ -122,7 +127,7 @@ fn find_v1_mount() -> Option<String> {
let fstype = more_fields[0];
if fstype == "tmpfs" && more_fields[2].contains("ro") {
let cgroups_mount = fields.nth(4).unwrap();
println!("found cgroups at {:?}", cgroups_mount);
info!("found cgroups at {:?}", cgroups_mount);
return Some(cgroups_mount.to_string());
}
}

View File

@@ -1,13 +1,18 @@
//! This module contains the implementation of the `hugetlb` cgroup subsystem.
//!
//!
//! See the Kernel's documentation for more information about this subsystem, found at:
//! [Documentation/cgroup-v1/hugetlb.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/hugetlb.txt)
use std::path::PathBuf;
use std::fs::File;
use std::io::{Write, Read};
use std::io::{Read, Write};
use std::path::PathBuf;
use {HugePageResources, Controllers, Controller, Resources, ControllIdentifier, Subsystem};
use error::*;
use error::ErrorKind::*;
use {
ControllIdentifier, ControllerInternal, Controllers, HugePageResources, Resources,
Subsystem,
};
/// A controller that allows controlling the `hugetlb` subsystem of a Cgroup.
///
@@ -19,21 +24,33 @@ pub struct HugeTlbController {
path: PathBuf,
}
impl Controller for HugeTlbController {
fn control_type(self: &Self) -> Controllers { Controllers::HugeTlb }
fn get_path<'a>(self: &'a Self) -> &'a PathBuf { &self.path }
fn get_path_mut<'a>(self: &'a mut Self) -> &'a mut PathBuf { &mut self.path }
fn get_base<'a>(self: &'a Self) -> &'a PathBuf { &self.base }
impl ControllerInternal for HugeTlbController {
fn control_type(&self) -> Controllers {
Controllers::HugeTlb
}
fn get_path(&self) -> &PathBuf {
&self.path
}
fn get_path_mut(&mut self) -> &mut PathBuf {
&mut self.path
}
fn get_base(&self) -> &PathBuf {
&self.base
}
fn apply(self: &Self, res: &Resources) {
/* get the resources that apply to this controller */
fn apply(&self, res: &Resources) -> Result<()> {
// get the resources that apply to this controller
let res: &HugePageResources = &res.hugepages;
if res.update_values {
for i in &res.limits {
self.set_limit_in_bytes(&i.size, i.limit);
let _ = self.set_limit_in_bytes(&i.size, i.limit);
if self.limit_in_bytes(&i.size)? != i.limit {
return Err(Error::new(Other));
}
}
}
Ok(())
}
}
@@ -51,16 +68,18 @@ impl<'a> From<&'a Subsystem> for &'a HugeTlbController {
_ => {
assert_eq!(1, 0);
::std::mem::uninitialized()
},
}
}
}
}
}
fn read_u64_from(mut file: File) -> Option<u64> {
fn read_u64_from(mut file: File) -> Result<u64> {
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(|e| Error::with_cause(ParseError, e)),
Err(e) => Err(Error::with_cause(ReadFailed, e)),
}
}
impl HugeTlbController {
@@ -75,44 +94,47 @@ impl HugeTlbController {
}
/// Whether the system supports `hugetlb_size` hugepages.
pub fn size_supported(self: &Self, _hugetlb_size: String) -> bool {
/* TODO */
pub fn size_supported(&self, _hugetlb_size: &str) -> bool {
// TODO
true
}
/// Check how many times has the limit of `hugetlb_size` hugepages been hit.
pub fn failcnt(self: &Self, hugetlb_size: &String) -> Option<u64> {
pub fn failcnt(&self, hugetlb_size: &str) -> Result<u64> {
self.open_path(&format!("hugetlb.{}.failcnt", hugetlb_size), false)
.and_then(read_u64_from)
}
/// Get the limit (in bytes) of how much memory can be backed by hugepages of a certain size
/// (`hugetlb_size`).
pub fn limit_in_bytes(self: &Self, hugetlb_size: &String) -> Option<u64> {
pub fn limit_in_bytes(&self, hugetlb_size: &str) -> Result<u64> {
self.open_path(&format!("hugetlb.{}.limit_in_bytes", hugetlb_size), false)
.and_then(read_u64_from)
}
/// Get the current usage of memory that is backed by hugepages of a certain size
/// (`hugetlb_size`).
pub fn usage_in_bytes(self: &Self, hugetlb_size: &String) -> Option<u64> {
pub fn usage_in_bytes(&self, hugetlb_size: &str) -> Result<u64> {
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> {
self.open_path(&format!("hugetlb.{}.max_usage_in_bytes", hugetlb_size), false)
.and_then(read_u64_from)
pub fn max_usage_in_bytes(&self, hugetlb_size: &str) -> Result<u64> {
self.open_path(
&format!("hugetlb.{}.max_usage_in_bytes", hugetlb_size),
false,
).and_then(read_u64_from)
}
/// Set the limit (in bytes) of how much memory can be backed by hugepages of a certain size
/// (`hugetlb_size`).
pub fn set_limit_in_bytes(self: &Self, hugetlb_size: &String, limit: u64) {
self.open_path(&format!("hugetlb.{}.limit_in_bytes", hugetlb_size), false)
pub fn set_limit_in_bytes(&self, hugetlb_size: &str, limit: u64) -> Result<()> {
self.open_path(&format!("hugetlb.{}.limit_in_bytes", hugetlb_size), true)
.and_then(|mut file| {
file.write_all(limit.to_string().as_ref()).ok()
});
file.write_all(limit.to_string().as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
})
}
}

View File

@@ -1,37 +1,45 @@
use std::path::PathBuf;
#[macro_use]
extern crate log;
use std::fs::File;
use std::io::Write;
use std::io::{BufRead, BufReader, Write};
use std::path::{Path, PathBuf};
pub mod hierarchies;
pub mod pid;
pub mod memory;
pub mod cpuset;
pub mod cpuacct;
pub mod cpu;
pub mod devices;
pub mod cgroup;
pub mod freezer;
pub mod net_cls;
pub mod blkio;
pub mod perf_event;
pub mod net_prio;
pub mod cgroup;
pub mod cpu;
pub mod cpuacct;
pub mod cpuset;
pub mod devices;
pub mod error;
pub mod freezer;
pub mod hierarchies;
pub mod hugetlb;
pub mod memory;
pub mod net_cls;
pub mod net_prio;
pub mod perf_event;
pub mod pid;
pub mod rdma;
pub mod cgroup_builder;
use pid::PidController;
use memory::MemController;
use cpuset::CpuSetController;
use cpuacct::CpuAcctController;
use cpu::CpuController;
use freezer::FreezerController;
use devices::DevicesController;
use net_cls::NetClsController;
use blkio::BlkIoController;
use perf_event::PerfEventController;
use net_prio::NetPrioController;
use cpu::CpuController;
use cpuacct::CpuAcctController;
use cpuset::CpuSetController;
use devices::DevicesController;
use error::*;
use freezer::FreezerController;
use hugetlb::HugeTlbController;
use memory::MemController;
use net_cls::NetClsController;
use net_prio::NetPrioController;
use perf_event::PerfEventController;
use pid::PidController;
use rdma::RdmaController;
pub use cgroup::Cgroup;
/// Contains all the subsystems that are available in this crate.
#[derive(Debug)]
pub enum Subsystem {
@@ -82,7 +90,7 @@ pub enum Controllers {
}
impl Controllers {
pub fn to_string(self: &Self) -> String {
pub fn to_string(&self) -> String {
match self {
Controllers::Pids => return "pids".to_string(),
Controllers::Mem => return "memory".to_string(),
@@ -101,78 +109,148 @@ impl Controllers {
}
}
mod sealed {
use super::*;
pub trait ControllerInternal {
fn apply(&self, res: &Resources) -> Result<()>;
// meta stuff
fn control_type(&self) -> Controllers;
fn get_path(&self) -> &PathBuf;
fn get_path_mut(&mut self) -> &mut PathBuf;
fn get_base(&self) -> &PathBuf;
fn verify_path(&self) -> Result<()> {
if self.get_path().starts_with(self.get_base()) {
Ok(())
} else {
Err(Error::new(ErrorKind::InvalidPath))
}
}
fn open_path(&self, p: &str, w: bool) -> Result<File> {
let mut path = self.get_path().clone();
path.push(p);
self.verify_path()?;
if w {
match File::create(&path) {
Err(e) => return Err(Error::with_cause(ErrorKind::WriteFailed, e)),
Ok(file) => return Ok(file),
}
} else {
match File::open(&path) {
Err(e) => return Err(Error::with_cause(ErrorKind::ReadFailed, e)),
Ok(file) => return Ok(file),
}
}
}
#[doc(hidden)]
fn path_exists(&self, p: &str) -> bool {
if let Err(_) = self.verify_path() {
return false;
}
std::path::Path::new(p).exists()
}
}
}
pub(crate) use sealed::ControllerInternal;
/// A Controller is a subsystem attached to the control group.
///
/// Implementors are able to control certain aspects of a control group.
pub trait Controller {
#[doc(hidden)]
fn control_type(&self) -> Controllers;
/// The file system path to the controller.
fn path(&self) -> &Path;
/// Apply a set of resources to the Controller, invoking its internal functions to pass the
/// kernel the information.
fn apply(self: &Self, res: &Resources);
fn apply(&self, res: &Resources) -> Result<()>;
/* meta stuff */
#[doc(hidden)]
fn control_type(self: &Self) -> Controllers;
#[doc(hidden)]
fn get_path<'a>(self: &'a Self) -> &'a PathBuf;
#[doc(hidden)]
fn get_path_mut<'a>(self: &'a mut Self) -> &'a mut PathBuf;
#[doc(hidden)]
fn get_base<'a>(self: &'a Self) -> &'a PathBuf;
/// Create this controller
fn create(&self);
#[doc(hidden)]
fn verify_path(self: &Self) -> bool {
self.get_path().starts_with(self.get_base())
/// Does this controller already exist?
fn exists(&self) -> bool;
/// Delete the controller.
fn delete(&self);
/// Attach a task to this controller.
fn add_task(&self, pid: &CgroupPid) -> Result<()>;
/// Get the list of tasks that this controller has.
fn tasks(&self) -> Vec<CgroupPid>;
}
impl<T> Controller for T where T: ControllerInternal {
fn control_type(&self) -> Controllers {
ControllerInternal::control_type(self)
}
fn path(&self) -> &Path {
self.get_path()
}
/// Apply a set of resources to the Controller, invoking its internal functions to pass the
/// kernel the information.
fn apply(&self, res: &Resources) -> Result<()> {
ControllerInternal::apply(self, res)
}
/// Create this controller
fn create(self: &Self) {
if self.verify_path() {
match ::std::fs::create_dir(self.get_path()) {
Ok(_) => (),
Err(e) => println!("error create_dir {:?}", e),
}
fn create(&self) {
self.verify_path().expect("path should be valid");
match ::std::fs::create_dir(self.get_path()) {
Ok(_) => (),
Err(e) => warn!("error create_dir {:?}", e),
}
}
/// Does this controller already exist?
fn exists(self: &Self) -> bool {
fn exists(&self) -> bool {
self.get_path().exists()
}
/// Delete the controller.
fn delete(self: &Self) {
fn delete(&self) {
if self.get_path().exists() {
let _ = ::std::fs::remove_dir(self.get_path());
}
}
#[doc(hidden)]
fn open_path(self: &Self, p: &str, w: bool) -> Option<File> {
let mut path = self.get_path().clone();
path.push(p);
if !self.verify_path() {
return None;
}
if w {
match File::create(&path) {
Err(_) => return None,
Ok(file) => return Some(file),
}
} else {
match File::open(&path) {
Err(_) => return None,
Ok(file) => return Some(file),
}
}
/// Attach a task to this controller.
fn add_task(&self, pid: &CgroupPid) -> Result<()> {
self.open_path("tasks", true).and_then(|mut file| {
file.write_all(pid.pid.to_string().as_ref())
.map_err(|e| Error::with_cause(ErrorKind::WriteFailed, e))
})
}
/// Attach a task to this controller.
fn add_task(self: &Self, pid: &CgroupPid) {
self.open_path("tasks", true).and_then(|mut file| {
file.write_all(pid.pid.to_string().as_ref()).ok()
});
/// Get the list of tasks that this controller has.
fn tasks(&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![])
}
}
@@ -185,13 +263,19 @@ pub trait ControllIdentifier {
/// implemented as well).
pub trait Hierarchy {
/// Returns what subsystems are supported by the hierarchy.
fn subsystems(self: &Self) -> Vec<Subsystem>;
fn subsystems(&self) -> Vec<Subsystem>;
/// Returns the root directory of the hierarchy.
fn root(self: &Self) -> PathBuf;
fn root(&self) -> PathBuf;
/// Return a handle to the root control group in the hierarchy.
fn root_control_group(&self) -> Cgroup;
/// Checks whether a certain subsystem is supported in the hierarchy.
///
/// This is an internal function and should not be used.
fn check_support(self: &Self, sub: Controllers) -> bool;
#[doc(hidden)]
fn check_support(&self, sub: Controllers) -> bool;
}
/// Resource limits for the memory subsystem.
@@ -236,14 +320,14 @@ pub struct PidResources {
pub struct CpuResources {
/// Whether values should be applied to the controller.
pub update_values: bool,
/* cpuset */
// cpuset
/// A comma-separated list of CPU IDs where the task in the control group can run. Dashes
/// between numbers indicate ranges.
pub cpus: String,
/// Same syntax as the `cpus` field of this structure, but applies to memory nodes instead of
/// processors.
pub mems: String,
/* cpu */
// cpu
/// Weight of how much of the total CPU time should this control group get. Note that this is
/// hierarchical, so this is weighted against the siblings of this control group.
pub shares: u64,
@@ -263,13 +347,13 @@ pub struct DeviceResource {
/// If true, access to the device is allowed, otherwise it's denied.
pub allow: bool,
/// `'c'` for character device, `'b'` for block device; or `'a'` for all devices.
pub devtype: String,
pub devtype: ::devices::DeviceType,
/// The major number of the device.
pub major: u64,
pub major: i64,
/// The minor number of the device.
pub minor: u64,
pub minor: i64,
/// Sequence of `'r'`, `'w'` or `'m'`, each denoting read, write or mknod permissions.
pub access: String,
pub access: Vec<::devices::DevicePermissions>,
}
/// Limit the usage of devices for the control group's tasks.
@@ -389,6 +473,7 @@ pub struct Resources {
/// A structure representing a `pid`. Currently implementations exist for `u64` and
/// `std::process::Child`.
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct CgroupPid {
/// The process identifier
pub pid: u64,
@@ -396,23 +481,18 @@ pub struct CgroupPid {
impl From<u64> for CgroupPid {
fn from(u: u64) -> CgroupPid {
CgroupPid {
pid: u,
}
CgroupPid { pid: u }
}
}
impl<'a> From<&'a std::process::Child> for CgroupPid {
fn from(u: &std::process::Child) -> CgroupPid {
CgroupPid {
pid: u.id() as u64,
}
CgroupPid { pid: u.id() as u64 }
}
}
impl Subsystem {
fn enter(self: Self, path: &String) -> Self {
fn enter(self, path: &Path) -> Self {
match self {
Subsystem::Pid(cont) => Subsystem::Pid({
let mut c = cont.clone();
@@ -482,7 +562,7 @@ impl Subsystem {
}
}
fn to_controller(self: &Self) -> &dyn Controller {
fn to_controller(&self) -> &dyn Controller {
match self {
Subsystem::Pid(cont) => cont,
Subsystem::Mem(cont) => cont,
@@ -500,67 +580,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

@@ -1,12 +1,17 @@
//! This module contains the implementation of the `memory` cgroup subsystem.
//!
//!
//! See the Kernel's documentation for more information about this subsystem, found at:
//! [Documentation/cgroup-v1/memory.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/memory.txt)
use std::path::PathBuf;
use std::io::{Write, Read};
use std::fs::File;
use std::io::{Read, Write};
use std::path::PathBuf;
use {Resources, MemoryResources, Controller, Controllers, Subsystem, ControllIdentifier};
use error::*;
use error::ErrorKind::*;
use {
ControllIdentifier, ControllerInternal, Controllers, MemoryResources, Resources, Subsystem,
};
/// A controller that allows controlling the `memory` subsystem of a Cgroup.
///
@@ -14,11 +19,286 @@ use {Resources, MemoryResources, Controller, Controllers, Subsystem, ControllIde
/// of the tasks in the control group. Additonally, one can also set powerful limits on their
/// memory usage.
#[derive(Debug, Clone)]
pub struct MemController{
pub struct MemController {
base: PathBuf,
path: PathBuf,
}
/// Controls statistics and controls about the OOM killer operating in this control group.
#[derive(Default, Debug, PartialEq, Eq)]
pub struct OomControl {
/// If true, the OOM killer has been disabled for the tasks in this control group.
pub oom_kill_disable: bool,
/// Is the OOM killer currently running for the tasks in the control group?
pub under_oom: bool,
/// How many tasks were killed by the OOM killer so far.
pub oom_kill: u64,
}
fn parse_oom_control(s: String) -> Result<OomControl> {
let spl = s.split_whitespace().collect::<Vec<_>>();
Ok(OomControl {
oom_kill_disable: spl[1].parse::<u64>().unwrap() == 1,
under_oom: spl[3].parse::<u64>().unwrap() == 1,
oom_kill: spl[5].parse::<u64>().unwrap(),
})
}
/// Contains statistics about the NUMA locality of the control group's tasks.
#[derive(Default, Debug, PartialEq, Eq)]
pub struct NumaStat {
/// Total amount of pages used by the control group.
pub total_pages: u64,
/// Total amount of pages used by the control group, broken down by NUMA node.
pub total_pages_per_node: Vec<u64>,
/// Total amount of file pages used by the control group.
pub file_pages: u64,
/// Total amount of file pages used by the control group, broken down by NUMA node.
pub file_pages_per_node: Vec<u64>,
/// Total amount of anonymous pages used by the control group.
pub anon_pages: u64,
/// Total amount of anonymous pages used by the control group, broken down by NUMA node.
pub anon_pages_per_node: Vec<u64>,
/// Total amount of unevictable pages used by the control group.
pub unevictable_pages: u64,
/// Total amount of unevictable pages used by the control group, broken down by NUMA node.
pub unevictable_pages_per_node: Vec<u64>,
/// Same as `total_pages`, but includes the descedant control groups' number as well.
pub hierarchical_total_pages: u64,
/// Same as `total_pages_per_node`, but includes the descedant control groups' number as well.
pub hierarchical_total_pages_per_node: Vec<u64>,
/// Same as `file_pages`, but includes the descedant control groups' number as well.
pub hierarchical_file_pages: u64,
/// Same as `file_pages_per_node`, but includes the descedant control groups' number as well.
pub hierarchical_file_pages_per_node: Vec<u64>,
/// Same as `anon_pages`, but includes the descedant control groups' number as well.
pub hierarchical_anon_pages: u64,
/// Same as `anon_pages_per_node`, but includes the descedant control groups' number as well.
pub hierarchical_anon_pages_per_node: Vec<u64>,
/// Same as `unevictable`, but includes the descedant control groups' number as well.
pub hierarchical_unevictable_pages: u64,
/// Same as `unevictable_per_node`, but includes the descedant control groups' number as well.
pub hierarchical_unevictable_pages_per_node: Vec<u64>,
}
fn parse_numa_stat(s: String) -> Result<NumaStat> {
// Parse the number of nodes
let nodes = (s.split_whitespace().collect::<Vec<_>>().len() - 8) / 8;
let mut ls = s.lines();
let total_line = ls.next().unwrap();
let file_line = ls.next().unwrap();
let anon_line = ls.next().unwrap();
let unevict_line = ls.next().unwrap();
let hier_total_line = ls.next().unwrap();
let hier_file_line = ls.next().unwrap();
let hier_anon_line = ls.next().unwrap();
let hier_unevict_line = ls.next().unwrap();
Ok(NumaStat {
total_pages: total_line
.split(|x| x == ' ' || x == '=')
.collect::<Vec<_>>()[1]
.parse::<u64>()
.unwrap_or(0),
total_pages_per_node: {
let spl = &total_line.split(" ").collect::<Vec<_>>()[1..];
spl.iter()
.map(|x| {
x.split("=").collect::<Vec<_>>()[1]
.parse::<u64>()
.unwrap_or(0)
}).collect()
},
file_pages: file_line
.split(|x| x == ' ' || x == '=')
.collect::<Vec<_>>()[1]
.parse::<u64>()
.unwrap_or(0),
file_pages_per_node: {
let spl = &file_line.split(" ").collect::<Vec<_>>()[1..];
spl.iter()
.map(|x| {
x.split("=").collect::<Vec<_>>()[1]
.parse::<u64>()
.unwrap_or(0)
}).collect()
},
anon_pages: anon_line
.split(|x| x == ' ' || x == '=')
.collect::<Vec<_>>()[1]
.parse::<u64>()
.unwrap_or(0),
anon_pages_per_node: {
let spl = &anon_line.split(" ").collect::<Vec<_>>()[1..];
spl.iter()
.map(|x| {
x.split("=").collect::<Vec<_>>()[1]
.parse::<u64>()
.unwrap_or(0)
}).collect()
},
unevictable_pages: unevict_line
.split(|x| x == ' ' || x == '=')
.collect::<Vec<_>>()[1]
.parse::<u64>()
.unwrap_or(0),
unevictable_pages_per_node: {
let spl = &unevict_line.split(" ").collect::<Vec<_>>()[1..];
spl.iter()
.map(|x| {
x.split("=").collect::<Vec<_>>()[1]
.parse::<u64>()
.unwrap_or(0)
}).collect()
},
hierarchical_total_pages: hier_total_line
.split(|x| x == ' ' || x == '=')
.collect::<Vec<_>>()[1]
.parse::<u64>()
.unwrap_or(0),
hierarchical_total_pages_per_node: {
let spl = &hier_total_line.split(" ").collect::<Vec<_>>()[1..];
spl.iter()
.map(|x| {
x.split("=").collect::<Vec<_>>()[1]
.parse::<u64>()
.unwrap_or(0)
}).collect()
},
hierarchical_file_pages: hier_file_line
.split(|x| x == ' ' || x == '=')
.collect::<Vec<_>>()[1]
.parse::<u64>()
.unwrap_or(0),
hierarchical_file_pages_per_node: {
let spl = &hier_file_line.split(" ").collect::<Vec<_>>()[1..];
spl.iter()
.map(|x| {
x.split("=").collect::<Vec<_>>()[1]
.parse::<u64>()
.unwrap_or(0)
}).collect()
},
hierarchical_anon_pages: hier_anon_line
.split(|x| x == ' ' || x == '=')
.collect::<Vec<_>>()[1]
.parse::<u64>()
.unwrap_or(0),
hierarchical_anon_pages_per_node: {
let spl = &hier_anon_line.split(" ").collect::<Vec<_>>()[1..];
spl.iter()
.map(|x| {
x.split("=").collect::<Vec<_>>()[1]
.parse::<u64>()
.unwrap_or(0)
}).collect()
},
hierarchical_unevictable_pages: hier_unevict_line
.split(|x| x == ' ' || x == '=')
.collect::<Vec<_>>()[1]
.parse::<u64>()
.unwrap_or(0),
hierarchical_unevictable_pages_per_node: {
let spl = &hier_unevict_line.split(" ").collect::<Vec<_>>()[1..];
spl.iter()
.map(|x| {
x.split("=").collect::<Vec<_>>()[1]
.parse::<u64>()
.unwrap_or(0)
}).collect()
},
})
}
#[derive(Default, Debug, PartialEq, Eq)]
pub struct MemoryStat {
pub cache: u64,
pub rss: u64,
pub rss_huge: u64,
pub shmem: u64,
pub mapped_file: u64,
pub dirty: u64,
pub writeback: u64,
pub swap: u64,
pub pgpgin: u64,
pub pgpgout: u64,
pub pgfault: u64,
pub pgmajfault: u64,
pub inactive_anon: u64,
pub active_anon: u64,
pub inactive_file: u64,
pub active_file: u64,
pub unevictable: u64,
pub hierarchical_memory_limit: u64,
pub hierarchical_memsw_limit: u64,
pub total_cache: u64,
pub total_rss: u64,
pub total_rss_huge: u64,
pub total_shmem: u64,
pub total_mapped_file: u64,
pub total_dirty: u64,
pub total_writeback: u64,
pub total_swap: u64,
pub total_pgpgin: u64,
pub total_pgpgout: u64,
pub total_pgfault: u64,
pub total_pgmajfault: u64,
pub total_inactive_anon: u64,
pub total_active_anon: u64,
pub total_inactive_file: u64,
pub total_active_file: u64,
pub total_unevictable: u64,
}
fn parse_memory_stat(s: String) -> Result<MemoryStat> {
let sp: Vec<&str> = s
.split_whitespace()
.filter(|x| x.parse::<u64>().is_ok())
.collect();
let mut spl = sp.iter();
Ok(MemoryStat {
cache: spl.next().unwrap().parse::<u64>().unwrap(),
rss: spl.next().unwrap().parse::<u64>().unwrap(),
rss_huge: spl.next().unwrap().parse::<u64>().unwrap(),
shmem: spl.next().unwrap().parse::<u64>().unwrap(),
mapped_file: spl.next().unwrap().parse::<u64>().unwrap(),
dirty: spl.next().unwrap().parse::<u64>().unwrap(),
writeback: spl.next().unwrap().parse::<u64>().unwrap(),
swap: spl.next().unwrap().parse::<u64>().unwrap(),
pgpgin: spl.next().unwrap().parse::<u64>().unwrap(),
pgpgout: spl.next().unwrap().parse::<u64>().unwrap(),
pgfault: spl.next().unwrap().parse::<u64>().unwrap(),
pgmajfault: spl.next().unwrap().parse::<u64>().unwrap(),
inactive_anon: spl.next().unwrap().parse::<u64>().unwrap(),
active_anon: spl.next().unwrap().parse::<u64>().unwrap(),
inactive_file: spl.next().unwrap().parse::<u64>().unwrap(),
active_file: spl.next().unwrap().parse::<u64>().unwrap(),
unevictable: spl.next().unwrap().parse::<u64>().unwrap(),
hierarchical_memory_limit: spl.next().unwrap().parse::<u64>().unwrap(),
hierarchical_memsw_limit: spl.next().unwrap().parse::<u64>().unwrap(),
total_cache: spl.next().unwrap().parse::<u64>().unwrap(),
total_rss: spl.next().unwrap().parse::<u64>().unwrap(),
total_rss_huge: spl.next().unwrap().parse::<u64>().unwrap(),
total_shmem: spl.next().unwrap().parse::<u64>().unwrap(),
total_mapped_file: spl.next().unwrap().parse::<u64>().unwrap(),
total_dirty: spl.next().unwrap().parse::<u64>().unwrap(),
total_writeback: spl.next().unwrap().parse::<u64>().unwrap(),
total_swap: spl.next().unwrap().parse::<u64>().unwrap(),
total_pgpgin: spl.next().unwrap().parse::<u64>().unwrap(),
total_pgpgout: spl.next().unwrap().parse::<u64>().unwrap(),
total_pgfault: spl.next().unwrap().parse::<u64>().unwrap(),
total_pgmajfault: spl.next().unwrap().parse::<u64>().unwrap(),
total_inactive_anon: spl.next().unwrap().parse::<u64>().unwrap(),
total_active_anon: spl.next().unwrap().parse::<u64>().unwrap(),
total_inactive_file: spl.next().unwrap().parse::<u64>().unwrap(),
total_active_file: spl.next().unwrap().parse::<u64>().unwrap(),
total_unevictable: spl.next().unwrap().parse::<u64>().unwrap(),
})
}
/// Contains statistics about the current usage of memory and swap (together, not seperately) by
/// the control group's tasks.
#[derive(Debug)]
@@ -47,28 +327,25 @@ pub struct Memory {
pub max_usage_in_bytes: u64,
/// Whether moving charges at immigrate is allowed.
pub move_charge_at_immigrate: u64,
/* TODO: parse this */
/// 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> ...
/// unevictable=<total anon pages> N0=<node 0 pages> N1=<node 1 pages> ...
/// hierarchical_<counter>=<counter pages> N0=<node 0 pages> N1=<node 1 pages> ...
/// ```
pub numa_stat: String,
/// If this equals "1", then the OOM killer is enabled for this control group (this is the
/// default setting).
pub oom_control: String,
pub numa_stat: NumaStat,
/// Various statistics and control information about the Out Of Memory killer.
pub oom_control: OomControl,
/// Allows setting a limit to memory usage which is enforced when the system (note, _not_ the
/// control group) detects memory pressure.
pub soft_limit_in_bytes: u64,
/* TODO: parse this */
/// Contains a wide array of statistics about the memory usage of the tasks in the control
/// group.
pub stat: String,
pub stat: MemoryStat,
/// Set the tendency of the kernel to swap out parts of the address space consumed by the
/// control group's tasks.
///
@@ -114,24 +391,34 @@ pub struct Kmem {
pub slabinfo: String,
}
impl Controller for MemController {
fn control_type(self: &Self) -> Controllers { Controllers::Mem }
fn get_path<'a>(self: &'a Self) -> &'a PathBuf { &self.path }
fn get_path_mut<'a>(self: &'a mut Self) -> &'a mut PathBuf { &mut self.path }
fn get_base<'a>(self: &'a Self) -> &'a PathBuf { &self.base }
impl ControllerInternal for MemController {
fn control_type(&self) -> Controllers {
Controllers::Mem
}
fn get_path(&self) -> &PathBuf {
&self.path
}
fn get_path_mut(&mut self) -> &mut PathBuf {
&mut self.path
}
fn get_base(&self) -> &PathBuf {
&self.base
}
fn apply(self: &Self, res: &Resources) {
/* get the resources that apply to this controller */
fn apply(&self, res: &Resources) -> Result<()> {
// get the resources that apply to this controller
let 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);
}
Ok(())
}
}
@@ -151,163 +438,224 @@ impl MemController {
///
/// See the individual fields for more explanation, and as always, remember to consult the
/// kernel Documentation and/or sources.
pub fn memory_stat(self: &Self) -> Memory {
pub fn memory_stat(&self) -> Memory {
Memory {
fail_cnt: self.open_path("memory.failcnt", false)
.and_then(|file| read_u64_from(file))
.unwrap_or(0),
limit_in_bytes: self.open_path("memory.limit_in_bytes", false)
.and_then(|file| read_u64_from(file))
.unwrap_or(0),
usage_in_bytes: self.open_path("memory.usage_in_bytes", false)
.and_then(|file| read_u64_from(file))
.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),
move_charge_at_immigrate: self.open_path("memory.move_charge_at_immigrate", false)
.and_then(|file| read_u64_from(file))
.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()),
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()),
soft_limit_in_bytes: self.open_path("memory.soft_limit_in_bytes", false)
.and_then(|file| read_u64_from(file))
.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()),
swappiness: self.open_path("memory.swappiness", false)
.and_then(|file| read_u64_from(file))
.unwrap_or(0),
use_hierarchy: self.open_path("memory.use_hierarchy", false)
.and_then(|file| read_u64_from(file))
.unwrap_or(0)
fail_cnt: self
.open_path("memory.failcnt", false)
.and_then(read_u64_from)
.unwrap_or(0),
limit_in_bytes: self
.open_path("memory.limit_in_bytes", false)
.and_then(read_u64_from)
.unwrap_or(0),
usage_in_bytes: self
.open_path("memory.usage_in_bytes", false)
.and_then(read_u64_from)
.unwrap_or(0),
max_usage_in_bytes: self
.open_path("memory.max_usage_in_bytes", false)
.and_then(read_u64_from)
.unwrap_or(0),
move_charge_at_immigrate: self
.open_path("memory.move_charge_at_immigrate", false)
.and_then(read_u64_from)
.unwrap_or(0),
numa_stat: self
.open_path("memory.numa_stat", false)
.and_then(read_string_from)
.and_then(parse_numa_stat)
.unwrap_or(NumaStat::default()),
oom_control: self
.open_path("memory.oom_control", false)
.and_then(read_string_from)
.and_then(parse_oom_control)
.unwrap_or(OomControl::default()),
soft_limit_in_bytes: self
.open_path("memory.soft_limit_in_bytes", false)
.and_then(read_u64_from)
.unwrap_or(0),
stat: self
.open_path("memory.stat", false)
.and_then(read_string_from)
.and_then(parse_memory_stat)
.unwrap_or(MemoryStat::default()),
swappiness: self
.open_path("memory.swappiness", false)
.and_then(read_u64_from)
.unwrap_or(0),
use_hierarchy: self
.open_path("memory.use_hierarchy", false)
.and_then(read_u64_from)
.unwrap_or(0),
}
}
/// Gathers information about the kernel memory usage of the control group's tasks.
pub fn kmem_stat(self: &Self) -> Kmem {
pub fn kmem_stat(&self) -> Kmem {
Kmem {
fail_cnt: self.open_path("memory.kmem.failcnt", false)
.and_then(|file| read_u64_from(file))
.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),
usage_in_bytes: self.open_path("memory.kmem.usage_in_bytes", false)
.and_then(|file| read_u64_from(file))
.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),
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()),
fail_cnt: self
.open_path("memory.kmem.failcnt", false)
.and_then(read_u64_from)
.unwrap_or(0),
limit_in_bytes: self
.open_path("memory.kmem.limit_in_bytes", false)
.and_then(read_u64_from)
.unwrap_or(0),
usage_in_bytes: self
.open_path("memory.kmem.usage_in_bytes", false)
.and_then(read_u64_from)
.unwrap_or(0),
max_usage_in_bytes: self
.open_path("memory.kmem.max_usage_in_bytes", false)
.and_then(read_u64_from)
.unwrap_or(0),
slabinfo: self
.open_path("memory.kmem.slabinfo", false)
.and_then(read_string_from)
.unwrap_or("".to_string()),
}
}
/// Gathers information about the control group's kernel memory usage where said memory is
/// TCP-related.
pub fn kmem_tcp_stat(self: &Self) -> Tcp {
pub fn kmem_tcp_stat(&self) -> Tcp {
Tcp {
fail_cnt: self.open_path("memory.kmem.tcp.failcnt", false)
.and_then(|file| read_u64_from(file))
.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),
usage_in_bytes: self.open_path("memory.kmem.tcp.usage_in_bytes", false)
.and_then(|file| read_u64_from(file))
.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),
fail_cnt: self
.open_path("memory.kmem.tcp.failcnt", false)
.and_then(read_u64_from)
.unwrap_or(0),
limit_in_bytes: self
.open_path("memory.kmem.tcp.limit_in_bytes", false)
.and_then(read_u64_from)
.unwrap_or(0),
usage_in_bytes: self
.open_path("memory.kmem.tcp.usage_in_bytes", false)
.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(read_u64_from)
.unwrap_or(0),
}
}
/// Gathers information about the memory usage of the control group including the swap usage
/// (if any).
pub fn memswap(self: &Self) -> MemSwap {
pub fn memswap(&self) -> MemSwap {
MemSwap {
fail_cnt: self.open_path("memory.memsw.failcnt", false)
.and_then(|file| read_u64_from(file))
.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),
usage_in_bytes: self.open_path("memory.memsw.usage_in_bytes", false)
.and_then(|file| read_u64_from(file))
.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),
fail_cnt: self
.open_path("memory.memsw.failcnt", false)
.and_then(read_u64_from)
.unwrap_or(0),
limit_in_bytes: self
.open_path("memory.memsw.limit_in_bytes", false)
.and_then(read_u64_from)
.unwrap_or(0),
usage_in_bytes: self
.open_path("memory.memsw.usage_in_bytes", false)
.and_then(read_u64_from)
.unwrap_or(0),
max_usage_in_bytes: self
.open_path("memory.memsw.max_usage_in_bytes", false)
.and_then(read_u64_from)
.unwrap_or(0),
}
}
/// Reset the fail counter
pub fn reset_fail_count(&self) -> Result<()> {
self.open_path("memory.failcnt", true)
.and_then(|mut file| {
file.write_all("0".to_string().as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
})
}
/// Reset the kernel memory fail counter
pub fn reset_kmem_fail_count(&self) -> Result<()> {
self.open_path("memory.kmem.failcnt", true)
.and_then(|mut file| {
file.write_all("0".to_string().as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
})
}
/// Reset the TCP related fail counter
pub fn reset_tcp_fail_count(&self) -> Result<()> {
self.open_path("memory.kmem.tcp.failcnt", true)
.and_then(|mut file| {
file.write_all("0".to_string().as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
})
}
/// Reset the memory+swap fail counter
pub fn reset_memswap_fail_count(&self) -> Result<()> {
self.open_path("memory.memsw.failcnt", true)
.and_then(|mut file| {
file.write_all("0".to_string().as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
})
}
/// Set the memory usage limit of the control group, in bytes.
pub fn set_limit(self: &Self, limit: u64) {
self.open_path("memory.limit_in_bytes", true).and_then(|mut file| {
file.write_all(limit.to_string().as_ref()).ok()
});
pub fn set_limit(&self, limit: u64) -> Result<()> {
self.open_path("memory.limit_in_bytes", true)
.and_then(|mut file| {
file.write_all(limit.to_string().as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
})
}
/// Set the kernel memory limit of the control group, in bytes.
pub fn set_kmem_limit(self: &Self, limit: u64) {
self.open_path("memory.kmem.limit_in_bytes", true).and_then(|mut file| {
file.write_all(limit.to_string().as_ref()).ok()
});
pub fn set_kmem_limit(&self, limit: u64) -> Result<()> {
self.open_path("memory.kmem.limit_in_bytes", true)
.and_then(|mut file| {
file.write_all(limit.to_string().as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
})
}
/// Set the memory+swap limit of the control group, in bytes.
pub fn set_memswap_limit(self: &Self, limit: u64) {
self.open_path("memory.memsw.limit_in_bytes", true).and_then(|mut file| {
file.write_all(limit.to_string().as_ref()).ok()
});
pub fn set_memswap_limit(&self, limit: u64) -> Result<()> {
self.open_path("memory.memsw.limit_in_bytes", true)
.and_then(|mut file| {
file.write_all(limit.to_string().as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
})
}
/// Set how much kernel memory can be used for TCP-related buffers by the control group.
pub fn set_tcp_limit(self: &Self, limit: u64) {
self.open_path("memory.kmem.tcp.limit_in_bytes", true).and_then(|mut file| {
file.write_all(limit.to_string().as_ref()).ok()
});
pub fn set_tcp_limit(&self, limit: u64) -> Result<()> {
self.open_path("memory.kmem.tcp.limit_in_bytes", true)
.and_then(|mut file| {
file.write_all(limit.to_string().as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
})
}
/// Set the soft limit of the control group, in bytes.
///
/// 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) {
self.open_path("memory.soft_limit_in_bytes", true).and_then(|mut file| {
file.write_all(limit.to_string().as_ref()).ok()
});
pub fn set_soft_limit(&self, limit: u64) -> Result<()> {
self.open_path("memory.soft_limit_in_bytes", true)
.and_then(|mut file| {
file.write_all(limit.to_string().as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
})
}
/// Set how likely the kernel is to swap out parts of the address space used by the control
/// 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) {
self.open_path("memory.swappiness", true).and_then(|mut file| {
file.write_all(swp.to_string().as_ref()).ok()
});
pub fn set_swappiness(&self, swp: u64) -> Result<()> {
self.open_path("memory.swappiness", true)
.and_then(|mut file| {
file.write_all(swp.to_string().as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
})
}
}
@@ -325,14 +673,173 @@ impl<'a> From<&'a Subsystem> for &'a MemController {
_ => {
assert_eq!(1, 0);
::std::mem::uninitialized()
},
}
}
}
}
}
fn read_u64_from(mut file: File) -> Option<u64> {
fn read_u64_from(mut file: File) -> Result<u64> {
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(|e| Error::with_cause(ParseError, e)),
Err(e) => Err(Error::with_cause(ReadFailed, e)),
}
}
fn read_string_from(mut file: File) -> Result<String> {
let mut string = String::new();
match file.read_to_string(&mut string) {
Ok(_) => Ok(string.trim().to_string()),
Err(e) => Err(Error::with_cause(ReadFailed, e)),
}
}
#[cfg(test)]
mod tests {
use memory::{
parse_memory_stat, parse_numa_stat, parse_oom_control, MemoryStat, NumaStat, OomControl,
};
static GOOD_VALUE: &str = "\
total=51189 N0=51189 N1=123
file=50175 N0=50175 N1=123
anon=1014 N0=1014 N1=123
unevictable=0 N0=0 N1=123
hierarchical_total=1628573 N0=1628573 N1=123
hierarchical_file=858151 N0=858151 N1=123
hierarchical_anon=770402 N0=770402 N1=123
hierarchical_unevictable=20 N0=20 N1=123
";
static GOOD_OOMCONTROL_VAL: &str = "\
oom_kill_disable 0
under_oom 1
oom_kill 1337
";
static GOOD_MEMORYSTAT_VAL: &str = "\
cache 178880512
rss 4206592
rss_huge 0
shmem 106496
mapped_file 7491584
dirty 114688
writeback 49152
swap 0
pgpgin 213928
pgpgout 169220
pgfault 87064
pgmajfault 202
inactive_anon 0
active_anon 4153344
inactive_file 84779008
active_file 94273536
unevictable 0
hierarchical_memory_limit 9223372036854771712
hierarchical_memsw_limit 9223372036854771712
total_cache 4200333312
total_rss 2927677440
total_rss_huge 0
total_shmem 590061568
total_mapped_file 1086164992
total_dirty 1769472
total_writeback 602112
total_swap 0
total_pgpgin 5267326291
total_pgpgout 5265586647
total_pgfault 9947902469
total_pgmajfault 25132
total_inactive_anon 585981952
total_active_anon 2928996352
total_inactive_file 1272135680
total_active_file 2338816000
total_unevictable 81920
";
#[test]
fn test_parse_numa_stat() {
let ok = parse_numa_stat(GOOD_VALUE.to_string()).unwrap();
assert_eq!(
ok,
NumaStat {
total_pages: 51189,
total_pages_per_node: vec![51189, 123],
file_pages: 50175,
file_pages_per_node: vec![50175, 123],
anon_pages: 1014,
anon_pages_per_node: vec![1014, 123],
unevictable_pages: 0,
unevictable_pages_per_node: vec![0, 123],
hierarchical_total_pages: 1628573,
hierarchical_total_pages_per_node: vec![1628573, 123],
hierarchical_file_pages: 858151,
hierarchical_file_pages_per_node: vec![858151, 123],
hierarchical_anon_pages: 770402,
hierarchical_anon_pages_per_node: vec![770402, 123],
hierarchical_unevictable_pages: 20,
hierarchical_unevictable_pages_per_node: vec![20, 123],
}
);
}
#[test]
fn test_parse_oom_control() {
let ok = parse_oom_control(GOOD_OOMCONTROL_VAL.to_string()).unwrap();
assert_eq!(
ok,
OomControl {
oom_kill_disable: false,
under_oom: true,
oom_kill: 1337,
}
);
}
#[test]
fn test_parse_memory_stat() {
let ok = parse_memory_stat(GOOD_MEMORYSTAT_VAL.to_string()).unwrap();
assert_eq!(
ok,
MemoryStat {
cache: 178880512,
rss: 4206592,
rss_huge: 0,
shmem: 106496,
mapped_file: 7491584,
dirty: 114688,
writeback: 49152,
swap: 0,
pgpgin: 213928,
pgpgout: 169220,
pgfault: 87064,
pgmajfault: 202,
inactive_anon: 0,
active_anon: 4153344,
inactive_file: 84779008,
active_file: 94273536,
unevictable: 0,
hierarchical_memory_limit: 9223372036854771712,
hierarchical_memsw_limit: 9223372036854771712,
total_cache: 4200333312,
total_rss: 2927677440,
total_rss_huge: 0,
total_shmem: 590061568,
total_mapped_file: 1086164992,
total_dirty: 1769472,
total_writeback: 602112,
total_swap: 0,
total_pgpgin: 5267326291,
total_pgpgout: 5265586647,
total_pgfault: 9947902469,
total_pgmajfault: 25132,
total_inactive_anon: 585981952,
total_active_anon: 2928996352,
total_inactive_file: 1272135680,
total_active_file: 2338816000,
total_unevictable: 81920,
}
);
}
}

View File

@@ -1,12 +1,18 @@
//! This module contains the implementation of the `net_cls` cgroup subsystem.
//!
//!
//! See the Kernel's documentation for more information about this subsystem, found at:
//! [Documentation/cgroup-v1/net_cls.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/net_cls.txt)
use std::path::PathBuf;
use std::io::{Read, Write};
use std::fs::File;
use std::io::{Read, Write};
use std::path::PathBuf;
use {NetworkResources, Controllers, Controller, Resources, ControllIdentifier, Subsystem};
use error::*;
use error::ErrorKind::*;
use {
ControllIdentifier, ControllerInternal, Controllers, NetworkResources, Resources,
Subsystem,
};
/// A controller that allows controlling the `net_cls` subsystem of a Cgroup.
///
@@ -19,19 +25,31 @@ pub struct NetClsController {
path: PathBuf,
}
impl Controller for NetClsController {
fn control_type(self: &Self) -> Controllers { Controllers::NetCls }
fn get_path<'a>(self: &'a Self) -> &'a PathBuf { &self.path }
fn get_path_mut<'a>(self: &'a mut Self) -> &'a mut PathBuf { &mut self.path }
fn get_base<'a>(self: &'a Self) -> &'a PathBuf { &self.base }
impl ControllerInternal for NetClsController {
fn control_type(&self) -> Controllers {
Controllers::NetCls
}
fn get_path(&self) -> &PathBuf {
&self.path
}
fn get_path_mut(&mut self) -> &mut PathBuf {
&mut self.path
}
fn get_base(&self) -> &PathBuf {
&self.base
}
fn apply(self: &Self, res: &Resources) {
/* get the resources that apply to this controller */
fn apply(&self, res: &Resources) -> Result<()> {
// get the resources that apply to this controller
let res: &NetworkResources = &res.network;
if res.update_values {
self.set_class(res.class_id);
let _ = self.set_class(res.class_id);
if self.get_class()? != res.class_id {
return Err(Error::new(Other));
}
}
return Ok(());
}
}
@@ -49,16 +67,18 @@ impl<'a> From<&'a Subsystem> for &'a NetClsController {
_ => {
assert_eq!(1, 0);
::std::mem::uninitialized()
},
}
}
}
}
}
fn read_u64_from(mut file: File) -> Option<u64> {
fn read_u64_from(mut file: File) -> Result<u64> {
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(|e| Error::with_cause(ParseError, e)),
Err(e) => Err(Error::with_cause(ReadFailed, e)),
}
}
impl NetClsController {
@@ -71,19 +91,19 @@ impl NetClsController {
path: root,
}
}
/// Set the network class id of the outgoing packets of the control group's tasks.
pub fn set_class(self: &Self, class: u64) {
self.open_path("net_cls.classid", true).and_then(|mut file| {
let s = format!("{:#08X}", class);
file.write_all(s.as_ref()).ok()
});
pub fn set_class(&self, class: u64) -> Result<()> {
self.open_path("net_cls.classid", true)
.and_then(|mut file| {
let s = format!("{:#08X}", class);
file.write_all(s.as_ref()).map_err(|e| Error::with_cause(WriteFailed, e))
})
}
/// Get the network class id of the outgoing packets of the control group's tasks.
pub fn get_class(self: &Self) -> u64 {
self.open_path("net_cls.classid", false).and_then(|file| {
read_u64_from(file)
}).unwrap_or(0u64)
pub fn get_class(&self) -> Result<u64> {
self.open_path("net_cls.classid", false)
.and_then(|file| read_u64_from(file))
}
}

View File

@@ -1,13 +1,19 @@
//! This module contains the implementation of the `net_prio` cgroup subsystem.
//!
//!
//! See the Kernel's documentation for more information about this subsystem, found at:
//! [Documentation/cgroup-v1/net_prio.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/net_prio.txt)
use std::path::PathBuf;
use std::io::{BufReader, BufRead, Write, Read};
use std::fs::File;
use std::collections::HashMap;
use std::fs::File;
use std::io::{BufRead, BufReader, Read, Write};
use std::path::PathBuf;
use {NetworkResources, Controllers, Controller, Resources, ControllIdentifier, Subsystem};
use error::*;
use error::ErrorKind::*;
use {
ControllIdentifier, ControllerInternal, Controllers, NetworkResources, Resources,
Subsystem,
};
/// A controller that allows controlling the `net_prio` subsystem of a Cgroup.
///
@@ -20,21 +26,31 @@ pub struct NetPrioController {
path: PathBuf,
}
impl Controller for NetPrioController {
fn control_type(self: &Self) -> Controllers { Controllers::NetPrio }
fn get_path<'a>(self: &'a Self) -> &'a PathBuf { &self.path }
fn get_path_mut<'a>(self: &'a mut Self) -> &'a mut PathBuf { &mut self.path }
fn get_base<'a>(self: &'a Self) -> &'a PathBuf { &self.base }
impl ControllerInternal for NetPrioController {
fn control_type(&self) -> Controllers {
Controllers::NetPrio
}
fn get_path(&self) -> &PathBuf {
&self.path
}
fn get_path_mut(&mut self) -> &mut PathBuf {
&mut self.path
}
fn get_base(&self) -> &PathBuf {
&self.base
}
fn apply(self: &Self, res: &Resources) {
/* get the resources that apply to this controller */
fn apply(&self, res: &Resources) -> Result<()> {
// get the resources that apply to this controller
let res: &NetworkResources = &res.network;
if res.update_values {
for i in &res.priorities {
self.set_if_prio(&i.name, i.priority);
let _ = self.set_if_prio(&i.name, i.priority);
}
}
Ok(())
}
}
@@ -52,16 +68,18 @@ impl<'a> From<&'a Subsystem> for &'a NetPrioController {
_ => {
assert_eq!(1, 0);
::std::mem::uninitialized()
},
}
}
}
}
}
fn read_u64_from(mut file: File) -> Option<u64> {
fn read_u64_from(mut file: File) -> Result<u64> {
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(|e| Error::with_cause(ParseError, e)),
Err(e) => Err(Error::with_cause(ReadFailed, e)),
}
}
impl NetPrioController {
@@ -76,31 +94,50 @@ impl NetPrioController {
}
/// Retrieves the current priority of the emitted packets.
pub fn prio_idx(self: &Self) -> u64 {
pub fn prio_idx(&self) -> u64 {
self.open_path("net_prio.prioidx", false)
.and_then(read_u64_from)
.unwrap_or(0)
}
/// A map of priorities for each network interface.
pub fn ifpriomap(self: &Self) -> HashMap<String, u64> {
pub fn ifpriomap(&self) -> Result<HashMap<String, u64>> {
self.open_path("net_prio.ifpriomap", false)
.and_then(|file| {
let bf = BufReader::new(file);
Some(bf.lines().map(|line| {
let l = line.unwrap();
let mut sp = l.split_whitespace();
(sp.nth(0).unwrap().to_string(),
sp.nth(1).unwrap().trim().parse().unwrap())
}).collect())
}).unwrap_or(HashMap::new())
bf.lines().fold(Ok(HashMap::new()), |acc, line| {
if acc.is_err() {
acc
} else {
let mut acc = acc.unwrap();
let l = line.unwrap();
let mut sp = l.split_whitespace();
let ifname = sp.nth(0);
let ifprio = sp.nth(1);
if ifname.is_none() || ifprio.is_none() {
Err(Error::new(ParseError))
} else {
let ifname = ifname.unwrap();
let ifprio = ifprio.unwrap().trim().parse();
match ifprio {
Err(e) => Err(Error::with_cause(ParseError, e)),
Ok(_) => {
acc.insert(ifname.to_string(), ifprio.unwrap());
Ok(acc)
}
}
}
}
})
})
}
/// Set the priority of the network traffic on `eif` to be `prio`.
pub fn set_if_prio(self: &Self, eif: &String, prio: u64) {
pub fn set_if_prio(&self, eif: &str, prio: u64) -> Result<()> {
self.open_path("net_prio.ifpriomap", true)
.and_then(|mut file| {
Some(file.write_all(format!("{} {}", eif, prio).as_ref()))
});
file.write_all(format!("{} {}", eif, prio).as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
})
}
}

View File

@@ -1,10 +1,12 @@
//! This module contains the implementation of the `perf_event` cgroup subsystem.
//!
//!
//! See the Kernel's documentation for more information about this subsystem, found at:
//! [tools/perf/Documentation/perf-record.txt](https://raw.githubusercontent.com/torvalds/linux/master/tools/perf/Documentation/perf-record.txt)
use std::path::PathBuf;
use {Controllers, Controller, Resources, ControllIdentifier, Subsystem};
use error::*;
use {ControllIdentifier, ControllerInternal, Controllers, Resources, Subsystem};
/// A controller that allows controlling the `perf_event` subsystem of a Cgroup.
///
@@ -16,13 +18,22 @@ pub struct PerfEventController {
path: PathBuf,
}
impl Controller for PerfEventController {
fn control_type(self: &Self) -> Controllers { Controllers::PerfEvent }
fn get_path<'a>(self: &'a Self) -> &'a PathBuf { &self.path }
fn get_path_mut<'a>(self: &'a mut Self) -> &'a mut PathBuf { &mut self.path }
fn get_base<'a>(self: &'a Self) -> &'a PathBuf { &self.base }
impl ControllerInternal for PerfEventController {
fn control_type(&self) -> Controllers {
Controllers::PerfEvent
}
fn get_path(&self) -> &PathBuf {
&self.path
}
fn get_path_mut(&mut self) -> &mut PathBuf {
&mut self.path
}
fn get_base(&self) -> &PathBuf {
&self.base
}
fn apply(self: &Self, _res: &Resources) {
fn apply(&self, _res: &Resources) -> Result<()> {
Ok(())
}
}
@@ -40,7 +51,7 @@ impl<'a> From<&'a Subsystem> for &'a PerfEventController {
_ => {
assert_eq!(1, 0);
::std::mem::uninitialized()
},
}
}
}
}

View File

@@ -1,11 +1,17 @@
//! This module contains the implementation of the `pids` cgroup subsystem.
//!
//!
//! See the Kernel's documentation for more information about this subsystem, found at:
//! [Documentation/cgroups-v1/pids.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/pids.txt)
use std::fs::File;
use std::io::{Read, Write};
use std::path::PathBuf;
use std::io::{Write, Read};
use {Resources, PidResources, Controller, ControllIdentifier, Subsystem, Controllers};
use error::*;
use error::ErrorKind::*;
use {
ControllIdentifier, ControllerInternal, Controllers, PidResources, Resources, Subsystem,
};
/// A controller that allows controlling the `pids` subsystem of a Cgroup.
#[derive(Debug, Clone)]
@@ -30,28 +36,45 @@ impl Default for PidMax {
}
}
impl Controller for PidController {
fn control_type(self: &Self) -> Controllers { Controllers::Pids }
fn get_path<'a>(self: &'a Self) -> &'a PathBuf { &self.path }
fn get_path_mut<'a>(self: &'a mut Self) -> &'a mut PathBuf { &mut self.path }
fn get_base<'a>(self: &'a Self) -> &'a PathBuf { &self.base }
impl ControllerInternal for PidController {
fn control_type(&self) -> Controllers {
Controllers::Pids
}
fn get_path(&self) -> &PathBuf {
&self.path
}
fn get_path_mut(&mut self) -> &mut PathBuf {
&mut self.path
}
fn get_base(&self) -> &PathBuf {
&self.base
}
fn apply(self: &Self, res: &Resources) {
/* get the resources that apply to this controller */
fn apply(&self, res: &Resources) -> Result<()> {
// get the resources that apply to this controller
let pidres: &PidResources = &res.pid;
if pidres.update_values {
/* apply pid_max */
self.set_pid_max(pidres.maximum_number_of_processes);
// apply pid_max
let _ = self.set_pid_max(pidres.maximum_number_of_processes);
// now, verify
if self.get_pid_max()? == pidres.maximum_number_of_processes {
return Ok(());
} else {
return Err(Error::new(Other));
}
}
Ok(())
}
}
/*impl<'a> ControllIdentifier for &'a PidController {
fn controller_type() -> Controllers {
Controllers::Pids
}
}*/
// impl<'a> ControllIdentifier for &'a PidController {
// fn controller_type() -> Controllers {
// Controllers::Pids
// }
// }
impl ControllIdentifier for PidController {
fn controller_type() -> Controllers {
@@ -67,12 +90,20 @@ impl<'a> From<&'a Subsystem> for &'a PidController {
_ => {
assert_eq!(1, 0);
::std::mem::uninitialized()
},
}
}
}
}
}
fn read_u64_from(mut file: File) -> Result<u64> {
let mut string = String::new();
match file.read_to_string(&mut string) {
Ok(_) => string.trim().parse().map_err(|e| Error::with_cause(ParseError, e)),
Err(e) => Err(Error::with_cause(ReadFailed, e)),
}
}
impl PidController {
/// Constructors a new `PidController` instance, with `oroot` serving as the controller's root
/// directory.
@@ -86,32 +117,43 @@ 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) -> Result<u64> {
self.open_path("pids.events", false).and_then(|mut file| {
let mut string = String::new();
let _ = file.read_to_string(&mut string);
Some(string.split_whitespace().nth(1).unwrap().parse().unwrap_or(0))
}).unwrap()
match file.read_to_string(&mut string) {
Ok(_) => match string.split_whitespace().nth(1) {
Some(elem) => match elem.parse() {
Ok(val) => Ok(val),
Err(e) => Err(Error::with_cause(ParseError, e)),
},
None => Err(Error::new(ParseError)),
},
Err(e) => Err(Error::with_cause(ReadFailed, e)),
}
})
}
/// The number of processes currently.
pub fn get_pid_current(self: &Self) -> i64 {
self.open_path("pids.current", false).and_then(|mut file| {
let mut string = String::new();
let _ = file.read_to_string(&mut string);
Some(string.trim().parse().unwrap_or(0))
}).unwrap()
pub fn get_pid_current(&self) -> Result<u64> {
self.open_path("pids.current", false)
.and_then(read_u64_from)
}
/// The maximum number of processes that can exist at one time in the control group.
pub fn get_pid_max(self: &Self) -> Option<PidMax> {
pub fn get_pid_max(&self) -> Result<PidMax> {
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(e) => Err(Error::with_cause(ParseError, e)),
}
},
Err(e) => Err(Error::with_cause(ReadFailed, e)),
}
})
}
@@ -121,17 +163,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, max_pid: PidMax) -> Result<()> {
self.open_path("pids.max", true).and_then(|mut file| {
let string_to_write = match max_pid {
PidMax::Max => "max".to_string(),
PidMax::Value(num) => num.to_string(),
};
match file.write_all(string_to_write.as_ref()) {
Ok(_) => (),
Err(e) => println!("error {:?}", e),
Ok(_) => Ok(()),
Err(e) => Err(Error::with_cause(WriteFailed, e)),
}
Some(0i64)
});
})
}
}

View File

@@ -1,12 +1,15 @@
//! This module contains the implementation of the `rdma` cgroup subsystem.
//!
//!
//! See the Kernel's documentation for more information about this subsystem, found at:
//! [Documentation/cgroup-v1/rdma.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/rdma.txt)
use std::path::PathBuf;
use std::io::{Write, Read};
use std::fs::File;
use std::io::{Read, Write};
use std::path::PathBuf;
use {Controllers, Controller, Resources, ControllIdentifier, Subsystem};
use error::*;
use error::ErrorKind::*;
use {ControllIdentifier, ControllerInternal, Controllers, Resources, Subsystem};
/// A controller that allows controlling the `rdma` subsystem of a Cgroup.
///
@@ -18,13 +21,22 @@ pub struct RdmaController {
path: PathBuf,
}
impl Controller for RdmaController {
fn control_type(self: &Self) -> Controllers { Controllers::Rdma }
fn get_path<'a>(self: &'a Self) -> &'a PathBuf { &self.path }
fn get_path_mut<'a>(self: &'a mut Self) -> &'a mut PathBuf { &mut self.path }
fn get_base<'a>(self: &'a Self) -> &'a PathBuf { &self.base }
impl ControllerInternal for RdmaController {
fn control_type(&self) -> Controllers {
Controllers::Rdma
}
fn get_path(&self) -> &PathBuf {
&self.path
}
fn get_path_mut(&mut self) -> &mut PathBuf {
&mut self.path
}
fn get_base(&self) -> &PathBuf {
&self.base
}
fn apply(self: &Self, _res: &Resources) {
fn apply(&self, _res: &Resources) -> Result<()> {
Ok(())
}
}
@@ -42,16 +54,18 @@ impl<'a> From<&'a Subsystem> for &'a RdmaController {
_ => {
assert_eq!(1, 0);
::std::mem::uninitialized()
},
}
}
}
}
}
fn read_string_from(mut file: File) -> Option<String> {
fn read_string_from(mut file: File) -> Result<String> {
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(Error::with_cause(ReadFailed, e)),
}
}
impl RdmaController {
@@ -66,17 +80,16 @@ impl RdmaController {
}
/// Returns the current usage of RDMA/IB specific resources.
pub fn current(self: &Self) -> String {
pub fn current(&self) -> Result<String> {
self.open_path("rdma.current", false)
.and_then(read_string_from)
.unwrap_or("".to_string())
}
/// Set a maximum usage for each RDMA/IB resource.
pub fn set_max(self: &Self, max: &String) {
self.open_path("rdma.max", true)
.and_then(|mut file| {
file.write_all(max.as_ref()).ok()
});
pub fn set_max(&self, max: &str) -> Result<()> {
self.open_path("rdma.max", true).and_then(|mut file| {
file.write_all(max.as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
})
}
}

145
tests/builder.rs Normal file
View File

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

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 libc;
extern crate nix;
#[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();
}

19
tests/cpuset.rs Normal file
View File

@@ -0,0 +1,19 @@
extern crate cgroups;
use cgroups::cpuset::CpuSetController;
use cgroups::error::ErrorKind;
use cgroups::Cgroup;
#[test]
fn test_cpuset_memory_pressure_root_cg() {
let hier = cgroups::hierarchies::V1::new();
let cg = Cgroup::new(&hier, String::from("test_cpuset_memory_pressure_root_cg"));
{
let cpuset: &CpuSetController = cg.controller_of().unwrap();
// This is not a root control group, so it should fail via InvalidOperation.
let res = cpuset.set_enable_memory_pressure(true);
assert_eq!(res.unwrap_err().kind(), &ErrorKind::InvalidOperation);
}
cg.delete();
}

57
tests/devices.rs Normal file
View File

@@ -0,0 +1,57 @@
//! Integration tests about the devices subsystem
extern crate cgroups;
use cgroups::devices::{DevicePermissions, DeviceType, DevicesController};
use cgroups::{Cgroup, DeviceResource};
#[test]
fn test_devices_parsing() {
let hier = cgroups::hierarchies::V1::new();
let cg = Cgroup::new(&hier, String::from("test_devices_parsing"));
{
let devices: &DevicesController = cg.controller_of().unwrap();
// Deny access to all devices first
devices.deny_device(
DeviceType::All,
-1,
-1,
&vec![
DevicePermissions::Read,
DevicePermissions::Write,
DevicePermissions::MkNod,
],
);
// Acquire the list of allowed devices after we denied all
let allowed_devices = devices.allowed_devices();
// Verify that there are no devices that we can access.
assert!(allowed_devices.is_ok());
assert_eq!(allowed_devices.unwrap(), Vec::new());
// Now add mknod access to /dev/null device
devices.allow_device(DeviceType::Char, 1, 3, &vec![DevicePermissions::MkNod]);
let allowed_devices = devices.allowed_devices();
assert!(allowed_devices.is_ok());
let allowed_devices = allowed_devices.unwrap();
assert_eq!(allowed_devices.len(), 1);
assert_eq!(
allowed_devices[0],
DeviceResource {
allow: true,
devtype: DeviceType::Char,
major: 1,
minor: 3,
access: vec![DevicePermissions::MkNod],
}
);
// Now deny, this device explicitly.
devices.deny_device(DeviceType::Char, 1, 3, &DevicePermissions::all());
// Finally, check that.
let allowed_devices = devices.allowed_devices();
// Verify that there are no devices that we can access.
assert!(allowed_devices.is_ok());
assert_eq!(allowed_devices.unwrap(), Vec::new());
}
cg.delete();
}

102
tests/pids.rs Normal file
View File

@@ -0,0 +1,102 @@
//! Integration tests about the pids subsystem
extern crate cgroups;
use cgroups::pid::{PidController, PidMax};
use cgroups::Controller;
use cgroups::{Cgroup, CgroupPid, PidResources, Resources};
extern crate nix;
use nix::sys::wait::{waitpid, WaitStatus};
use nix::unistd::{fork, ForkResult, Pid};
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));
let max = pidcontroller.get_pid_max();
assert!(max.is_ok());
assert_eq!(max.unwrap(), 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();
let current = pidcontroller.get_pid_current();
assert_eq!(current.unwrap(), 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();
let events = pidcontroller.get_pid_events();
assert!(events.is_ok());
assert_eq!(events.unwrap(), 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();
let before = before.unwrap();
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
let events = pids.get_pid_events();
assert!(events.is_ok());
assert_eq!(events.unwrap(), before + 1);
}
Ok(ForkResult::Child) => loop {
let pids_max = pids.get_pid_max();
if pids_max.is_ok() && pids_max.unwrap() == PidMax::Value(1) {
if let Err(_) = fork() {
unsafe { libc::exit(0) };
} else {
unsafe { libc::exit(1) };
}
}
},
Err(_) => panic!("failed to fork"),
}
}
cg.delete();
}

28
tests/resources.rs Normal file
View File

@@ -0,0 +1,28 @@
//! Integration test about setting resources using `apply()`
extern crate cgroups;
use cgroups::pid::{PidController, PidMax};
use cgroups::{Cgroup, PidResources, Resources};
#[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();
let pid_max = pidcontroller.get_pid_max();
assert_eq!(pid_max.is_ok(), true);
assert_eq!(pid_max.unwrap(), 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