use std::io::Write;
use std::path::PathBuf;
use crate::error::ErrorKind::*;
use crate::error::*;
use crate::{read_string_from, read_u64_from};
use crate::{
BlkIoResources, ControllIdentifier, ControllerInternal, Controllers, Resources, Subsystem,
};
#[derive(Debug, Clone)]
pub struct BlkIoController {
base: PathBuf,
path: PathBuf,
v2: bool,
}
#[derive(Eq, PartialEq, Debug)]
pub struct BlkIoData {
pub major: i16,
pub minor: i16,
pub data: u64,
}
#[derive(Eq, PartialEq, Debug)]
pub struct IoService {
pub major: i16,
pub minor: i16,
pub read: u64,
pub write: u64,
pub sync: u64,
pub r#async: u64,
pub total: u64,
}
#[derive(Eq, PartialEq, Debug)]
pub struct IoStat {
pub major: i16,
pub minor: i16,
pub rbytes: u64,
pub wbytes: u64,
pub rios: u64,
pub wios: u64,
pub dbytes: u64,
pub dios: u64,
}
fn parse_io_service(s: String) -> Result<Vec<IoService>> {
s.lines()
.filter(|x| x.split_whitespace().count() == 3)
.map(|x| {
let mut spl = x.split_whitespace();
(spl.next().unwrap(), spl.next().unwrap(), spl.next().unwrap())
})
.map(|(a, b, c)| {
let mut spl = a.split(':');
(spl.next().unwrap(), spl.next().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(),
r#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 get_value(s: &str) -> String {
let arr = s.split(':').collect::<Vec<&str>>();
if arr.len() != 2 {
return "0".to_string();
}
arr[1].to_string()
}
fn parse_io_stat(s: String) -> Vec<IoStat> {
s.lines()
.filter(|x| x.split_whitespace().count() == 7)
.map(|x| {
let arr = x.split_whitespace().collect::<Vec<&str>>();
let device = arr[0].split(':').collect::<Vec<&str>>();
let (major, minor) = (device[0], device[1]);
IoStat {
major: major.parse::<i16>().unwrap(),
minor: minor.parse::<i16>().unwrap(),
rbytes: get_value(arr[1]).parse::<u64>().unwrap(),
wbytes: get_value(arr[2]).parse::<u64>().unwrap(),
rios: get_value(arr[3]).parse::<u64>().unwrap(),
wios: get_value(arr[4]).parse::<u64>().unwrap(),
dbytes: get_value(arr[5]).parse::<u64>().unwrap(),
dios: get_value(arr[6]).parse::<u64>().unwrap(),
}
})
.collect::<Vec<IoStat>>()
}
fn parse_io_service_total(s: String) -> Result<u64> {
s.lines()
.filter(|x| x.split_whitespace().count() == 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() {
Err(Error::new(ParseError))
} else {
Ok(res)
}
}
#[derive(Default, Debug)]
pub struct BlkIoThrottle {
pub io_service_bytes: Vec<IoService>,
pub io_service_bytes_total: u64,
pub io_service_bytes_recursive: Vec<IoService>,
pub io_service_bytes_recursive_total: u64,
pub io_serviced: Vec<IoService>,
pub io_serviced_total: u64,
pub io_serviced_recursive: Vec<IoService>,
pub io_serviced_recursive_total: u64,
pub read_bps_device: Vec<BlkIoData>,
pub read_iops_device: Vec<BlkIoData>,
pub write_bps_device: Vec<BlkIoData>,
pub write_iops_device: Vec<BlkIoData>,
}
#[derive(Default, Debug)]
pub struct BlkIo {
pub io_merged: Vec<IoService>,
pub io_merged_total: u64,
pub io_merged_recursive: Vec<IoService>,
pub io_merged_recursive_total: u64,
pub io_queued: Vec<IoService>,
pub io_queued_total: u64,
pub io_queued_recursive: Vec<IoService>,
pub io_queued_recursive_total: u64,
pub io_service_bytes: Vec<IoService>,
pub io_service_bytes_total: u64,
pub io_service_bytes_recursive: Vec<IoService>,
pub io_service_bytes_recursive_total: u64,
pub io_serviced: Vec<IoService>,
pub io_serviced_total: u64,
pub io_serviced_recursive: Vec<IoService>,
pub io_serviced_recursive_total: u64,
pub io_service_time: Vec<IoService>,
pub io_service_time_total: u64,
pub io_service_time_recursive: Vec<IoService>,
pub io_service_time_recursive_total: u64,
pub io_wait_time: Vec<IoService>,
pub io_wait_time_total: u64,
pub io_wait_time_recursive: Vec<IoService>,
pub io_wait_time_recursive_total: u64,
pub leaf_weight: u64,
pub leaf_weight_device: Vec<BlkIoData>,
pub sectors: Vec<BlkIoData>,
pub sectors_recursive: Vec<BlkIoData>,
pub throttle: BlkIoThrottle,
pub time: Vec<BlkIoData>,
pub time_recursive: Vec<BlkIoData>,
pub weight: u64,
pub weight_device: Vec<BlkIoData>,
pub io_stat: Vec<IoStat>,
}
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<()> {
let res: &BlkIoResources = &res.blkio;
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()
}
}
}
}
}
impl BlkIoController {
pub fn new(root: PathBuf, v2: bool) -> Self {
Self {
base: root.clone(),
path: root,
v2,
}
}
fn blkio_v2(&self) -> BlkIo {
BlkIo {
io_stat: self
.open_path("io.stat", false)
.and_then(read_string_from)
.map(parse_io_stat)
.unwrap_or_default(),
..Default::default()
}
}
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_default(),
io_merged_total: self
.open_path("blkio.io_merged", false)
.and_then(read_string_from)
.and_then(parse_io_service_total)
.unwrap_or_default(),
io_merged_recursive: self
.open_path("blkio.io_merged_recursive", false)
.and_then(read_string_from)
.and_then(parse_io_service)
.unwrap_or_default(),
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_default(),
io_queued: self
.open_path("blkio.io_queued", false)
.and_then(read_string_from)
.and_then(parse_io_service)
.unwrap_or_default(),
io_queued_total: self
.open_path("blkio.io_queued", false)
.and_then(read_string_from)
.and_then(parse_io_service_total)
.unwrap_or_default(),
io_queued_recursive: self
.open_path("blkio.io_queued_recursive", false)
.and_then(read_string_from)
.and_then(parse_io_service)
.unwrap_or_default(),
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_default(),
io_service_bytes: self
.open_path("blkio.io_service_bytes", false)
.and_then(read_string_from)
.and_then(parse_io_service)
.unwrap_or_default(),
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_default(),
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_default(),
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_default(),
io_serviced: self
.open_path("blkio.io_serviced", false)
.and_then(read_string_from)
.and_then(parse_io_service)
.unwrap_or_default(),
io_serviced_total: self
.open_path("blkio.io_serviced", false)
.and_then(read_string_from)
.and_then(parse_io_service_total)
.unwrap_or_default(),
io_serviced_recursive: self
.open_path("blkio.io_serviced_recursive", false)
.and_then(read_string_from)
.and_then(parse_io_service)
.unwrap_or_default(),
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_default(),
io_service_time: self
.open_path("blkio.io_service_time", false)
.and_then(read_string_from)
.and_then(parse_io_service)
.unwrap_or_default(),
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_default(),
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_default(),
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_default(),
io_wait_time: self
.open_path("blkio.io_wait_time", false)
.and_then(read_string_from)
.and_then(parse_io_service)
.unwrap_or_default(),
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_default(),
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_default(),
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_default(),
leaf_weight: self
.open_path("blkio.leaf_weight", false)
.and_then(read_u64_from)
.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_default(),
sectors: self
.open_path("blkio.sectors", false)
.and_then(read_string_from)
.and_then(parse_blkio_data)
.unwrap_or_default(),
sectors_recursive: self
.open_path("blkio.sectors_recursive", false)
.and_then(read_string_from)
.and_then(parse_blkio_data)
.unwrap_or_default(),
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_default(),
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_default(),
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_default(),
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_default(),
io_serviced: self
.open_path("blkio.throttle.io_serviced", false)
.and_then(read_string_from)
.and_then(parse_io_service)
.unwrap_or_default(),
io_serviced_total: self
.open_path("blkio.throttle.io_serviced", false)
.and_then(read_string_from)
.and_then(parse_io_service_total)
.unwrap_or_default(),
io_serviced_recursive: self
.open_path("blkio.throttle.io_serviced_recursive", false)
.and_then(read_string_from)
.and_then(parse_io_service)
.unwrap_or_default(),
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_default(),
read_bps_device: self
.open_path("blkio.throttle.read_bps_device", false)
.and_then(read_string_from)
.and_then(parse_blkio_data)
.unwrap_or_default(),
read_iops_device: self
.open_path("blkio.throttle.read_iops_device", false)
.and_then(read_string_from)
.and_then(parse_blkio_data)
.unwrap_or_default(),
write_bps_device: self
.open_path("blkio.throttle.write_bps_device", false)
.and_then(read_string_from)
.and_then(parse_blkio_data)
.unwrap_or_default(),
write_iops_device: self
.open_path("blkio.throttle.write_iops_device", false)
.and_then(read_string_from)
.and_then(parse_blkio_data)
.unwrap_or_default(),
},
time: self
.open_path("blkio.time", false)
.and_then(read_string_from)
.and_then(parse_blkio_data)
.unwrap_or_default(),
time_recursive: self
.open_path("blkio.time_recursive", false)
.and_then(read_string_from)
.and_then(parse_blkio_data)
.unwrap_or_default(),
weight: self
.open_path("blkio.weight", false)
.and_then(read_u64_from)
.unwrap_or(0u64),
weight_device: self
.open_path("blkio.weight_device", false)
.and_then(read_string_from)
.and_then(parse_blkio_data)
.unwrap_or_default(),
io_stat: Vec::new(),
}
}
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))
})
}
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))
})
}
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))
})
}
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))
})
}
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))
})
}
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))
})
}
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))
})
}
pub fn set_weight(&self, w: u64) -> Result<()> {
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))
})
}
pub fn set_weight_for_device(&self, major: u64, minor: u64, weight: u64) -> Result<()> {
let mut file = "blkio.weight_device";
if self.v2 {
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,
}
]
);
}
}