use std::fs::read_to_string;
use crate::ProcSysParserError;
#[derive(Debug, PartialEq, Default)]
pub struct ProcLoadavg {
pub load_1: f64,
pub load_5: f64,
pub load_15: f64,
pub current_runnable: u64,
pub total: u64,
pub last_pid: u64,
}
#[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: "loadavg".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<ProcLoadavg, ProcSysParserError> {
ProcLoadavg::read_proc_loadavg(format!("{}/{}", &self.proc_path, &self.proc_file).as_str())
}
}
pub fn read() -> Result<ProcLoadavg, ProcSysParserError> {
Builder::new().read()
}
impl ProcLoadavg {
pub fn new() -> ProcLoadavg {
ProcLoadavg::default()
}
pub fn parse_proc_loadavg( proc_loadavg: &str,) -> Result<ProcLoadavg, ProcSysParserError>
{
let mut fields = proc_loadavg.split_whitespace();
let mut fields_copy = fields.clone();
Ok(ProcLoadavg {
load_1: fields.next()
.ok_or(ProcSysParserError::IteratorItemError {item: "loadavg load_1".to_string() })?
.parse::<f64>().map_err(ProcSysParserError::ParseToFloatError)?,
load_5: fields.next()
.ok_or(ProcSysParserError::IteratorItemError {item: "loadavg load_5".to_string() })?
.parse::<f64>().map_err(ProcSysParserError::ParseToFloatError)?,
load_15: fields.next()
.ok_or(ProcSysParserError::IteratorItemError {item: "loadavg load_15". to_string() })?
.parse::<f64>().map_err(ProcSysParserError::ParseToFloatError)?,
current_runnable: fields.next()
.ok_or(ProcSysParserError::IteratorItemError {item: "loadavg current_runnable".to_string() })?
.split('/').next().ok_or(ProcSysParserError::IteratorItemError {item: "loadavg current_runnable split".to_string() })?
.parse::<u64>().map_err(ProcSysParserError::ParseToIntegerError)?,
total: fields_copy.nth(3)
.ok_or(ProcSysParserError::IteratorItemError {item: "loadavg total".to_string() })?
.split('/').nth(1).ok_or(ProcSysParserError::IteratorItemError {item: "loadavg total split".to_string() })?
.parse::<u64>().map_err(ProcSysParserError::ParseToIntegerError)?,
last_pid: fields.next()
.ok_or(ProcSysParserError::IteratorItemError {item: "loadavg last_pid".to_string() })?
.parse::<u64>().map_err(ProcSysParserError::ParseToIntegerError)?,
})
}
pub fn read_proc_loadavg(proc_loadavg: &str) -> Result<ProcLoadavg, ProcSysParserError>
{
let proc_loadavg_output = read_to_string(proc_loadavg)
.map_err(|error| ProcSysParserError::FileReadError { file: proc_loadavg.to_string(), error })?;
ProcLoadavg::parse_proc_loadavg(&proc_loadavg_output)
}
}
#[cfg(test)]
mod tests {
use std::fs::{write, remove_dir_all, create_dir_all};
use rand::{thread_rng, Rng};
use rand::distributions::Alphanumeric;
use super::*;
#[test]
fn parse_proc_loadavg_line() {
let loadavg_line = format!("0.05 0.19 0.13 1/161 7\n");
let result = ProcLoadavg::parse_proc_loadavg(&loadavg_line).unwrap();
assert_eq!(result, ProcLoadavg { load_1: 0.05, load_5: 0.19, load_15: 0.13, current_runnable: 1, total: 161, last_pid: 7 });
}
#[test]
fn create_proc_loadavg_file_and_read() {
let proc_loadavg = format!("0.05 0.19 0.13 1/161 7\n");
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(format!("{}", test_path)).expect("Error creating mock directory.");
write(format!("{}/loadavg", test_path), proc_loadavg).expect(format!("Error writing to {}/loadavg", test_path).as_str());
let result = Builder::new().path(&test_path).read().unwrap();
remove_dir_all(test_path).unwrap();
assert_eq!(result, ProcLoadavg { load_1: 0.05, load_5: 0.19, load_15: 0.13, current_runnable: 1, total: 161, last_pid: 7 });
}
#[test]
fn read_nonexistent_loadavg_file() -> Result<(), ProcSysParserError> {
Ok(assert!(Builder::new().path("/xxxxxxxxxxxx").read().is_err()))
}
#[test]
fn parse_corrupted_loadavg_line_missing_entries() -> Result<(), ProcSysParserError> {
let loadavg_line = format!("0.05 0.19\n");
let result = ProcLoadavg::parse_proc_loadavg(&loadavg_line);
Ok(assert!(result.is_err()))
}
#[test]
fn parse_corrupted_loadavg_line_wrong_entry() -> Result<(), ProcSysParserError> {
let loadavg_line = format!("AAA 0.19 0.13 1/161 7\n");
let result = ProcLoadavg::parse_proc_loadavg(&loadavg_line);
Ok(assert!(result.is_err()))
}
}