diff --git a/below/config/src/lib.rs b/below/config/src/lib.rs index 1f82e9eb..d26d6014 100644 --- a/below/config/src/lib.rs +++ b/below/config/src/lib.rs @@ -46,6 +46,7 @@ pub struct BelowConfig { pub btrfs_samples: u64, pub btrfs_min_pct: f64, pub enable_ethtool_stats: bool, + pub enable_ksm_stats: bool, pub enable_resctrl_stats: bool, pub enable_tc_stats: bool, } @@ -63,6 +64,7 @@ impl Default for BelowConfig { btrfs_samples: btrfs::DEFAULT_SAMPLES, btrfs_min_pct: btrfs::DEFAULT_MIN_PCT, enable_ethtool_stats: false, + enable_ksm_stats: false, enable_resctrl_stats: false, enable_tc_stats: false, } diff --git a/below/model/src/collector.rs b/below/model/src/collector.rs index 49ee4788..8e93310f 100644 --- a/below/model/src/collector.rs +++ b/below/model/src/collector.rs @@ -30,6 +30,7 @@ pub struct CollectorOptions { pub disable_disk_stat: bool, pub enable_btrfs_stats: bool, pub enable_ethtool_stats: bool, + pub enable_ksm_stats: bool, pub enable_resctrl_stats: bool, pub enable_tc_stats: bool, pub btrfs_samples: u64, @@ -50,6 +51,7 @@ impl Default for CollectorOptions { disable_disk_stat: false, enable_btrfs_stats: false, enable_ethtool_stats: false, + enable_ksm_stats: false, enable_resctrl_stats: false, enable_tc_stats: false, btrfs_samples: btrfs::DEFAULT_SAMPLES, @@ -186,6 +188,7 @@ fn collect_sample( let btrfs_reader = btrfs::BtrfsReader::new(options.btrfs_samples, options.btrfs_min_pct, logger.clone()); let ethtool_reader = ethtool::EthtoolReader::new(); + let ksm_reader = procfs::KsmReader::new(); // Take mutex, then take all values out of shared map and replace with default map // @@ -217,6 +220,11 @@ fn collect_sample( meminfo: reader.read_meminfo()?, vmstat: reader.read_vmstat()?, slabinfo: reader.read_slabinfo().unwrap_or_default(), + ksm: if !options.enable_ksm_stats { + None + } else { + Some(ksm_reader.read_ksm()) + }, hostname: get_hostname()?, kernel_version: match reader.read_kernel_version() { Ok(k) => Some(k), diff --git a/below/model/src/sample.rs b/below/model/src/sample.rs index 02bad8d4..f768f78b 100644 --- a/below/model/src/sample.rs +++ b/below/model/src/sample.rs @@ -66,6 +66,7 @@ pub struct SystemSample { pub vmstat: procfs::VmStat, #[serde(default)] pub slabinfo: procfs::SlabInfoMap, + pub ksm: Option, pub hostname: String, pub disks: procfs::DiskMap, pub btrfs: Option, diff --git a/below/procfs/src/lib.rs b/below/procfs/src/lib.rs index bc614ab4..8272125c 100644 --- a/below/procfs/src/lib.rs +++ b/below/procfs/src/lib.rs @@ -22,6 +22,7 @@ use std::io::ErrorKind; use std::io::Read; use std::path::Path; use std::path::PathBuf; +use std::str::FromStr; use std::sync::mpsc::channel; use std::sync::mpsc::RecvTimeoutError; use std::time::Duration; @@ -39,6 +40,7 @@ pub use types::*; #[cfg(test)] mod test; +pub const KSM_SYSFS: &str = "/sys/kernel/mm/ksm"; pub const NET_SYSFS: &str = "/sys/class/net/"; pub const NET_PROCFS: &str = "/proc/net"; @@ -1363,6 +1365,80 @@ impl NetReader { } } +pub struct KsmReader { + path: PathBuf, +} + +impl Default for KsmReader { + fn default() -> Self { + Self::new() + } +} + +impl KsmReader { + pub fn new() -> KsmReader { + KsmReader { + path: Path::new(KSM_SYSFS).to_path_buf(), + } + } + + pub fn new_with_custom_path(path: PathBuf) -> KsmReader { + KsmReader { path } + } + + pub fn read_ksm(&self) -> Ksm { + Ksm { + advisor_max_cpu: self.read("advisor_max_cpu"), + advisor_max_pages_to_scan: self.read("advisor_max_pages_to_scan"), + advisor_min_pages_to_scan: self.read("advisor_min_pages_to_scan"), + advisor_mode: self.read_selection("advisor_mode"), + advisor_target_scan_time: self.read("advisor_target_scan_time"), + full_scans: self.read("full_scans"), + general_profit: self.read("general_profit"), + ksm_zero_pages: self.read("ksm_zero_pages"), + max_page_sharing: self.read("max_page_sharing"), + merge_across_nodes: self.read("merge_across_nodes"), + pages_scanned: self.read("pages_scanned"), + pages_shared: self.read("pages_shared"), + pages_sharing: self.read("pages_sharing"), + pages_skipped: self.read("pages_skipped"), + pages_to_scan: self.read("pages_to_scan"), + pages_unshared: self.read("pages_unshared"), + pages_volatile: self.read("pages_volatile"), + run: self.read("run"), + sleep_millisecs: self.read("sleep_millisecs"), + smart_scan: self.read("smart_scan"), + stable_node_chains: self.read("stable_node_chains"), + stable_node_chains_prune_millisecs: self.read("stable_node_chains_prune_millisecs"), + stable_node_dups: self.read("stable_node_dups"), + use_zero_pages: self.read("use_zero_pages"), + } + } + + fn read(&self, name: &str) -> Option + where + F: FromStr, + { + std::fs::read_to_string(self.path.join(name)) + .ok()? + .trim() + .parse() + .ok() + } + + // parses from string representing one selection out of some choices + // i.e. "one [two] three" -> returns "two" + fn read_selection(&self, name: &str) -> Option { + let val: String = self.read(name)?; + let left_bracket_idx = val.find('[')?; + let right_bracket_idx = val.rfind(']')?; + if left_bracket_idx >= right_bracket_idx { + return None; + } + Some(val[left_bracket_idx + 1..right_bracket_idx].to_string()) + } +} + /// Wraps the result into an `Option` if the result is not an error. /// If the error is of type `ENOENT`, it is returned as `Ok(None)`. /// Else, the error itself is returned. diff --git a/below/procfs/src/test.rs b/below/procfs/src/test.rs index 301084db..7fdf1888 100644 --- a/below/procfs/src/test.rs +++ b/below/procfs/src/test.rs @@ -21,6 +21,7 @@ use slog::Drain; use tempfile::TempDir; use crate::types::*; +use crate::KsmReader; use crate::NetReader; use crate::ProcReader; use crate::PAGE_SIZE; @@ -44,6 +45,10 @@ impl TestProcfs { ProcReader::new_with_custom_procfs(self.path().to_path_buf()) } + fn get_ksm_reader(&self) -> KsmReader { + KsmReader::new_with_custom_path(self.path().to_path_buf()) + } + fn create_dir>(&self, p: P) { let path = self.path().join(p); std::fs::create_dir_all(&path) @@ -578,6 +583,70 @@ fn test_read_slabinfo() { assert_eq!(slabinfo, expected_slabinfo); } +#[test] +fn test_ksm() { + let ksm_inputs = std::collections::BTreeMap::from([ + ("advisor_max_cpu", "70"), + ("advisor_max_pages_to_scan", "30000"), + ("advisor_min_pages_to_scan", "500"), + ("advisor_mode", "none [scan-time]"), + ("advisor_target_scan_time", "200"), + ("full_scans", "25"), + ("general_profit", "0"), + ("ksm_zero_pages", "0"), + ("max_page_sharing", "256"), + ("merge_across_nodes", "1"), + ("pages_scanned", "5149"), + ("pages_shared", "0"), + ("pages_sharing", "0"), + ("pages_skipped", "25"), + ("pages_to_scan", "100"), + ("pages_unshared", "0"), + ("pages_volatile", "0"), + ("run", "1"), + ("sleep_millisecs", "20"), + ("smart_scan", "1"), + ("stable_node_chains", "1"), + ("stable_node_chains_prune_millisecs", "2000"), + ("stable_node_dups", "0"), + ("use_zero_pages", "0"), + ]); + + let procfs = TestProcfs::new(); + + for (key, val) in ksm_inputs.iter() { + procfs.create_file_with_content(key, val.as_bytes()); + } + + let reader = procfs.get_ksm_reader(); + let ksm = reader.read_ksm(); + + assert_eq!(ksm.advisor_max_cpu, Some(70)); + assert_eq!(ksm.advisor_max_pages_to_scan, Some(30000)); + assert_eq!(ksm.advisor_min_pages_to_scan, Some(500)); + assert_eq!(ksm.advisor_mode, Some(String::from("scan-time"))); + assert_eq!(ksm.advisor_target_scan_time, Some(200)); + assert_eq!(ksm.full_scans, Some(25)); + assert_eq!(ksm.general_profit, Some(0)); + assert_eq!(ksm.ksm_zero_pages, Some(0)); + assert_eq!(ksm.max_page_sharing, Some(256)); + assert_eq!(ksm.merge_across_nodes, Some(1)); + assert_eq!(ksm.pages_scanned, Some(5149)); + assert_eq!(ksm.pages_shared, Some(0)); + assert_eq!(ksm.pages_sharing, Some(0)); + assert_eq!(ksm.pages_skipped, Some(25)); + assert_eq!(ksm.pages_to_scan, Some(100)); + assert_eq!(ksm.pages_unshared, Some(0)); + assert_eq!(ksm.pages_volatile, Some(0)); + assert_eq!(ksm.run, Some(1)); + assert_eq!(ksm.sleep_millisecs, Some(20)); + assert_eq!(ksm.smart_scan, Some(1)); + assert_eq!(ksm.stable_node_chains, Some(1)); + assert_eq!(ksm.stable_node_chains_prune_millisecs, Some(2000)); + assert_eq!(ksm.stable_node_dups, Some(0)); + assert_eq!(ksm.use_zero_pages, Some(0)); +} + #[test] fn test_disk_stat() { let diskstats = b" 1 0 ram0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 diff --git a/below/procfs/src/types.rs b/below/procfs/src/types.rs index ea326b96..b1713966 100644 --- a/below/procfs/src/types.rs +++ b/below/procfs/src/types.rs @@ -284,6 +284,34 @@ pub struct SlabInfo { pub num_slabs: Option, } +#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct Ksm { + pub advisor_max_cpu: Option, + pub advisor_max_pages_to_scan: Option, + pub advisor_min_pages_to_scan: Option, + pub advisor_mode: Option, + pub advisor_target_scan_time: Option, + pub full_scans: Option, + pub general_profit: Option, + pub ksm_zero_pages: Option, + pub max_page_sharing: Option, + pub merge_across_nodes: Option, + pub pages_scanned: Option, + pub pages_shared: Option, + pub pages_sharing: Option, + pub pages_skipped: Option, + pub pages_to_scan: Option, + pub pages_unshared: Option, + pub pages_volatile: Option, + pub run: Option, + pub sleep_millisecs: Option, + pub smart_scan: Option, + pub stable_node_chains: Option, + pub stable_node_chains_prune_millisecs: Option, + pub stable_node_dups: Option, + pub use_zero_pages: Option, +} + #[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize)] pub struct MountInfo { pub mnt_id: Option, diff --git a/below/src/main.rs b/below/src/main.rs index 621a8e21..9fbc8320 100644 --- a/below/src/main.rs +++ b/below/src/main.rs @@ -1084,6 +1084,7 @@ fn record( disable_disk_stat, enable_btrfs_stats: below_config.enable_btrfs_stats, enable_ethtool_stats: below_config.enable_ethtool_stats, + enable_ksm_stats: below_config.enable_ksm_stats, enable_resctrl_stats: below_config.enable_resctrl_stats, enable_tc_stats: below_config.enable_tc_stats, btrfs_samples: below_config.btrfs_samples, @@ -1215,6 +1216,7 @@ fn live_local( exit_data: exit_buffer, enable_btrfs_stats: below_config.enable_btrfs_stats, enable_ethtool_stats: below_config.enable_ethtool_stats, + enable_ksm_stats: below_config.enable_ksm_stats, enable_resctrl_stats: below_config.enable_resctrl_stats, btrfs_samples: below_config.btrfs_samples, btrfs_min_pct: below_config.btrfs_min_pct,