add basic v2 cpu/memory functions

Signed-off-by: bin liu <bin@hyper.sh>
This commit is contained in:
bin liu
2020-09-02 22:03:11 +08:00
parent 704db324ae
commit c623dc3fba
7 changed files with 256 additions and 55 deletions

View File

@@ -4,6 +4,7 @@ use crate::error::*;
use crate::{CgroupPid, ControllIdentifier, Controller, Hierarchy, Resources, Subsystem};
use std::collections::HashMap;
use std::convert::From;
use std::fs;
use std::path::{Path, PathBuf};
@@ -41,6 +42,10 @@ impl<'b> Cgroup<'b> {
}
}
pub fn v2(&self) -> bool {
self.hier.v2()
}
/// Create a new control group in the hierarchy `hier`, with name `path`.
///
/// Returns a handle to the control group that can be used to manipulate it.
@@ -79,6 +84,42 @@ impl<'b> Cgroup<'b> {
cg
}
pub fn new_with_prefix<P: AsRef<Path>>(hier: Box<&'b dyn Hierarchy>, path: P, prefixes: HashMap<String, String>) -> Cgroup<'b> {
let cg = Cgroup::load_with_prefix(hier, path, prefixes);
cg.create();
cg
}
pub fn load_with_prefix<P: AsRef<Path>>(hier: Box<&'b dyn Hierarchy>, path: P, prefixes: HashMap<String, String>) -> Cgroup<'b> {
let path = path.as_ref();
let mut subsystems = hier.subsystems();
if path.as_os_str() != "" {
subsystems = subsystems
.into_iter()
.map(|x| {
let cn = x.controller_name();
if prefixes.contains_key(&cn) {
let prefix = prefixes.get(&cn).unwrap();
let valid_path = prefix.trim_start_matches("/").to_string();
let mut p = PathBuf::from(valid_path);
p.push(path);
x.enter(p.as_ref())
}else {
x.enter(path)
}
})
.collect::<Vec<_>>();
}
let cg = Cgroup {
subsystems: subsystems,
hier: hier,
path: path.to_str().unwrap().to_string(),
};
cg
}
/// The list of subsystems that this control group supports.
pub fn subsystems(&self) -> &Vec<Subsystem> {
&self.subsystems
@@ -175,6 +216,16 @@ impl<'b> Cgroup<'b> {
pub const UNIFIED_MOUNTPOINT: &'static str = "/sys/fs/cgroup";
fn enable_controllers(controllers: &Vec<String>, path: &PathBuf) {
let mut f = path.clone();
f.push("cgroup.subtree_control");
for c in controllers{
let body = format!("+{}", c);
// FIXME set mode to 0644
let _rest = fs::write(f.as_path(), body.as_bytes());
}
}
fn supported_controllers(p: &PathBuf) -> Vec<String>{
let p = format!("{}/{}", UNIFIED_MOUNTPOINT, "cgroup.controllers");
let ret = fs::read_to_string(p.as_str());
@@ -186,6 +237,9 @@ fn create_v2_cgroup(root: PathBuf, path: &str) -> Result<()> {
let controllers = supported_controllers(&root);
let mut fp = root;
// enable for root
enable_controllers(&controllers, &fp);
// path: "a/b/c"
let elements = path.split("/").collect::<Vec<&str>>();
let last_index = elements.len() - 1 ;
@@ -203,13 +257,7 @@ fn create_v2_cgroup(root: PathBuf, path: &str) -> Result<()> {
if i < last_index {
// enable controllers for substree
let mut f = fp.clone();
f.push("cgroup.subtree_control");
for c in &controllers{
let body = format!("+{}", c);
// FIXME set mode to 0644
let _rest = fs::write(f.as_path(), body.as_bytes());
}
enable_controllers(&controllers, &fp);
}
}

View File

@@ -149,7 +149,12 @@ impl CpuController {
/// `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, shares: u64) -> Result<()> {
self.open_path("cpu.shares", true).and_then(|mut file| {
let mut file = "cpu.shares";
if self.v2 {
file = "cpu.weight";
}
// NOTE: .CpuShares is not used here. Conversion is the caller's responsibility.
self.open_path(file, true).and_then(|mut file| {
file.write_all(shares.to_string().as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
})
@@ -194,4 +199,44 @@ impl CpuController {
self.open_path("cpu.cfs_quota_us", false)
.and_then(read_u64_from)
}
pub fn set_cfs_quota_and_period(&self, quota: u64, period: u64) -> Result<()> {
if !self.v2 {
self.set_cfs_quota(quota)?;
return self.set_cfs_period(period);
}
let mut line = "max".to_string();
if quota > 0 {
line = quota.to_string();
}
let mut p = period;
if period == 0 {
// This default value is documented in
// https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html
p = 100000
}
line = format!("{} {}", line, p);
self.open_path("cpu.max", true)
.and_then(|mut file| {
file.write_all(line.as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
})
}
pub fn set_rt_runtime(&self, us: i64) -> Result<()> {
self.open_path("cpu.rt_runtime_us", true)
.and_then(|mut file| {
file.write_all(us.to_string().as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
})
}
pub fn set_rt_period_us(&self, us: u64) -> Result<()> {
self.open_path("cpu.rt_period_us", true)
.and_then(|mut file| {
file.write_all(us.to_string().as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
})
}
}

View File

@@ -70,7 +70,7 @@ impl Hierarchy for V1 {
subs.push(Subsystem::NetCls(NetClsController::new(self.root())));
}
if self.check_support(Controllers::BlkIo) {
subs.push(Subsystem::BlkIo(BlkIoController::new(self.root(), true)));
subs.push(Subsystem::BlkIo(BlkIoController::new(self.root(), false)));
}
if self.check_support(Controllers::PerfEvent) {
subs.push(Subsystem::PerfEvent(PerfEventController::new(self.root())));
@@ -125,7 +125,6 @@ impl Hierarchy for V2 {
}
let controllers = ret.unwrap().trim().to_string();
println!("controllers: {:?}", controllers);
let controller_list: Vec<&str> = controllers.split(' ').collect();
@@ -140,30 +139,30 @@ impl Hierarchy for V2 {
}
}
if self.check_support(Controllers::CpuAcct) {
subs.push(Subsystem::CpuAcct(CpuAcctController::new(self.root())));
}
if self.check_support(Controllers::Devices) {
subs.push(Subsystem::Devices(DevicesController::new(self.root())));
}
if self.check_support(Controllers::Freezer) {
subs.push(Subsystem::Freezer(FreezerController::new(self.root())));
}
if self.check_support(Controllers::NetCls) {
subs.push(Subsystem::NetCls(NetClsController::new(self.root())));
}
if self.check_support(Controllers::PerfEvent) {
subs.push(Subsystem::PerfEvent(PerfEventController::new(self.root())));
}
if self.check_support(Controllers::NetPrio) {
subs.push(Subsystem::NetPrio(NetPrioController::new(self.root())));
}
if self.check_support(Controllers::HugeTlb) {
subs.push(Subsystem::HugeTlb(HugeTlbController::new(self.root(), true)));
}
if self.check_support(Controllers::Rdma) {
subs.push(Subsystem::Rdma(RdmaController::new(self.root())));
}
// if self.check_support(Controllers::CpuAcct) {
// subs.push(Subsystem::CpuAcct(CpuAcctController::new(self.root())));
// }
// if self.check_support(Controllers::Devices) {
// subs.push(Subsystem::Devices(DevicesController::new(self.root())));
// }
// if self.check_support(Controllers::Freezer) {
// subs.push(Subsystem::Freezer(FreezerController::new(self.root())));
// }
// if self.check_support(Controllers::NetCls) {
// subs.push(Subsystem::NetCls(NetClsController::new(self.root())));
// }
// if self.check_support(Controllers::PerfEvent) {
// subs.push(Subsystem::PerfEvent(PerfEventController::new(self.root())));
// }
// if self.check_support(Controllers::NetPrio) {
// subs.push(Subsystem::NetPrio(NetPrioController::new(self.root())));
// }
// if self.check_support(Controllers::HugeTlb) {
// subs.push(Subsystem::HugeTlb(HugeTlbController::new(self.root(), true)));
// }
// if self.check_support(Controllers::Rdma) {
// subs.push(Subsystem::Rdma(RdmaController::new(self.root())));
// }
subs
}
@@ -205,6 +204,7 @@ impl V2 {
pub const UNIFIED_MOUNTPOINT: &'static str = "/sys/fs/cgroup";
#[cfg(all(target_os = "linux", not(target_env = "musl")))]
pub fn is_cgroup2_unified_mode() -> bool {
let path = Path::new(UNIFIED_MOUNTPOINT);
let fs_stat = statfs::statfs(path);
@@ -212,9 +212,32 @@ pub fn is_cgroup2_unified_mode() -> bool {
return false
}
// FIXME notwork, nix will not compile CGROUP2_SUPER_MAGIC because not(target_env = "musl")
fs_stat.unwrap().filesystem_type() == statfs::CGROUP2_SUPER_MAGIC
}
pub const INIT_CGROUP_PATHS: &'static str = "/proc/1/cgroup";
#[cfg(all(target_os = "linux", target_env = "musl"))]
pub fn is_cgroup2_unified_mode() -> bool {
let lines = fs::read_to_string(INIT_CGROUP_PATHS);
if lines.is_err() {
return false
}
for line in lines.unwrap().lines(){
let fields: Vec<&str> = line.split(':').collect();
if fields.len() != 3 {
continue;
}
if fields[0] != "0" {
return false;
}
}
true
}
pub fn auto() -> Box<dyn Hierarchy> {
if is_cgroup2_unified_mode() {
Box::new(V2::new())

View File

@@ -231,7 +231,7 @@ impl<T> Controller for T where T: ControllerInternal {
/// Create this controller
fn create(&self) {
self.verify_path().expect("path should be valid");
self.verify_path().expect(format!("path should be valid: {:?}", self.path()).as_str());
match ::std::fs::create_dir_all(self.get_path()) {
Ok(_) => self.post_create(),
@@ -253,7 +253,11 @@ impl<T> Controller for T where T: ControllerInternal {
/// Attach a task to this controller.
fn add_task(&self, pid: &CgroupPid) -> Result<()> {
self.open_path("tasks", true).and_then(|mut file| {
let mut file = "tasks";
if self.is_v2() {
file = "cgroup.procs";
}
self.open_path(file, true).and_then(|mut file| {
file.write_all(pid.pid.to_string().as_ref())
.map_err(|e| Error::with_cause(ErrorKind::WriteFailed, e))
})
@@ -261,7 +265,11 @@ impl<T> Controller for T where T: ControllerInternal {
/// Get the list of tasks that this controller has.
fn tasks(&self) -> Vec<CgroupPid> {
self.open_path("tasks", false)
let mut file = "tasks";
if self.is_v2() {
file = "cgroup.procs";
}
self.open_path(file, false)
.and_then(|file| {
let bf = BufReader::new(file);
let mut v = Vec::new();
@@ -608,6 +616,10 @@ impl Subsystem {
Subsystem::Rdma(cont) => cont,
}
}
fn controller_name(&self) -> String {
self.to_controller().control_type().to_string()
}
}
@@ -627,6 +639,22 @@ impl Default for MaxValue {
}
}
impl MaxValue {
fn to_i64(&self) -> i64 {
match self {
MaxValue::Max => -1,
MaxValue::Value(num) => *num,
}
}
fn to_string(&self) -> String {
match self {
MaxValue::Max => "max".to_string(),
MaxValue::Value(num) => num.to_string(),
}
}
}
pub fn parse_max_value(s: &String) -> Result<MaxValue> {
if s.trim() == "max" {
return Ok(MaxValue::Max)
@@ -636,10 +664,3 @@ pub fn parse_max_value(s: &String) -> Result<MaxValue> {
Err(e) => Err(Error::with_cause(ParseError, e)),
}
}
pub fn max_value_to_string(m: MaxValue) -> String {
match m {
MaxValue::Max => "max".to_string(),
MaxValue::Value(num) => num.to_string(),
}
}

View File

@@ -13,11 +13,9 @@ use crate::error::*;
use crate::events;
use crate::{
ControllIdentifier, ControllerInternal, Controllers, MemoryResources, Resources, Subsystem,
ControllIdentifier, ControllerInternal, Controllers, MaxValue, MemoryResources, Resources, Subsystem,
};
use crate::{MaxValue, max_value_to_string, parse_max_value};
/// A controller that allows controlling the `memory` subsystem of a Cgroup.
///
/// In essence, using the memory controller, the user can gather statistics about the memory usage
@@ -481,8 +479,7 @@ impl MemController {
let v = value.0;
let f = value.1;
if v.is_some() {
let v = v.unwrap();
let v = max_value_to_string(v);
let v = v.unwrap().to_string();
self.open_path(f, true)
.and_then(|mut file| {
file.write_all(v.as_ref())
@@ -504,12 +501,44 @@ impl MemController {
Ok(m)
}
fn memory_stat_v2(&self) -> Memory {
let set = self.get_mem().unwrap();
Memory {
fail_cnt: 0,
limit_in_bytes: set.max.unwrap().to_i64(),
usage_in_bytes: self
.open_path("memory.current", false)
.and_then(read_u64_from)
.unwrap_or(0),
max_usage_in_bytes: 0,
move_charge_at_immigrate: 0,
numa_stat: NumaStat::default(),
oom_control: OomControl::default(),
soft_limit_in_bytes: set.high.unwrap().to_i64(),
stat: self
.open_path("memory.stat", false)
.and_then(read_string_from)
.and_then(parse_memory_stat)
.unwrap_or(MemoryStat::default()),
swappiness: self
.open_path("memory.swap.current", false)
.and_then(read_u64_from)
.unwrap_or(0),
use_hierarchy: 0,
}
}
/// Gathers overall statistics (and the current state of) about the memory usage of the control
/// group's tasks.
///
/// See the individual fields for more explanation, and as always, remember to consult the
/// kernel Documentation and/or sources.
pub fn memory_stat(&self) -> Memory {
if self.v2 {
return self.memory_stat_v2();
}
Memory {
fail_cnt: self
.open_path("memory.failcnt", false)
@@ -670,7 +699,11 @@ impl MemController {
/// Set the memory usage limit of the control group, in bytes.
pub fn set_limit(&self, limit: i64) -> Result<()> {
self.open_path("memory.limit_in_bytes", true)
let mut file = "memory.limit_in_bytes";
if self.v2 {
file = "memory.max";
}
self.open_path(file, true)
.and_then(|mut file| {
file.write_all(limit.to_string().as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
@@ -688,7 +721,11 @@ impl MemController {
/// Set the memory+swap limit of the control group, in bytes.
pub fn set_memswap_limit(&self, limit: i64) -> Result<()> {
self.open_path("memory.memsw.limit_in_bytes", true)
let mut file = "memory.memsw.limit_in_bytes";
if self.v2 {
file = "memory.swap.max";
}
self.open_path(file, true)
.and_then(|mut file| {
file.write_all(limit.to_string().as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))
@@ -709,7 +746,11 @@ impl MemController {
/// This limit is enforced when the system is nearing OOM conditions. Contrast this with the
/// hard limit, which is _always_ enforced.
pub fn set_soft_limit(&self, limit: i64) -> Result<()> {
self.open_path("memory.soft_limit_in_bytes", true)
let mut file = "memory.soft_limit_in_bytes";
if self.v2 {
file = "memory.low"
}
self.open_path(file, true)
.and_then(|mut file| {
file.write_all(limit.to_string().as_ref())
.map_err(|e| Error::with_cause(WriteFailed, e))

View File

@@ -10,7 +10,7 @@ use crate::error::*;
use crate::error::ErrorKind::*;
use crate::{
ControllIdentifier, ControllerInternal, Controllers, MaxValue, max_value_to_string, parse_max_value, PidResources, Resources, Subsystem,
ControllIdentifier, ControllerInternal, Controllers, MaxValue, parse_max_value, PidResources, Resources, Subsystem,
};
/// A controller that allows controlling the `pids` subsystem of a Cgroup.
@@ -150,7 +150,7 @@ impl PidController {
/// extra processes to a control group disregards the limit.
pub fn set_pid_max(&self, max_pid: MaxValue) -> Result<()> {
self.open_path("pids.max", true).and_then(|mut file| {
let string_to_write = max_value_to_string(max_pid);
let string_to_write = max_pid.to_string();
match file.write_all(string_to_write.as_ref()) {
Ok(_) => Ok(()),
Err(e) => Err(Error::with_cause(WriteFailed, e)),

View File

@@ -1,5 +1,7 @@
//! Simple unit tests about the control groups system.
use cgroups::{Cgroup, CgroupPid, Hierarchy};
use cgroups::{Cgroup, CgroupPid, Hierarchy, Subsystem};
use cgroups::memory::{MemController, SetMemory};
use std::collections::HashMap;
#[test]
fn test_tasks_iterator() {
@@ -24,3 +26,24 @@ fn test_tasks_iterator() {
}
cg.delete();
}
#[test]
fn test_cgroup_with_prefix() {
let h = cgroups::hierarchies::auto();
let h = Box::new(&*h);
let mut prefixes = HashMap::new();
prefixes.insert("memory".to_string(), "/memory/abc/def".to_string());
let cg = Cgroup::new_with_prefix(h, String::from("test_cgroup_with_prefix"), prefixes);
{
let subsystems = cg.subsystems();
println!("mem path: {:?}", &subsystems);
subsystems.into_iter().for_each(|sub| match sub {
Subsystem::Pid(c) => {println!("path {:?}", c);},
// base: "/sys/fs/cgroup", path: "/sys/fs/cgroup/memory/abc/def/test_cgroup_with_prefix"
Subsystem::Mem(c) => {println!("path {:?}", c);},
_ => {},
});
}
cg.delete();
}