| // Copyright 2024 The ChromiumOS Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use std::fs::File; |
| use std::io; |
| use std::io::BufRead; |
| use std::io::BufReader; |
| |
| /// Struct to hold parsed /proc/vmstat data, only contains used fields. |
| #[derive(Debug, Default, Clone)] |
| pub struct Vmstat { |
| pub workingset_refault_anon: usize, |
| pub workingset_refault_file: usize, |
| pub pgsteal_direct: usize, |
| } |
| |
| impl Vmstat { |
| /// Load /proc/vmstat and parse it. |
| pub fn load() -> io::Result<Self> { |
| let reader = File::open("/proc/vmstat")?; |
| let reader = BufReader::new(reader); |
| Self::parse(reader) |
| } |
| |
| fn parse<R: BufRead>(reader: R) -> io::Result<Self> { |
| let mut result = Self::default(); |
| for line in reader.lines() { |
| let line = line?; |
| let mut tokens = line.split_whitespace(); |
| let Some(key) = tokens.next() else { |
| continue; |
| }; |
| let field = match key { |
| "workingset_refault_anon" => &mut result.workingset_refault_anon, |
| "workingset_refault_file" => &mut result.workingset_refault_file, |
| // kernels before 5.9 don't have workingset_refault_anon and |
| // workingset_refault_file but workingset_refault only for file page cache. |
| "workingset_refault" => &mut result.workingset_refault_file, |
| "pgsteal_direct" => &mut result.pgsteal_direct, |
| _ => { |
| continue; |
| } |
| }; |
| let Some(value) = tokens.next() else { |
| continue; |
| }; |
| let Ok(value) = value.parse::<usize>() else { |
| continue; |
| }; |
| *field = value; |
| } |
| Ok(result) |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| |
| #[test] |
| fn test_parse_vmstat() { |
| let mock_vmstat = r#" |
| nr_free_pages 44453 |
| nr_zone_inactive_anon 100986 |
| nr_zone_active_anon 294376 |
| nr_zone_inactive_file 61713 |
| nr_zone_active_file 62802 |
| nr_zone_unevictable 22615 |
| nr_zone_write_pending 400 |
| nr_mlock 18 |
| nr_bounce 0 |
| nr_zspages 270755 |
| nr_free_cma 0 |
| nr_inactive_anon 100986 |
| nr_active_anon 294376 |
| nr_inactive_file 61713 |
| nr_active_file 62802 |
| nr_unevictable 22615 |
| nr_slab_reclaimable 20307 |
| nr_slab_unreclaimable 39410 |
| nr_isolated_anon 0 |
| nr_isolated_file 0 |
| workingset_nodes 2292 |
| workingset_refault_anon 72178310 |
| workingset_refault_file 3998735 |
| workingset_activate_anon 2282026 |
| workingset_activate_file 301341 |
| workingset_restore_anon 70302599 |
| workingset_restore_file 2139605 |
| workingset_nodereclaim 0 |
| nr_anon_pages 299987 |
| nr_mapped 123606 |
| nr_file_pages 243333 |
| nr_dirty 400 |
| nr_writeback 0 |
| nr_writeback_temp 0 |
| nr_shmem 115859 |
| nr_shmem_hugepages 0 |
| nr_shmem_pmdmapped 0 |
| nr_file_hugepages 0 |
| nr_file_pmdmapped 0 |
| nr_anon_transparent_hugepages 0 |
| nr_vmscan_write 103599919 |
| nr_vmscan_immediate_reclaim 0 |
| nr_dirtied 10919793 |
| nr_written 115964117 |
| nr_kernel_misc_reclaimable 0 |
| nr_foll_pin_acquired 0 |
| nr_foll_pin_released 0 |
| nr_kernel_stack 15792 |
| nr_page_table_pages 18652 |
| nr_swapcached 2959 |
| nr_dirty_threshold 95570 |
| nr_dirty_background_threshold 7935 |
| pgpgin 50300534 |
| pgpgout 97467164 |
| pswpin 90414350 |
| pswpout 105433731 |
| pgalloc_dma 18365 |
| pgalloc_dma32 355149358 |
| pgalloc_normal 372416197 |
| pgalloc_movable 0 |
| pgsteal_kswapd 41539543 |
| pgsteal_direct 5087936 |
| pgsteal_anon 34022233 |
| pgsteal_file 12605246"#; |
| let vmstat = Vmstat::parse(mock_vmstat.as_bytes()).unwrap(); |
| assert_eq!(vmstat.workingset_refault_anon, 72178310); |
| assert_eq!(vmstat.workingset_refault_file, 3998735); |
| assert_eq!(vmstat.pgsteal_direct, 5087936); |
| } |
| |
| #[test] |
| fn test_parse_vmstat_workingset_refault() { |
| let mock_vmstat = "workingset_refault 12345"; |
| let vmstat = Vmstat::parse(mock_vmstat.as_bytes()).unwrap(); |
| assert_eq!(vmstat.workingset_refault_anon, 0); |
| assert_eq!(vmstat.workingset_refault_file, 12345); |
| } |
| } |