use std::fs::read_to_string;
use crate::ProcSysParserError;
use log::warn;
#[derive(Debug, PartialEq, Default)]
pub struct ProcPressure {
pub psi: Option<Psi>,
}
#[derive(Debug, PartialEq, Default)]
pub struct Psi {
pub cpu_some_avg10: f64,
pub cpu_some_avg60: f64,
pub cpu_some_avg300: f64,
pub cpu_some_total: u64,
pub cpu_full_avg10: Option<f64>,
pub cpu_full_avg60: Option<f64>,
pub cpu_full_avg300: Option<f64>,
pub cpu_full_total: Option<u64>,
pub io_some_avg10: f64,
pub io_some_avg60: f64,
pub io_some_avg300: f64,
pub io_some_total: u64,
pub io_full_avg10: f64,
pub io_full_avg60: f64,
pub io_full_avg300: f64,
pub io_full_total: u64,
pub memory_some_avg10: f64,
pub memory_some_avg60: f64,
pub memory_some_avg300: f64,
pub memory_some_total: u64,
pub memory_full_avg10: f64,
pub memory_full_avg60: f64,
pub memory_full_avg300: f64,
pub memory_full_total: u64,
}
impl Psi {
pub fn new() -> Psi {
Psi::default()
}
}
#[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: "pressure".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<ProcPressure, ProcSysParserError> {
ProcPressure::read_proc_pressure(format!("{}/pressure", &self.proc_path).as_str())
}
}
pub fn read() -> Result<ProcPressure, ProcSysParserError> {
Builder::new().read()
}
impl ProcPressure {
pub fn new() -> ProcPressure {
ProcPressure {
psi: None,
}
}
pub fn read_proc_pressure(proc_pressure_path: &str) -> Result<ProcPressure, ProcSysParserError> {
let mut proc_pressure = ProcPressure::new();
let mut psi = Psi::new();
for psi_target in ["cpu", "io", "memory"] {
if ProcPressure::parse_pressure_entity(psi_target, proc_pressure_path, &mut psi)?.is_none() {
return Ok(proc_pressure);
}
}
proc_pressure.psi = Some(psi);
Ok(proc_pressure)
}
fn parse_pressure_entity(file: &str, proc_pressure_path: &str, psi: &mut Psi) -> Result<Option<usize>, ProcSysParserError> {
match read_to_string(format!("{}/{}", &proc_pressure_path, file)) {
Ok(psi_contents) => {
for line in psi_contents.lines() {
match line.split_whitespace().next() {
Some("some") => {
match file {
"cpu" => {
psi.cpu_some_avg10 = line.split_whitespace().nth(1)
.ok_or(ProcSysParserError::IteratorItemError {item: "pressure cpu_some_avg10".to_string() })?
.split('=').nth(1)
.ok_or(ProcSysParserError::IteratorItemError {item: "pressure cpu_some_avg10 after split =".to_string() })?
.parse::<f64>().map_err(ProcSysParserError::ParseToFloatError)?;
psi.cpu_some_avg60 = line.split_whitespace().nth(2)
.ok_or(ProcSysParserError::IteratorItemError {item: "pressure cpu_some_avg60".to_string() })?
.split('=').nth(1)
.ok_or(ProcSysParserError::IteratorItemError {item: "pressure cpu_some_avg60 after split =".to_string() })?
.parse::<f64>().map_err(ProcSysParserError::ParseToFloatError)?;
psi.cpu_some_avg300 = line.split_whitespace().nth(3)
.ok_or(ProcSysParserError::IteratorItemError {item: "pressure cpu_some_avg300".to_string() })?
.split('=').nth(1)
.ok_or(ProcSysParserError::IteratorItemError {item: "pressure cpu_some_avg300 after split =".to_string() })?
.parse::<f64>().map_err(ProcSysParserError::ParseToFloatError)?;
psi.cpu_some_total = line.split_whitespace().nth(4)
.ok_or(ProcSysParserError::IteratorItemError {item: "pressure cpu_some_total".to_string() })?
.split('=').nth(1)
.ok_or(ProcSysParserError::IteratorItemError {item: "pressure cpu_some_total after split =".to_string() })?
.parse::<u64>().map_err(ProcSysParserError::ParseToIntegerError)?;
},
"io" => {
psi.io_some_avg10 = line.split_whitespace().nth(1)
.ok_or(ProcSysParserError::IteratorItemError {item: "pressure io_some_avg10".to_string() })?
.split('=').nth(1)
.ok_or(ProcSysParserError::IteratorItemError {item: "pressure io_some_avg10 after split =".to_string() })?
.parse::<f64>().map_err(ProcSysParserError::ParseToFloatError)?;
psi.io_some_avg60 = line.split_whitespace().nth(2)
.ok_or(ProcSysParserError::IteratorItemError {item: "pressure io_some_avg60".to_string() })?
.split('=').nth(1)
.ok_or(ProcSysParserError::IteratorItemError {item: "pressure io_some_avg60 after split =".to_string() })?
.parse::<f64>().map_err(ProcSysParserError::ParseToFloatError)?;
psi.io_some_avg300 = line.split_whitespace().nth(3)
.ok_or(ProcSysParserError::IteratorItemError {item: "pressure io_some_avg300".to_string() })?
.split('=').nth(1)
.ok_or(ProcSysParserError::IteratorItemError {item: "pressure io_some_avg300 after split =".to_string() })?
.parse::<f64>().map_err(ProcSysParserError::ParseToFloatError)?;
psi.io_some_total = line.split_whitespace().nth(4)
.ok_or(ProcSysParserError::IteratorItemError {item: "pressure io_some_total".to_string() })?
.split('=').nth(1)
.ok_or(ProcSysParserError::IteratorItemError {item: "pressure io_some_total after split =".to_string() })?
.parse::<u64>().map_err(ProcSysParserError::ParseToIntegerError)?;
},
"memory" => {
psi.memory_some_avg10 = line.split_whitespace().nth(1)
.ok_or(ProcSysParserError::IteratorItemError {item: "pressure memory_some_avg10".to_string() })?
.split('=').nth(1)
.ok_or(ProcSysParserError::IteratorItemError {item: "pressure memory_some_avg10 after split =".to_string() })?
.parse::<f64>().map_err(ProcSysParserError::ParseToFloatError)?;
psi.memory_some_avg60 = line.split_whitespace().nth(2)
.ok_or(ProcSysParserError::IteratorItemError {item: "pressure memory_some_avg60".to_string() })?
.split('=').nth(1)
.ok_or(ProcSysParserError::IteratorItemError {item: "pressure memory_some_avg60 after split =".to_string() })?
.parse::<f64>().map_err(ProcSysParserError::ParseToFloatError)?;
psi.memory_some_avg300 = line.split_whitespace().nth(3)
.ok_or(ProcSysParserError::IteratorItemError {item: "pressure memory_some_avg300".to_string() })?
.split('=').nth(1)
.ok_or(ProcSysParserError::IteratorItemError {item: "pressure memory_some_avg300 after split =".to_string() })?
.parse::<f64>().map_err(ProcSysParserError::ParseToFloatError)?;
psi.memory_some_total = line.split_whitespace().nth(4)
.ok_or(ProcSysParserError::IteratorItemError {item: "pressure memory_some_total".to_string() })?
.split('=').nth(1)
.ok_or(ProcSysParserError::IteratorItemError {item: "pressure memory_some_total after split =".to_string() })?
.parse::<u64>().map_err(ProcSysParserError::ParseToIntegerError)?;
},
&_ => warn!("Unknown entry in some: {}, {}", file, line),
}
},
Some("full") => {
match file {
"cpu" => {
psi.cpu_full_avg10 = Some(line.split_whitespace().nth(1)
.ok_or(ProcSysParserError::IteratorItemError {item: "pressure cpu_some_avg10".to_string() })?
.split('=').nth(1)
.ok_or(ProcSysParserError::IteratorItemError {item: "pressure cpu_some_avg10 after split =".to_string() })?
.parse::<f64>().map_err(ProcSysParserError::ParseToFloatError)?);
psi.cpu_full_avg60 = Some(line.split_whitespace().nth(2)
.ok_or(ProcSysParserError::IteratorItemError {item: "pressure cpu_some_avg60".to_string() })?
.split('=').nth(1)
.ok_or(ProcSysParserError::IteratorItemError {item: "pressure cpu_some_avg60 after split =".to_string() })?
.parse::<f64>().map_err(ProcSysParserError::ParseToFloatError)?);
psi.cpu_full_avg300 = Some(line.split_whitespace().nth(3)
.ok_or(ProcSysParserError::IteratorItemError {item: "pressure cpu_some_avg300".to_string() })?
.split('=').nth(1)
.ok_or(ProcSysParserError::IteratorItemError {item: "pressure cpu_some_avg300 after split =".to_string() })?
.parse::<f64>().map_err(ProcSysParserError::ParseToFloatError)?);
psi.cpu_full_total = Some(line.split_whitespace().nth(4)
.ok_or(ProcSysParserError::IteratorItemError {item: "pressure cpu_some_total".to_string() })?
.split('=').nth(1)
.ok_or(ProcSysParserError::IteratorItemError {item: "pressure cpu_some_total after split =".to_string() })?
.parse::<u64>().map_err(ProcSysParserError::ParseToIntegerError)?);
},
"io" => {
psi.io_full_avg10 = line.split_whitespace().nth(1)
.ok_or(ProcSysParserError::IteratorItemError {item: "pressure io_some_avg10".to_string() })?
.split('=').nth(1)
.ok_or(ProcSysParserError::IteratorItemError {item: "pressure io_some_avg10 after split =".to_string() })?
.parse::<f64>().map_err(ProcSysParserError::ParseToFloatError)?;
psi.io_full_avg60 = line.split_whitespace().nth(2)
.ok_or(ProcSysParserError::IteratorItemError {item: "pressure io_some_avg60".to_string() })?
.split('=').nth(1)
.ok_or(ProcSysParserError::IteratorItemError {item: "pressure io_some_avg60 after split =".to_string() })?
.parse::<f64>().map_err(ProcSysParserError::ParseToFloatError)?;
psi.io_full_avg300 = line.split_whitespace().nth(3)
.ok_or(ProcSysParserError::IteratorItemError {item: "pressure io_some_avg300".to_string() })?
.split('=').nth(1)
.ok_or(ProcSysParserError::IteratorItemError {item: "pressure io_some_avg300 after split =".to_string() })?
.parse::<f64>().map_err(ProcSysParserError::ParseToFloatError)?;
psi.io_full_total = line.split_whitespace().nth(4)
.ok_or(ProcSysParserError::IteratorItemError {item: "pressure io_some_total".to_string() })?
.split('=').nth(1)
.ok_or(ProcSysParserError::IteratorItemError {item: "pressure io_some_total after split =".to_string() })?
.parse::<u64>().map_err(ProcSysParserError::ParseToIntegerError)?;
},
"memory" => {
psi.memory_full_avg10 = line.split_whitespace().nth(1)
.ok_or(ProcSysParserError::IteratorItemError {item: "pressure memory_some_avg10".to_string() })?
.split('=').nth(1)
.ok_or(ProcSysParserError::IteratorItemError {item: "pressure memory_some_avg10 after split =".to_string() })?
.parse::<f64>().map_err(ProcSysParserError::ParseToFloatError)?;
psi.memory_full_avg60 = line.split_whitespace().nth(2)
.ok_or(ProcSysParserError::IteratorItemError {item: "pressure memory_some_avg60".to_string() })?
.split('=').nth(1)
.ok_or(ProcSysParserError::IteratorItemError {item: "pressure memory_some_avg60 after split =".to_string() })?
.parse::<f64>().map_err(ProcSysParserError::ParseToFloatError)?;
psi.memory_full_avg300 = line.split_whitespace().nth(3)
.ok_or(ProcSysParserError::IteratorItemError {item: "pressure memory_some_avg300".to_string() })?
.split('=').nth(1)
.ok_or(ProcSysParserError::IteratorItemError {item: "pressure memory_some_avg300 after split =".to_string() })?
.parse::<f64>().map_err(ProcSysParserError::ParseToFloatError)?;
psi.memory_full_total = line.split_whitespace().nth(4)
.ok_or(ProcSysParserError::IteratorItemError {item: "pressure memory_some_total".to_string() })?
.split('=').nth(1)
.ok_or(ProcSysParserError::IteratorItemError {item: "pressure memory_some_total after split =".to_string() })?
.parse::<u64>().map_err(ProcSysParserError::ParseToIntegerError)?;
},
&_ => warn!("Unknown entry in full: {}, {}", file, line),
}
},
Some(&_) => warn!("Unknown entry found: {}", line),
None => {},
}
}
Ok(Some(1))
},
Err(_) => {
Ok(None)
},
}
}
}
#[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 create_proc_pressure_directory_and_files_and_read() {
let proc_pressure_cpu = "some avg10=1.00 avg60=2.00 avg300=3.00 total=373300065
full avg10=4.00 avg60=5.00 avg300=6.00 total=0
";
let proc_pressure_io = "some avg10=7.00 avg60=8.00 avg300=9.00 total=55345502
full avg10=10.00 avg60=11.00 avg300=12.00 total=53895423
";
let proc_pressure_memory = "some avg10=13.00 avg60=14.00 avg300=15.00 total=5425111
full avg10=16.00 avg60=17.00 avg300=18.00 total=5390695
";
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!("{}/pressure", test_path)).expect("Error creating mock directory.");
write(format!("{}/pressure/cpu", test_path), proc_pressure_cpu).expect(format!("Error writing to {}/pressure/cpu", test_path).as_str());
write(format!("{}/pressure/io", test_path), proc_pressure_io).expect(format!("Error writing to {}/pressure/io", test_path).as_str());
write(format!("{}/pressure/memory", test_path), proc_pressure_memory).expect(format!("Error writing to {}/pressure/memory", test_path).as_str());
let result = Builder::new().path(&test_path).read().unwrap();
remove_dir_all(test_path).unwrap();
assert_eq!(result, ProcPressure {
psi: Some(
Psi {
cpu_some_avg10: 1.0,
cpu_some_avg60: 2.0,
cpu_some_avg300: 3.0,
cpu_some_total: 373300065,
cpu_full_avg10: Some(
4.0,
),
cpu_full_avg60: Some(
5.0,
),
cpu_full_avg300: Some(
6.0,
),
cpu_full_total: Some(
0,
),
io_some_avg10: 7.0,
io_some_avg60: 8.0,
io_some_avg300: 9.0,
io_some_total: 55345502,
io_full_avg10: 10.0,
io_full_avg60: 11.0,
io_full_avg300: 12.0,
io_full_total: 53895423,
memory_some_avg10: 13.0,
memory_some_avg60: 14.0,
memory_some_avg300: 15.0,
memory_some_total: 5425111,
memory_full_avg10: 16.0,
memory_full_avg60: 17.0,
memory_full_avg300: 18.0,
memory_full_total: 5390695,
},
),
});
}
#[test]
fn do_not_create_proc_pressure_directory_for_nonexistent_cases_and_read() {
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.");
let result = Builder::new().path(&test_path).read().unwrap();
remove_dir_all(test_path).unwrap();
assert_eq!(result, ProcPressure { psi: None });
}
}