pmap: implemented more-extended and most-extended options (#415)

* pmap: implemented more-extended and most-extended options

* fixed lint errors

* fixed test_pmap lint error

* Update tests/by-util/test_pmap.rs

Co-authored-by: Daniel Hofstetter <daniel.hofstetter@42dh.com>

* Update tests/by-util/test_pmap.rs

Co-authored-by: Daniel Hofstetter <daniel.hofstetter@42dh.com>

* fixed typo

---------

Co-authored-by: Daniel Hofstetter <daniel.hofstetter@42dh.com>
This commit is contained in:
estodi
2025-05-14 06:20:02 -07:00
committed by GitHub
parent 23ad5d8f14
commit 1dafe8db4c
5 changed files with 1138 additions and 148 deletions
+87 -34
View File
@@ -3,6 +3,7 @@
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
use crate::pmap_config::PmapConfig;
use std::fmt;
use std::io::{Error, ErrorKind};
@@ -40,13 +41,25 @@ impl From<&str> for Perms {
}
}
// Please note: While `Perms` has four boolean fields, it's string representation has five
// characters because pmap's default and device formats use five characters for the perms,
// with the last character always being '-'.
impl fmt::Display for Perms {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}{}{}{}",
if self.readable { 'r' } else { '-' },
if self.writable { 'w' } else { '-' },
if self.executable { 'x' } else { '-' },
if self.shared { 's' } else { 'p' },
)
}
}
// Please note: While `Perms` has four boolean fields, its `Mode` representation
// used in pmap's default and device formats has five characters for the perms,
// with the last character always being '-'.
impl Perms {
pub fn mode(&self) -> String {
format!(
"{}{}{}{}-",
if self.readable { 'r' } else { '-' },
if self.writable { 'w' } else { '-' },
@@ -90,8 +103,7 @@ pub fn parse_map_line(line: &str) -> Result<MapLine, Error> {
let inode = inode
.parse::<u64>()
.map_err(|_| Error::from(ErrorKind::InvalidData))?;
let mapping = mapping.trim_ascii_start();
let mapping = parse_mapping(mapping);
let mapping = mapping.trim_ascii_start().to_string();
Ok(MapLine {
address,
@@ -126,18 +138,29 @@ fn parse_device(device: &str) -> Result<String, Error> {
Ok(format!("{major:0>3}:{minor:0>5}"))
}
fn parse_mapping(mapping: &str) -> String {
if mapping == "[stack]" {
return " [ stack ]".into();
}
impl MapLine {
pub fn parse_mapping(&self, pmap_config: &PmapConfig) -> String {
if pmap_config.custom_format_enabled {
if self.mapping.starts_with('[') {
return self.mapping.clone();
}
} else {
if self.mapping == "[stack]" {
return " [ stack ]".into();
}
if mapping.is_empty() || mapping.starts_with('[') || mapping.starts_with("anon") {
return " [ anon ]".into();
}
if self.mapping.is_empty()
|| self.mapping.starts_with('[')
|| self.mapping.starts_with("anon")
{
return " [ anon ]".into();
}
}
match mapping.rsplit_once('/') {
Some((_, name)) => name.into(),
None => mapping.into(),
match self.mapping.rsplit_once('/') {
Some((_, name)) => name.into(),
None => self.mapping.clone(),
}
}
}
@@ -167,40 +190,47 @@ mod test {
#[test]
fn test_perms_to_string() {
assert_eq!("-----", Perms::from("---p").to_string());
assert_eq!("---s-", Perms::from("---s").to_string());
assert_eq!("rwx--", Perms::from("rwxp").to_string());
assert_eq!("---p", Perms::from("---p").to_string());
assert_eq!("---s", Perms::from("---s").to_string());
assert_eq!("rwxp", Perms::from("rwxp").to_string());
}
#[test]
fn test_perms_mode() {
assert_eq!("-----", Perms::from("---p").mode());
assert_eq!("---s-", Perms::from("---s").mode());
assert_eq!("rwx--", Perms::from("rwxp").mode());
}
#[test]
fn test_parse_map_line() {
let data = [
(
create_map_line("000062442eb9e000", 16, Perms::from("r--p"), "0000000000000000", "008:00008", 10813151, "konsole"),
create_map_line("000062442eb9e000", 16, Perms::from("r--p"), "0000000000000000", "008:00008", 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, " [ anon ]"),
create_map_line("000071af50000000", 132, Perms::from("rw-p"), "0000000000000000", "000:00000", 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("00007ffc3f8df000", 132, Perms::from("rw-p"), "0000000000000000", "000:00000", 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 ]"),
create_map_line("000071af8c9e6000", 16, Perms::from("rw-s"), "0000000105830000", "000:00010", 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("000071af6cf0c000", 3560, Perms::from("rw-s"), "0000000000000000", "000:00001", 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, " [ anon ]"),
create_map_line("ffffffffff600000", 4, Perms::from("--xp"), "0000000000000000", "000:00000", 0, "[vsyscall]"),
"ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]"
),
(
create_map_line("00005e8187da8000", 24, Perms::from("r--p"), "0000000000000000", "008:00008", 9524160, "hello world"),
create_map_line("00005e8187da8000", 24, Perms::from("r--p"), "0000000000000000", "008:00008", 9524160, "/usr/bin/hello world"),
"5e8187da8000-5e8187dae000 r--p 00000000 08:08 9524160 /usr/bin/hello world"
),
];
@@ -250,14 +280,37 @@ mod test {
#[test]
fn test_parse_mapping() {
assert_eq!(" [ anon ]", parse_mapping(""));
assert_eq!(" [ anon ]", parse_mapping("[vvar]"));
assert_eq!(" [ anon ]", parse_mapping("[vdso]"));
assert_eq!(" [ anon ]", parse_mapping("anon_inode:i915.gem"));
assert_eq!(" [ stack ]", parse_mapping("[stack]"));
assert_eq!(
"ld-linux-x86-64.so.2",
parse_mapping("/usr/lib/ld-linux-x86-64.so.2")
);
let mut mapline = MapLine::default();
let mut pmap_config = PmapConfig::default();
mapline.mapping = "".to_string();
pmap_config.custom_format_enabled = false;
assert_eq!(" [ anon ]", mapline.parse_mapping(&pmap_config));
pmap_config.custom_format_enabled = true;
assert_eq!("", mapline.parse_mapping(&pmap_config));
mapline.mapping = "[vvar]".to_string();
pmap_config.custom_format_enabled = false;
assert_eq!(" [ anon ]", mapline.parse_mapping(&pmap_config));
pmap_config.custom_format_enabled = true;
assert_eq!("[vvar]", mapline.parse_mapping(&pmap_config));
mapline.mapping = "anon_inode:i915.gem".to_string();
pmap_config.custom_format_enabled = false;
assert_eq!(" [ anon ]", mapline.parse_mapping(&pmap_config));
pmap_config.custom_format_enabled = true;
assert_eq!("anon_inode:i915.gem", mapline.parse_mapping(&pmap_config));
mapline.mapping = "[stack]".to_string();
pmap_config.custom_format_enabled = false;
assert_eq!(" [ stack ]", mapline.parse_mapping(&pmap_config));
pmap_config.custom_format_enabled = true;
assert_eq!("[stack]", mapline.parse_mapping(&pmap_config));
mapline.mapping = "/usr/lib/ld-linux-x86-64.so.2".to_string();
pmap_config.custom_format_enabled = false;
assert_eq!("ld-linux-x86-64.so.2", mapline.parse_mapping(&pmap_config));
pmap_config.custom_format_enabled = true;
assert_eq!("ld-linux-x86-64.so.2", mapline.parse_mapping(&pmap_config));
}
}
+195 -53
View File
@@ -5,7 +5,8 @@
use clap::{crate_version, Arg, ArgAction, Command};
use maps_format_parser::{parse_map_line, MapLine};
use smaps_format_parser::{parse_smap_entries, SmapEntry};
use pmap_config::{pmap_field_name, PmapConfig};
use smaps_format_parser::{parse_smaps, SmapTable};
use std::env;
use std::fs;
use std::io::Error;
@@ -13,6 +14,7 @@ use uucore::error::{set_exit_code, UResult};
use uucore::{format_usage, help_about, help_usage};
mod maps_format_parser;
mod pmap_config;
mod smaps_format_parser;
const ABOUT: &str = help_about!("pmap.md");
@@ -36,6 +38,15 @@ mod options {
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().try_get_matches_from(args)?;
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();
}
let pids = matches
.get_many::<String>(options::PID)
.expect("PID required");
@@ -52,13 +63,19 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
}
if matches.get_flag(options::EXTENDED) {
output_extended_format(pid)
output_extended_format(pid, &pmap_config)
.map_err(|_| set_exit_code(1))
.ok();
} else if matches.get_flag(options::DEVICE) {
output_device_format(pid).map_err(|_| set_exit_code(1)).ok();
output_device_format(pid, &pmap_config)
.map_err(|_| set_exit_code(1))
.ok();
} else if pmap_config.custom_format_enabled {
output_custom_format(pid, &mut pmap_config)
.map_err(|_| set_exit_code(1))
.ok();
} else {
output_default_format(pid)
output_default_format(pid, &pmap_config)
.map_err(|_| set_exit_code(1))
.ok();
}
@@ -100,32 +117,22 @@ where
Ok(())
}
fn process_smaps<F>(pid: &str, header: Option<&str>, mut process_entry: F) -> Result<(), Error>
where
F: FnMut(&SmapEntry),
{
fn get_smap_table(pid: &str) -> Result<SmapTable, Error> {
let path = format!("/proc/{pid}/smaps");
let contents = fs::read_to_string(path)?;
let smap_entries = parse_smap_entries(&contents)?;
if let Some(header) = header {
println!("{header}");
}
for entry in smap_entries {
process_entry(&entry);
}
Ok(())
parse_smaps(&contents)
}
fn output_default_format(pid: &str) -> Result<(), Error> {
fn output_default_format(pid: &str, pmap_config: &PmapConfig) -> Result<(), Error> {
let mut total = 0;
process_maps(pid, None, |map_line| {
println!(
"{} {:>6}K {} {}",
map_line.address, map_line.size_in_kb, map_line.perms, map_line.mapping
map_line.address,
map_line.size_in_kb,
map_line.perms.mode(),
map_line.parse_mapping(pmap_config)
);
total += map_line.size_in_kb;
})?;
@@ -135,37 +142,145 @@ fn output_default_format(pid: &str) -> Result<(), Error> {
Ok(())
}
fn output_extended_format(pid: &str) -> Result<(), Error> {
let mut total_mapped = 0;
let mut total_rss = 0;
let mut total_dirty = 0;
fn output_extended_format(pid: &str, pmap_config: &PmapConfig) -> Result<(), Error> {
let smap_table = get_smap_table(pid)?;
process_smaps(
pid,
Some("Address Kbytes RSS Dirty Mode Mapping"),
|smap_entry| {
println!(
"{} {:>7} {:>7} {:>7} {} {}",
smap_entry.map_line.address,
smap_entry.map_line.size_in_kb,
smap_entry.rss_in_kb,
smap_entry.shared_dirty_in_kb + smap_entry.private_dirty_in_kb,
smap_entry.map_line.perms,
smap_entry.map_line.mapping
);
total_mapped += smap_entry.map_line.size_in_kb;
total_rss += smap_entry.rss_in_kb;
total_dirty += smap_entry.shared_dirty_in_kb + smap_entry.private_dirty_in_kb;
},
)?;
println!("Address Kbytes RSS Dirty Mode Mapping");
for smap_entry in smap_table.entries {
println!(
"{} {:>7} {:>7} {:>7} {} {}",
smap_entry.map_line.address,
smap_entry.map_line.size_in_kb,
smap_entry.rss_in_kb,
smap_entry.shared_dirty_in_kb + smap_entry.private_dirty_in_kb,
smap_entry.map_line.perms.mode(),
smap_entry.map_line.parse_mapping(pmap_config)
);
}
println!("---------------- ------- ------- ------- ");
println!("total kB {total_mapped:>7} {total_rss:>7} {total_dirty:>7}");
println!(
"total kB {:>7} {:>7} {:>7}",
smap_table.info.total_size_in_kb,
smap_table.info.total_rss_in_kb,
smap_table.info.total_shared_dirty_in_kb + smap_table.info.total_private_dirty_in_kb,
);
Ok(())
}
fn output_custom_format(pid: &str, pmap_config: &mut PmapConfig) -> Result<(), Error> {
let smap_table = get_smap_table(pid)?;
if !smap_table.info.has_ksm {
pmap_config.disable_field(pmap_field_name::KSM);
}
if !smap_table.info.has_protection_key {
pmap_config.disable_field(pmap_field_name::PROTECTION_KEY);
}
// Header
{
let mut line = format!(
"{:>width$} ",
pmap_field_name::ADDRESS,
width = smap_table.info.get_width(pmap_field_name::ADDRESS)
);
for field_name in pmap_config.get_field_list() {
if pmap_config.is_enabled(field_name) {
line += &format!(
"{:>width$} ",
field_name,
width = smap_table.info.get_width(field_name)
);
}
}
if pmap_config.is_enabled(pmap_field_name::MAPPING) {
line += pmap_field_name::MAPPING;
}
println!("{line}");
}
// Main
for smap_entry in smap_table.entries {
let mut line = format!(
"{:>width$} ",
smap_entry.get_field(pmap_field_name::ADDRESS),
width = smap_table.info.get_width(pmap_field_name::ADDRESS)
);
for field_name in pmap_config.get_field_list() {
if pmap_config.is_enabled(field_name) {
line += &format!(
"{:>width$} ",
smap_entry.get_field(field_name),
width = smap_table.info.get_width(field_name)
);
}
}
if pmap_config.is_enabled(pmap_field_name::MAPPING) {
line += &smap_entry.map_line.parse_mapping(pmap_config);
}
println!("{line}");
}
// Footer
{
// Separator
let mut line = format!(
"{:>width$} ",
"",
width = smap_table.info.get_width(pmap_field_name::ADDRESS)
);
for field_name in pmap_config.get_field_list() {
if pmap_config.is_enabled(field_name) && field_name != pmap_field_name::VMFLAGS {
if pmap_config.needs_footer(field_name) {
line += &format!(
"{:=>width$} ",
"",
width = smap_table.info.get_width(field_name)
);
} else {
line += &format!(
"{:>width$} ",
"",
width = smap_table.info.get_width(field_name)
);
}
}
}
println!("{line}");
// Total values
let mut line = format!(
"{:>width$} ",
"",
width = smap_table.info.get_width(pmap_field_name::ADDRESS)
);
for field_name in pmap_config.get_field_list() {
if pmap_config.is_enabled(field_name) && field_name != pmap_field_name::VMFLAGS {
if pmap_config.needs_footer(field_name) {
line += &format!(
"{:>width$} ",
smap_table.info.get_total(field_name),
width = smap_table.info.get_width(field_name)
);
} else {
line += &format!(
"{:>width$} ",
"",
width = smap_table.info.get_width(field_name)
);
}
}
}
println!("{line}KB ");
}
Ok(())
}
fn output_device_format(pid: &str) -> Result<(), Error> {
fn output_device_format(pid: &str, pmap_config: &PmapConfig) -> Result<(), Error> {
let mut total_mapped = 0;
let mut total_writeable_private = 0;
let mut total_shared = 0;
@@ -178,10 +293,10 @@ fn output_device_format(pid: &str) -> Result<(), Error> {
"{} {:>7} {} {} {} {}",
map_line.address,
map_line.size_in_kb,
map_line.perms,
map_line.perms.mode(),
map_line.offset,
map_line.device,
map_line.mapping
map_line.parse_mapping(pmap_config)
);
total_mapped += map_line.size_in_kb;
@@ -220,20 +335,47 @@ pub fn uu_app() -> Command {
.short('x')
.long("extended")
.help("show details")
.action(ArgAction::SetTrue),
)
.action(ArgAction::SetTrue)
.conflicts_with_all([
"read-rc",
"read-rc-from",
"device",
"create-rc",
"create-rc-to",
"more-extended",
"most-extended",
]),
) // pmap: options -c, -C, -d, -n, -N, -x, -X are mutually exclusive
.arg(
Arg::new(options::MORE_EXTENDED)
.short('X')
.help("show even more details")
.action(ArgAction::SetTrue),
)
.action(ArgAction::SetTrue)
.conflicts_with_all([
"read-rc",
"read-rc-from",
"device",
"create-rc",
"create-rc-to",
"extended",
"most-extended",
]),
) // pmap: options -c, -C, -d, -n, -N, -x, -X are mutually exclusive
.arg(
Arg::new(options::MOST_EXTENDED)
.long("XX")
.help("show everything the kernel provides")
.action(ArgAction::SetTrue),
)
.action(ArgAction::SetTrue)
.conflicts_with_all([
"read-rc",
"read-rc-from",
"device",
"create-rc",
"create-rc-to",
"extended",
"more-extended",
]),
) // pmap: options -c, -C, -d, -n, -N, -x, -X are mutually exclusive
.arg(
Arg::new(options::READ_RC)
.short('c')
+244
View File
@@ -0,0 +1,244 @@
// 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.
pub mod pmap_field_name {
pub const ADDRESS: &str = "Address";
pub const PERM: &str = "Perm";
pub const OFFSET: &str = "Offset";
pub const DEVICE: &str = "Device";
pub const INODE: &str = "Inode";
pub const SIZE: &str = "Size";
pub const KERNEL_PAGE_SIZE: &str = "KernelPageSize";
pub const MMU_PAGE_SIZE: &str = "MMUPageSize";
pub const RSS: &str = "Rss";
pub const PSS: &str = "Pss";
pub const PSS_DIRTY: &str = "Pss_Dirty";
pub const SHARED_CLEAN: &str = "Shared_Clean";
pub const SHARED_DIRTY: &str = "Shared_Dirty";
pub const PRIVATE_CLEAN: &str = "Private_Clean";
pub const PRIVATE_DIRTY: &str = "Private_Dirty";
pub const REFERENCED: &str = "Referenced";
pub const ANONYMOUS: &str = "Anonymous";
pub const KSM: &str = "KSM";
pub const LAZY_FREE: &str = "LazyFree";
pub const ANON_HUGE_PAGES: &str = "AnonHugePages";
pub const SHMEM_PMD_MAPPED: &str = "ShmemPmdMapped";
pub const FILE_PMD_MAPPED: &str = "FilePmdMapped";
pub const SHARED_HUGETLB: &str = "Shared_Hugetlb";
pub const PRIVATE_HUGETLB: &str = "Private_Hugetlb";
pub const SWAP: &str = "Swap";
pub const SWAP_PSS: &str = "SwapPss";
pub const LOCKED: &str = "Locked";
pub const THP_ELIGIBLE: &str = "THPeligible";
pub const PROTECTION_KEY: &str = "ProtectionKey";
pub const VMFLAGS: &str = "VmFlags";
pub const MAPPING: &str = "Mapping";
}
// Represents the configuration for enabling specific fields.
// Note: Address field is always enabled.
#[derive(Debug, Clone, Default, PartialEq)]
pub struct PmapConfig {
// [Fields Display] category
pub perm: bool,
pub offset: bool,
pub device: bool,
pub inode: bool,
pub size: bool,
pub kernel_page_size: bool,
pub mmu_page_size: bool,
pub rss: bool,
pub pss: bool,
pub pss_dirty: bool,
pub shared_clean: bool,
pub shared_dirty: bool,
pub private_clean: bool,
pub private_dirty: bool,
pub referenced: bool,
pub anonymous: bool,
pub ksm: bool,
pub lazy_free: bool,
pub anon_huge_pages: bool,
pub shmem_pmd_mapped: bool,
pub file_pmd_mapped: bool,
pub shared_hugetlb: bool,
pub private_hugetlb: bool,
pub swap: bool,
pub swap_pss: bool,
pub locked: bool,
pub thp_eligible: bool,
pub protection_key: bool,
pub vmflags: bool,
pub mapping: bool,
// Misc
pub custom_format_enabled: bool,
}
impl PmapConfig {
pub fn get_field_list(&self) -> [&'static str; 29] {
// Note: Address and Mapping are treated separately from other fields.
[
pmap_field_name::PERM,
pmap_field_name::OFFSET,
pmap_field_name::DEVICE,
pmap_field_name::INODE,
pmap_field_name::SIZE,
pmap_field_name::KERNEL_PAGE_SIZE,
pmap_field_name::MMU_PAGE_SIZE,
pmap_field_name::RSS,
pmap_field_name::PSS,
pmap_field_name::PSS_DIRTY,
pmap_field_name::SHARED_CLEAN,
pmap_field_name::SHARED_DIRTY,
pmap_field_name::PRIVATE_CLEAN,
pmap_field_name::PRIVATE_DIRTY,
pmap_field_name::REFERENCED,
pmap_field_name::ANONYMOUS,
pmap_field_name::KSM,
pmap_field_name::LAZY_FREE,
pmap_field_name::ANON_HUGE_PAGES,
pmap_field_name::SHMEM_PMD_MAPPED,
pmap_field_name::FILE_PMD_MAPPED,
pmap_field_name::SHARED_HUGETLB,
pmap_field_name::PRIVATE_HUGETLB,
pmap_field_name::SWAP,
pmap_field_name::SWAP_PSS,
pmap_field_name::LOCKED,
pmap_field_name::THP_ELIGIBLE,
pmap_field_name::PROTECTION_KEY,
pmap_field_name::VMFLAGS,
]
}
pub fn needs_footer(&self, field_name: &str) -> bool {
!matches!(
field_name,
pmap_field_name::ADDRESS
| pmap_field_name::PERM
| pmap_field_name::OFFSET
| pmap_field_name::DEVICE
| pmap_field_name::INODE
| pmap_field_name::VMFLAGS
| pmap_field_name::MAPPING
)
}
pub fn is_enabled(&self, field_name: &str) -> bool {
match field_name {
pmap_field_name::PERM => self.perm,
pmap_field_name::OFFSET => self.offset,
pmap_field_name::DEVICE => self.device,
pmap_field_name::INODE => self.inode,
pmap_field_name::SIZE => self.size,
pmap_field_name::KERNEL_PAGE_SIZE => self.kernel_page_size,
pmap_field_name::MMU_PAGE_SIZE => self.mmu_page_size,
pmap_field_name::RSS => self.rss,
pmap_field_name::PSS => self.pss,
pmap_field_name::PSS_DIRTY => self.pss_dirty,
pmap_field_name::SHARED_CLEAN => self.shared_clean,
pmap_field_name::SHARED_DIRTY => self.shared_dirty,
pmap_field_name::PRIVATE_CLEAN => self.private_clean,
pmap_field_name::PRIVATE_DIRTY => self.private_dirty,
pmap_field_name::REFERENCED => self.referenced,
pmap_field_name::ANONYMOUS => self.anonymous,
pmap_field_name::KSM => self.ksm,
pmap_field_name::LAZY_FREE => self.lazy_free,
pmap_field_name::ANON_HUGE_PAGES => self.anon_huge_pages,
pmap_field_name::SHMEM_PMD_MAPPED => self.shmem_pmd_mapped,
pmap_field_name::FILE_PMD_MAPPED => self.file_pmd_mapped,
pmap_field_name::SHARED_HUGETLB => self.shared_hugetlb,
pmap_field_name::PRIVATE_HUGETLB => self.private_hugetlb,
pmap_field_name::SWAP => self.swap,
pmap_field_name::SWAP_PSS => self.swap_pss,
pmap_field_name::LOCKED => self.locked,
pmap_field_name::THP_ELIGIBLE => self.thp_eligible,
pmap_field_name::PROTECTION_KEY => self.protection_key,
pmap_field_name::VMFLAGS => self.vmflags,
pmap_field_name::MAPPING => self.mapping,
_ => false,
}
}
fn set_field(&mut self, field_name: &str, val: bool) {
match field_name {
pmap_field_name::PERM => self.perm = val,
pmap_field_name::OFFSET => self.offset = val,
pmap_field_name::DEVICE => self.device = val,
pmap_field_name::INODE => self.inode = val,
pmap_field_name::SIZE => self.size = val,
pmap_field_name::KERNEL_PAGE_SIZE => self.kernel_page_size = val,
pmap_field_name::MMU_PAGE_SIZE => self.mmu_page_size = val,
pmap_field_name::RSS => self.rss = val,
pmap_field_name::PSS => self.pss = val,
pmap_field_name::PSS_DIRTY => self.pss_dirty = val,
pmap_field_name::SHARED_CLEAN => self.shared_clean = val,
pmap_field_name::SHARED_DIRTY => self.shared_dirty = val,
pmap_field_name::PRIVATE_CLEAN => self.private_clean = val,
pmap_field_name::PRIVATE_DIRTY => self.private_dirty = val,
pmap_field_name::REFERENCED => self.referenced = val,
pmap_field_name::ANONYMOUS => self.anonymous = val,
pmap_field_name::KSM => self.ksm = val,
pmap_field_name::LAZY_FREE => self.lazy_free = val,
pmap_field_name::ANON_HUGE_PAGES => self.anon_huge_pages = val,
pmap_field_name::SHMEM_PMD_MAPPED => self.shmem_pmd_mapped = val,
pmap_field_name::FILE_PMD_MAPPED => self.file_pmd_mapped = val,
pmap_field_name::SHARED_HUGETLB => self.shared_hugetlb = val,
pmap_field_name::PRIVATE_HUGETLB => self.private_hugetlb = val,
pmap_field_name::SWAP => self.swap = val,
pmap_field_name::SWAP_PSS => self.swap_pss = val,
pmap_field_name::LOCKED => self.locked = val,
pmap_field_name::THP_ELIGIBLE => self.thp_eligible = val,
pmap_field_name::PROTECTION_KEY => self.protection_key = val,
pmap_field_name::VMFLAGS => self.vmflags = val,
pmap_field_name::MAPPING => self.mapping = val,
_ => (),
}
}
pub fn disable_field(&mut self, field_name: &str) {
self.set_field(field_name, false);
}
// Preset for more-extended option
pub fn set_more_extended(&mut self) {
self.custom_format_enabled = true;
self.perm = true;
self.offset = true;
self.device = true;
self.inode = true;
self.size = true;
self.rss = true;
self.pss = true;
self.pss_dirty = true;
self.referenced = true;
self.anonymous = true;
self.ksm = true;
self.lazy_free = true;
self.shmem_pmd_mapped = true;
self.file_pmd_mapped = true;
self.shared_hugetlb = true;
self.private_hugetlb = true;
self.swap = true;
self.swap_pss = true;
self.locked = true;
self.thp_eligible = true;
self.protection_key = true;
self.mapping = true;
}
// Preset for most-extended option
pub fn set_most_extended(&mut self) {
self.custom_format_enabled = true;
self.set_more_extended();
self.kernel_page_size = true;
self.mmu_page_size = true;
self.shared_clean = true;
self.shared_dirty = true;
self.private_clean = true;
self.private_dirty = true;
self.anon_huge_pages = true;
self.vmflags = true;
}
}
File diff suppressed because it is too large Load Diff
+124 -5
View File
@@ -107,6 +107,36 @@ fn test_extended() {
}
}
#[test]
#[cfg(target_os = "linux")]
fn test_more_extended() {
let pid = process::id();
let arg = "-X";
let result = new_ucmd!()
.arg(arg)
.arg(pid.to_string())
.succeeds()
.stdout_move_str();
assert_more_extended_format(pid, &result);
}
#[test]
#[cfg(target_os = "linux")]
fn test_most_extended() {
let pid = process::id();
let arg = "--XX";
let result = new_ucmd!()
.arg(arg)
.arg(pid.to_string())
.succeeds()
.stdout_move_str();
assert_most_extended_format(pid, &result);
}
#[test]
#[cfg(target_os = "linux")]
fn test_extended_permission_denied() {
@@ -211,10 +241,10 @@ fn assert_extended_format(pid: u32, s: &str) {
let line_count = lines.len();
let re = Regex::new(&format!("^{pid}: .+[^ ]$")).unwrap();
assert!(re.is_match(lines[0]));
assert!(re.is_match(lines[0]), "failing line: '{}'", lines[0]);
let expected_header = "Address Kbytes RSS Dirty Mode Mapping";
assert_eq!(expected_header, lines[1]);
assert_eq!(expected_header, lines[1], "failing line: '{}'", lines[1]);
let re = Regex::new(
r"^[0-9a-f]{16} +[1-9][0-9]* +\d+ +\d+ (-|r)(-|w)(-|x)(-|s)- ( \[ (anon|stack) \]|[a-zA-Z0-9._-]+)$",
@@ -222,16 +252,105 @@ fn assert_extended_format(pid: u32, s: &str) {
.unwrap();
for line in lines.iter().take(line_count - 2).skip(2) {
assert!(re.is_match(line), "failing line: {line}");
assert!(re.is_match(line), "failing line: '{line}'");
}
let expected_separator = "---------------- ------- ------- ------- ";
assert_eq!(expected_separator, lines[line_count - 2]);
assert_eq!(
expected_separator,
lines[line_count - 2],
"failing line: '{}'",
lines[line_count - 2]
);
let re = Regex::new(r"^total kB +[1-9][0-9]* +\d+ +\d+$").unwrap();
assert!(
re.is_match(lines[line_count - 1]),
"failing line: {}",
"failing line: '{}'",
lines[line_count - 1]
);
}
// 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)
// ...
// ==== ==== ==== ========= ========== ========= ======== ============== ============= ============== =============== ==== ======= ====== =========== (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) {
let lines: Vec<_> = s.lines().collect();
let line_count = lines.len();
let re = Regex::new(&format!("^{pid}: .+[^ ]$")).unwrap();
assert!(re.is_match(lines[0]));
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 re = Regex::new(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+)? (|\[[a-zA-Z_ ]+\]|[a-zA-Z0-9._-]+)$").unwrap();
for line in lines.iter().take(line_count - 2).skip(2) {
assert!(re.is_match(line), "failing line: '{line}'");
}
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();
assert!(
re.is_match(lines[line_count - 1]),
"failing line: '{}'",
lines[line_count - 1]
);
}
// 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)
// ...
// ==== ============== =========== ==== ==== ========= ============ ============ ============= ============= ========== ========= ======== ============= ============== ============= ============== =============== ==== ======= ====== =========== (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) {
let lines: Vec<_> = s.lines().collect();
let line_count = lines.len();
let re = Regex::new(&format!("^{pid}: .+[^ ]$")).unwrap();
assert!(re.is_match(lines[0]));
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 re = Regex::new(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] )*(|\[[a-zA-Z_ ]+\]|[a-zA-Z0-9._-]+)$").unwrap();
for line in lines.iter().take(line_count - 2).skip(2) {
assert!(re.is_match(line), "failing line: '{line}'");
}
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();
assert!(
re.is_match(lines[line_count - 1]),
"failing line: '{}'",
lines[line_count - 1]
);
}