proc_sys_parser 0.1.22

This crate provides routines for parsing linux /proc files into Rust structs. There are multiple other crates doing this, but these either do not choose to process the statistics in way to make them directly usable, or generalize the statistics and loose the detail.
Documentation
/*!
Read data from `/proc/diskstats` into the struct [`ProcDiskStats`].
`/proc/diskstats` contains statistics of both block devices and partitions inside the blockdevices.
A more comprehensive view of block devices only can be found in the module Block.

The documentation for `/proc/diskstats` is found here: <https://www.kernel.org/doc/Documentation/iostats.txt>
And here: <https://www.kernel.org/doc/Documentation/ABI/testing/procfs-diskstats>.

Here is an example obtaining the data from `/proc/diskstats`:
```no_run
use proc_sys_parser::{diskstats, diskstats::ProcDiskStats};

let proc_diskstats = diskstats::read();

println!("{:#?}", proc_diskstats);
```
Example output:
```text
        DiskStats {
            block_major: 253,
            block_minor: 0,
            device_name: "vda",
            reads_completed_success: 13534,
            reads_merged: 4237,
            reads_sectors: 1645451,
            reads_time_spent_ms: 3763,
            writes_completed_success: 10172,
            writes_merged: 10577,
            writes_sectors: 1730555,
            writes_time_spent_ms: 12701,
            ios_in_progress: 0,
            ios_time_spent_ms: 23356,
            ios_weighted_time_spent_ms: 18881,
            discards_completed_success: Some(
                7179,
            ),
            discards_merged: Some(
                0,
            ),
            discards_sectors: Some(
                89620507,
            ),
            discards_time_spent_ms: Some(
                396,
            ),
            flush_requests_completed_success: Some(
                3929,
            ),
            flush_requests_time_spent_ms: Some(
                2019,
            ),
        },
```
(edited for readability)

If you want to change the path and/or file that is read for [`ProcDiskStats`], which is `/proc/diskstats`
by default, use:
```no_run
use proc_sys_parser::{diskstats, diskstats::{ProcDiskStats, Builder}};

let proc_diskstats = Builder::new().path("/myproc").read();
```

*/
use std::fs::read_to_string;
use crate::ProcSysParserError;

/// Struct for holding `/proc/diskstats` statistics
#[derive(Debug, PartialEq, Default)]
pub struct ProcDiskStats {
    pub disk_stats: Vec<DiskStats>
}

/// Builder pattern for [`ProcDiskStats`]
#[derive(Default)]
pub struct Builder {
    pub proc_path : String,
    pub proc_file : String,
}

impl Builder {
    pub fn new() -> Builder {
        Builder { 
            proc_path: "/proc".to_string(),
            proc_file: "diskstats".to_string(),
        }
    }

    pub fn path(mut self, proc_path: &str) -> Builder {
        self.proc_path = proc_path.to_string();
        self
    }
    pub fn file(mut self, proc_file: &str) -> Builder {
        self.proc_file = proc_file.to_string();
        self
    }
    pub fn read(self) -> Result<ProcDiskStats, ProcSysParserError> {
        ProcDiskStats::read_proc_diskstats(format!("{}/{}", &self.proc_path, &self.proc_file).as_str())
    }
}

/// The main function for building a [`ProcDiskStats`] struct with current data.
/// This uses the Builder pattern, which allows settings such as the filename to specified.
pub fn read() -> Result<ProcDiskStats, ProcSysParserError> {
   Builder::new().read()
}

/// Struct for holding `/proc/diskstats` statistics
#[derive(Debug, PartialEq, Default)]
pub struct DiskStats {
    pub block_major: u64,
    pub block_minor: u64,
    pub device_name: String,
    pub reads_completed_success: u64,
    pub reads_merged: u64,
    pub reads_sectors: u64,
    pub reads_time_spent_ms: u64,
    pub writes_completed_success: u64,
    pub writes_merged: u64,
    pub writes_sectors: u64,
    pub writes_time_spent_ms: u64,
    pub ios_in_progress: u64,
    pub ios_time_spent_ms: u64,
    pub ios_weighted_time_spent_ms: u64,
    /// kernel 4.18+
    pub discards_completed_success: Option<u64>,
    /// kernel 4.18+
    pub discards_merged: Option<u64>,
    /// kernel 4.18+
    pub discards_sectors: Option<u64>,
    /// kernel 4.18+
    pub discards_time_spent_ms: Option<u64>,
    /// kernel 5.5+
    pub flush_requests_completed_success: Option<u64>,
    /// kernel 5.5+
    pub flush_requests_time_spent_ms: Option<u64>,
}

