pmap: implemented quiet option (#423)

* pmap: implemented quiet option

* pmap: added tests for quiet option

* pmap: fixed quiet option handling in tests

* Update src/uu/pmap/src/pmap.rs

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

---------

Co-authored-by: Daniel Hofstetter <daniel.hofstetter@42dh.com>
This commit is contained in:
estodi
2025-05-21 08:01:19 -07:00
committed by GitHub
parent e427ebd0e3
commit 9e562bbe41
3 changed files with 230 additions and 147 deletions
+34 -15
View File
@@ -48,6 +48,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
}
// Options independent with field selection:
pmap_config.quiet = matches.get_flag(options::QUIET);
if matches.get_flag(options::SHOW_PATH) {
pmap_config.show_path = true;
}
@@ -142,7 +143,9 @@ fn output_default_format(pid: &str, pmap_config: &PmapConfig) -> Result<(), Erro
total += map_line.size_in_kb;
})?;
println!(" total {total:>16}K");
if !pmap_config.quiet {
println!(" total {total:>16}K");
}
Ok(())
}
@@ -150,7 +153,9 @@ fn output_default_format(pid: &str, pmap_config: &PmapConfig) -> Result<(), Erro
fn output_extended_format(pid: &str, pmap_config: &PmapConfig) -> Result<(), Error> {
let smap_table = get_smap_table(pid)?;
println!("Address Kbytes RSS Dirty Mode Mapping");
if !pmap_config.quiet {
println!("Address Kbytes RSS Dirty Mode Mapping");
}
for smap_entry in smap_table.entries {
println!(
@@ -164,13 +169,16 @@ fn output_extended_format(pid: &str, pmap_config: &PmapConfig) -> Result<(), Err
);
}
println!("---------------- ------- ------- ------- ");
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,
);
if !pmap_config.quiet {
println!("---------------- ------- ------- ------- ");
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(())
}
@@ -185,15 +193,20 @@ fn output_custom_format(pid: &str, pmap_config: &mut PmapConfig) -> Result<(), E
}
// Header
{
if !pmap_config.quiet {
let mut line = format!(
"{:>width$} ",
pmap_field_name::ADDRESS,
width = smap_table.info.get_width(pmap_field_name::ADDRESS)
);
pmap_config.quiet = true;
for field_name in pmap_config.get_field_list() {
if pmap_config.is_enabled(field_name) {
// If there is any field that needs footer, we can't suppress the footer
if pmap_config.needs_footer(field_name) {
pmap_config.quiet = false;
}
line += &format!(
"{:>width$} ",
field_name,
@@ -230,7 +243,7 @@ fn output_custom_format(pid: &str, pmap_config: &mut PmapConfig) -> Result<(), E
}
// Footer
{
if !pmap_config.quiet {
// Separator
let mut line = format!(
"{:>width$} ",
@@ -292,7 +305,11 @@ fn output_device_format(pid: &str, pmap_config: &PmapConfig) -> Result<(), Error
process_maps(
pid,
Some("Address Kbytes Mode Offset Device Mapping"),
if !pmap_config.quiet {
Some("Address Kbytes Mode Offset Device Mapping")
} else {
None
},
|map_line| {
println!(
"{} {:>7} {} {} {} {}",
@@ -315,9 +332,11 @@ fn output_device_format(pid: &str, pmap_config: &PmapConfig) -> Result<(), Error
},
)?;
println!(
"mapped: {total_mapped}K writeable/private: {total_writeable_private}K shared: {total_shared}K"
);
if !pmap_config.quiet {
println!(
"mapped: {total_mapped}K writeable/private: {total_writeable_private}K shared: {total_shared}K"
);
}
Ok(())
}
+1
View File
@@ -75,6 +75,7 @@ pub struct PmapConfig {
// [Mapping] category
pub show_path: bool,
// Misc
pub quiet: bool,
pub custom_format_enabled: bool,
}
+195 -132
View File
@@ -30,7 +30,7 @@ fn test_existing_pid() {
.succeeds()
.stdout_move_str();
assert_format(pid, &result, false);
assert_format(pid, &result, false, false);
}
#[test]
@@ -50,8 +50,8 @@ fn test_multiple_existing_pids() {
let pos_second_pid = result.iter().rposition(|line| re.is_match(line)).unwrap();
let (left, right) = result.split_at(pos_second_pid);
assert_format(pid, &left.join("\n"), false);
assert_format(pid, &right.join("\n"), false);
assert_format(pid, &left.join("\n"), false, false);
assert_format(pid, &right.join("\n"), false, false);
}
#[test]
@@ -65,7 +65,7 @@ fn test_non_existing_and_existing_pid() {
.fails();
let result = result.code_is(42).no_stderr().stdout_str();
assert_format(pid, result, false);
assert_format(pid, result, false, false);
}
#[test]
@@ -103,7 +103,7 @@ fn test_extended() {
.succeeds()
.stdout_move_str();
assert_extended_format(pid, &result, false);
assert_extended_format(pid, &result, false, false);
}
}
@@ -119,7 +119,7 @@ fn test_more_extended() {
.succeeds()
.stdout_move_str();
assert_more_extended_format(pid, &result, false);
assert_more_extended_format(pid, &result, false, false);
}
#[test]
@@ -134,7 +134,7 @@ fn test_most_extended() {
.succeeds()
.stdout_move_str();
assert_most_extended_format(pid, &result, false);
assert_most_extended_format(pid, &result, false, false);
}
#[test]
@@ -166,7 +166,7 @@ fn test_device() {
.succeeds()
.stdout_move_str();
assert_device_format(pid, &result, false);
assert_device_format(pid, &result, false, false);
}
}
@@ -187,63 +187,88 @@ fn test_device_permission_denied() {
}
}
#[test]
#[cfg(target_os = "linux")]
fn test_quiet() {
let pid = process::id();
for arg in ["-q", "--quiet"] {
_test_multiple_formats(pid, arg, true, false);
}
}
#[test]
#[cfg(target_os = "linux")]
fn test_showpath() {
let pid = process::id();
for arg in ["-p", "--show-path"] {
// default format
let result = new_ucmd!()
.arg(arg)
.arg(pid.to_string())
.succeeds()
.stdout_move_str();
assert_format(pid, &result, true);
// extended format
let result = new_ucmd!()
.arg(arg)
.arg("--extended")
.arg(pid.to_string())
.succeeds()
.stdout_move_str();
assert_extended_format(pid, &result, true);
// more-extended format
let result = new_ucmd!()
.arg(arg)
.arg("-X")
.arg(pid.to_string())
.succeeds()
.stdout_move_str();
assert_more_extended_format(pid, &result, true);
// most-extended format
let result = new_ucmd!()
.arg(arg)
.arg("--XX")
.arg(pid.to_string())
.succeeds()
.stdout_move_str();
assert_most_extended_format(pid, &result, true);
// device format
let result = new_ucmd!()
.arg(arg)
.arg("--device")
.arg(pid.to_string())
.succeeds()
.stdout_move_str();
assert_device_format(pid, &result, true);
_test_multiple_formats(pid, arg, false, true);
}
}
#[test]
#[cfg(target_os = "linux")]
fn test_quiet_showpath() {
let pid = process::id();
for arg in ["-qp", "-pq"] {
_test_multiple_formats(pid, arg, true, true);
}
}
#[cfg(target_os = "linux")]
fn _test_multiple_formats(pid: u32, arg: &str, quiet: bool, show_path: bool) {
// default format
let result = new_ucmd!()
.arg(arg)
.arg(pid.to_string())
.succeeds()
.stdout_move_str();
assert_format(pid, &result, quiet, show_path);
// extended format
let result = new_ucmd!()
.arg(arg)
.arg("--extended")
.arg(pid.to_string())
.succeeds()
.stdout_move_str();
assert_extended_format(pid, &result, quiet, show_path);
// more-extended format
let result = new_ucmd!()
.arg(arg)
.arg("-X")
.arg(pid.to_string())
.succeeds()
.stdout_move_str();
assert_more_extended_format(pid, &result, quiet, show_path);
// most-extended format
let result = new_ucmd!()
.arg(arg)
.arg("--XX")
.arg(pid.to_string())
.succeeds()
.stdout_move_str();
assert_most_extended_format(pid, &result, quiet, show_path);
// device format
let result = new_ucmd!()
.arg(arg)
.arg("--device")
.arg(pid.to_string())
.succeeds()
.stdout_move_str();
assert_device_format(pid, &result, quiet, show_path);
}
#[test]
fn test_invalid_arg() {
new_ucmd!().arg("--definitely-invalid").fails().code_is(1);
@@ -268,13 +293,11 @@ fn assert_cmdline_only(pid: &str, s: &str) {
// ...
// total 1040320K
#[cfg(target_os = "linux")]
fn assert_format(pid: u32, s: &str, show_path: bool) {
fn assert_format(pid: u32, s: &str, quiet: bool, show_path: bool) {
let (first_line, rest) = s.split_once('\n').unwrap();
let re = Regex::new(&format!("^{pid}: .+[^ ]$")).unwrap();
assert!(re.is_match(first_line));
let rest = rest.trim_end();
let (memory_map, last_line) = rest.rsplit_once('\n').unwrap();
let base_pattern = r"(?m)^[0-9a-f]{16} +[1-9][0-9]*K (-|r)(-|w)(-|x)(-|s)-";
let mapping_pattern = if show_path {
r" ( \[ (anon|stack) \]|/[/a-zA-Z0-9._-]+)$"
@@ -282,10 +305,17 @@ fn assert_format(pid: u32, s: &str, show_path: bool) {
r" ( \[ (anon|stack) \]|[a-zA-Z0-9._-]+)$"
};
let re = Regex::new(&format!("{base_pattern}{mapping_pattern}")).unwrap();
assert!(re.is_match(memory_map));
let re = Regex::new("^ total +[1-9][0-9]*K$").unwrap();
assert!(re.is_match(last_line));
let rest = rest.trim_end();
if quiet {
assert!(re.is_match(rest));
} else {
let (memory_map, last_line) = rest.rsplit_once('\n').unwrap();
assert!(re.is_match(memory_map));
let re = Regex::new("^ total +[1-9][0-9]*K$").unwrap();
assert!(re.is_match(last_line));
}
}
// Ensure `s` has the following extended format (--extended):
@@ -299,15 +329,17 @@ fn assert_format(pid: u32, s: &str, show_path: bool) {
// ---------------- ------- ------- ------- (one intentional trailing space)
// total kB 144 7 14
#[cfg(target_os = "linux")]
fn assert_extended_format(pid: u32, s: &str, show_path: bool) {
fn assert_extended_format(pid: u32, s: &str, quiet: bool, show_path: bool) {
let lines: Vec<_> = s.lines().collect();
let line_count = lines.len();
let re = Regex::new(&format!("^{pid}: .+[^ ]$")).unwrap();
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], "failing line: '{}'", lines[1]);
if !quiet {
let expected_header = "Address Kbytes RSS Dirty Mode Mapping";
assert_eq!(expected_header, lines[1], "failing line: '{}'", lines[1]);
}
let base_pattern = r"^[0-9a-f]{16} +[1-9][0-9]* +\d+ +\d+ (-|r)(-|w)(-|x)(-|s)-";
let mapping_pattern = if show_path {
@@ -317,24 +349,30 @@ fn assert_extended_format(pid: u32, s: &str, show_path: bool) {
};
let re = Regex::new(&format!("{base_pattern}{mapping_pattern}")).unwrap();
for line in lines.iter().take(line_count - 2).skip(2) {
assert!(re.is_match(line), "failing line: '{line}'");
if quiet {
for line in lines.iter().skip(1) {
assert!(re.is_match(line), "failing line: '{line}'");
}
} else {
for line in lines.iter().take(line_count - 2).skip(2) {
assert!(re.is_match(line), "failing line: '{line}'");
}
let expected_separator = "---------------- ------- ------- ------- ";
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: '{}'",
lines[line_count - 1]
);
}
let expected_separator = "---------------- ------- ------- ------- ";
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: '{}'",
lines[line_count - 1]
);
}
// Ensure `s` has the following more extended format (-X):
@@ -348,15 +386,17 @@ fn assert_extended_format(pid: u32, s: &str, show_path: bool) {
// ==== ==== ==== ========= ========== ========= ======== ============== ============= ============== =============== ==== ======= ====== =========== (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, show_path: bool) {
fn assert_more_extended_format(pid: u32, s: &str, quiet: bool, show_path: bool) {
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]);
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();
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 mapping_pattern = if show_path {
@@ -366,23 +406,29 @@ fn assert_more_extended_format(pid: u32, s: &str, show_path: bool) {
};
let re = Regex::new(&format!("{base_pattern}{mapping_pattern}")).unwrap();
for line in lines.iter().take(line_count - 2).skip(2) {
assert!(re.is_match(line), "failing line: '{line}'");
if quiet {
for line in lines.iter().skip(1) {
assert!(re.is_match(line), "failing line: '{line}'");
}
} else {
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]
);
}
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):
@@ -396,15 +442,17 @@ fn assert_more_extended_format(pid: u32, s: &str, show_path: bool) {
// ==== ============== =========== ==== ==== ========= ============ ============ ============= ============= ========== ========= ======== ============= ============== ============= ============== =============== ==== ======= ====== =========== (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, show_path: bool) {
fn assert_most_extended_format(pid: u32, s: &str, quiet: bool, show_path: bool) {
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]);
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();
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 mapping_pattern = if show_path {
@@ -414,23 +462,29 @@ fn assert_most_extended_format(pid: u32, s: &str, show_path: bool) {
};
let re = Regex::new(&format!("{base_pattern}{mapping_pattern}")).unwrap();
for line in lines.iter().take(line_count - 2).skip(2) {
assert!(re.is_match(line), "failing line: '{line}'");
if quiet {
for line in lines.iter().skip(1) {
assert!(re.is_match(line), "failing line: '{line}'");
}
} else {
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]
);
}
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]
);
}
// Ensure `s` has the following device format (--device):
@@ -443,15 +497,17 @@ fn assert_most_extended_format(pid: u32, s: &str, show_path: bool) {
// ...
// mapped: 3060K writeable/private: 348K shared: 0K
#[cfg(target_os = "linux")]
fn assert_device_format(pid: u32, s: &str, show_path: bool) {
fn assert_device_format(pid: u32, s: &str, quiet: bool, show_path: bool) {
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 expected_header = "Address Kbytes Mode Offset Device Mapping";
assert_eq!(expected_header, lines[1]);
if !quiet {
let expected_header = "Address Kbytes Mode Offset Device Mapping";
assert_eq!(expected_header, lines[1]);
}
let base_pattern =
r"^[0-9a-f]{16} +[1-9][0-9]* (-|r)(-|w)(-|x)(-|s)- [0-9a-f]{16} [0-9a-f]{3}:[0-9a-f]{5}";
@@ -462,14 +518,21 @@ fn assert_device_format(pid: u32, s: &str, show_path: bool) {
};
let re = Regex::new(&format!("{base_pattern}{mapping_pattern}")).unwrap();
for line in lines.iter().take(line_count - 1).skip(2) {
assert!(re.is_match(line), "failing line: {line}");
}
if quiet {
for line in lines.iter().skip(1) {
assert!(re.is_match(line), "failing line: {line}");
}
} else {
for line in lines.iter().take(line_count - 1).skip(2) {
assert!(re.is_match(line), "failing line: {line}");
}
let re = Regex::new(r"^mapped: \d+K\s{4}writeable/private: \d+K\s{4}shared: \d+K$").unwrap();
assert!(
re.is_match(lines[line_count - 1]),
"failing line: {}",
lines[line_count - 1]
);
let re =
Regex::new(r"^mapped: \d+K\s{4}writeable/private: \d+K\s{4}shared: \d+K$").unwrap();
assert!(
re.is_match(lines[line_count - 1]),
"failing line: {}",
lines[line_count - 1]
);
}
}