//! 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::fs::File; use std::io::{Read, Write}; use std::path::PathBuf; use error::*; use error::ErrorKind::*; use { BlkIoResources, ControllIdentifier, ControllerInternal, Controllers, Resources, Subsystem, }; /// A controller that allows controlling the `blkio` subsystem of a Cgroup. /// /// In essence, using the `blkio` controller one can limit and throttle the tasks' usage of block /// devices in the control group. #[derive(Debug, Clone)] pub struct BlkIoController { base: PathBuf, 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> { s.lines() .filter(|x| x.split_whitespace().collect::>().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::>() .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::().unwrap(), minor: minor.parse::().unwrap(), read: read_val.parse::().unwrap(), write: write_val.parse::().unwrap(), sync: sync_val.parse::().unwrap(), async: async_val.parse::().unwrap(), total: total_val.parse::().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 { s.lines() .filter(|x| x.split_whitespace().collect::>().len() == 2) .fold(Err(Error::new(ParseError)), |_, x| { match x.split_whitespace().collect::>().as_slice() { ["Total", val] => val.parse::().map_err(|_| Error::new(ParseError)), _ => Err(Error::new(ParseError)), } }) } fn parse_blkio_data(s: String) -> Result> { let r = s .chars() .map(|x| if x == ':' { ' ' } else { x }) .collect::(); let r = r .lines() .flat_map(|x| x.split_whitespace()) .collect::>(); let r = r.chunks(3).collect::>(); let mut res = Vec::new(); let err = r.iter().try_for_each(|x| match x { [major, minor, data] => { res.push(BlkIoData { major: major.parse::().unwrap(), minor: minor.parse::().unwrap(), data: data.parse::().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, /// Total amount of bytes transferred to and from the block devices. pub io_service_bytes_total: u64, /// Same as `io_service_bytes`, but contains all descendant control groups. pub io_service_bytes_recursive: Vec, /// 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: Vec, /// 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: Vec, /// 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: Vec, /// The upper limit of I/O operation per second, when said operation is a read operation. pub read_iops_device: Vec, /// 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: Vec, /// The upper limit of I/O operation per second, when said operation is a write operation. pub write_iops_device: Vec, } /// 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: Vec, /// 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: Vec, /// 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: Vec, /// 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: Vec, /// 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, /// Same as `io_service_bytes`, but contains all descendant control groups. pub io_service_bytes_total: u64, /// Same as `io_service_bytes`, but contains all descendant control groups. pub io_service_bytes_recursive: Vec, /// 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: Vec, /// 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: Vec, /// 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: Vec, /// 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: Vec, /// 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: Vec, /// 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: Vec, /// 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: Vec, /// Total number of sectors transferred between the block devices and the control group's /// tasks. pub sectors: Vec, /// Same as `sectors`, but contains all descendant control groups. pub sectors_recursive: Vec, /// 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: Vec, /// Same as `time`, but contains all descendant control groups. pub time_recursive: Vec, /// The weight of this control group. pub weight: u64, /// Same as `weight`, but per-block-device. pub weight_device: Vec, } 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, res: &Resources) -> Result<()> { // get the resources that apply to this controller let res: &BlkIoResources = &res.blkio; if res.update_values { let _ = self.set_weight(res.weight as u64); let _ = self.set_leaf_weight(res.leaf_weight as u64); for dev in &res.weight_device { let _ = self.set_weight_for_device(dev.major, dev.minor, dev.weight as u64); } for dev in &res.throttle_read_bps_device { let _ = self.throttle_read_bps_for_device(dev.major, dev.minor, dev.rate); } for dev in &res.throttle_write_bps_device { let _ = self.throttle_write_bps_for_device(dev.major, dev.minor, dev.rate); } for dev in &res.throttle_read_iops_device { let _ = self.throttle_read_iops_for_device(dev.major, dev.minor, dev.rate); } for dev in &res.throttle_write_iops_device { let _ = self.throttle_write_iops_for_device(dev.major, dev.minor, dev.rate); } } Ok(()) } } impl ControllIdentifier for BlkIoController { fn controller_type() -> Controllers { Controllers::BlkIo } } impl<'a> From<&'a Subsystem> for &'a BlkIoController { fn from(sub: &'a Subsystem) -> &'a BlkIoController { unsafe { match sub { Subsystem::BlkIo(c) => c, _ => { assert_eq!(1, 0); ::std::mem::uninitialized() } } } } } fn read_string_from(mut file: File) -> Result { 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)), } } fn read_u64_from(mut file: File) -> Result { 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 BlkIoController { /// Constructs a new `BlkIoController` with `oroot` serving as the root of the control group. pub fn new(oroot: PathBuf) -> Self { let mut root = oroot; root.push(Self::controller_type().to_string()); Self { base: root.clone(), path: root, } } /// Gathers statistics about and reports the state of the block devices used by the control /// group's tasks. pub fn blkio(&self) -> BlkIo { BlkIo { 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(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(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, 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, d: String) -> 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))) } /// 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) .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, 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, 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, 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, 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, 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_weight()`, but settable per each block device. 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, } ] ); } }