impl ProcDiskStats {
    pub fn new() -> ProcDiskStats {
        ProcDiskStats::default() 
    }
    pub fn parse_proc_diskstats(
        proc_diskstats: &str,
    ) -> Result<ProcDiskStats, ProcSysParserError> {
        let mut procdiskstats = ProcDiskStats::new();
        for line in proc_diskstats.lines() {
            procdiskstats.disk_stats.push(ProcDiskStats::parse_proc_diskstats_line(line)?);
        }
        Ok(procdiskstats)
    }
    fn parse_proc_diskstats_line(proc_diskstats_line: &str) -> Result<DiskStats, ProcSysParserError> {
        let mut fields = proc_diskstats_line.split_whitespace();

        let parse_next_and_conversion_into_option_u64 = |result: Option<&str>| -> Option<u64> {
            match result {
                None => None,
                Some(value) => {
                    match value.parse::<u64>() {
                        Err(_) => None,
                        Ok(number) => Some(number),
                    }
                },
            }
        };

        Ok(
            DiskStats {
            block_major: fields.next()
                .ok_or(ProcSysParserError::IteratorItemError { item: "diskstats block_major".to_string() })?
                .parse::<u64>().map_err(ProcSysParserError::ParseToIntegerError)?,
            block_minor: fields.next()
                .ok_or(ProcSysParserError::IteratorItemError { item: "diskstats block_minor".to_string() })?
                .parse::<u64>().map_err(ProcSysParserError::ParseToIntegerError)?,
            device_name: fields.next()
                .ok_or(ProcSysParserError::IteratorItemError { item: "diskstats device_name".to_string() })?
                .to_string(),
            reads_completed_success: fields.next()
                .ok_or(ProcSysParserError::IteratorItemError { item: "diskstats reads_completed_success".to_string() })?
                .parse::<u64>().map_err(ProcSysParserError::ParseToIntegerError)?,
            reads_merged: fields.next()
                .ok_or(ProcSysParserError::IteratorItemError { item: "diskstats reads_merged".to_string() })?
                .parse::<u64>().map_err(ProcSysParserError::ParseToIntegerError)?,
            reads_sectors: fields.next()
                .ok_or(ProcSysParserError::IteratorItemError { item: "diskstats reads_sectors".to_string() })?
                .parse::<u64>().map_err(ProcSysParserError::ParseToIntegerError)?,
            reads_time_spent_ms: fields.next()
                .ok_or(ProcSysParserError::IteratorItemError { item: "diskstats reads_time_spent_ms".to_string() })?
                .parse::<u64>().map_err(ProcSysParserError::ParseToIntegerError)?,
            writes_completed_success: fields.next()
                .ok_or(ProcSysParserError::IteratorItemError { item: "diskstats writes_completed_success".to_string() })?
                .parse::<u64>().map_err(ProcSysParserError::ParseToIntegerError)?,
            writes_merged: fields.next()
                .ok_or(ProcSysParserError::IteratorItemError { item: "diskstats writes_merged".to_string() })?
                .parse::<u64>().map_err(ProcSysParserError::ParseToIntegerError)?,
            writes_sectors: fields.next()
                .ok_or(ProcSysParserError::IteratorItemError { item: "diskstats writes_sectors".to_string() })?
                .parse::<u64>().map_err(ProcSysParserError::ParseToIntegerError)?,
            writes_time_spent_ms: fields.next()
                .ok_or(ProcSysParserError::IteratorItemError { item: "diskstats writes_time_spent_ms".to_string() })?
                .parse::<u64>().map_err(ProcSysParserError::ParseToIntegerError)?,
            ios_in_progress: fields.next()
                .ok_or(ProcSysParserError::IteratorItemError { item: "diskstats ios_in_progress".to_string() })?
                .parse::<u64>().map_err(ProcSysParserError::ParseToIntegerError)?,
            ios_time_spent_ms: fields.next()
                .ok_or(ProcSysParserError::IteratorItemError { item: "diskstats ios_time_spent_ms".to_string() })?
                .parse::<u64>().map_err(ProcSysParserError::ParseToIntegerError)?,
            ios_weighted_time_spent_ms: fields.next()
                .ok_or(ProcSysParserError::IteratorItemError { item: "diskstats ios_weighted_time_spent_ms".to_string() })?
                .parse::<u64>().map_err(ProcSysParserError::ParseToIntegerError)?,
            discards_completed_success: parse_next_and_conversion_into_option_u64(fields.next()),
            discards_merged: parse_next_and_conversion_into_option_u64(fields.next()),
            discards_sectors: parse_next_and_conversion_into_option_u64(fields.next()),
            discards_time_spent_ms: parse_next_and_conversion_into_option_u64(fields.next()),
            flush_requests_completed_success: parse_next_and_conversion_into_option_u64(fields.next()),
            flush_requests_time_spent_ms: parse_next_and_conversion_into_option_u64(fields.next()),
        })
    }

