From b4cc91f977026d7195cf9510d9aafcd4579b76a1 Mon Sep 17 00:00:00 2001 From: Levente Kurusa Date: Sun, 21 Oct 2018 17:21:53 +0200 Subject: [PATCH] Builder pattern for control groups Signed-off-by: Levente Kurusa --- src/blkio.rs | 17 ++- src/cgroup_builder.rs | 322 ++++++++++++++++++++++++++++++++++++++++++ src/hugetlb.rs | 2 +- src/lib.rs | 1 + tests/builder.rs | 145 +++++++++++++++++++ 5 files changed, 482 insertions(+), 5 deletions(-) create mode 100644 src/cgroup_builder.rs create mode 100644 tests/builder.rs diff --git a/src/blkio.rs b/src/blkio.rs index 8d1ac08..1fded4c 100644 --- a/src/blkio.rs +++ b/src/blkio.rs @@ -279,6 +279,7 @@ impl ControllerInternal for BlkIoController { for dev in &res.weight_device { 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 { @@ -587,14 +588,22 @@ impl BlkIoController { } /// Same as `set_leaf_weight()`, but settable per each block device. - pub fn set_leaf_weight_for_device(&self, d: String) -> Result<()> { + 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(d.as_ref()).map_err(|e| Error::with_cause(WriteFailed, e))) + .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) -> Result<()> { - self.open_path("blkio.leaf_weight_device", true) + 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)) @@ -662,7 +671,7 @@ impl BlkIoController { /// Set the weight of the control group's tasks. pub fn set_weight(&self, w: u64) -> Result<()> { - self.open_path("blkio.leaf_weight", true) + 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)) diff --git a/src/cgroup_builder.rs b/src/cgroup_builder.rs new file mode 100644 index 0000000..d37a036 --- /dev/null +++ b/src/cgroup_builder.rs @@ -0,0 +1,322 @@ +//! This module allows the user to create a control group using the Builder pattern. +use error::*; + +use {pid, BlkIoDeviceResource, BlkIoDeviceThrottleResource, Cgroup, DeviceResource, Hierarchy, HugePageResource, NetworkPriority, Resources}; + +// 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![Read, Write, MkNod] +// .device(6, 1, DeviceType::Char, false, vec![]) +// .done() +// .network() +// .class_id(1337) +// .priority("eth0", 100) +// .priority("wl0", 200) +// .done() +// .hugepages() +// .limit("2M", 0) +// .limit("4M", 4 * 1024 * 1024 * 100) +// .limit("2G", 2 * 1024 * 1024 * 1024) +// .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(); +// + + +macro_rules! gen_setter { + ($res:ident, $name:ident, $ty:ty) => { + 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: +/// +/// # Example +/// Bla bla. TODO. +pub struct CgroupBuilder<'a> { + name: String, + hierarchy: &'a Hierarchy, + /// Internal, unsupported field: use the associated builders instead. + resources: Resources, // XXX: this should not be public. +} + +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(), + } + } + + pub fn memory(self) -> MemoryResourceBuilder<'a> { + MemoryResourceBuilder { + cgroup: self, + } + } + + pub fn pid(self) -> PidResourceBuilder<'a> { + PidResourceBuilder { + cgroup: self, + } + } + + pub fn cpu(self) -> CpuResourceBuilder<'a> { + CpuResourceBuilder { + cgroup: self, + } + } + + pub fn devices(self) -> DeviceResourceBuilder<'a> { + DeviceResourceBuilder { + cgroup: self, + } + } + + pub fn network(self) -> NetworkResourceBuilder<'a> { + NetworkResourceBuilder { + cgroup: self, + } + } + + pub fn hugepages(self) -> HugepagesResourceBuilder<'a> { + HugepagesResourceBuilder { + cgroup: self, + } + } + + 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 + } +} + +pub struct MemoryResourceBuilder<'a> { + cgroup: CgroupBuilder<'a>, +} + +impl<'a> MemoryResourceBuilder<'a> { + + gen_setter!(memory, kernel_memory_limit, u64); + gen_setter!(memory, memory_hard_limit, u64); + gen_setter!(memory, memory_soft_limit, u64); + gen_setter!(memory, kernel_tcp_memory_limit, u64); + gen_setter!(memory, memory_swap_limit, u64); + gen_setter!(memory, swappiness, u64); + + pub fn done(self) -> CgroupBuilder<'a> { + self.cgroup + } +} + +pub struct PidResourceBuilder<'a> { + cgroup: CgroupBuilder<'a>, +} + +impl<'a> PidResourceBuilder<'a> { + + gen_setter!(pid, maximum_number_of_processes, pid::PidMax); + + pub fn done(self) -> CgroupBuilder<'a> { + self.cgroup + } +} + +pub struct CpuResourceBuilder<'a> { + cgroup: CgroupBuilder<'a>, +} + +impl<'a> CpuResourceBuilder<'a> { + + gen_setter!(cpu, cpus, String); + gen_setter!(cpu, mems, String); + gen_setter!(cpu, shares, u64); + gen_setter!(cpu, quota, i64); + gen_setter!(cpu, period, u64); + gen_setter!(cpu, realtime_runtime, i64); + gen_setter!(cpu, realtime_period, u64); + + pub fn done(self) -> CgroupBuilder<'a> { + self.cgroup + } +} + +pub struct DeviceResourceBuilder<'a> { + cgroup: CgroupBuilder<'a>, +} + +impl<'a> DeviceResourceBuilder<'a> { + + 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 + } + + pub fn done(self) -> CgroupBuilder<'a> { + self.cgroup + } +} + +pub struct NetworkResourceBuilder<'a> { + cgroup: CgroupBuilder<'a>, +} + +impl<'a> NetworkResourceBuilder<'a> { + + gen_setter!(network, class_id, u64); + + 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 + } + + pub fn done(self) -> CgroupBuilder<'a> { + self.cgroup + } +} + +pub struct HugepagesResourceBuilder<'a> { + cgroup: CgroupBuilder<'a>, +} + +impl<'a> HugepagesResourceBuilder<'a> { + + 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 + } + + pub fn done(self) -> CgroupBuilder<'a> { + self.cgroup + } +} + +pub struct BlkIoResourcesBuilder<'a> { + cgroup: CgroupBuilder<'a>, + throttling_iops: bool, +} + +impl<'a> BlkIoResourcesBuilder<'a> { + + gen_setter!(blkio, weight, u16); + gen_setter!(blkio, leaf_weight, u16); + + 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 + } + + pub fn throttle_iops(mut self) -> BlkIoResourcesBuilder<'a> { + self.throttling_iops = true; + self + } + + pub fn throttle_bps(mut self) -> BlkIoResourcesBuilder<'a> { + self.throttling_iops = false; + self + } + + 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 + } + + 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 + } + + pub fn done(self) -> CgroupBuilder<'a> { + self.cgroup + } +} diff --git a/src/hugetlb.rs b/src/hugetlb.rs index 87272ac..ee5cbf4 100644 --- a/src/hugetlb.rs +++ b/src/hugetlb.rs @@ -131,7 +131,7 @@ impl HugeTlbController { /// 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, hugetlb_size: &String, limit: u64) -> Result<()> { - self.open_path(&format!("hugetlb.{}.limit_in_bytes", hugetlb_size), false) + self.open_path(&format!("hugetlb.{}.limit_in_bytes", hugetlb_size), true) .and_then(|mut file| { file.write_all(limit.to_string().as_ref()) .map_err(|e| Error::with_cause(WriteFailed, e)) diff --git a/src/lib.rs b/src/lib.rs index 6d6ea68..03a3739 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,6 +21,7 @@ pub mod net_prio; pub mod perf_event; pub mod pid; pub mod rdma; +pub mod cgroup_builder; use blkio::BlkIoController; use cpu::CpuController; diff --git a/tests/builder.rs b/tests/builder.rs new file mode 100644 index 0000000..c19fd31 --- /dev/null +++ b/tests/builder.rs @@ -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(); +}