From 468ab4f6916294c81f110dd1dbf8802cc1a6ed12 Mon Sep 17 00:00:00 2001 From: Krysztal Huang Date: Thu, 27 Mar 2025 15:49:34 +0800 Subject: [PATCH 1/3] slabtop: implemented tui --- Cargo.lock | 60 +++++++++- Cargo.toml | 2 + src/uu/slabtop/Cargo.toml | 6 +- src/uu/slabtop/src/parse.rs | 35 +++--- src/uu/slabtop/src/slabtop.rs | 203 +++++++++++++++++----------------- src/uu/slabtop/src/tui.rs | 147 ++++++++++++++++++++++++ 6 files changed, 334 insertions(+), 119 deletions(-) create mode 100644 src/uu/slabtop/src/tui.rs diff --git a/Cargo.lock b/Cargo.lock index 2b9431d0..81e01a74 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -94,6 +94,15 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +[[package]] +name = "bitmaps" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2" +dependencies = [ + "typenum", +] + [[package]] name = "bumpalo" version = "3.17.0" @@ -655,6 +664,20 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "im" +version = "15.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0acd33ff0285af998aaf9b57342af478078f53492322fafc47450e09397e0e9" +dependencies = [ + "bitmaps", + "rand_core 0.6.4", + "rand_xoshiro", + "sized-chunks", + "typenum", + "version_check", +] + [[package]] name = "indoc" version = "2.0.6" @@ -1132,6 +1155,15 @@ dependencies = [ "getrandom 0.3.2", ] +[[package]] +name = "rand_xoshiro" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" +dependencies = [ + "rand_core 0.6.4", +] + [[package]] name = "ratatui" version = "0.29.0" @@ -1344,6 +1376,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" +[[package]] +name = "sized-chunks" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e" +dependencies = [ + "bitmaps", + "typenum", +] + [[package]] name = "smallvec" version = "1.15.0" @@ -1563,6 +1605,12 @@ dependencies = [ "rustc-hash", ] +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + [[package]] name = "unic-langid" version = "0.9.6" @@ -1740,6 +1788,10 @@ name = "uu_slabtop" version = "0.0.1" dependencies = [ "clap", + "crossterm 0.29.0", + "im", + "parking_lot", + "ratatui", "uucore", ] @@ -1887,6 +1939,12 @@ dependencies = [ "xattr", ] +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "walkdir" version = "2.5.0" @@ -2001,7 +2059,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index a707bb2a..d2409b6d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,8 +58,10 @@ clap_complete = "4.5.2" clap_mangen = "0.2.20" crossterm = "0.29.0" ctor = "0.4.1" +im = "15.1.0" libc = "0.2.154" nix = { version = "0.30", default-features = false, features = ["process"] } +parking_lot = "0.12.3" phf = "0.11.2" phf_codegen = "0.11.2" prettytable-rs = "0.10.0" diff --git a/src/uu/slabtop/Cargo.toml b/src/uu/slabtop/Cargo.toml index 48f1d905..e3c09b24 100644 --- a/src/uu/slabtop/Cargo.toml +++ b/src/uu/slabtop/Cargo.toml @@ -11,8 +11,12 @@ license.workspace = true version.workspace = true [dependencies] -uucore = { workspace = true } clap = { workspace = true } +crossterm = { workspace = true } +im = { workspace = true } +parking_lot = { workspace = true } +ratatui = { workspace = true } +uucore = { workspace = true } [lib] path = "src/slabtop.rs" diff --git a/src/uu/slabtop/src/parse.rs b/src/uu/slabtop/src/parse.rs index f073f3a5..4345ef28 100644 --- a/src/uu/slabtop/src/parse.rs +++ b/src/uu/slabtop/src/parse.rs @@ -9,27 +9,29 @@ use std::{ io::{Error, ErrorKind}, }; -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone)] pub(crate) struct SlabInfo { - pub(crate) meta: Vec, - pub(crate) data: Vec<(String, Vec)>, + pub(crate) meta: im::Vector, + pub(crate) data: im::Vector<(String, Vec)>, } impl SlabInfo { // parse slabinfo from /proc/slabinfo + // // need root permission pub fn new() -> Result { let content = fs::read_to_string("/proc/slabinfo")?; - Self::parse(&content).ok_or(ErrorKind::Unsupported.into()) + Self::with_slabinfo(&content).ok_or(ErrorKind::Unsupported.into()) } - pub fn parse(content: &str) -> Option { + pub fn with_slabinfo(content: &str) -> Option { let mut lines: Vec<&str> = content.lines().collect(); let _ = parse_version(lines.remove(0))?; let meta = parse_meta(lines.remove(0)); - let data: Vec<(String, Vec)> = lines.into_iter().filter_map(parse_data).collect(); + let data: im::Vector<(String, Vec)> = + lines.into_iter().filter_map(parse_data).collect(); Some(SlabInfo { meta, data }) } @@ -43,6 +45,7 @@ impl SlabInfo { item.get(offset).copied() } + // Get all processes name pub fn names(&self) -> Vec<&String> { self.data.iter().map(|(k, _)| k).collect() } @@ -190,15 +193,17 @@ impl SlabInfo { return 0; }; - let iter = self.data.iter().filter_map(|(_, data)| data.get(offset)); - - let count = iter.clone().count(); - let sum = iter.sum::(); + let objsize = self + .data + .iter() + .filter_map(|(_, data)| data.get(offset)) + .collect::>(); + let count = objsize.clone().iter().count(); if count == 0 { 0 } else { - (sum) / (count as u64) + (objsize.into_iter().sum::()) / (count as u64) } } @@ -266,7 +271,7 @@ pub(crate) fn parse_version(line: &str) -> Option { .map(String::from) } -pub(crate) fn parse_meta(line: &str) -> Vec { +pub(crate) fn parse_meta(line: &str) -> im::Vector { line.replace(['#', ':'], " ") .split_whitespace() .filter(|it| it.starts_with('<') && it.ends_with('>')) @@ -310,8 +315,8 @@ mod tests { let result = parse_meta(test); assert_eq!( - result, - [ + result.into_iter().collect::>(), + vec![ "active_objs", "num_objs", "objsize", @@ -348,7 +353,7 @@ mod tests { #[test] fn test_parse() { let test = include_str!("../../../../tests/fixtures/slabtop/data.txt"); - let result = SlabInfo::parse(test.into()).unwrap(); + let result = SlabInfo::with_slabinfo(test).unwrap(); assert_eq!(result.fetch("nf_conntrack_expect", "objsize").unwrap(), 208); assert_eq!( diff --git a/src/uu/slabtop/src/slabtop.rs b/src/uu/slabtop/src/slabtop.rs index b48f90e3..c33b732a 100644 --- a/src/uu/slabtop/src/slabtop.rs +++ b/src/uu/slabtop/src/slabtop.rs @@ -3,8 +3,21 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. +use std::{ + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, + thread::{self, sleep}, + time::Duration, +}; + use crate::parse::SlabInfo; -use clap::{arg, crate_version, ArgAction, Command}; +use clap::{arg, crate_version, value_parser, ArgAction, ArgMatches, Command}; +use crossterm::event::{self, KeyCode, KeyEvent, KeyModifiers}; +use parking_lot::RwLock; +use ratatui::widgets::Widget; +use tui::Tui; use uucore::{error::UResult, format_usage, help_about, help_section, help_usage}; const ABOUT: &str = help_about!("slabtop.md"); @@ -12,114 +25,97 @@ const AFTER_HELP: &str = help_section!("after help", "slabtop.md"); const USAGE: &str = help_usage!("slabtop.md"); mod parse; +mod tui; -#[uucore::main] -pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from(args)?; - - let sort_flag = matches - .try_get_one::("sort") - .ok() - .unwrap_or(Some(&'o')) - .unwrap_or(&'o'); - - let slabinfo = SlabInfo::new()?.sort(*sort_flag, false); - - if matches.get_flag("once") { - output_header(&slabinfo); - println!(); - output_list(&slabinfo); - } else { - // TODO: implement TUI - output_header(&slabinfo); - println!(); - output_list(&slabinfo); - } - - Ok(()) -} - -fn to_kb(byte: u64) -> f64 { - byte as f64 / 1024.0 +#[derive(Debug)] +struct Settings { + pub(crate) delay: u64, + pub(crate) once: bool, + pub(crate) short_by: char, } -fn percentage(numerator: u64, denominator: u64) -> f64 { - if denominator == 0 { - return 0.0; +impl Settings { + fn new(arg: &ArgMatches) -> Settings { + Settings { + delay: *arg.get_one::("delay").unwrap_or(&3), + once: arg.get_flag("once"), + short_by: *arg.get_one::("sort").unwrap_or(&'o'), + } } - - let numerator = numerator as f64; - let denominator = denominator as f64; - - (numerator / denominator) * 100.0 -} - -fn output_header(slabinfo: &SlabInfo) { - println!( - r" Active / Total Objects (% used) : {} / {} ({:.1}%)", - slabinfo.total_active_objs(), - slabinfo.total_objs(), - percentage(slabinfo.total_active_objs(), slabinfo.total_objs()) - ); - - println!( - r" Active / Total Slabs (% used) : {} / {} ({:.1}%)", - slabinfo.total_active_slabs(), - slabinfo.total_slabs(), - percentage(slabinfo.total_active_slabs(), slabinfo.total_slabs(),) - ); - - // TODO: I don't know the 'cache' meaning. - println!( - r" Active / Total Caches (% used) : {} / {} ({:.1}%)", - slabinfo.total_active_cache(), - slabinfo.total_cache(), - percentage(slabinfo.total_active_cache(), slabinfo.total_cache()) - ); - - println!( - r" Active / Total Size (% used) : {:.2}K / {:.2}K ({:.1}%)", - to_kb(slabinfo.total_active_size()), - to_kb(slabinfo.total_size()), - percentage(slabinfo.total_active_size(), slabinfo.total_size()) - ); - - println!( - r" Minimum / Average / Maximum Object : {:.2}K / {:.2}K / {:.2}K", - to_kb(slabinfo.object_minimum()), - to_kb(slabinfo.object_avg()), - to_kb(slabinfo.object_maximum()) - ); } -fn output_list(info: &SlabInfo) { - let title = format!( - "{:>6} {:>6} {:>4} {:>8} {:>6} {:>8} {:>10} {:<}", - "OBJS", "ACTIVE", "USE", "OBJ SIZE", "SLABS", "OBJ/SLAB", "CACHE SIZE", "NAME" - ); - println!("{}", title); - - for name in info.names() { - let objs = info.fetch(name, "num_objs").unwrap_or_default(); - let active = info.fetch(name, "active_objs").unwrap_or_default(); - let used = format!("{:.0}%", percentage(active, objs)); - let objsize = { - let size = info.fetch(name, "objsize").unwrap_or_default(); // Byte to KB :1024 - size as f64 / 1024.0 - }; - let slabs = info.fetch(name, "num_slabs").unwrap_or_default(); - let obj_per_slab = info.fetch(name, "objperslab").unwrap_or_default(); - - let cache_size = (objsize * (objs as f64)) as u64; - let objsize = format!("{:.2}", objsize); +#[uucore::main] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { + let matches = uu_app().try_get_matches_from(args)?; + let settings = Settings::new(&matches); + + let slabinfo = Arc::new(RwLock::new(SlabInfo::new()?.sort(settings.short_by, false))); + let should_update = Arc::new(AtomicBool::new(true)); + + // Timer + { + let should_update = should_update.clone(); + thread::spawn(move || loop { + sleep(Duration::from_secs(settings.delay)); + should_update.store(true, Ordering::Relaxed); + }); + } + // Update + { + let should_update = should_update.clone(); + let slabinfo = slabinfo.clone(); + thread::spawn(move || loop { + if should_update.load(Ordering::Relaxed) { + *slabinfo.write() = SlabInfo::new().unwrap().sort(settings.short_by, false); + should_update.store(false, Ordering::Relaxed); + } + sleep(Duration::from_millis(20)); + }); + } - let content = format!( - "{:>6} {:>6} {:>4} {:>7}K {:>6} {:>8} {:>10} {:<}", - objs, active, used, objsize, slabs, obj_per_slab, cache_size, name - ); + let mut terminal = ratatui::init(); + loop { + if let Ok(true) = event::poll(Duration::from_millis(10)) { + // If event available, break this loop + if let Ok(e) = event::read() { + match e { + event::Event::Key(KeyEvent { + code: KeyCode::Char('c'), + modifiers: KeyModifiers::CONTROL, + .. + }) + | event::Event::Key(KeyEvent { + code: KeyCode::Char('q'), + .. + }) => { + uucore::error::set_exit_code(0); + break; + } + event::Event::Key(KeyEvent { + code: KeyCode::Char(' '), + .. + }) => should_update.store(true, Ordering::Relaxed), + _ => {} + } + } + } + + terminal.draw(|frame| { + Tui::new(&slabinfo.read()).render(frame.area(), frame.buffer_mut()); + })?; + + if settings.once { + break; + } else { + sleep(Duration::from_millis(10)); + } + } - println!("{}", content); + if !settings.once { + ratatui::restore(); } + + Ok(()) } #[allow(clippy::cognitive_complexity)] @@ -130,9 +126,12 @@ pub fn uu_app() -> Command { .override_usage(format_usage(USAGE)) .infer_long_args(true) .args([ - // arg!(-d --delay "delay updates"), + arg!(-d --delay "delay updates") + .value_parser(value_parser!(u64)) + .default_value("3"), arg!(-o --once "only display once, then exit").action(ArgAction::SetTrue), - arg!(-s --sort "specify sort criteria by character (see below)"), + arg!(-s --sort "specify sort criteria by character (see below)") + .value_parser(value_parser!(char)), ]) .after_help(AFTER_HELP) } diff --git a/src/uu/slabtop/src/tui.rs b/src/uu/slabtop/src/tui.rs new file mode 100644 index 00000000..214012f1 --- /dev/null +++ b/src/uu/slabtop/src/tui.rs @@ -0,0 +1,147 @@ +// This file is part of the uutils procps package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +use ratatui::{ + prelude::*, + widgets::{List, ListItem, Widget}, +}; + +use crate::SlabInfo; + +pub(crate) struct Tui<'a> { + slabinfo: &'a SlabInfo, +} + +impl Tui<'_> { + pub(crate) fn new(slabinfo: &SlabInfo) -> Tui { + Tui { slabinfo } + } + + fn render_header(&self, area: Rect, buf: &mut Buffer) { + let lines = vec![ + format!( + r" Active / Total Objects (% used) : {} / {} ({:.1}%)", + self.slabinfo.total_active_objs(), + self.slabinfo.total_objs(), + percentage( + self.slabinfo.total_active_objs(), + self.slabinfo.total_objs() + ) + ), + format!( + r" Active / Total Slabs (% used) : {} / {} ({:.1}%)", + self.slabinfo.total_active_slabs(), + self.slabinfo.total_slabs(), + percentage( + self.slabinfo.total_active_slabs(), + self.slabinfo.total_slabs(), + ) + ), + // TODO: I don't know the 'cache' meaning. + format!( + r" Active / Total Caches (% used) : {} / {} ({:.1}%)", + self.slabinfo.total_active_cache(), + self.slabinfo.total_cache(), + percentage( + self.slabinfo.total_active_cache(), + self.slabinfo.total_cache() + ) + ), + format!( + r" Active / Total Size (% used) : {:.2}K / {:.2}K ({:.1}%)", + to_kb(self.slabinfo.total_active_size()), + to_kb(self.slabinfo.total_size()), + percentage( + self.slabinfo.total_active_size(), + self.slabinfo.total_size() + ) + ), + format!( + r" Minimum / Average / Maximum Object : {:.2}K / {:.2}K / {:.2}K", + to_kb(self.slabinfo.object_minimum()), + to_kb(self.slabinfo.object_avg()), + to_kb(self.slabinfo.object_maximum()) + ), + ] + .into_iter() + .map(Line::from); + + Widget::render(List::new(lines), area, buf); + } + + fn render_list(&self, area: Rect, buf: &mut Buffer) { + let mut list = vec![ListItem::from(format!( + "{:>6} {:>6} {:>4} {:>8} {:>6} {:>8} {:>10} {:<}", + "OBJS", "ACTIVE", "USE", "OBJ SIZE", "SLABS", "OBJ/SLAB", "CACHE SIZE", "NAME" + )) + .bg(Color::Black)]; + + self.slabinfo.names().truncate(area.height.into()); + list.extend( + self.slabinfo + .names() + .iter() + .map(|name| self.build_list_item(name)), + ); + + Widget::render(List::new(list), area, buf); + } + + fn build_list_item(&self, name: &str) -> ListItem<'_> { + let objs = self.slabinfo.fetch(name, "num_objs").unwrap_or_default(); + let active = self.slabinfo.fetch(name, "active_objs").unwrap_or_default(); + let used = format!("{:.0}%", percentage(active, objs)); + let objsize = { + let size = self.slabinfo.fetch(name, "objsize").unwrap_or_default(); // Byte to KB :1024 + size as f64 / 1024.0 + }; + let slabs = self.slabinfo.fetch(name, "num_slabs").unwrap_or_default(); + let obj_per_slab = self.slabinfo.fetch(name, "objperslab").unwrap_or_default(); + + let cache_size = (objsize * (objs as f64)) as u64; + let objsize = format!("{:.2}", objsize); + + ListItem::from(format!( + "{:>6} {:>6} {:>4} {:>7}K {:>6} {:>8} {:>10} {:<}", + objs, active, used, objsize, slabs, obj_per_slab, cache_size, name + )) + } +} + +impl Widget for Tui<'_> { + fn render(self, area: Rect, buf: &mut Buffer) + where + Self: Sized, + { + // layout[0]: Header + // layout[1]: List of process + let layout = Layout::new( + Direction::Vertical, + [Constraint::Max(6), Constraint::Min(0)], + ) + .split(area); + + let header = layout[0]; + let list = layout[1]; + + self.render_header(header, buf); + self.render_list(list, buf); + } +} + +fn to_kb(byte: u64) -> f64 { + byte as f64 / 1024.0 +} + +fn percentage(numerator: u64, denominator: u64) -> f64 { + if denominator == 0 { + return 0.0; + } + + let numerator = numerator as f64; + let denominator = denominator as f64; + + (numerator / denominator) * 100.0 +} From 8022388054f756765fbe8f55351abbc9963d6563 Mon Sep 17 00:00:00 2001 From: Krysztal Huang Date: Sat, 29 Mar 2025 01:25:16 +0800 Subject: [PATCH 2/3] slabtop: reduce CPU usage --- src/uu/slabtop/src/slabtop.rs | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/uu/slabtop/src/slabtop.rs b/src/uu/slabtop/src/slabtop.rs index c33b732a..f73dc77b 100644 --- a/src/uu/slabtop/src/slabtop.rs +++ b/src/uu/slabtop/src/slabtop.rs @@ -74,8 +74,13 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } let mut terminal = ratatui::init(); + // Initial output + terminal.draw(|frame| { + Tui::new(&slabinfo.read()).render(frame.area(), frame.buffer_mut()); + })?; + loop { - if let Ok(true) = event::poll(Duration::from_millis(10)) { + if let Ok(true) = event::poll(Duration::from_millis(20)) { // If event available, break this loop if let Ok(e) = event::read() { match e { @@ -91,23 +96,22 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { uucore::error::set_exit_code(0); break; } - event::Event::Key(KeyEvent { - code: KeyCode::Char(' '), - .. - }) => should_update.store(true, Ordering::Relaxed), + event::Event::Resize(_, _) => should_update.store(true, Ordering::Relaxed), _ => {} } } } - terminal.draw(|frame| { - Tui::new(&slabinfo.read()).render(frame.area(), frame.buffer_mut()); - })?; + if should_update.load(Ordering::Relaxed) { + terminal.draw(|frame| { + Tui::new(&slabinfo.read()).render(frame.area(), frame.buffer_mut()); + })?; + } if settings.once { break; } else { - sleep(Duration::from_millis(10)); + sleep(Duration::from_millis(20)); } } From f29bc35084cd76ab39e18be3cf2597d949477bc8 Mon Sep 17 00:00:00 2001 From: Krysztal Huang Date: Tue, 15 Jul 2025 01:45:25 +0800 Subject: [PATCH 3/3] chore: fix conflict between `main` and `feat/slabtop/tui` --- Cargo.lock | 46 ++++++++- Cargo.toml | 3 +- src/uu/pmap/Cargo.toml | 1 + src/uu/pmap/src/maps_format_parser.rs | 129 ++++++++++++++++++++----- src/uu/pmap/src/pmap.rs | 126 +++++++++++++++++++++--- src/uu/pmap/src/pmap_config.rs | 95 ++++++++++++++++++ src/uu/pmap/src/smaps_format_parser.rs | 64 ++++++++---- src/uu/slabtop/src/parse.rs | 32 +++--- src/uu/slabtop/src/slabtop.rs | 5 +- src/uu/vmstat/Cargo.toml | 2 + src/uu/vmstat/src/vmstat.rs | 51 ++++++++-- tests/by-util/test_pmap.rs | 112 +++++++++++++++++---- 12 files changed, 561 insertions(+), 105 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ae8bd12d..91898e0b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -412,6 +412,15 @@ version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + [[package]] name = "dirs-next" version = "2.0.0" @@ -422,6 +431,18 @@ dependencies = [ "dirs-sys-next", ] +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users 0.5.0", + "windows-sys 0.60.2", +] + [[package]] name = "dirs-sys-next" version = "0.1.2" @@ -429,7 +450,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" dependencies = [ "libc", - "redox_users", + "redox_users 0.4.6", "winapi", ] @@ -912,6 +933,12 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "os_display" version = "0.1.4" @@ -1183,6 +1210,17 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "redox_users" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" +dependencies = [ + "getrandom 0.2.16", + "libredox", + "thiserror 2.0.12", +] + [[package]] name = "regex" version = "1.11.1" @@ -1433,9 +1471,9 @@ dependencies = [ [[package]] name = "sysinfo" -version = "0.35.2" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3ffa3e4ff2b324a57f7aeb3c349656c7b127c3c189520251a648102a92496e" +checksum = "aab138f5c1bb35231de19049060a87977ad23e04f2303e953bc5c2947ac7dec4" dependencies = [ "libc", "memchr", @@ -1736,6 +1774,7 @@ name = "uu_pmap" version = "0.0.1" dependencies = [ "clap", + "dirs", "uucore", ] @@ -1833,6 +1872,7 @@ dependencies = [ "chrono", "clap", "terminal_size", + "uu_slabtop", "uucore", ] diff --git a/Cargo.toml b/Cargo.toml index dfb59f90..8fdf27ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,6 +59,7 @@ clap_mangen = "0.2.20" crossterm = "0.29.0" ctor = "0.4.1" im = "15.1.0" +dirs = "6.0.0" libc = "0.2.154" nix = { version = "0.30", default-features = false, features = ["process"] } parking_lot = "0.12.3" @@ -68,7 +69,7 @@ prettytable-rs = "0.10.0" rand = { version = "0.9.0", features = ["small_rng"] } ratatui = "0.29.0" regex = "1.10.4" -sysinfo = "0.35.0" +sysinfo = "0.36.0" tempfile = "3.10.1" terminal_size = "0.4.2" textwrap = { version = "0.16.1", features = ["terminal_size"] } diff --git a/src/uu/pmap/Cargo.toml b/src/uu/pmap/Cargo.toml index dd17918a..60a42ab0 100644 --- a/src/uu/pmap/Cargo.toml +++ b/src/uu/pmap/Cargo.toml @@ -13,6 +13,7 @@ version.workspace = true [dependencies] uucore = { workspace = true } clap = { workspace = true } +dirs = { workspace = true } [lib] path = "src/pmap.rs" diff --git a/src/uu/pmap/src/maps_format_parser.rs b/src/uu/pmap/src/maps_format_parser.rs index 65163634..0e077609 100644 --- a/src/uu/pmap/src/maps_format_parser.rs +++ b/src/uu/pmap/src/maps_format_parser.rs @@ -10,15 +10,37 @@ use std::io::{Error, ErrorKind}; // Represents a parsed single line from /proc//maps. #[derive(Debug, Clone, Default, PartialEq)] pub struct MapLine { - pub address: String, + pub address: Address, pub size_in_kb: u64, pub perms: Perms, pub offset: String, - pub device: String, + pub device: Device, pub inode: u64, pub mapping: String, } +#[derive(Debug, Clone, Default, PartialEq)] +pub struct Address { + pub start: String, + pub low: u64, + pub high: u64, +} + +impl fmt::Display for Address { + // By default, pads with white spaces. + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{: >16}", self.start) + } +} + +impl Address { + // Format for default, extended option, and device option. + // Pads the start address with zero. + pub fn zero_pad(&self) -> String { + format!("{:0>16}", self.start) + } +} + // Represents a set of permissions from the "perms" column of /proc//maps. #[derive(Clone, Copy, Debug, Default, PartialEq)] pub struct Perms { @@ -69,6 +91,28 @@ impl Perms { } } +#[derive(Debug, Clone, Default, PartialEq)] +pub struct Device { + pub major: String, + pub minor: String, + pub width: usize, +} + +impl fmt::Display for Device { + // By default, does not pad. + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}:{}", self.major, self.minor) + } +} + +impl Device { + // Format for device option. + // Pads the device info from /proc//maps with zeros and turns AB:CD into 0AB:000CD. + pub fn device(&self) -> String { + format!("{:0>3}:{:0>5}", self.major, self.minor) + } +} + // Parses a single line from /proc//maps. See // https://www.kernel.org/doc/html/latest/filesystems/proc.html for details about the expected // format. @@ -90,7 +134,7 @@ pub fn parse_map_line(line: &str) -> Result { let (offset, rest) = rest .split_once(' ') .ok_or_else(|| Error::from(ErrorKind::InvalidData))?; - let offset = format!("{offset:0>16}"); + let offset = format!("{offset:0>8}"); let (device, rest) = rest .split_once(' ') @@ -116,9 +160,8 @@ pub fn parse_map_line(line: &str) -> Result { }) } -// Returns the start address and the size of the provided memory range. The start address is always -// 16-digits and padded with 0, if necessary. The size is in KB. -fn parse_address(memory_range: &str) -> Result<(String, u64), Error> { +// Returns Address instance and the size of the provided memory range. The size is in KB. +fn parse_address(memory_range: &str) -> Result<(Address, u64), Error> { let (start, end) = memory_range .split_once('-') .ok_or_else(|| Error::from(ErrorKind::InvalidData))?; @@ -127,15 +170,26 @@ fn parse_address(memory_range: &str) -> Result<(String, u64), Error> { let high = u64::from_str_radix(end, 16).map_err(|_| Error::from(ErrorKind::InvalidData))?; let size_in_kb = (high - low) / 1024; - Ok((format!("{start:0>16}"), size_in_kb)) + Ok(( + Address { + start: start.to_string(), + low, + high, + }, + size_in_kb, + )) } -// Pads the device info from /proc//maps with zeros and turns AB:CD into 0AB:000CD. -fn parse_device(device: &str) -> Result { +// Returns Device instance. +fn parse_device(device: &str) -> Result { let (major, minor) = device .split_once(':') .ok_or_else(|| Error::from(ErrorKind::InvalidData))?; - Ok(format!("{major:0>3}:{minor:0>5}")) + Ok(Device { + major: major.to_string(), + minor: minor.to_string(), + width: device.len(), + }) } impl MapLine { @@ -174,19 +228,31 @@ mod test { fn create_map_line( address: &str, + low: u64, + high: u64, size_in_kb: u64, perms: Perms, offset: &str, - device: &str, + major: &str, + minor: &str, + width: usize, inode: u64, mapping: &str, ) -> MapLine { MapLine { - address: address.to_string(), + address: Address { + start: address.to_string(), + low, + high, + }, size_in_kb, perms, offset: offset.to_string(), - device: device.to_string(), + device: Device { + major: major.to_string(), + minor: minor.to_string(), + width, + }, inode, mapping: mapping.to_string(), } @@ -210,31 +276,31 @@ mod test { fn test_parse_map_line() { let data = [ ( - create_map_line("000062442eb9e000", 16, Perms::from("r--p"), "0000000000000000", "008:00008", 10813151, "/usr/bin/konsole"), + create_map_line("62442eb9e000", 0x62442eb9e000, 0x62442eba2000, 16, Perms::from("r--p"), "00000000", "08", "08", 5, 10813151, "/usr/bin/konsole"), "62442eb9e000-62442eba2000 r--p 00000000 08:08 10813151 /usr/bin/konsole" ), ( - create_map_line("000071af50000000", 132, Perms::from("rw-p"), "0000000000000000", "000:00000", 0, ""), + create_map_line("71af50000000", 0x71af50000000, 0x71af50021000, 132, Perms::from("rw-p"), "00000000", "00", "00", 5, 0, ""), "71af50000000-71af50021000 rw-p 00000000 00:00 0 " ), ( - create_map_line("00007ffc3f8df000", 132, Perms::from("rw-p"), "0000000000000000", "000:00000", 0, "[stack]"), + create_map_line("7ffc3f8df000", 0x7ffc3f8df000, 0x7ffc3f900000, 132, Perms::from("rw-p"), "00000000", "00", "00", 5, 0, "[stack]"), "7ffc3f8df000-7ffc3f900000 rw-p 00000000 00:00 0 [stack]" ), ( - create_map_line("000071af8c9e6000", 16, Perms::from("rw-s"), "0000000105830000", "000:00010", 1075, "anon_inode:i915.gem"), + create_map_line("71af8c9e6000", 0x71af8c9e6000, 0x71af8c9ea000, 16, Perms::from("rw-s"), "105830000", "00", "10", 5, 1075, "anon_inode:i915.gem"), "71af8c9e6000-71af8c9ea000 rw-s 105830000 00:10 1075 anon_inode:i915.gem" ), ( - create_map_line("000071af6cf0c000", 3560, Perms::from("rw-s"), "0000000000000000", "000:00001", 256481, "/memfd:wayland-shm (deleted)"), + create_map_line("71af6cf0c000", 0x71af6cf0c000, 0x71af6d286000, 3560, Perms::from("rw-s"), "00000000", "00", "01", 5, 256481, "/memfd:wayland-shm (deleted)"), "71af6cf0c000-71af6d286000 rw-s 00000000 00:01 256481 /memfd:wayland-shm (deleted)" ), ( - create_map_line("ffffffffff600000", 4, Perms::from("--xp"), "0000000000000000", "000:00000", 0, "[vsyscall]"), + create_map_line("ffffffffff600000", 0xffffffffff600000, 0xffffffffff601000, 4, Perms::from("--xp"), "00000000", "00", "00", 5, 0, "[vsyscall]"), "ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]" ), ( - create_map_line("00005e8187da8000", 24, Perms::from("r--p"), "0000000000000000", "008:00008", 9524160, "/usr/bin/hello world"), + create_map_line("5e8187da8000", 0x5e8187da8000, 0x5e8187dae000, 24, Perms::from("r--p"), "00000000", "08", "08", 5, 9524160, "/usr/bin/hello world"), "5e8187da8000-5e8187dae000 r--p 00000000 08:08 9524160 /usr/bin/hello world" ), ]; @@ -251,12 +317,16 @@ mod test { #[test] fn test_parse_address() { - let (start, size) = parse_address("ffffffffff600000-ffffffffff601000").unwrap(); - assert_eq!(start, "ffffffffff600000"); + let (address, size) = parse_address("ffffffffff600000-ffffffffff601000").unwrap(); + assert_eq!(address.start, "ffffffffff600000"); + assert_eq!(address.low, 0xffffffffff600000); + assert_eq!(address.high, 0xffffffffff601000); assert_eq!(size, 4); - let (start, size) = parse_address("7ffc4f0c2000-7ffc4f0e3000").unwrap(); - assert_eq!(start, "00007ffc4f0c2000"); + let (address, size) = parse_address("7ffc4f0c2000-7ffc4f0e3000").unwrap(); + assert_eq!(address.start, "7ffc4f0c2000"); + assert_eq!(address.low, 0x7ffc4f0c2000); + assert_eq!(address.high, 0x7ffc4f0e3000); assert_eq!(size, 132); } @@ -273,8 +343,15 @@ mod test { #[test] fn test_parse_device() { - assert_eq!("012:00034", parse_device("12:34").unwrap()); - assert_eq!("000:00000", parse_device("00:00").unwrap()); + assert_eq!("12:34", parse_device("12:34").unwrap().to_string()); + assert_eq!("00:00", parse_device("00:00").unwrap().to_string()); + assert_eq!("fe:01", parse_device("fe:01").unwrap().to_string()); + assert_eq!("103:100", parse_device("103:100").unwrap().to_string()); + + assert_eq!("012:00034", parse_device("12:34").unwrap().device()); + assert_eq!("000:00000", parse_device("00:00").unwrap().device()); + assert_eq!("0fe:00001", parse_device("fe:01").unwrap().device()); + assert_eq!("103:00100", parse_device("103:100").unwrap().device()); } #[test] diff --git a/src/uu/pmap/src/pmap.rs b/src/uu/pmap/src/pmap.rs index 34073b46..aa5bb943 100644 --- a/src/uu/pmap/src/pmap.rs +++ b/src/uu/pmap/src/pmap.rs @@ -5,7 +5,7 @@ use clap::{crate_version, Arg, ArgAction, Command}; use maps_format_parser::{parse_map_line, MapLine}; -use pmap_config::{pmap_field_name, PmapConfig}; +use pmap_config::{create_rc, pmap_field_name, PmapConfig}; use smaps_format_parser::{parse_smaps, SmapTable}; use std::env; use std::fs; @@ -39,12 +39,61 @@ mod options { pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app().try_get_matches_from(args)?; + if matches.get_flag(options::CREATE_RC) { + let path = pmap_config::get_rc_default_path(); + if std::fs::exists(&path)? { + eprintln!("pmap: the file already exists - delete or rename it first"); + eprintln!( + "pmap: couldn't create {}", + pmap_config::get_rc_default_path_str() + ); + set_exit_code(1); + } else { + create_rc(&path)?; + eprintln!( + "pmap: {} file successfully created, feel free to edit the content", + pmap_config::get_rc_default_path_str() + ); + } + return Ok(()); + } else if let Some(path_str) = matches.get_one::(options::CREATE_RC_TO) { + let path = std::path::PathBuf::from(path_str); + if std::fs::exists(&path)? { + eprintln!("pmap: the file already exists - delete or rename it first"); + eprintln!("pmap: couldn't create the rc file"); + set_exit_code(1); + } else { + create_rc(&path)?; + eprintln!("pmap: rc file successfully created, feel free to edit the content"); + } + return Ok(()); + } + let mut pmap_config = PmapConfig::default(); if matches.get_flag(options::MORE_EXTENDED) { pmap_config.set_more_extended(); } else if matches.get_flag(options::MOST_EXTENDED) { pmap_config.set_most_extended(); + } else if matches.get_flag(options::READ_RC) { + let path = pmap_config::get_rc_default_path(); + if !std::fs::exists(&path)? { + eprintln!( + "pmap: couldn't read {}", + pmap_config::get_rc_default_path_str() + ); + set_exit_code(1); + return Ok(()); + } + pmap_config.read_rc(&path)?; + } else if let Some(path) = matches.get_one::(options::READ_RC_FROM) { + let path = std::fs::canonicalize(path)?; + if !std::fs::exists(&path)? { + eprintln!("pmap: couldn't read the rc file"); + set_exit_code(1); + return Ok(()); + } + pmap_config.read_rc(&path)?; } // Options independent with field selection: @@ -135,7 +184,7 @@ fn output_default_format(pid: &str, pmap_config: &PmapConfig) -> Result<(), Erro process_maps(pid, None, |map_line| { println!( "{} {:>6}K {} {}", - map_line.address, + map_line.address.zero_pad(), map_line.size_in_kb, map_line.perms.mode(), map_line.parse_mapping(pmap_config) @@ -160,7 +209,7 @@ fn output_extended_format(pid: &str, pmap_config: &PmapConfig) -> Result<(), Err for smap_entry in smap_table.entries { println!( "{} {:>7} {:>7} {:>7} {} {}", - smap_entry.map_line.address, + smap_entry.map_line.address.zero_pad(), smap_entry.map_line.size_in_kb, smap_entry.rss_in_kb, smap_entry.shared_dirty_in_kb + smap_entry.private_dirty_in_kb, @@ -312,12 +361,12 @@ fn output_device_format(pid: &str, pmap_config: &PmapConfig) -> Result<(), Error }, |map_line| { println!( - "{} {:>7} {} {} {} {}", - map_line.address, + "{} {:>7} {} {:0>16} {} {}", + map_line.address.zero_pad(), map_line.size_in_kb, map_line.perms.mode(), map_line.offset, - map_line.device, + map_line.device.device(), map_line.parse_mapping(pmap_config) ); total_mapped += map_line.size_in_kb; @@ -405,36 +454,81 @@ pub fn uu_app() -> Command { .short('c') .long("read-rc") .help("read the default rc") - .action(ArgAction::SetTrue), - ) + .action(ArgAction::SetTrue) + .conflicts_with_all([ + "read-rc-from", + "device", + "create-rc", + "create-rc-to", + "extended", + "more-extended", + "most-extended", + ]), + ) // pmap: options -c, -C, -d, -n, -N, -x, -X are mutually exclusive .arg( Arg::new(options::READ_RC_FROM) .short('C') .long("read-rc-from") .num_args(1) - .help("read the rc from file"), - ) + .help("read the rc from file") + .conflicts_with_all([ + "read-rc", + "device", + "create-rc", + "create-rc-to", + "extended", + "more-extended", + "most-extended", + ]), + ) // pmap: options -c, -C, -d, -n, -N, -x, -X are mutually exclusive .arg( Arg::new(options::CREATE_RC) .short('n') .long("create-rc") .help("create new default rc") - .action(ArgAction::SetTrue), - ) + .action(ArgAction::SetTrue) + .conflicts_with_all([ + "read-rc", + "read-rc-from", + "device", + "create-rc-to", + "extended", + "more-extended", + "most-extended", + ]), + ) // pmap: options -c, -C, -d, -n, -N, -x, -X are mutually exclusive .arg( Arg::new(options::CREATE_RC_TO) .short('N') .long("create-rc-to") .num_args(1) - .help("create new rc to file"), - ) + .help("create new rc to file") + .conflicts_with_all([ + "read-rc", + "read-rc-from", + "device", + "create-rc", + "extended", + "more-extended", + "most-extended", + ]), + ) // pmap: options -c, -C, -d, -n, -N, -x, -X are mutually exclusive .arg( Arg::new(options::DEVICE) .short('d') .long("device") .help("show the device format") - .action(ArgAction::SetTrue), - ) + .action(ArgAction::SetTrue) + .conflicts_with_all([ + "read-rc", + "read-rc-from", + "create-rc", + "create-rc-to", + "extended", + "more-extended", + "most-extended", + ]), + ) // pmap: options -c, -C, -d, -n, -N, -x, -X are mutually exclusive .arg( Arg::new(options::QUIET) .short('q') diff --git a/src/uu/pmap/src/pmap_config.rs b/src/uu/pmap/src/pmap_config.rs index 5f544c89..073aa6c9 100644 --- a/src/uu/pmap/src/pmap_config.rs +++ b/src/uu/pmap/src/pmap_config.rs @@ -3,6 +3,10 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. +use dirs::home_dir; +use std::io::Error; +use std::path::PathBuf; + pub mod pmap_field_name { pub const ADDRESS: &str = "Address"; pub const PERM: &str = "Perm"; @@ -200,6 +204,10 @@ impl PmapConfig { } } + pub fn enable_field(&mut self, field_name: &str) { + self.set_field(field_name, true); + } + pub fn disable_field(&mut self, field_name: &str) { self.set_field(field_name, false); } @@ -244,4 +252,91 @@ impl PmapConfig { self.anon_huge_pages = true; self.vmflags = true; } + + pub fn read_rc(&mut self, path: &PathBuf) -> Result<(), Error> { + self.custom_format_enabled = true; + + let contents = std::fs::read_to_string(path)?; + + let mut in_field_display = false; + let mut in_mapping = false; + + for line in contents.lines() { + let line = line.trim_ascii(); + if line.starts_with("#") || line.is_empty() { + continue; + } + + // The leftmost category on the line is recoginized. + if line.starts_with("[Fields Display]") { + in_field_display = true; + in_mapping = false; + continue; + } else if line.starts_with("[Mapping]") { + in_field_display = false; + in_mapping = true; + continue; + } + + if in_field_display { + self.enable_field(line); + } else if in_mapping && line == "ShowPath" { + self.show_path = true; + } + } + + Ok(()) + } +} + +pub fn create_rc(path: &PathBuf) -> Result<(), Error> { + let contents = "# pmap's Config File\n".to_string() + + "\n" + + "# All the entries are case sensitive.\n" + + "# Unsupported entries are ignored!\n" + + "\n" + + "[Fields Display]\n" + + "\n" + + "# To enable a field uncomment its entry\n" + + "\n" + + "#Perm\n" + + "#Offset\n" + + "#Device\n" + + "#Inode\n" + + "#Size\n" + + "#Rss\n" + + "#Pss\n" + + "#Shared_Clean\n" + + "#Shared_Dirty\n" + + "#Private_Clean\n" + + "#Private_Dirty\n" + + "#Referenced\n" + + "#Anonymous\n" + + "#AnonHugePages\n" + + "#Swap\n" + + "#KernelPageSize\n" + + "#MMUPageSize\n" + + "#Locked\n" + + "#VmFlags\n" + + "#Mapping\n" + + "\n" + + "[Mapping]\n" + + "\n" + + "# to show paths in the mapping column uncomment the following line\n" + + "#ShowPath\n" + + "\n"; + + std::fs::write(path, contents)?; + + Ok(()) +} + +pub fn get_rc_default_path() -> PathBuf { + let mut path = home_dir().expect("home directory should not be None"); + path.push(".pmaprc"); + path +} + +pub fn get_rc_default_path_str() -> &'static str { + "~/.pmaprc" } diff --git a/src/uu/pmap/src/smaps_format_parser.rs b/src/uu/pmap/src/smaps_format_parser.rs index 779a2b8e..f4789165 100644 --- a/src/uu/pmap/src/smaps_format_parser.rs +++ b/src/uu/pmap/src/smaps_format_parser.rs @@ -40,10 +40,10 @@ pub struct SmapEntry { impl SmapEntry { pub fn get_field(&self, field_name: &str) -> String { match field_name { - pmap_field_name::ADDRESS => self.map_line.address.clone(), + pmap_field_name::ADDRESS => self.map_line.address.to_string(), pmap_field_name::PERM => self.map_line.perms.to_string(), pmap_field_name::OFFSET => self.map_line.offset.clone(), - pmap_field_name::DEVICE => self.map_line.device.clone(), + pmap_field_name::DEVICE => self.map_line.device.to_string(), pmap_field_name::INODE => self.map_line.inode.to_string(), pmap_field_name::SIZE => self.map_line.size_in_kb.to_string(), pmap_field_name::KERNEL_PAGE_SIZE => self.kernel_page_size_in_kb.to_string(), @@ -107,6 +107,9 @@ pub struct SmapTableInfo { pub total_thp_eligible: u64, pub total_protection_key: u64, // Width + pub offset_width: usize, + pub device_width: usize, + pub inode_width: usize, pub size_in_kb_width: usize, pub kernel_page_size_in_kb_width: usize, pub mmu_page_size_in_kb_width: usize, @@ -165,6 +168,9 @@ impl Default for SmapTableInfo { total_thp_eligible: 0, total_protection_key: 0, + device_width: pmap_field_name::DEVICE.len(), + offset_width: pmap_field_name::OFFSET.len(), + inode_width: pmap_field_name::INODE.len(), size_in_kb_width: pmap_field_name::SIZE.len(), kernel_page_size_in_kb_width: pmap_field_name::KERNEL_PAGE_SIZE.len(), mmu_page_size_in_kb_width: pmap_field_name::MMU_PAGE_SIZE.len(), @@ -200,9 +206,9 @@ impl SmapTableInfo { match field_name { pmap_field_name::ADDRESS => 16, // See maps_format_parser.rs pmap_field_name::PERM => 4, // See maps_format_parser.rs - pmap_field_name::OFFSET => 16, // See maps_format_parser.rs - pmap_field_name::DEVICE => 9, // See maps_format_parser.rs - pmap_field_name::INODE => 10, // See maps_format_parser.rs + pmap_field_name::OFFSET => self.offset_width, + pmap_field_name::DEVICE => self.device_width, + pmap_field_name::INODE => self.inode_width, pmap_field_name::SIZE => self.size_in_kb_width, pmap_field_name::KERNEL_PAGE_SIZE => self.kernel_page_size_in_kb_width, pmap_field_name::MMU_PAGE_SIZE => self.mmu_page_size_in_kb_width, @@ -289,6 +295,12 @@ pub fn parse_smaps(contents: &str) -> Result { smap_entry = SmapEntry::default(); } smap_table.info.total_size_in_kb += map_line.size_in_kb; + smap_table.info.offset_width = smap_table.info.offset_width.max(map_line.offset.len()); + smap_table.info.device_width = smap_table.info.device_width.max(map_line.device.width); + smap_table.info.inode_width = smap_table + .info + .inode_width + .max(map_line.inode.to_string().len()); smap_entry.map_line = map_line; } else { let (key, val) = line @@ -544,9 +556,13 @@ mod test { #[allow(clippy::too_many_arguments)] fn create_smap_entry( address: &str, + low: u64, + high: u64, perms: Perms, offset: &str, - device: &str, + major: &str, + minor: &str, + width: usize, inode: u64, mapping: &str, size_in_kb: u64, @@ -577,11 +593,19 @@ mod test { ) -> SmapEntry { SmapEntry { map_line: MapLine { - address: address.to_string(), + address: crate::maps_format_parser::Address { + start: address.to_string(), + low, + high, + }, size_in_kb, perms, offset: offset.to_string(), - device: device.to_string(), + device: crate::maps_format_parser::Device { + major: major.to_string(), + minor: minor.to_string(), + width, + }, inode, mapping: mapping.to_string(), }, @@ -617,7 +641,7 @@ mod test { let data = [ ( vec![create_smap_entry( - "0000560880413000", Perms::from("r--p"), "0000000000000000", "008:00008", 10813151, "/usr/bin/konsole", + "560880413000", 0x560880413000, 0x560880440000, Perms::from("r--p"), "00000000", "08", "08", 5, 10813151, "/usr/bin/konsole", 180, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 0, "rd mr mw me dw sd")], concat!( @@ -651,7 +675,7 @@ mod test { ), ( vec![create_smap_entry( - "000071af50000000", Perms::from("rw-p"), "0000000000000000", "000:00000", 0, "", + "71af50000000", 0x71af50000000, 0x71af50021000, Perms::from("rw-p"), "00000000", "00", "00", 5, 0, "", 132, 4, 4, 128, 9, 9, 128, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, "rd mr mw me sd")], concat!( @@ -684,7 +708,7 @@ mod test { ), ( vec![create_smap_entry( - "00007ffc3f8df000", Perms::from("rw-p"), "0000000000000000", "000:00000", 0, "[stack]", + "7ffc3f8df000", 0x7ffc3f8df000, 0x7ffc3f900000, Perms::from("rw-p"), "00000000", "00", "00", 5, 0, "[stack]", 132, 4, 4, 108, 108, 108, 0, 0, 0, 108, 108, 108, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, "rd wr mr mw me gd ac")], concat!( @@ -717,7 +741,7 @@ mod test { ), ( vec![create_smap_entry( - "000071af8c9e6000", Perms::from("rw-s"), "0000000105830000", "000:00010", 1075, "anon_inode:i915.gem", + "71af8c9e6000", 0x71af8c9e6000, 0x71af8c9ea000, Perms::from("rw-s"), "105830000", "00", "10", 5, 1075, "anon_inode:i915.gem", 16, 4, 4, 16, 16, 16, 0, 0, 0, 16, 16, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "rd wr mr mw me ac sd")], concat!( @@ -749,7 +773,7 @@ mod test { ), ( vec![create_smap_entry( - "000071af6cf0c000", Perms::from("rw-s"), "0000000000000000", "000:00001", 256481, "/memfd:wayland-shm (deleted)", + "71af6cf0c000", 0x71af6cf0c000, 0x71af6d286000, Perms::from("rw-s"), "00000000", "00", "01", 5, 256481, "/memfd:wayland-shm (deleted)", 3560, 4, 4, 532, 108, 0, 524, 0, 8, 0, 532, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "rd mr mw me sd")], concat!( @@ -781,7 +805,7 @@ mod test { ), ( vec![create_smap_entry( - "ffffffffff600000", Perms::from("--xp"), "0000000000000000", "000:00000", 0, "[vsyscall]", + "ffffffffff600000", 0xffffffffff600000, 0xffffffffff601000, Perms::from("--xp"), "00000000", "00", "00", 5, 0, "[vsyscall]", 4, 4, 4, 4, 4, 4, 0, 0, 0, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "rd wr mr mw me ac sd")], concat!( @@ -813,7 +837,7 @@ mod test { ), ( vec![create_smap_entry( - "00005e8187da8000", Perms::from("r--p"), "0000000000000000", "008:00008", 9524160, "/usr/bin/hello world", + "5e8187da8000", 0x5e8187da8000, 0x5e8187dae000, Perms::from("r--p"), "00000000", "08", "08", 5, 9524160, "/usr/bin/hello world", 24, 4, 4, 24, 0, 0, 24, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "rd ex mr mw me sd")], concat!( @@ -846,11 +870,11 @@ mod test { ( vec![ create_smap_entry( - "000071af8c9e6000", Perms::from("rw-s"), "0000000105830000", "000:00010", 1075, "anon_inode:i915.gem", + "71af8c9e6000", 0x71af8c9e6000, 0x71af8c9ea000, Perms::from("rw-s"), "105830000", "00", "10", 5, 1075, "anon_inode:i915.gem", 16, 4, 4, 16, 16, 16, 0, 0, 0, 16, 16, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "rd wr mr mw me ac sd"), create_smap_entry( - "000071af6cf0c000", Perms::from("rw-s"), "0000000000000000", "000:00001", 256481, "/memfd:wayland-shm (deleted)", + "71af6cf0c000", 0x71af6cf0c000, 0x71af6d286000, Perms::from("rw-s"), "00000000", "00", "01", 5, 256481, "/memfd:wayland-shm (deleted)", 3560, 4, 4, 532, 108, 0, 524, 0, 8, 0, 532, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "rd mr mw me sd"), ], @@ -909,15 +933,15 @@ mod test { ( vec![ create_smap_entry( - "000071af8c9e6000", Perms::from("rw-s"), "0000000105830000", "000:00010", 1075, "anon_inode:i915.gem", + "71af8c9e6000", 0x71af8c9e6000, 0x71af8c9ea000, Perms::from("rw-s"), "105830000", "00", "10", 5, 1075, "anon_inode:i915.gem", 16, 4, 4, 16, 16, 16, 0, 0, 0, 16, 16, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, "rd wr mr mw me ac sd"), create_smap_entry( - "0000560880413000", Perms::from("r--p"), "0000000000000000", "008:00008", 10813151, "/usr/bin/konsole", + "560880413000", 0x560880413000, 0x560880440000, Perms::from("r--p"), "00000000", "08", "08", 5, 10813151, "/usr/bin/konsole", 180, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ""), create_smap_entry( - "000071af6cf0c000", Perms::from("rw-s"), "0000000000000000", "000:00001", 256481, "/memfd:wayland-shm (deleted)", + "71af6cf0c000", 0x71af6cf0c000, 0x71af6d286000, Perms::from("rw-s"), "00000000", "00", "01", 5, 256481, "/memfd:wayland-shm (deleted)", 3560, 4, 4, 532, 108, 0, 524, 0, 8, 0, 532, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "rd mr mw me sd"), ], diff --git a/src/uu/slabtop/src/parse.rs b/src/uu/slabtop/src/parse.rs index 4345ef28..21013d7d 100644 --- a/src/uu/slabtop/src/parse.rs +++ b/src/uu/slabtop/src/parse.rs @@ -3,26 +3,36 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -use std::{ - cmp::Ordering, - fs, - io::{Error, ErrorKind}, -}; +use std::io::Error; +use std::{cmp::Ordering, fs, io::ErrorKind}; +use uucore::error::{UError, UResult, USimpleError}; #[derive(Debug, Default, Clone)] -pub(crate) struct SlabInfo { - pub(crate) meta: im::Vector, - pub(crate) data: im::Vector<(String, Vec)>, +pub struct SlabInfo { + pub meta: im::Vector, + pub data: im::Vector<(String, Vec)>, } impl SlabInfo { // parse slabinfo from /proc/slabinfo // // need root permission - pub fn new() -> Result { - let content = fs::read_to_string("/proc/slabinfo")?; + pub fn new() -> UResult { + let error_wrapper = |e: Error| { + USimpleError::new( + 1, + format!( + "Unable to create slabinfo structure: {}", + Box::::from(e) // We need Display impl of UError + ), + ) + }; + + let content = fs::read_to_string("/proc/slabinfo").map_err(error_wrapper)?; - Self::with_slabinfo(&content).ok_or(ErrorKind::Unsupported.into()) + Self::with_slabinfo(&content) + .ok_or(ErrorKind::Unsupported.into()) + .map_err(error_wrapper) } pub fn with_slabinfo(content: &str) -> Option { diff --git a/src/uu/slabtop/src/slabtop.rs b/src/uu/slabtop/src/slabtop.rs index f73dc77b..493a4f89 100644 --- a/src/uu/slabtop/src/slabtop.rs +++ b/src/uu/slabtop/src/slabtop.rs @@ -12,7 +12,6 @@ use std::{ time::Duration, }; -use crate::parse::SlabInfo; use clap::{arg, crate_version, value_parser, ArgAction, ArgMatches, Command}; use crossterm::event::{self, KeyCode, KeyEvent, KeyModifiers}; use parking_lot::RwLock; @@ -20,11 +19,13 @@ use ratatui::widgets::Widget; use tui::Tui; use uucore::{error::UResult, format_usage, help_about, help_section, help_usage}; +use crate::parse::SlabInfo; + const ABOUT: &str = help_about!("slabtop.md"); const AFTER_HELP: &str = help_section!("after help", "slabtop.md"); const USAGE: &str = help_usage!("slabtop.md"); -mod parse; +pub mod parse; mod tui; #[derive(Debug)] diff --git a/src/uu/vmstat/Cargo.toml b/src/uu/vmstat/Cargo.toml index 6538ed46..f9122619 100644 --- a/src/uu/vmstat/Cargo.toml +++ b/src/uu/vmstat/Cargo.toml @@ -17,6 +17,8 @@ clap = { workspace = true } terminal_size = { workspace = true } uucore = { workspace = true, features = ["custom-tz-fmt"] } +uu_slabtop = {path = "../slabtop"} + [lib] path = "src/vmstat.rs" diff --git a/src/uu/vmstat/src/vmstat.rs b/src/uu/vmstat/src/vmstat.rs index 93996afa..00596916 100644 --- a/src/uu/vmstat/src/vmstat.rs +++ b/src/uu/vmstat/src/vmstat.rs @@ -38,6 +38,13 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let one_header = matches.get_flag("one-header"); let no_first = matches.get_flag("no-first"); + let term_height = terminal_size::terminal_size() + .map(|size| size.1 .0) + .unwrap_or(0); + + if matches.get_flag("slabs") { + return print_slabs(one_header, term_height); + } let delay = matches.get_one::("delay"); let count = matches.get_one::("count"); @@ -57,14 +64,10 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { line_count += 1; } - let term_height = terminal_size::terminal_size() - .map(|size| size.1 .0) - .unwrap_or(0); - while count.is_none() || line_count < count.unwrap() { std::thread::sleep(std::time::Duration::from_secs(delay)); let proc_data_now = ProcData::new(); - if !one_header && term_height > 0 && ((line_count + 3) % term_height as u64 == 0) { + if needs_header(one_header, term_height, line_count) { print_header(&pickers); } print_data(&pickers, &proc_data_now, Some(&proc_data), &matches); @@ -76,6 +79,42 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { Ok(()) } +#[cfg(target_os = "linux")] +fn print_slabs(one_header: bool, term_height: u16) -> UResult<()> { + let slab_data = uu_slabtop::parse::SlabInfo::new()?.data; + let mut slab_data = slab_data.iter().collect::>(); + + slab_data.sort_by_key(|k| k.0.to_lowercase()); + + print_slab_header(); + + for (line_count, slab_item) in slab_data.into_iter().enumerate() { + if needs_header(one_header, term_height, line_count as u64) { + print_slab_header(); + } + + println!( + "{:<24} {:>6} {:>6} {:>6} {:>6}", + slab_item.0, slab_item.1[0], slab_item.1[1], slab_item.1[2], slab_item.1[3] + ); + } + + Ok(()) +} + +#[cfg(target_os = "linux")] +fn needs_header(one_header: bool, term_height: u16, line_count: u64) -> bool { + !one_header && term_height > 0 && (line_count + 3).is_multiple_of(term_height as u64) +} + +#[cfg(target_os = "linux")] +fn print_slab_header() { + println!( + "{:<24} {:>6} {:>6} {:>6} {:>6}", + "Cache", "Num", "Total", "Size", "Pages" + ); +} + #[cfg(target_os = "linux")] fn print_header(pickers: &[Picker]) { let mut section: Vec<&str> = vec![]; @@ -126,7 +165,7 @@ pub fn uu_app() -> Command { .value_parser(value_parser!(u64)), arg!(-a --active "Display active and inactive memory"), // arg!(-f --forks "switch displays the number of forks since boot"), - // arg!(-m --slabs "Display slabinfo"), + arg!(-m --slabs "Display slabinfo"), arg!(-n --"one-header" "Display the header only once rather than periodically"), // arg!(-s --stats "Displays a table of various event counters and memory statistics"), // arg!(-d --disk "Report disk statistics"), diff --git a/tests/by-util/test_pmap.rs b/tests/by-util/test_pmap.rs index fab26be9..e4b9f8ce 100644 --- a/tests/by-util/test_pmap.rs +++ b/tests/by-util/test_pmap.rs @@ -20,6 +20,78 @@ fn test_no_args() { new_ucmd!().fails().code_is(1); } +#[test] +#[cfg(target_os = "linux")] +fn test_default_rc() { + if !uutests::util::is_ci() { + return; + } + + let pid = process::id(); + let ts = TestScenario::new(util_name!()); + + // Fails to read before creating rc file + for arg in ["-c", "--read-rc"] { + ts.ucmd().arg(arg).arg(pid.to_string()).fails().code_is(1); + } + + // Create rc file + ts.ucmd().arg("-n").succeeds(); + + // Fails to create because rc file already exists + for arg in ["-n", "--create-rc"] { + ts.ucmd().arg(arg).fails().code_is(1); + } + + // Succeeds to read now + for arg in ["-c", "--read-rc"] { + ts.ucmd().arg(arg).arg(pid.to_string()).succeeds(); + } +} + +#[test] +#[cfg(target_os = "linux")] +fn test_create_rc_to() { + let ts = TestScenario::new(util_name!()); + + ts.ucmd().args(&["-N", "pmap_rc_file_name"]).succeeds(); + + // Fails to create because rc file already exists + for arg in ["-N", "--create-rc-to"] { + ts.ucmd() + .args(&[arg, "pmap_rc_file_name"]) + .fails() + .code_is(1); + } +} + +#[test] +#[cfg(target_os = "linux")] +fn test_read_rc_from() { + let pid = process::id(); + let ts = TestScenario::new(util_name!()); + + // Fails to read before creating rc file + for arg in ["-C", "--read-rc-from"] { + ts.ucmd() + .args(&[arg, "pmap_rc_file_name"]) + .arg(pid.to_string()) + .fails() + .code_is(1); + } + + // Create rc file + ts.ucmd().args(&["-N", "pmap_rc_file_name"]).succeeds(); + + // Succeeds to read now + for arg in ["-C", "--read-rc-from"] { + ts.ucmd() + .args(&[arg, "pmap_rc_file_name"]) + .arg(pid.to_string()) + .succeeds(); + } +} + #[test] #[cfg(target_os = "linux")] fn test_existing_pid() { @@ -378,13 +450,13 @@ fn assert_extended_format(pid: u32, s: &str, quiet: bool, show_path: bool) { // Ensure `s` has the following more extended format (-X): // // 1234: /some/path -// Address Perm Offset Device Inode Size Rss Pss Pss_Dirty Referenced Anonymous LazyFree ShmemPmdMapped FilePmdMapped Shared_Hugetlb Private_Hugetlb Swap SwapPss Locked THPeligible Mapping -// 000073eb5f4c7000 r-xp 0000000000036000 008:00008 2274176 1284 1148 1148 0 1148 0 0 0 0 0 0 0 0 0 0 ld-linux-x86-64.so.2 -// 00007ffd588fc000 r--p 0000000000000000 000:00000 2274176 20 20 20 20 20 20 0 0 0 0 0 0 0 0 0 [stack] -// ffffffffff600000 rw-p 0000000000000000 000:00000 2274176 36 36 36 36 36 36 0 0 0 0 0 0 0 0 0 (one intentional trailing space) +// Address Perm Offset Device Inode Size Rss Pss Pss_Dirty Referenced Anonymous LazyFree ShmemPmdMapped FilePmdMapped Shared_Hugetlb Private_Hugetlb Swap SwapPss Locked THPeligible Mapping +// 73eb5f4c7000 r-xp 00036000 08:08 2274176 1284 1148 1148 0 1148 0 0 0 0 0 0 0 0 0 0 ld-linux-x86-64.so.2 +// 7ffd588fc000 r--p 00000000 00:00 2274176 20 20 20 20 20 20 0 0 0 0 0 0 0 0 0 [stack] +// ffffffffff600000 rw-p 00000000 00:00 2274176 36 36 36 36 36 36 0 0 0 0 0 0 0 0 0 (one intentional trailing space) // ... -// ==== ==== ==== ========= ========== ========= ======== ============== ============= ============== =============== ==== ======= ====== =========== (one intentional trailing space) -// 4164 3448 2826 552 3448 552 0 0 0 0 0 0 0 0 0 KB (one intentional trailing space) +// ==== ==== ==== ========= ========== ========= ======== ============== ============= ============== =============== ==== ======= ====== =========== (one intentional trailing space) +// 4164 3448 2826 552 3448 552 0 0 0 0 0 0 0 0 0 KB (one intentional trailing space) #[cfg(target_os = "linux")] fn assert_more_extended_format(pid: u32, s: &str, quiet: bool, show_path: bool) { let lines: Vec<_> = s.lines().collect(); @@ -394,11 +466,11 @@ fn assert_more_extended_format(pid: u32, s: &str, quiet: bool, show_path: bool) assert!(re.is_match(lines[0])); if !quiet { - let re = Regex::new(r"^ Address Perm Offset Device Inode +Size +Rss +Pss +Pss_Dirty +Referenced +Anonymous( +KSM)? +LazyFree +ShmemPmdMapped +FilePmdMapped +Shared_Hugetlb +Private_Hugetlb +Swap +SwapPss +Locked +THPeligible( +ProtectionKey)? +Mapping$").unwrap(); + let re = Regex::new(r"^ Address Perm Offset +Device +Inode +Size +Rss +Pss +Pss_Dirty +Referenced +Anonymous( +KSM)? +LazyFree +ShmemPmdMapped +FilePmdMapped +Shared_Hugetlb +Private_Hugetlb +Swap +SwapPss +Locked +THPeligible( +ProtectionKey)? +Mapping$").unwrap(); assert!(re.is_match(lines[1]), "failing line: '{}'", lines[1]); } - let base_pattern = r"^[0-9a-f]{16} (-|r)(-|w)(-|x)(p|s) [0-9a-f]{16} [0-9a-f]{3}:[0-9a-f]{5} +\d+ +[1-9][0-9]* +\d+ +\d+ +\d+ +\d+ +\d+( +\d+)? +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+( +\d+)?"; + let base_pattern = r"^[ 0-9a-f]{16} (-|r)(-|w)(-|x)(p|s) [0-9a-f]{8} +[0-9a-f]+:[0-9a-f]+ +\d+ +[1-9][0-9]* +\d+ +\d+ +\d+ +\d+ +\d+( +\d+)? +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+( +\d+)?"; let mapping_pattern = if show_path { r" (|\[[a-zA-Z_ ]+\]|/[/a-zA-Z0-9._-]+)$" } else { @@ -415,14 +487,14 @@ fn assert_more_extended_format(pid: u32, s: &str, quiet: bool, show_path: bool) assert!(re.is_match(line), "failing line: '{line}'"); } - let re = Regex::new(r"^ +=+ =+ =+ =+ =+ =+( =+)? =+ =+ =+ =+ =+ =+ =+ =+ =+( =+)? $").unwrap(); + let re = Regex::new(r"^ +=+ =+ =+ =+ =+ =+( =+)? =+ =+ =+ =+ =+ =+ =+ =+ =+( =+)? $").unwrap(); assert!( re.is_match(lines[line_count - 2]), "failing line: '{}'", lines[line_count - 2] ); - let re = Regex::new(r"^ +[1-9][0-9]* +\d+ +\d+ +\d+ +\d+ +\d+( +\d+)? +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+( +\d+)? KB $").unwrap(); + let re = Regex::new(r"^ +[1-9][0-9]* +\d+ +\d+ +\d+ +\d+ +\d+( +\d+)? +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+( +\d+)? KB $").unwrap(); assert!( re.is_match(lines[line_count - 1]), "failing line: '{}'", @@ -434,13 +506,13 @@ fn assert_more_extended_format(pid: u32, s: &str, quiet: bool, show_path: bool) // Ensure `s` has the following most extended format (--XX): // // 1234: /some/path -// Address Perm Offset Device Inode Size KernelPageSize MMUPageSize Rss Pss Pss_Dirty Shared_Clean Shared_Dirty Private_Clean Private_Dirty Referenced Anonymous LazyFree AnonHugePages ShmemPmdMapped FilePmdMapped Shared_Hugetlb Private_Hugetlb Swap SwapPss Locked THPeligible VmFlags Mapping -// 000073eb5f4c7000 r-xp 0000000000036000 008:00008 2274176 1284 4 4 1148 1148 0 0 0 1148 0 1148 0 0 0 0 0 0 0 0 0 0 0 rd ex mr mw me ld-linux-x86-64.so.2 -// 00007ffd588fc000 r--p 0000000000000000 000:00000 2274176 20 4 4 20 20 20 0 0 0 20 20 20 0 0 0 0 0 0 0 0 0 0 rd mr mw me ac [stack] -// ffffffffff600000 rw-p 0000000000000000 000:00000 2274176 36 4 4 36 36 36 0 0 0 36 36 36 0 0 0 0 0 0 0 0 0 0 rd wr mr mw me ac (one intentional trailing space) +// Address Perm Offset Device Inode Size KernelPageSize MMUPageSize Rss Pss Pss_Dirty Shared_Clean Shared_Dirty Private_Clean Private_Dirty Referenced Anonymous LazyFree AnonHugePages ShmemPmdMapped FilePmdMapped Shared_Hugetlb Private_Hugetlb Swap SwapPss Locked THPeligible VmFlags Mapping +// 73eb5f4c7000 r-xp 00036000 08:08 2274176 1284 4 4 1148 1148 0 0 0 1148 0 1148 0 0 0 0 0 0 0 0 0 0 0 rd ex mr mw me ld-linux-x86-64.so.2 +// 7ffd588fc000 r--p 00000000 00:00 2274176 20 4 4 20 20 20 0 0 0 20 20 20 0 0 0 0 0 0 0 0 0 0 rd mr mw me ac [stack] +// ffffffffff600000 rw-p 00000000 00:00 2274176 36 4 4 36 36 36 0 0 0 36 36 36 0 0 0 0 0 0 0 0 0 0 rd wr mr mw me ac (one intentional trailing space) // ... -// ==== ============== =========== ==== ==== ========= ============ ============ ============= ============= ========== ========= ======== ============= ============== ============= ============== =============== ==== ======= ====== =========== (one intentional trailing space) -// 4164 92 92 3448 2880 552 1132 0 1764 552 3448 552 0 0 0 0 0 0 0 0 0 0 KB (one intentional trailing space) +// ==== ============== =========== ==== ==== ========= ============ ============ ============= ============= ========== ========= ======== ============= ============== ============= ============== =============== ==== ======= ====== =========== (one intentional trailing space) +// 4164 92 92 3448 2880 552 1132 0 1764 552 3448 552 0 0 0 0 0 0 0 0 0 0 KB (one intentional trailing space) #[cfg(target_os = "linux")] fn assert_most_extended_format(pid: u32, s: &str, quiet: bool, show_path: bool) { let lines: Vec<_> = s.lines().collect(); @@ -450,11 +522,11 @@ fn assert_most_extended_format(pid: u32, s: &str, quiet: bool, show_path: bool) assert!(re.is_match(lines[0])); if !quiet { - let re = Regex::new(r"^ Address Perm Offset Device Inode +Size +KernelPageSize +MMUPageSize +Rss +Pss +Pss_Dirty +Shared_Clean +Shared_Dirty +Private_Clean +Private_Dirty +Referenced +Anonymous( +KSM)? +LazyFree +AnonHugePages +ShmemPmdMapped +FilePmdMapped +Shared_Hugetlb +Private_Hugetlb +Swap +SwapPss +Locked +THPeligible( +ProtectionKey)? +VmFlags +Mapping$").unwrap(); + let re = Regex::new(r"^ Address Perm Offset +Device +Inode +Size +KernelPageSize +MMUPageSize +Rss +Pss +Pss_Dirty +Shared_Clean +Shared_Dirty +Private_Clean +Private_Dirty +Referenced +Anonymous( +KSM)? +LazyFree +AnonHugePages +ShmemPmdMapped +FilePmdMapped +Shared_Hugetlb +Private_Hugetlb +Swap +SwapPss +Locked +THPeligible( +ProtectionKey)? +VmFlags +Mapping$").unwrap(); assert!(re.is_match(lines[1]), "failing line: '{}'", lines[1]); } - let base_pattern = r"^[0-9a-f]{16} (-|r)(-|w)(-|x)(p|s) [0-9a-f]{16} [0-9a-f]{3}:[0-9a-f]{5} +\d+ +[1-9][0-9]* +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+( +\d+)? +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+( +\d+)? +([a-z][a-z] )*"; + let base_pattern = r"^[ 0-9a-f]{16} (-|r)(-|w)(-|x)(p|s) [0-9a-f]{8} +[0-9a-f]+:[0-9a-f]+ +\d+ +[1-9][0-9]* +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+( +\d+)? +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+( +\d+)? +([a-z][a-z] )*"; let mapping_pattern = if show_path { r"(|\[[a-zA-Z_ ]+\]|/[/a-zA-Z0-9._-]+)$" } else { @@ -471,14 +543,14 @@ fn assert_most_extended_format(pid: u32, s: &str, quiet: bool, show_path: bool) assert!(re.is_match(line), "failing line: '{line}'"); } - let re = Regex::new(r"^ +=+ =+ =+ =+ =+ =+ =+ =+ =+ =+ =+ =+( =+)? =+ =+ =+ =+ =+ =+ =+ =+ =+ =+( =+)? $").unwrap(); + let re = Regex::new(r"^ +=+ =+ =+ =+ =+ =+ =+ =+ =+ =+ =+ =+( =+)? =+ =+ =+ =+ =+ =+ =+ =+ =+ =+( =+)? $").unwrap(); assert!( re.is_match(lines[line_count - 2]), "failing line: '{}'", lines[line_count - 2] ); - let re = Regex::new(r"^ +[1-9][0-9]* +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+( +\d+)? +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+( +\d+)? KB $").unwrap(); + let re = Regex::new(r"^ +[1-9][0-9]* +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+( +\d+)? +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+( +\d+)? KB $").unwrap(); assert!( re.is_match(lines[line_count - 1]), "failing line: '{}'",