diff --git a/src/process/mod.rs b/src/process/mod.rs index 0729fbb9..51184526 100644 --- a/src/process/mod.rs +++ b/src/process/mod.rs @@ -93,6 +93,9 @@ pub use status::*; mod schedstat; pub use schedstat::*; +mod smaps_rollup; +pub use smaps_rollup::*; + mod task; pub use task::*; @@ -1062,6 +1065,15 @@ impl Process { Ok(vec) } + /// This is the sum of all the smaps data but it is much more performant to get it this way. + /// + /// Since 4.14 and requires CONFIG_PROC_PAGE_MONITOR. + pub fn smaps_rollup(&self) -> ProcResult { + let file = FileWrapper::open_at(&self.root, &self.fd, "smaps_rollup")?; + + SmapsRollup::from_reader(file) + } + /// Returns a struct that can be used to access information in the `/proc/pid/pagemap` file. pub fn pagemap(&self) -> ProcResult { let path = self.root.join("pagemap"); diff --git a/src/process/smaps_rollup.rs b/src/process/smaps_rollup.rs new file mode 100644 index 00000000..b6c3adb5 --- /dev/null +++ b/src/process/smaps_rollup.rs @@ -0,0 +1,58 @@ +use super::{MemoryMap, MemoryMapData}; +use crate::{ProcError, ProcResult}; +use std::io::{BufRead, BufReader, Read}; + +#[derive(Debug)] +pub struct SmapsRollup { + pub memory_map: MemoryMap, + pub memory_map_data: MemoryMapData, +} + +impl SmapsRollup { + // this implemenation is similar but not identical to Process::smaps() + pub fn from_reader(r: R) -> ProcResult { + let reader = BufReader::new(r); + + let mut memory_map = MemoryMap::new(); + let mut memory_map_data: MemoryMapData = Default::default(); + let mut first = true; + for line in reader.lines() { + let line = line.map_err(|_| ProcError::Incomplete(None))?; + + if first { + memory_map = MemoryMap::from_line(&line)?; + first = false; + continue; + } + + let mut parts = line.split_ascii_whitespace(); + + let key = parts.next(); + let value = parts.next(); + + if let (Some(k), Some(v)) = (key, value) { + // While most entries do have one, not all of them do. + let size_suffix = parts.next(); + + // Limited poking at /proc//smaps and then checking if "MB", "GB", and "TB" appear in the C file that is + // supposedly responsible for creating smaps, has lead me to believe that the only size suffixes we'll ever encounter + // "kB", which is most likely kibibytes. Actually checking if the size suffix is any of the above is a way to + // future-proof the code, but I am not sure it is worth doing so. + let size_multiplier = if size_suffix.is_some() { 1024 } else { 1 }; + + let v = v + .parse::() + .map_err(|_| ProcError::Other("Value in `Key: Value` pair was not actually a number".into()))?; + + // This ignores the case when our Key: Value pairs are really Key Value pairs. Is this a good idea? + let k = k.trim_end_matches(':'); + + memory_map_data.map.insert(k.into(), v * size_multiplier); + } + } + Ok(SmapsRollup { + memory_map, + memory_map_data, + }) + } +} diff --git a/src/process/tests.rs b/src/process/tests.rs index 434d1b2e..cf18bcc8 100644 --- a/src/process/tests.rs +++ b/src/process/tests.rs @@ -200,6 +200,20 @@ fn test_smaps() { println!("{:#?}", smaps); } +#[test] +fn test_smaps_rollup() { + let me = Process::myself().unwrap(); + let smaps_rollup = match me.smaps_rollup() { + Ok(x) => x, + Err(ProcError::NotFound(_)) => { + // ignored because not all kernerls have smaps_rollup + return; + } + Err(e) => panic!("{}", e), + }; + println!("{:#?}", smaps_rollup); +} + #[test] fn test_proc_alive() { let myself = Process::myself().unwrap();