    pub fn read_proc_diskstats(proc_diskstats_file: &str) -> Result<ProcDiskStats, ProcSysParserError> {
        let proc_diskstats_output = read_to_string(proc_diskstats_file)
            .map_err(|error| ProcSysParserError::FileReadError { file: proc_diskstats_file.to_string(), error })?;

        ProcDiskStats::parse_proc_diskstats(&proc_diskstats_output)
    }
}

#[cfg(test)]
mod tests {
    use std::fs::{write, create_dir_all, remove_dir_all};
    use rand::{thread_rng, Rng};
    use rand::distributions::Alphanumeric;
    use super::*;

    #[test]
    fn parse_proc_diskstats_line() {
        let diskstats_line = "   7       0 loop0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17";
        let result = ProcDiskStats::parse_proc_diskstats_line(&diskstats_line).unwrap();
        assert_eq!(result, DiskStats { block_major: 7,
            block_minor: 0,
            device_name: "loop0".to_string(),
            reads_completed_success: 1,
            reads_merged: 2,
            reads_sectors: 3,
            reads_time_spent_ms: 4,
            writes_completed_success: 5,
            writes_merged: 6,
            writes_sectors: 7,
            writes_time_spent_ms: 8,
            ios_in_progress: 9,
            ios_time_spent_ms: 10,
            ios_weighted_time_spent_ms: 11,
            discards_completed_success: Some(12),
            discards_merged: Some(13),
            discards_sectors: Some(14),
            discards_time_spent_ms: Some(15),
            flush_requests_completed_success: Some(16),
            flush_requests_time_spent_ms: Some(17),
        });
    }
    #[test]
    fn parse_proc_diskstats_line_before_linux_4_18() {
        let diskstats_line = "   7       0 loop0 1 2 3 4 5 6 7 8 9 10 11";
        let result = ProcDiskStats::parse_proc_diskstats_line(&diskstats_line).unwrap();
        assert_eq!(result, DiskStats { block_major: 7,
            block_minor: 0,
            device_name: "loop0".to_string(),
            reads_completed_success: 1,
            reads_merged: 2,
            reads_sectors: 3,
            reads_time_spent_ms: 4,
            writes_completed_success: 5,
            writes_merged: 6,
            writes_sectors: 7,
            writes_time_spent_ms: 8,
            ios_in_progress: 9,
            ios_time_spent_ms: 10,
            ios_weighted_time_spent_ms: 11,
            discards_completed_success: None,
            discards_merged: None,
            discards_sectors: None,
            discards_time_spent_ms: None,
            flush_requests_completed_success: None,
            flush_requests_time_spent_ms: None
        });
    }

