// Copyright (c) 2018 Levente Kurusa // Copyright (c) 2020 And Group // // SPDX-License-Identifier: Apache-2.0 or MIT // //! 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 crate::error::ErrorKind::*; use crate::error::*; use crate::{ 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, v2: bool, } #[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 r#async: u64, /// Total number of items transferred. pub total: u64, } #[derive(Eq, PartialEq, Debug)] /// Per-device activity from the control group. /// Only for cgroup v2 pub struct IoStat { /// The major number of the device. pub major: i16, /// The minor number of the device. pub minor: i16, /// How many bytes were read from the device. pub rbytes: u64, /// How many bytes were written to the device. pub wbytes: u64, /// How many iops were read from the device. pub rios: u64, /// How many iops were written to the device. pub wios: u64, /// How many discard bytes were read from the device. pub dbytes: u64, /// How many discard iops were written to the device. pub dios: 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(), r#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 get_value(s: &str) -> String { let arr = s.split(':').collect::>(); if arr.len() != 2 { return "0".to_string(); } arr[1].to_string() } fn parse_io_stat(s: String) -> Result> { // line: // 8:0 rbytes=180224 wbytes=0 rios=3 wios=0 dbytes=0 dios=0 let v = s .lines() .filter(|x| x.split_whitespace().collect::>().len() == 7) .map(|x| { let arr = x.split_whitespace().collect::>(); let device = arr[0].split(":").collect::>(); let (major, minor) = (device[0], device[1]); IoStat { major: major.parse::().unwrap(), minor: minor.parse::().unwrap(), rbytes: get_value(arr[1]).parse::().unwrap(), wbytes: get_value(arr[2]).parse::().unwrap(), rios: get_value(arr[3]).parse::().unwrap(), wios: get_value(arr[4]).parse::().unwrap(), dbytes: get_value(arr[5]).parse::().unwrap(), dios: get_value(arr[6]).parse::().unwrap(), } }) .collect::>(); Ok(v) } 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(Default, 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(Default, 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, /// IoStat for cgroup v2 pub io_stat: 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 is_v2(&self) -> bool { self.v2 } fn apply(&self, res: &Resources) -> Result<()> { // get the resources that apply to this controller let res: &BlkIoResources = &res.blkio; if res.update_values { if let Some(weight) = res.weight { let _ = self.set_weight(weight as u64); } if let Some(leaf_weight) = res.leaf_weight { let _ = self.set_leaf_weight(leaf_weight as u64); } for dev in &res.weight_device { if let Some(weight) = dev.weight { let _ = self.set_weight_for_device(dev.major, dev.minor, weight as u64); } if let Some(leaf_weight) = dev.leaf_weight { let _ = self.set_leaf_weight_for_device(dev.major, dev.minor, leaf_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); let v = std::mem::MaybeUninit::uninit(); v.assume_init() } } } } } 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, v2: bool) -> Self { let mut root = oroot; if !v2 { root.push(Self::controller_type().to_string()); } Self { base: root.clone(), path: root, v2: v2, } } fn blkio_v2(&self) -> BlkIo { let mut blkio: BlkIo = Default::default(); blkio.io_stat = self .open_path("io.stat", false) .and_then(read_string_from) .and_then(parse_io_stat) .unwrap_or(Vec::new()); blkio } /// Gathers statistics about and reports the state of the block devices used by the control /// group's tasks. pub fn blkio(&self) -> BlkIo { if self.v2 { return self.blkio_v2(); } 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()), io_stat: 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, 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) -> 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, major: u64, minor: u64, bps: u64) -> Result<()> { let mut file = "blkio.throttle.read_bps_device"; let mut content = format!("{}:{} {}", major, minor, bps); if self.v2 { file = "io.max"; content = format!("{}:{} rbps={}", major, minor, bps); } self.open_path(file, true).and_then(|mut file| { file.write_all(content.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<()> { let mut file = "blkio.throttle.read_iops_device"; let mut content = format!("{}:{} {}", major, minor, iops); if self.v2 { file = "io.max"; content = format!("{}:{} riops={}", major, minor, iops); } self.open_path(file, true).and_then(|mut file| { file.write_all(content.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<()> { let mut file = "blkio.throttle.write_bps_device"; let mut content = format!("{}:{} {}", major, minor, bps); if self.v2 { file = "io.max"; content = format!("{}:{} wbps={}", major, minor, bps); } self.open_path(file, true).and_then(|mut file| { file.write_all(content.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<()> { let mut file = "blkio.throttle.write_iops_device"; let mut content = format!("{}:{} {}", major, minor, iops); if self.v2 { file = "io.max"; content = format!("{}:{} wiops={}", major, minor, iops); } self.open_path(file, true).and_then(|mut file| { file.write_all(content.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<()> { // Attation: may not find in high kernel version. let mut file = "blkio.weight"; if self.v2 { file = "io.bfq.weight"; } self.open_path(file, 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<()> { let mut file = "blkio.weight_device"; if self.v2 { // Attation: there is no weight for device in runc // https://github.com/opencontainers/runc/blob/46be7b612e2533c494e6a251111de46d8e286ed5/libcontainer/cgroups/fs2/io.go#L30 // may depends on IO schedulers https://wiki.ubuntu.com/Kernel/Reference/IOSchedulers file = "io.bfq.weight"; } self.open_path(file, 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 crate::blkio::{parse_blkio_data, BlkIoData}; use crate::blkio::{parse_io_service, parse_io_service_total, IoService}; use crate::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, r#async: 0, total: 4280320, }, IoService { major: 8, minor: 48, read: 5705479168, write: 56096055296, sync: 11213923328, r#async: 50587611136, total: 61801534464, }, IoService { major: 8, minor: 16, read: 10059776, write: 0, sync: 10059776, r#async: 0, total: 10059776, }, IoService { major: 8, minor: 0, read: 7192576, write: 0, sync: 7192576, r#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, } ] ); } }