diff options
31 files changed, 456 insertions, 20 deletions
diff --git a/below/Cargo.toml b/below/Cargo.toml index 8ecfe567..b8634b92 100644 --- a/below/Cargo.toml +++ b/below/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "below" version = "0.8.1" -authors = ["Daniel Xu <dlxu@fb.com>", "Facebook"] +authors = ["Meta Platforms, Inc. and affiliates"] edition = "2021" description = "An interactive tool to view and record historical system data" readme = "../README.md" @@ -29,7 +29,7 @@ config = { package = "below-config", version = "0.8.1", path = "config" } cursive = { version = "0.20.0", features = ["crossterm-backend"], default-features = false } dump = { package = "below-dump", version = "0.8.1", path = "dump" } indicatif = { version = "0.17.6", features = ["improved_unicode", "rayon", "tokio"] } -libbpf-rs = { version = "0.23", default-features = false } +libbpf-rs = { version = "0.23.1", default-features = false } libc = "0.2.139" model = { package = "below-model", version = "0.8.1", path = "model" } once_cell = "1.12" @@ -52,7 +52,7 @@ maplit = "1.0" portpicker = "0.1.1" [build-dependencies] -libbpf-cargo = { version = "0.23", default-features = false } +libbpf-cargo = { version = "0.23.1", default-features = false } [features] default = ["libbpf-cargo/default", "libbpf-rs/default"] diff --git a/below/below_derive/Cargo.toml b/below/below_derive/Cargo.toml index dae99d08..59e4c791 100644 --- a/below/below_derive/Cargo.toml +++ b/below/below_derive/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "below_derive" version = "0.8.1" -authors = ["Daniel Xu <dlxu@fb.com>", "Facebook"] +authors = ["Meta Platforms, Inc. and affiliates"] edition = "2021" description = "Proc macros for below" repository = "https://github.com/facebookincubator/below" diff --git a/below/btrfs/Cargo.toml b/below/btrfs/Cargo.toml index 90f2ad6f..329f511e 100644 --- a/below/btrfs/Cargo.toml +++ b/below/btrfs/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "below-btrfs" version = "0.8.1" -authors = ["Daniel Xu <dlxu@fb.com>", "Facebook"] +authors = ["Meta Platforms, Inc. and affiliates"] edition = "2021" description = "A crate for reading btrfs" readme = "README" diff --git a/below/cgroupfs/Cargo.toml b/below/cgroupfs/Cargo.toml index 2a2308e8..67745de5 100644 --- a/below/cgroupfs/Cargo.toml +++ b/below/cgroupfs/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "cgroupfs" version = "0.8.1" -authors = ["Daniel Xu <dlxu@fb.com>", "Facebook"] +authors = ["Meta Platforms, Inc. and affiliates"] edition = "2021" description = "A crate for reading cgroupv2 data" readme = "README" diff --git a/below/cgroupfs/src/lib.rs b/below/cgroupfs/src/lib.rs index 2688bdc7..5a2430ed 100644 --- a/below/cgroupfs/src/lib.rs +++ b/below/cgroupfs/src/lib.rs @@ -428,6 +428,10 @@ impl CgroupReader { MemoryEvents::read(self) } + pub fn read_memory_events_local(&self) -> Result<MemoryEventsLocal> { + MemoryEventsLocal::read(self) + } + pub fn read_cgroup_stat(&self) -> Result<CgroupStat> { CgroupStat::read(self) } @@ -713,6 +717,14 @@ key_values_format!(MemoryEvents; memory.events; [ oom_kill ]); +key_values_format!(MemoryEventsLocal; memory.events.local; [ + low, + high, + max, + oom, + oom_kill +]); + key_values_format!(CgroupStat; cgroup.stat; [nr_descendants, nr_dying_descendants]); // Trait to add a read() method for `<string> key=value` formatted files diff --git a/below/cgroupfs/src/types.rs b/below/cgroupfs/src/types.rs index 764fc49d..d69e11e2 100644 --- a/below/cgroupfs/src/types.rs +++ b/below/cgroupfs/src/types.rs @@ -133,6 +133,15 @@ pub struct MemoryEvents { } #[derive(Default, Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] +pub struct MemoryEventsLocal { + pub low: Option<u64>, + pub high: Option<u64>, + pub max: Option<u64>, + pub oom: Option<u64>, + pub oom_kill: Option<u64>, +} + +#[derive(Default, Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] pub struct CgroupStat { pub nr_descendants: Option<u32>, pub nr_dying_descendants: Option<u32>, diff --git a/below/common/Cargo.toml b/below/common/Cargo.toml index bf4c6c55..96c0f43d 100644 --- a/below/common/Cargo.toml +++ b/below/common/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "below-common" version = "0.8.1" -authors = ["Daniel Xu <dlxu@fb.com>", "Facebook"] +authors = ["Meta Platforms, Inc. and affiliates"] edition = "2021" description = "Common below code" repository = "https://github.com/facebookincubator/below" diff --git a/below/config/Cargo.toml b/below/config/Cargo.toml index 386c4a4e..3bc0c9a1 100644 --- a/below/config/Cargo.toml +++ b/below/config/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "below-config" version = "0.8.1" -authors = ["Daniel Xu <dlxu@fb.com>", "Facebook"] +authors = ["Meta Platforms, Inc. and affiliates"] edition = "2021" description = "Configurations for below" repository = "https://github.com/facebookincubator/below" 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/dump/Cargo.toml b/below/dump/Cargo.toml index 3ac4c2c0..a729f16e 100644 --- a/below/dump/Cargo.toml +++ b/below/dump/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "below-dump" version = "0.8.1" -authors = ["Daniel Xu <dlxu@fb.com>", "Facebook"] +authors = ["Meta Platforms, Inc. and affiliates"] edition = "2021" description = "Dump crate for below" repository = "https://github.com/facebookincubator/below" diff --git a/below/dump/src/test.rs b/below/dump/src/test.rs index 4a0d53f5..765e1f91 100644 --- a/below/dump/src/test.rs +++ b/below/dump/src/test.rs @@ -534,6 +534,11 @@ fn test_dump_cgroup_titles() { "Events Max", "Events OOM", "Events Kill", + "Events Local Low", + "Events Local High", + "Events Local Max", + "Events Local OOM", + "Events Local Kill", "RBytes", "WBytes", "R I/O", @@ -999,6 +1004,10 @@ fn test_dump_queue_content() { hostname: "h".to_string(), }; + // we are dumping timestamps assuming they are local time + // so the timezone needs to be set to the expected TZ + std::env::set_var("TZ", "US/Pacific"); + let result = queue_dumper .dump_model(&ctx, &model, &mut queue_content, &mut round, false) .expect("Failed to dump queue model"); diff --git a/below/ethtool/Cargo.toml b/below/ethtool/Cargo.toml index 3a1053d4..74f6bbe2 100644 --- a/below/ethtool/Cargo.toml +++ b/below/ethtool/Cargo.toml @@ -3,9 +3,9 @@ [package] name = "below-ethtool" version = "0.8.1" -authors = ["Daniel Xu <dlxu@fb.com>", "Facebook"] +authors = ["Meta Platforms, Inc. and affiliates"] edition = "2021" -description = "Model crate for below" +description = "ethtool crate for below" repository = "https://github.com/facebookincubator/below" license = "Apache-2.0" diff --git a/below/gpu_stats/Cargo.toml b/below/gpu_stats/Cargo.toml index 482f19c1..9ce20b60 100644 --- a/below/gpu_stats/Cargo.toml +++ b/below/gpu_stats/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "below-gpu-stats" version = "0.8.1" -authors = ["Daniel Xu <dlxu@fb.com>", "Facebook"] +authors = ["Meta Platforms, Inc. and affiliates"] edition = "2021" description = "GPU stats crate for below" repository = "https://github.com/facebookincubator/below" diff --git a/below/model/Cargo.toml b/below/model/Cargo.toml index d1c1226a..46522df2 100644 --- a/below/model/Cargo.toml +++ b/below/model/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "below-model" version = "0.8.1" -authors = ["Daniel Xu <dlxu@fb.com>", "Facebook"] +authors = ["Meta Platforms, Inc. and affiliates"] edition = "2021" description = "Model crate for below" repository = "https://github.com/facebookincubator/below" diff --git a/below/model/src/cgroup.rs b/below/model/src/cgroup.rs index b94f3626..d8d20e8a 100644 --- a/below/model/src/cgroup.rs +++ b/below/model/src/cgroup.rs @@ -460,6 +460,11 @@ pub struct CgroupMemoryModel { pub events_max: Option<u64>, pub events_oom: Option<u64>, pub events_oom_kill: Option<u64>, + pub events_local_low: Option<u64>, + pub events_local_high: Option<u64>, + pub events_local_max: Option<u64>, + pub events_local_oom: Option<u64>, + pub events_local_oom_kill: Option<u64>, } impl std::ops::Add for CgroupMemoryModel { @@ -533,6 +538,11 @@ impl std::ops::Add for CgroupMemoryModel { events_max: opt_add(self.events_max, other.events_max), events_oom: opt_add(self.events_oom, other.events_oom), events_oom_kill: opt_add(self.events_oom_kill, other.events_oom_kill), + events_local_low: opt_add(self.events_local_low, other.events_local_low), + events_local_high: opt_add(self.events_local_high, other.events_local_high), + events_local_max: opt_add(self.events_local_max, other.events_local_max), + events_local_oom: opt_add(self.events_local_oom, other.events_local_oom), + events_local_oom_kill: opt_add(self.events_local_oom_kill, other.events_local_oom_kill), } } } @@ -555,6 +565,13 @@ impl CgroupMemoryModel { model.events_oom = events.oom; model.events_oom_kill = events.oom_kill; } + if let Some(events_local) = &sample.memory_events_local { + model.events_local_low = events_local.low; + model.events_local_high = events_local.high; + model.events_local_max = events_local.max; + model.events_local_oom = events_local.oom; + model.events_local_oom_kill = events_local.oom_kill; + } if let Some(stat) = &sample.memory_stat { model.anon = stat.anon; model.file = stat.file; diff --git a/below/model/src/collector.rs b/below/model/src/collector.rs index bf577f09..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), @@ -440,6 +448,7 @@ fn collect_cgroup_sample( memory_swap_max: wrap(reader.read_memory_swap_max())?, memory_zswap_max: wrap(reader.read_memory_zswap_max())?, memory_events: wrap(reader.read_memory_events())?.map(Into::into), + memory_events_local: wrap(reader.read_memory_events_local())?.map(Into::into), inode_number: match reader.read_inode_number() { Ok(st_ino) => Some(st_ino as i64), Err(e) => { diff --git a/below/model/src/common_field_ids.rs b/below/model/src/common_field_ids.rs index 3b752298..6659fdc5 100644 --- a/below/model/src/common_field_ids.rs +++ b/below/model/src/common_field_ids.rs @@ -23,7 +23,7 @@ /// /// This list also servers as documentation for available field ids that could /// be used in other below crates. A test ensures that this list is up-to-date. -pub const COMMON_MODEL_FIELD_IDS: [&str; 449] = [ +pub const COMMON_MODEL_FIELD_IDS: [&str; 478] = [ "system.hostname", "system.kernel_version", "system.os_release", @@ -113,6 +113,30 @@ pub const COMMON_MODEL_FIELD_IDS: [&str; 449] = [ "system.slab.<key>.num_caches", "system.slab.<key>.active_size", "system.slab.<key>.total_size", + "system.ksm.advisor_max_cpu", + "system.ksm.advisor_max_pages_to_scan", + "system.ksm.advisor_min_pages_to_scan", + "system.ksm.advisor_mode", + "system.ksm.advisor_target_scan_time", + "system.ksm.full_scans", + "system.ksm.general_profit", + "system.ksm.ksm_zero_pages", + "system.ksm.max_page_sharing", + "system.ksm.merge_across_nodes", + "system.ksm.pages_scanned", + "system.ksm.pages_shared", + "system.ksm.pages_sharing", + "system.ksm.pages_skipped", + "system.ksm.pages_to_scan", + "system.ksm.pages_unshared", + "system.ksm.pages_volatile", + "system.ksm.run", + "system.ksm.sleep_millisecs", + "system.ksm.smart_scan", + "system.ksm.stable_node_chains", + "system.ksm.stable_node_chains_prune_millisecs", + "system.ksm.stable_node_dups", + "system.ksm.use_zero_pages", "system.disks.<key>.name", "system.disks.<key>.disk_usage", "system.disks.<key>.partition_size", @@ -209,6 +233,11 @@ pub const COMMON_MODEL_FIELD_IDS: [&str; 449] = [ "cgroup.[path:/<cgroup_path>/.]mem.events_max", "cgroup.[path:/<cgroup_path>/.]mem.events_oom", "cgroup.[path:/<cgroup_path>/.]mem.events_oom_kill", + "cgroup.[path:/<cgroup_path>/.]mem.events_local_low", + "cgroup.[path:/<cgroup_path>/.]mem.events_local_high", + "cgroup.[path:/<cgroup_path>/.]mem.events_local_max", + "cgroup.[path:/<cgroup_path>/.]mem.events_local_oom", + "cgroup.[path:/<cgroup_path>/.]mem.events_local_oom_kill", "cgroup.[path:/<cgroup_path>/.]io_details.<key>.rbytes_per_sec", "cgroup.[path:/<cgroup_path>/.]io_details.<key>.wbytes_per_sec", "cgroup.[path:/<cgroup_path>/.]io_details.<key>.rios_per_sec", diff --git a/below/model/src/sample.rs b/below/model/src/sample.rs index 61c33dde..f768f78b 100644 --- a/below/model/src/sample.rs +++ b/below/model/src/sample.rs @@ -45,6 +45,7 @@ pub struct CgroupSample { pub memory_swap_max: Option<i64>, pub memory_zswap_max: Option<i64>, pub memory_events: Option<cgroupfs::MemoryEvents>, + pub memory_events_local: Option<cgroupfs::MemoryEventsLocal>, pub inode_number: Option<i64>, pub cgroup_stat: Option<cgroupfs::CgroupStat>, pub memory_numa_stat: Option<BTreeMap<u32, cgroupfs::MemoryNumaStat>>, @@ -65,6 +66,7 @@ pub struct SystemSample { pub vmstat: procfs::VmStat, #[serde(default)] pub slabinfo: procfs::SlabInfoMap, + pub ksm: Option<procfs::Ksm>, pub hostname: String, pub disks: procfs::DiskMap, pub btrfs: Option<btrfs::BtrfsMap>, diff --git a/below/model/src/sample_model.rs b/below/model/src/sample_model.rs index 3c026c31..69b9dc7b 100644 --- a/below/model/src/sample_model.rs +++ b/below/model/src/sample_model.rs @@ -149,6 +149,32 @@ pub const SAMPLE_MODEL_JSON: &str = r#" "num_slabs": 100000 } }, + "ksm": { + "advisor_max_cpu": 70, + "advisor_max_pages_to_scan": 30000, + "advisor_min_pages_to_scan": 500, + "advisor_mode": "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 + }, "disks": { "vda": { "name": "vda", diff --git a/below/model/src/system.rs b/below/model/src/system.rs index 4d783f9f..635a8e0c 100644 --- a/below/model/src/system.rs +++ b/below/model/src/system.rs @@ -33,6 +33,8 @@ pub struct SystemModel { #[queriable(subquery)] pub slab: BTreeMap<String, SingleSlabModel>, #[queriable(subquery)] + pub ksm: Option<KsmModel>, + #[queriable(subquery)] pub disks: BTreeMap<String, SingleDiskModel>, #[queriable(subquery)] pub btrfs: Option<BTreeMap<String, BtrfsModel>>, @@ -97,6 +99,8 @@ impl SystemModel { ); slab.insert(String::from("TOTAL"), slab_total); + let ksm = sample.ksm.as_ref().map(KsmModel::new); + let mut disks: BTreeMap<String, SingleDiskModel> = BTreeMap::new(); sample.disks.iter().for_each(|(disk_name, end_disk_stat)| { disks.insert( @@ -141,6 +145,7 @@ impl SystemModel { mem, vm, slab, + ksm, disks, btrfs, } @@ -409,6 +414,65 @@ impl SingleSlabModel { } #[::below_derive::queriable_derives] +pub struct KsmModel { + pub advisor_max_cpu: Option<u64>, + pub advisor_max_pages_to_scan: Option<u64>, + pub advisor_min_pages_to_scan: Option<u64>, + pub advisor_mode: Option<String>, + pub advisor_target_scan_time: Option<u64>, + pub full_scans: Option<u64>, + pub general_profit: Option<i64>, + pub ksm_zero_pages: Option<i64>, + pub max_page_sharing: Option<u64>, + pub merge_across_nodes: Option<u64>, + pub pages_scanned: Option<u64>, + pub pages_shared: Option<u64>, + pub pages_sharing: Option<u64>, + pub pages_skipped: Option<u64>, + pub pages_to_scan: Option<u64>, + pub pages_unshared: Option<u64>, + pub pages_volatile: Option<u64>, + pub run: Option<u64>, + pub sleep_millisecs: Option<u64>, + pub smart_scan: Option<u64>, + pub stable_node_chains: Option<u64>, + pub stable_node_chains_prune_millisecs: Option<u64>, + pub stable_node_dups: Option<u64>, + pub use_zero_pages: Option<u64>, +} + +impl KsmModel { + fn new(ksm: &procfs::Ksm) -> KsmModel { + KsmModel { + advisor_max_cpu: ksm.advisor_max_cpu, + advisor_max_pages_to_scan: ksm.advisor_max_pages_to_scan, + advisor_min_pages_to_scan: ksm.advisor_min_pages_to_scan, + advisor_mode: ksm.advisor_mode.clone(), + advisor_target_scan_time: ksm.advisor_target_scan_time, + full_scans: ksm.full_scans, + general_profit: ksm.general_profit, + ksm_zero_pages: ksm.ksm_zero_pages, + max_page_sharing: ksm.max_page_sharing, + merge_across_nodes: ksm.merge_across_nodes, + pages_scanned: ksm.pages_scanned, + pages_shared: ksm.pages_shared, + pages_sharing: ksm.pages_sharing, + pages_skipped: ksm.pages_skipped, + pages_to_scan: ksm.pages_to_scan, + pages_unshared: ksm.pages_unshared, + pages_volatile: ksm.pages_volatile, + run: ksm.run, + sleep_millisecs: ksm.sleep_millisecs, + smart_scan: ksm.smart_scan, + stable_node_chains: ksm.stable_node_chains, + stable_node_chains_prune_millisecs: ksm.stable_node_chains_prune_millisecs, + stable_node_dups: ksm.stable_node_dups, + use_zero_pages: ksm.use_zero_pages, + } + } +} + +#[::below_derive::queriable_derives] pub struct SingleDiskModel { pub name: Option<String>, pub disk_usage: Option<f32>, @@ -528,6 +592,7 @@ mod test { "mem": {}, "vm": {}, "slab": {}, + "ksm": {}, "disks": { "sda": { "name": "sda", diff --git a/below/procfs/Cargo.toml b/below/procfs/Cargo.toml index 33a8f3fa..7cfe4605 100644 --- a/below/procfs/Cargo.toml +++ b/below/procfs/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "fb_procfs" version = "0.8.1" -authors = ["Daniel Xu <dlxu@fb.com>", "Facebook"] +authors = ["Meta Platforms, Inc. and affiliates"] edition = "2021" description = "A crate for reading procfs" readme = "README" 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<F>(&self, name: &str) -> Option<F> + 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<String> { + 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<P: AsRef<Path>>(&self, p: P) { let path = self.path().join(p); std::fs::create_dir_all(&path) @@ -579,6 +584,70 @@ fn test_read_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_unshare |