    #[test]
    fn parse_full_proc_diskstats_file() {
        let proc_diskstats = "   7       0 loop0 11 0 28 0 0 0 0 0 0 4 0 0 0 0 0 0 0
   7       1 loop1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
   7       2 loop2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
   7       3 loop3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
   7       4 loop4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
   7       5 loop5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
   7       6 loop6 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
   7       7 loop7 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 253       0 vda 13534 4237 1645451 3763 10172 10577 1730555 12701 0 23356 18881 7179 0 89620507 396 3929 2019
 253       1 vda1 13192 2675 1623109 3692 10151 10555 1730312 12688 0 23324 16775 7151 0 87803128 394 0 0
 253      15 vda15 136 1547 9919 20 1 0 1 0 0 52 21 1 0 186691 0 0 0
 259       0 vda16 159 15 10711 31 20 22 242 12 0 108 46 27 0 1630688 1 0 0
  11       0 sr0 291 0 75108 68 0 0 0 0 0 156 68 0 0 0 0 0 0";
        let result = ProcDiskStats::parse_proc_diskstats(proc_diskstats).unwrap();
        assert_eq!(result, ProcDiskStats {
            disk_stats: vec![
                DiskStats { block_major: 7, block_minor: 0, device_name: "loop0".to_string(), reads_completed_success: 11, reads_merged: 0, reads_sectors: 28, reads_time_spent_ms: 0, writes_completed_success: 0, writes_merged: 0, writes_sectors: 0, writes_time_spent_ms: 0, ios_in_progress: 0, ios_time_spent_ms: 4, ios_weighted_time_spent_ms: 0, discards_completed_success: Some(0), discards_merged: Some(0), discards_sectors: Some(0), discards_time_spent_ms: Some(0), flush_requests_completed_success: Some(0), flush_requests_time_spent_ms: Some(0) },
                DiskStats { block_major: 7, block_minor: 1, device_name: "loop1".to_string(), reads_completed_success: 0, reads_merged: 0, reads_sectors: 0, reads_time_spent_ms: 0, writes_completed_success: 0, writes_merged: 0, writes_sectors: 0, writes_time_spent_ms: 0, ios_in_progress: 0, ios_time_spent_ms: 0, ios_weighted_time_spent_ms: 0, discards_completed_success: Some(0), discards_merged: Some(0), discards_sectors: Some(0), discards_time_spent_ms: Some(0), flush_requests_completed_success: Some(0), flush_requests_time_spent_ms: Some(0) },
                DiskStats { block_major: 7, block_minor: 2, device_name: "loop2".to_string(), reads_completed_success: 0, reads_merged: 0, reads_sectors: 0, reads_time_spent_ms: 0, writes_completed_success: 0, writes_merged: 0, writes_sectors: 0, writes_time_spent_ms: 0, ios_in_progress: 0, ios_time_spent_ms: 0, ios_weighted_time_spent_ms: 0, discards_completed_success: Some(0), discards_merged: Some(0), discards_sectors: Some(0), discards_time_spent_ms: Some(0), flush_requests_completed_success: Some(0), flush_requests_time_spent_ms: Some(0) },
                DiskStats { block_major: 7, block_minor: 3, device_name: "loop3".to_string(), reads_completed_success: 0, reads_merged: 0, reads_sectors: 0, reads_time_spent_ms: 0, writes_completed_success: 0, writes_merged: 0, writes_sectors: 0, writes_time_spent_ms: 0, ios_in_progress: 0, ios_time_spent_ms: 0, ios_weighted_time_spent_ms: 0, discards_completed_success: Some(0), discards_merged: Some(0), discards_sectors: Some(0), discards_time_spent_ms: Some(0), flush_requests_completed_success: Some(0), flush_requests_time_spent_ms: Some(0) },
                DiskStats { block_major: 7, block_minor: 4, device_name: "loop4".to_string(), reads_completed_success: 0, reads_merged: 0, reads_sectors: 0, reads_time_spent_ms: 0, writes_completed_success: 0, writes_merged: 0, writes_sectors: 0, writes_time_spent_ms: 0, ios_in_progress: 0, ios_time_spent_ms: 0, ios_weighted_time_spent_ms: 0, discards_completed_success: Some(0), discards_merged: Some(0), discards_sectors: Some(0), discards_time_spent_ms: Some(0), flush_requests_completed_success: Some(0), flush_requests_time_spent_ms: Some(0) },
                DiskStats { block_major: 7, block_minor: 5, device_name: "loop5".to_string(), reads_completed_success: 0, reads_merged: 0, reads_sectors: 0, reads_time_spent_ms: 0, writes_completed_success: 0, writes_merged: 0, writes_sectors: 0, writes_time_spent_ms: 0, ios_in_progress: 0, ios_time_spent_ms: 0, ios_weighted_time_spent_ms: 0, discards_completed_success: Some(0), discards_merged: Some(0), discards_sectors: Some(0), discards_time_spent_ms: Some(0), flush_requests_completed_success: Some(0), flush_requests_time_spent_ms: Some(0) },
                DiskStats { block_major: 7, block_minor: 6, device_name: "loop6".to_string(), reads_completed_success: 0, reads_merged: 0, reads_sectors: 0, reads_time_spent_ms: 0, writes_completed_success: 0, writes_merged: 0, writes_sectors: 0, writes_time_spent_ms: 0, ios_in_progress: 0, ios_time_spent_ms: 0, ios_weighted_time_spent_ms: 0, discards_completed_success: Some(0), discards_merged: Some(0), discards_sectors: Some(0), discards_time_spent_ms: Some(0), flush_requests_completed_success: Some(0), flush_requests_time_spent_ms: Some(0) },
                DiskStats { block_major: 7, block_minor: 7, device_name: "loop7".to_string(), reads_completed_success: 0, reads_merged: 0, reads_sectors: 0, reads_time_spent_ms: 0, writes_completed_success: 0, writes_merged: 0, writes_sectors: 0, writes_time_spent_ms: 0, ios_in_progress: 0, ios_time_spent_ms: 0, ios_weighted_time_spent_ms: 0, discards_completed_success: Some(0), discards_merged: Some(0), discards_sectors: Some(0), discards_time_spent_ms: Some(0), flush_requests_completed_success: Some(0), flush_requests_time_spent_ms: Some(0) },
                DiskStats { block_major: 253, block_minor: 0, device_name: "vda".to_string(), reads_completed_success: 13534, reads_merged: 4237, reads_sectors: 1645451, reads_time_spent_ms: 3763, writes_completed_success: 10172, writes_merged: 10577, writes_sectors: 1730555, writes_time_spent_ms: 12701, ios_in_progress: 0, ios_time_spent_ms: 23356, ios_weighted_time_spent_ms: 18881, discards_completed_success: Some(7179), discards_merged: Some(0), discards_sectors: Some(89620507), discards_time_spent_ms: Some(396), flush_requests_completed_success: Some(3929), flush_requests_time_spent_ms: Some(2019) },
                DiskStats { block_major: 253, block_minor: 1, device_name: "vda1".to_string(), reads_completed_success: 13192, reads_merged: 2675, reads_sectors: 1623109, reads_time_spent_ms: 3692, writes_completed_success: 10151, writes_merged: 10555, writes_sectors: 1730312, writes_time_spent_ms: 12688, ios_in_progress: 0, ios_time_spent_ms: 23324, ios_weighted_time_spent_ms: 16775, discards_completed_success: Some(7151), discards_merged: Some(0), discards_sectors: Some(87803128), discards_time_spent_ms: Some(394), flush_requests_completed_success: Some(0), flush_requests_time_spent_ms: Some(0) },
                DiskStats { block_major: 253, block_minor: 15, device_name: "vda15".to_string(), reads_completed_success: 136, reads_merged: 1547, reads_sectors: 9919, reads_time_spent_ms: 20, writes_completed_success: 1, writes_merged: 0, writes_sectors: 1, writes_time_spent_ms: 0, ios_in_progress: 0, ios_time_spent_ms: 52, ios_weighted_time_spent_ms: 21, discards_completed_success: Some(1), discards_merged: Some(0), discards_sectors: Some(186691), discards_time_spent_ms: Some(0), flush_requests_completed_success: Some(0), flush_requests_time_spent_ms: Some(0) },
                DiskStats { block_major: 259, block_minor: 0, device_name: "vda16".to_string(), reads_completed_success: 159, reads_merged: 15, reads_sectors: 10711, reads_time_spent_ms: 31, writes_completed_success: 20, writes_merged: 22, writes_sectors: 242, writes_time_spent_ms: 12, ios_in_progress: 0, ios_time_spent_ms: 108, ios_weighted_time_spent_ms: 46, discards_completed_success: Some(27), discards_merged: Some(0), discards_sectors: Some(1630688), discards_time_spent_ms: Some(1), flush_requests_completed_success: Some(0), flush_requests_time_spent_ms: Some(0) },
                DiskStats { block_major: 11, block_minor: 0, device_name: "sr0".to_string(), reads_completed_success: 291, reads_merged: 0, reads_sectors: 75108, reads_time_spent_ms: 68, writes_completed_success: 0, writes_merged: 0, writes_sectors: 0, writes_time_spent_ms: 0, ios_in_progress: 0, ios_time_spent_ms: 156, ios_weighted_time_spent_ms: 68, discards_completed_success: Some(0), discards_merged: Some(0), discards_sectors: Some(0), discards_time_spent_ms: Some(0), flush_requests_completed_success: Some(0), flush_requests_time_spent_ms: Some(0) }
            ]
        });
    }

