proc_sys_parser/
loadavg.rs

1/*!
2Read data from `/proc/loadavg` into the struct [`ProcLoadavg`].
3
4The processor of `/proc/loadavg` takes the values in the file, and puts them in the struct [`ProcLoadavg`].
5The values are the load averages for 1, 5 and 15 minutes, the current number runnable processes *slash* total number of
6processes and the last pid created.
7
8Documentation: <https://docs.kernel.org/filesystems/proc.html>
9
10Here is an example obtaining the data from `/proc/loadavg`:
11```no_run
12use proc_sys_parser::loadavg;
13
14let proc_loadavg = loadavg::read();
15
16println!("{:#?}", proc_loadavg);
17```
18Example output:
19```text
20ProcLoadavg {
21    load_1: 0.0,
22    load_5: 0.0,
23    load_15: 0.0,
24    current_runnable: 0,
25    total: 0,
26    last_pid: 12345,
27}
28```
29(edited for readability)
30
31If you want to change the path and/or file that is read for [`ProcLoadavg`], which is `/proc/loadavg`
32by default, use:
33```no_run
34use proc_sys_parser::{meminfo, meminfo::Builder};
35
36let proc_loadavg = Builder::new().path("/myproc").read();
37```
38
39*/
40use std::fs::read_to_string;
41use crate::ProcSysParserError;
42
43/// Struct for holding `/proc/loadavg` statistics
44#[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/// Builder pattern for [`ProcLoadavg`]
55#[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> {
78    pub fn read(self) -> Result<ProcLoadavg, ProcSysParserError> {
79        ProcLoadavg::read_proc_loadavg(format!("{}/{}", &self.proc_path, &self.proc_file).as_str())
80    }
81}
82
83/// The main function for building a [`ProcMemInfo`] struct with current data.
84/// This uses the Builder pattern, which allows settings such as the filename to specified.
85//pub fn read() -> Result<()> {
86pub 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().ok_or(ProcSysParserError::IteratorItemError)?.parse::<f64>().unwrap(),
101            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        // uncomment to see the error message
164        //let _result = Builder::new().path("/xxxxxxxxxxxx").read()?;
165        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)?;
172        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)?;
179        let result = ProcLoadavg::parse_proc_loadavg(&loadavg_line);
180        Ok(assert!(result.is_err()))
181    }
182}
183
184