proc_sys_parser/
loadavg.rs1use std::fs::read_to_string;
41use crate::ProcSysParserError;
42
43#[derive(Debug, PartialEq, Default)]
45pub struct ProcLoadavg {
46 pub load_1: f64,
47 pub load_5: f64,
48 pub load_15: f64,
49 pub current_runnable: u64,
50 pub total: u64,
51 pub last_pid: u64,
52}
53
54#[derive(Default)]
56pub struct Builder {
57 pub proc_path: String,
58 pub proc_file: String,
59}
60
61impl Builder {
62 pub fn new() -> Builder {
63 Builder {
64 proc_path: "/proc".to_string(),
65 proc_file: "loadavg".to_string(),
66 }
67 }
68
69 pub fn path(mut self, proc_path: &str) -> Builder {
70 self.proc_path = proc_path.to_string();
71 self
72 }
73 pub fn file(mut self, proc_file: &str) -> Builder {
74 self.proc_file = proc_file.to_string();
75 self
76 }
77 pub fn read(self) -> Result<ProcLoadavg, ProcSysParserError> {
79 ProcLoadavg::read_proc_loadavg(format!("{}/{}", &self.proc_path, &self.proc_file).as_str())
80 }
81}
82
83pub fn read() -> Result<ProcLoadavg, ProcSysParserError> {
87 Builder::new().read()
88}
89
90impl ProcLoadavg {
91 pub fn new() -> ProcLoadavg {
92 ProcLoadavg::default()
93 }
94 pub fn parse_proc_loadavg( proc_loadavg: &str,) -> Result<ProcLoadavg, ProcSysParserError>
95 {
96 let mut fields = proc_loadavg.split_whitespace();
97 let mut fields_copy = fields.clone();
98
99 Ok(ProcLoadavg {
100 load_1: fields.next()
102 .ok_or(ProcSysParserError::IteratorItemError {item: "loadavg load_1".to_string() })?
103 .parse::<f64>().map_err(ProcSysParserError::ParseToFloatError)?,
104 load_5: fields.next()
105 .ok_or(ProcSysParserError::IteratorItemError {item: "loadavg load_5".to_string() })?
106 .parse::<f64>().map_err(ProcSysParserError::ParseToFloatError)?,
107 load_15: fields.next()
108 .ok_or(ProcSysParserError::IteratorItemError {item: "loadavg load_15". to_string() })?
109 .parse::<f64>().map_err(ProcSysParserError::ParseToFloatError)?,
110 current_runnable: fields.next()
111 .ok_or(ProcSysParserError::IteratorItemError {item: "loadavg current_runnable".to_string() })?
112 .split('/').next().ok_or(ProcSysParserError::IteratorItemError {item: "loadavg current_runnable split".to_string() })?
113 .parse::<u64>().map_err(ProcSysParserError::ParseToIntegerError)?,
114 total: fields_copy.nth(3)
115 .ok_or(ProcSysParserError::IteratorItemError {item: "loadavg total".to_string() })?
116 .split('/').nth(1).ok_or(ProcSysParserError::IteratorItemError {item: "loadavg total split".to_string() })?
117 .parse::<u64>().map_err(ProcSysParserError::ParseToIntegerError)?,
118 last_pid: fields.next()
119 .ok_or(ProcSysParserError::IteratorItemError {item: "loadavg last_pid".to_string() })?
120 .parse::<u64>().map_err(ProcSysParserError::ParseToIntegerError)?,
121 })
122 }
123 pub fn read_proc_loadavg(proc_loadavg: &str) -> Result<ProcLoadavg, ProcSysParserError>
124 {
125 let proc_loadavg_output = read_to_string(proc_loadavg)
126 .map_err(|error| ProcSysParserError::FileReadError { file: proc_loadavg.to_string(), error })?;
127
128 ProcLoadavg::parse_proc_loadavg(&proc_loadavg_output)
129 }
130}
131
132#[cfg(test)]
133mod tests {
134 use std::fs::{write, remove_dir_all, create_dir_all};
135 use rand::{thread_rng, Rng};
136 use rand::distributions::Alphanumeric;
137 use super::*;
138
139 #[test]
140 fn parse_proc_loadavg_line() {
141 let loadavg_line = format!("0.05 0.19 0.13 1/161 7\n");
142 let result = ProcLoadavg::parse_proc_loadavg(&loadavg_line).unwrap();
143 assert_eq!(result, ProcLoadavg { load_1: 0.05, load_5: 0.19, load_15: 0.13, current_runnable: 1, total: 161, last_pid: 7 });
144 }
145
146 #[test]
147 fn create_proc_loadavg_file_and_read() {
148 let proc_loadavg = format!("0.05 0.19 0.13 1/161 7\n");
149
150 let directory_suffix: String = thread_rng().sample_iter(&Alphanumeric).take(8).map(char::from).collect();
151 let test_path = format!("/tmp/test.{}", directory_suffix);
152 create_dir_all(format!("{}", test_path)).expect("Error creating mock directory.");
153
154 write(format!("{}/loadavg", test_path), proc_loadavg).expect(format!("Error writing to {}/loadavg", test_path).as_str());
155 let result = Builder::new().path(&test_path).read().unwrap();
156
157 remove_dir_all(test_path).unwrap();
158
159 assert_eq!(result, ProcLoadavg { load_1: 0.05, load_5: 0.19, load_15: 0.13, current_runnable: 1, total: 161, last_pid: 7 });
160 }
161 #[test]
162 fn read_nonexistent_loadavg_file() -> Result<(), ProcSysParserError> {
163 Ok(assert!(Builder::new().path("/xxxxxxxxxxxx").read().is_err()))
166 }
167
168 #[test]
169 fn parse_corrupted_loadavg_line_missing_entries() -> Result<(), ProcSysParserError> {
170 let loadavg_line = format!("0.05 0.19\n");
171 let result = ProcLoadavg::parse_proc_loadavg(&loadavg_line);
173 Ok(assert!(result.is_err()))
174 }
175 #[test]
176 fn parse_corrupted_loadavg_line_wrong_entry() -> Result<(), ProcSysParserError> {
177 let loadavg_line = format!("AAA 0.19 0.13 1/161 7\n");
178 let result = ProcLoadavg::parse_proc_loadavg(&loadavg_line);
180 Ok(assert!(result.is_err()))
181 }
182}
183
184