    #[test]
    fn create_proc_diskstats_file_and_read() {
        let proc_diskstats = "   7       0 loop0 11 0 28 0 0 0 0 0 0 4 0 0 0 0 0 0 0
   7       1 loop1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
   7       2 loop2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
   7       3 loop3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
   7       4 loop4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
   7       5 loop5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
   7       6 loop6 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
   7       7 loop7 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 253       0 vda 13534 4237 1645451 3763 10172 10577 1730555 12701 0 23356 18881 7179 0 89620507 396 3929 2019
 253       1 vda1 13192 2675 1623109 3692 10151 10555 1730312 12688 0 23324 16775 7151 0 87803128 394 0 0
 253      15 vda15 136 1547 9919 20 1 0 1 0 0 52 21 1 0 186691 0 0 0
 259       0 vda16 159 15 10711 31 20 22 242 12 0 108 46 27 0 1630688 1 0 0
  11       0 sr0 291 0 75108 68 0 0 0 0 0 156 68 0 0 0 0 0 0";
        let directory_suffix: String = thread_rng().sample_iter(&Alphanumeric).take(8).map(char::from).collect();
        let test_path = format!("/tmp/test.{}", directory_suffix);

        create_dir_all(test_path.clone()).expect("Error creating mock sysfs directories.");
        write(format!("{}/diskstats", test_path), proc_diskstats).expect(format!("Error writing to {}/diskstats", test_path).as_str());
        let result = Builder::new().path(&test_path).read().unwrap();
        remove_dir_all(test_path).unwrap();

        assert_eq!(result, ProcDiskStats { disk_stats: vec![
            DiskStats { block_major: 7, block_minor: 0, device_name: "loop0".to_string(), reads_completed_success: 11, reads_merged: 0, reads_sectors: 28, reads_time_spent_ms: 0, writes_completed_success: 0, writes_merged: 0, writes_sectors: 0, writes_time_spent_ms: 0, ios_in_progress: 0, ios_time_spent_ms: 4, ios_weighted_time_spent_ms: 0, discards_completed_success: Some(0), discards_merged: Some(0), discards_sectors: Some(0), discards_time_spent_ms: Some(0), flush_requests_completed_success: Some(0), flush_requests_time_spent_ms: Some(0) },
            DiskStats { block_major: 7, block_minor: 1, device_name: "loop1".to_string(), reads_completed_success: 0, reads_merged: 0, reads_sectors: 0, reads_time_spent_ms: 0, writes_completed_success: 0, writes_merged: 0, writes_sectors: 0, writes_time_spent_ms: 0, ios_in_progress: 0, ios_time_spent_ms: 0, ios_weighted_time_spent_ms: 0, discards_completed_success: Some(0), discards_merged: Some(0), discards_sectors: Some(0), discards_time_spent_ms: Some(0), flush_requests_completed_success: Some(0), flush_requests_time_spent_ms: Some(0) },
            DiskStats { block_major: 7, block_minor: 2, device_name: "loop2".to_string(), reads_completed_success: 0, reads_merged: 0, reads_sectors: 0, reads_time_spent_ms: 0, writes_completed_success: 0, writes_merged: 0, writes_sectors: 0, writes_time_spent_ms: 0, ios_in_progress: 0, ios_time_spent_ms: 0, ios_weighted_time_spent_ms: 0, discards_completed_success: Some(0), discards_merged: Some(0), discards_sectors: Some(0), discards_time_spent_ms: Some(0), flush_requests_completed_success: Some(0), flush_requests_time_spent_ms: Some(0) },
            DiskStats { block_major: 7, block_minor: 3, device_name: "loop3".to_string(), reads_completed_success: 0, reads_merged: 0, reads_sectors: 0, reads_time_spent_ms: 0, writes_completed_success: 0, writes_merged: 0, writes_sectors: 0, writes_time_spent_ms: 0, ios_in_progress: 0, ios_time_spent_ms: 0, ios_weighted_time_spent_ms: 0, discards_completed_success: Some(0), discards_merged: Some(0), discards_sectors: Some(0), discards_time_spent_ms: Some(0), flush_requests_completed_success: Some(0), flush_requests_time_spent_ms: Some(0) },
            DiskStats { block_major: 7, block_minor: 4, device_name: "loop4".to_string(), reads_completed_success: 0, reads_merged: 0, reads_sectors: 0, reads_time_spent_ms: 0, writes_completed_success: 0, writes_merged: 0, writes_sectors: 0, writes_time_spent_ms: 0, ios_in_progress: 0, ios_time_spent_ms: 0, ios_weighted_time_spent_ms: 0, discards_completed_success: Some(0), discards_merged: Some(0), discards_sectors: Some(0), discards_time_spent_ms: Some(0), flush_requests_completed_success: Some(0), flush_requests_time_spent_ms: Some(0) },
            DiskStats { block_major: 7, block_minor: 5, device_name: "loop5".to_string(), reads_completed_success: 0, reads_merged: 0, reads_sectors: 0, reads_time_spent_ms: 0, writes_completed_success: 0, writes_merged: 0, writes_sectors: 0, writes_time_spent_ms: 0, ios_in_progress: 0, ios_time_spent_ms: 0, ios_weighted_time_spent_ms: 0, discards_completed_success: Some(0), discards_merged: Some(0), discards_sectors: Some(0), discards_time_spent_ms: Some(0), flush_requests_completed_success: Some(0), flush_requests_time_spent_ms: Some(0) },
            DiskStats { block_major: 7, block_minor: 6, device_name: "loop6".to_string(), reads_completed_success: 0, reads_merged: 0, reads_sectors: 0, reads_time_spent_ms: 0, writes_completed_success: 0, writes_merged: 0, writes_sectors: 0, writes_time_spent_ms: 0, ios_in_progress: 0, ios_time_spent_ms: 0, ios_weighted_time_spent_ms: 0, discards_completed_success: Some(0), discards_merged: Some(0), discards_sectors: Some(0), discards_time_spent_ms: Some(0), flush_requests_completed_success: Some(0), flush_requests_time_spent_ms: Some(0) },
            DiskStats { block_major: 7, block_minor: 7, device_name: "loop7".to_string(), reads_completed_success: 0, reads_merged: 0, reads_sectors: 0, reads_time_spent_ms: 0, writes_completed_success: 0, writes_merged: 0, writes_sectors: 0, writes_time_spent_ms: 0, ios_in_progress: 0, ios_time_spent_ms: 0, ios_weighted_time_spent_ms: 0, discards_completed_success: Some(0), discards_merged: Some(0), discards_sectors: Some(0), discards_time_spent_ms: Some(0), flush_requests_completed_success: Some(0), flush_requests_time_spent_ms: Some(0) },
            DiskStats { block_major: 253, block_minor: 0, device_name: "vda".to_string(), reads_completed_success: 13534, reads_merged: 4237, reads_sectors: 1645451, reads_time_spent_ms: 3763, writes_completed_success: 10172, writes_merged: 10577, writes_sectors: 1730555, writes_time_spent_ms: 12701, ios_in_progress: 0, ios_time_spent_ms: 23356, ios_weighted_time_spent_ms: 18881, discards_completed_success: Some(7179), discards_merged: Some(0), discards_sectors: Some(89620507), discards_time_spent_ms: Some(396), flush_requests_completed_success: Some(3929), flush_requests_time_spent_ms: Some(2019) },
            DiskStats { block_major: 253, block_minor: 1, device_name: "vda1".to_string(), reads_completed_success: 13192, reads_merged: 2675, reads_sectors: 1623109, reads_time_spent_ms: 3692, writes_completed_success: 10151, writes_merged: 10555, writes_sectors: 1730312, writes_time_spent_ms: 12688, ios_in_progress: 0, ios_time_spent_ms: 23324, ios_weighted_time_spent_ms: 16775, discards_completed_success: Some(7151), discards_merged: Some(0), discards_sectors: Some(87803128), discards_time_spent_ms: Some(394), flush_requests_completed_success: Some(0), flush_requests_time_spent_ms: Some(0) },
            DiskStats { block_major: 253, block_minor: 15, device_name: "vda15".to_string(), reads_completed_success: 136, reads_merged: 1547, reads_sectors: 9919, reads_time_spent_ms: 20, writes_completed_success: 1, writes_merged: 0, writes_sectors: 1, writes_time_spent_ms: 0, ios_in_progress: 0, ios_time_spent_ms: 52, ios_weighted_time_spent_ms: 21, discards_completed_success: Some(1), discards_merged: Some(0), discards_sectors: Some(186691), discards_time_spent_ms: Some(0), flush_requests_completed_success: Some(0), flush_requests_time_spent_ms: Some(0) },
            DiskStats { block_major: 259, block_minor: 0, device_name: "vda16".to_string(), reads_completed_success: 159, reads_merged: 15, reads_sectors: 10711, reads_time_spent_ms: 31, writes_completed_success: 20, writes_merged: 22, writes_sectors: 242, writes_time_spent_ms: 12, ios_in_progress: 0, ios_time_spent_ms: 108, ios_weighted_time_spent_ms: 46, discards_completed_success: Some(27), discards_merged: Some(0), discards_sectors: Some(1630688), discards_time_spent_ms: Some(1), flush_requests_completed_success: Some(0), flush_requests_time_spent_ms: Some(0) },
            DiskStats { block_major: 11, block_minor: 0, device_name: "sr0".to_string(), reads_completed_success: 291, reads_merged: 0, reads_sectors: 75108, reads_time_spent_ms: 68, writes_completed_success: 0, writes_merged: 0, writes_sectors: 0, writes_time_spent_ms: 0, ios_in_progress: 0, ios_time_spent_ms: 156, ios_weighted_time_spent_ms: 68, discards_completed_success: Some(0), discards_merged: Some(0), discards_sectors: Some(0), discards_time_spent_ms: Some(0), flush_requests_completed_success: Some(0), flush_requests_time_spent_ms: Some(0) }
        ]});
    }
    #[test]
    fn create_proc_diskstats_file_with_pre_kernel_4_18_fields_removed_and_read() {
        let proc_diskstats = " 253       0 vda 13534 4237 1645451 3763 10172 10577 1730555 12701 0 23356 18881
 253       1 vda1 13192 2675 1623109 3692 10151 10555 1730312 12688 0 23324 16775
 253      15 vda15 136 1547 9919 20 1 0 1 0 0 52 21
 259       0 vda16 159 15 10711 31 20 22 242 12 0 108 46";
        let directory_suffix: String = thread_rng().sample_iter(&Alphanumeric).take(8).map(char::from).collect();
        let test_path = format!("/tmp/test.{}", directory_suffix);

        create_dir_all(test_path.clone()).expect("Error creating mock sysfs directories.");
        write(format!("{}/diskstats", test_path), proc_diskstats).expect(format!("Error writing to {}/diskstats", test_path).as_str());
        let result = Builder::new().path(&test_path).read().unwrap();
        remove_dir_all(test_path).unwrap();

        assert_eq!(result, ProcDiskStats { disk_stats: vec![
            DiskStats { block_major: 253, block_minor: 0, device_name: "vda".to_string(), reads_completed_success: 13534, reads_merged: 4237, reads_sectors: 1645451, reads_time_spent_ms: 3763, writes_completed_success: 10172, writes_merged: 10577, writes_sectors: 1730555, writes_time_spent_ms: 12701, ios_in_progress: 0, ios_time_spent_ms: 23356, ios_weighted_time_spent_ms: 18881, discards_completed_success: None, discards_merged: None, discards_sectors: None, discards_time_spent_ms: None, flush_requests_completed_success: None, flush_requests_time_spent_ms: None },
            DiskStats { block_major: 253, block_minor: 1, device_name: "vda1".to_string(), reads_completed_success: 13192, reads_merged: 2675, reads_sectors: 1623109, reads_time_spent_ms: 3692, writes_completed_success: 10151, writes_merged: 10555, writes_sectors: 1730312, writes_time_spent_ms: 12688, ios_in_progress: 0, ios_time_spent_ms: 23324, ios_weighted_time_spent_ms: 16775, discards_completed_success: None, discards_merged: None, discards_sectors: None, discards_time_spent_ms: None, flush_requests_completed_success: None, flush_requests_time_spent_ms: None },
            DiskStats { block_major: 253, block_minor: 15, device_name: "vda15".to_string(), reads_completed_success: 136, reads_merged: 1547, reads_sectors: 9919, reads_time_spent_ms: 20, writes_completed_success: 1, writes_merged: 0, writes_sectors: 1, writes_time_spent_ms: 0, ios_in_progress: 0, ios_time_spent_ms: 52, ios_weighted_time_spent_ms: 21, discards_completed_success: None, discards_merged: None, discards_sectors: None, discards_time_spent_ms: None, flush_requests_completed_success: None, flush_requests_time_spent_ms: None },
            DiskStats { block_major: 259, block_minor: 0, device_name: "vda16".to_string(), reads_completed_success: 159, reads_merged: 15, reads_sectors: 10711, reads_time_spent_ms: 31, writes_completed_success: 20, writes_merged: 22, writes_sectors: 242, writes_time_spent_ms: 12, ios_in_progress: 0, ios_time_spent_ms: 108, ios_weighted_time_spent_ms: 46, discards_completed_success: None, discards_merged: None, discards_sectors: None, discards_time_spent_ms: None, flush_requests_completed_success: None, flush_requests_time_spent_ms: None },
        ]});
    }
}