mirror of
https://github.com/uutils/procps.git
synced 2026-05-06 06:06:43 -04:00
add hugetop command
This commit is contained in:
@@ -50,9 +50,8 @@ jobs:
|
|||||||
- name: build and test all programs separately
|
- name: build and test all programs separately
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
## TODO: add hugetop
|
|
||||||
set -e
|
set -e
|
||||||
programs="free pgrep pidof pidwait pkill pmap ps pwdx skill slabtop snice sysctl tload top vmstat w watch"
|
programs="free hugetop pgrep pidof pidwait pkill pmap ps pwdx skill slabtop snice sysctl tload top vmstat w watch"
|
||||||
for program in $programs; do
|
for program in $programs; do
|
||||||
echo "Building and testing $program"
|
echo "Building and testing $program"
|
||||||
cargo test -p "uu_$program"
|
cargo test -p "uu_$program"
|
||||||
|
|||||||
Generated
+10
@@ -1541,6 +1541,7 @@ dependencies = [
|
|||||||
"tempfile",
|
"tempfile",
|
||||||
"textwrap",
|
"textwrap",
|
||||||
"uu_free",
|
"uu_free",
|
||||||
|
"uu_hugetop",
|
||||||
"uu_pgrep",
|
"uu_pgrep",
|
||||||
"uu_pidof",
|
"uu_pidof",
|
||||||
"uu_pidwait",
|
"uu_pidwait",
|
||||||
@@ -2363,6 +2364,15 @@ dependencies = [
|
|||||||
"windows",
|
"windows",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "uu_hugetop"
|
||||||
|
version = "0.0.1"
|
||||||
|
dependencies = [
|
||||||
|
"clap",
|
||||||
|
"tempfile",
|
||||||
|
"uucore",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uu_pgrep"
|
name = "uu_pgrep"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
[workspace]
|
||||||
|
members = ["src/uu/hugetop"]
|
||||||
# procps (uutils)
|
# procps (uutils)
|
||||||
# * see the repository LICENSE, README, and CONTRIBUTING files for more information
|
# * see the repository LICENSE, README, and CONTRIBUTING files for more information
|
||||||
|
|
||||||
@@ -33,6 +35,7 @@ uudoc = []
|
|||||||
|
|
||||||
feat_common_core = [
|
feat_common_core = [
|
||||||
"free",
|
"free",
|
||||||
|
"hugetop",
|
||||||
"pgrep",
|
"pgrep",
|
||||||
"pidof",
|
"pidof",
|
||||||
"pidwait",
|
"pidwait",
|
||||||
@@ -94,6 +97,7 @@ uucore = { workspace = true }
|
|||||||
|
|
||||||
#
|
#
|
||||||
free = { optional = true, version = "0.0.1", package = "uu_free", path = "src/uu/free" }
|
free = { optional = true, version = "0.0.1", package = "uu_free", path = "src/uu/free" }
|
||||||
|
hugetop = { optional = true, version = "0.0.1", package = "uu_hugetop", path = "src/uu/hugetop" }
|
||||||
pgrep = { optional = true, version = "0.0.1", package = "uu_pgrep", path = "src/uu/pgrep" }
|
pgrep = { optional = true, version = "0.0.1", package = "uu_pgrep", path = "src/uu/pgrep" }
|
||||||
pidof = { optional = true, version = "0.0.1", package = "uu_pidof", path = "src/uu/pidof" }
|
pidof = { optional = true, version = "0.0.1", package = "uu_pidof", path = "src/uu/pidof" }
|
||||||
pidwait = { optional = true, version = "0.0.1", package = "uu_pidwait", path = "src/uu/pidwait" }
|
pidwait = { optional = true, version = "0.0.1", package = "uu_pidwait", path = "src/uu/pidwait" }
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ Provides command line and full screen utilities for browsing procfs, a "pseudo"
|
|||||||
|
|
||||||
Ongoing:
|
Ongoing:
|
||||||
* `free`: Shows the amount of free and used memory in the system.
|
* `free`: Shows the amount of free and used memory in the system.
|
||||||
|
* `hugetop`: Report hugepage usage of processes and the system as a whole.
|
||||||
* `pgrep`: Searches for processes based on name and other attributes.
|
* `pgrep`: Searches for processes based on name and other attributes.
|
||||||
* `pidof`: Find the process ID of a running program.
|
* `pidof`: Find the process ID of a running program.
|
||||||
* `pidwait`: Waits for a specific process to terminate.
|
* `pidwait`: Waits for a specific process to terminate.
|
||||||
@@ -30,9 +31,6 @@ Ongoing:
|
|||||||
* `w`: Shows who is logged on and what they are doing.
|
* `w`: Shows who is logged on and what they are doing.
|
||||||
* `watch`: Executes a program periodically, showing output fullscreen.
|
* `watch`: Executes a program periodically, showing output fullscreen.
|
||||||
|
|
||||||
TODO:
|
|
||||||
* `hugetop`: Report hugepage usage of processes and the system as a whole.
|
|
||||||
|
|
||||||
Elsewhere:
|
Elsewhere:
|
||||||
|
|
||||||
* `kill` is already implemented in https://github.com/uutils/coreutils
|
* `kill` is already implemented in https://github.com/uutils/coreutils
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
[package]
|
||||||
|
name = "uu_hugetop"
|
||||||
|
description = "hugetop ~ (uutils) Report hugepage usage for the system and processes"
|
||||||
|
repository = "https://github.com/uutils/procps/tree/main/src/uu/hugetop"
|
||||||
|
authors.workspace = true
|
||||||
|
categories.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
homepage.workspace = true
|
||||||
|
keywords.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
version.workspace = true
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
clap = { workspace = true }
|
||||||
|
uucore = { workspace = true }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
tempfile = { workspace = true }
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
path = "src/hugetop.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "hugetop"
|
||||||
|
path = "src/main.rs"
|
||||||
@@ -0,0 +1,469 @@
|
|||||||
|
// 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 clap::{value_parser, Arg, Command};
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use std::fs;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::thread::sleep;
|
||||||
|
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||||
|
use uucore::error::UResult;
|
||||||
|
|
||||||
|
const DEFAULT_HUGEPAGES_ROOT: &str = "/sys/kernel/mm/hugepages";
|
||||||
|
const SYS_NODES_ROOT: &str = "/sys/devices/system/node";
|
||||||
|
const DEFAULT_PROC_ROOT: &str = "/proc";
|
||||||
|
|
||||||
|
/// Hugepage statistics from /proc/[pid]/smaps_rollup file.
|
||||||
|
///
|
||||||
|
/// These values represent:
|
||||||
|
/// - `0`: AnonHugePages (Anonymous Hugepage memory in kB)
|
||||||
|
/// - `1`: Shared_Hugetlb (Hugetlb memory shared with other processes in kB)
|
||||||
|
/// - `2`: Private_Hugetlb (Hugetlb memory private to the process in kB)
|
||||||
|
///
|
||||||
|
/// See Linux kernel documentation:
|
||||||
|
/// - https://www.kernel.org/doc/html/latest/filesystems/proc.html#proc-pid-smaps-smaps-rollup
|
||||||
|
type SmapsRollupValues = (u64, u64, u64);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
struct HugePagePool {
|
||||||
|
size_kb: u64,
|
||||||
|
total_pages: u64,
|
||||||
|
free_pages: u64,
|
||||||
|
reserved_pages: u64,
|
||||||
|
surplus_pages: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
struct ProcessHugeUsage {
|
||||||
|
pid: u32,
|
||||||
|
command: String,
|
||||||
|
anon_huge_kb: u64,
|
||||||
|
shared_hugetlb_kb: u64,
|
||||||
|
private_hugetlb_kb: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProcessHugeUsage {
|
||||||
|
fn total_kb(&self) -> u64 {
|
||||||
|
self.anon_huge_kb + self.shared_hugetlb_kb + self.private_hugetlb_kb
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[uucore::main]
|
||||||
|
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
|
let matches = uu_app().try_get_matches_from(args)?;
|
||||||
|
|
||||||
|
let limit = matches.get_one::<usize>("lines").copied();
|
||||||
|
let numa = matches.get_flag("numa");
|
||||||
|
let human = matches.get_flag("human");
|
||||||
|
let once = matches.get_flag("once");
|
||||||
|
let delay = *matches.get_one::<u64>("delay").unwrap_or(&0);
|
||||||
|
|
||||||
|
if once || delay == 0 {
|
||||||
|
run(numa, human, limit)
|
||||||
|
} else {
|
||||||
|
loop {
|
||||||
|
// Clear the terminal to roughly approximate hugetop's screen refresh behavior.
|
||||||
|
print!("\x1B[2J\x1B[H");
|
||||||
|
run(numa, human, limit)?;
|
||||||
|
sleep(Duration::from_secs(delay));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(numa: bool, human: bool, limit: Option<usize>) -> UResult<()> {
|
||||||
|
print_summary(numa, human);
|
||||||
|
print_headings();
|
||||||
|
print_procs(human, limit);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn uu_app() -> Command {
|
||||||
|
Command::new(uucore::util_name())
|
||||||
|
.about("Report hugepage usage of processes and the system as a whole")
|
||||||
|
.arg(
|
||||||
|
Arg::new("delay")
|
||||||
|
.short('d')
|
||||||
|
.long("delay")
|
||||||
|
.value_name("SECONDS")
|
||||||
|
.help("Delay between updates (0 = run once)")
|
||||||
|
.value_parser(value_parser!(u64)),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("numa")
|
||||||
|
.short('n')
|
||||||
|
.long("numa")
|
||||||
|
.help("Display per NUMA node huge page information")
|
||||||
|
.action(clap::ArgAction::SetTrue),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("once")
|
||||||
|
.short('o')
|
||||||
|
.long("once")
|
||||||
|
.help("Only display once, then exit")
|
||||||
|
.action(clap::ArgAction::SetTrue),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("human")
|
||||||
|
.short('H')
|
||||||
|
.long("human")
|
||||||
|
.help("Display human-readable output")
|
||||||
|
.action(clap::ArgAction::SetTrue),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("lines")
|
||||||
|
.short('l')
|
||||||
|
.long("lines")
|
||||||
|
.value_name("N")
|
||||||
|
.help("Show the top N processes")
|
||||||
|
.value_parser(value_parser!(usize)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_summary(numa: bool, human: bool) {
|
||||||
|
let now = SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.map(|d| d.as_secs())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
println!("hugetop - {}", now);
|
||||||
|
|
||||||
|
let pools = match read_node_hugepage_pools() {
|
||||||
|
Ok(nodes) if numa => {
|
||||||
|
for (node, pools) in &nodes {
|
||||||
|
print_node(node, pools, human);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Ok(nodes) => merge_node_pools(&nodes),
|
||||||
|
Err(_) => Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if pools.is_empty() {
|
||||||
|
let pools = read_hugepage_pools(Path::new(DEFAULT_HUGEPAGES_ROOT)).unwrap_or_default();
|
||||||
|
if pools.is_empty() {
|
||||||
|
println!("(no hugepage pools found)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
print_node("node(s)", &pools, human);
|
||||||
|
} else {
|
||||||
|
print_node("node(s)", &pools, human);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_headings() {
|
||||||
|
println!("{:>8} {:>10} {:>10} COMMAND", "PID", "SHARED", "PRIVATE");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_procs(human: bool, limit: Option<usize>) {
|
||||||
|
let mut processes =
|
||||||
|
read_process_hugepage_usage(Path::new(DEFAULT_PROC_ROOT)).unwrap_or_default();
|
||||||
|
|
||||||
|
processes.sort_by_key(|usage| std::cmp::Reverse(usage.total_kb()));
|
||||||
|
|
||||||
|
let limit = limit.unwrap_or(processes.len());
|
||||||
|
|
||||||
|
for (shown, usage) in processes.into_iter().enumerate() {
|
||||||
|
if shown >= limit {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let shared = format_kb(usage.shared_hugetlb_kb, human);
|
||||||
|
let private = format_kb(usage.private_hugetlb_kb, human);
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"{:>8} {:>10} {:>10} {}",
|
||||||
|
usage.pid, shared, private, usage.command
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_node(node: &str, pools: &[HugePagePool], human: bool) {
|
||||||
|
let mut line = String::new();
|
||||||
|
line.push_str(node);
|
||||||
|
line.push(':');
|
||||||
|
|
||||||
|
for (i, pool) in pools.iter().enumerate() {
|
||||||
|
if i > 0 {
|
||||||
|
line.push(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
let size = if human {
|
||||||
|
humanized(pool.size_kb, false)
|
||||||
|
} else {
|
||||||
|
format!("{}kB", pool.size_kb)
|
||||||
|
};
|
||||||
|
|
||||||
|
line.push_str(&format!(
|
||||||
|
" {} - {}/{}",
|
||||||
|
size, pool.free_pages, pool.total_pages
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("{}", line);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_kb(kb: u64, human: bool) -> String {
|
||||||
|
if human {
|
||||||
|
humanized(kb, false)
|
||||||
|
} else {
|
||||||
|
format!("{}", kb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn humanized(kib: u64, si: bool) -> String {
|
||||||
|
let b = kib * 1024;
|
||||||
|
let units = ['B', 'K', 'M', 'G', 'T', 'P'];
|
||||||
|
let mut level = 0;
|
||||||
|
let mut divisor = 1u64;
|
||||||
|
|
||||||
|
while level < units.len() - 1 && divisor * 100 <= b {
|
||||||
|
divisor *= if si { 1000 } else { 1024 };
|
||||||
|
level += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if level == 0 {
|
||||||
|
return format!("{}{}", b, units[level]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let value = (b as f64) / (divisor as f64);
|
||||||
|
let formatted_value = if (value * 10.0).round() < 100.0 {
|
||||||
|
format!("{:.1}", (value * 10.0).round() / 10.0)
|
||||||
|
} else {
|
||||||
|
(value as u64).to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
format!(
|
||||||
|
"{}{}{}",
|
||||||
|
formatted_value,
|
||||||
|
units[level].to_owned(),
|
||||||
|
if si { "" } else { "i" }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_node_hugepage_pools() -> UResult<Vec<(String, Vec<HugePagePool>)>> {
|
||||||
|
let mut nodes = Vec::new();
|
||||||
|
let Ok(entries) = fs::read_dir(SYS_NODES_ROOT) else {
|
||||||
|
return Ok(nodes);
|
||||||
|
};
|
||||||
|
|
||||||
|
for entry in entries.flatten() {
|
||||||
|
let file_name = entry.file_name();
|
||||||
|
let name = match file_name.to_str() {
|
||||||
|
Some(n) if n.starts_with("node") => n.to_string(),
|
||||||
|
_ => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
let path = entry.path().join("hugepages");
|
||||||
|
if !path.is_dir() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let pools = read_hugepage_pools(&path)?;
|
||||||
|
if pools.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
nodes.push((name, pools));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(nodes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn merge_node_pools(nodes: &[(String, Vec<HugePagePool>)]) -> Vec<HugePagePool> {
|
||||||
|
let mut map: BTreeMap<u64, HugePagePool> = BTreeMap::new();
|
||||||
|
|
||||||
|
for (_, pools) in nodes {
|
||||||
|
for pool in pools {
|
||||||
|
let entry = map.entry(pool.size_kb).or_insert_with(|| HugePagePool {
|
||||||
|
size_kb: pool.size_kb,
|
||||||
|
total_pages: 0,
|
||||||
|
free_pages: 0,
|
||||||
|
reserved_pages: 0,
|
||||||
|
surplus_pages: 0,
|
||||||
|
});
|
||||||
|
entry.total_pages += pool.total_pages;
|
||||||
|
entry.free_pages += pool.free_pages;
|
||||||
|
entry.reserved_pages += pool.reserved_pages;
|
||||||
|
entry.surplus_pages += pool.surplus_pages;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
map.into_values().collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_hugepage_pools(root: impl AsRef<Path>) -> UResult<Vec<HugePagePool>> {
|
||||||
|
let mut pools = Vec::new();
|
||||||
|
|
||||||
|
let Ok(entries) = fs::read_dir(&root) else {
|
||||||
|
return Ok(pools);
|
||||||
|
};
|
||||||
|
|
||||||
|
for entry in entries.flatten() {
|
||||||
|
let path = entry.path();
|
||||||
|
let Some(name) = path.file_name().and_then(|s| s.to_str()) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(size_kb) = parse_hugepage_dir_name(name) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let total_pages = read_u64(path.join("nr_hugepages"));
|
||||||
|
let free_pages = read_u64(path.join("free_hugepages"));
|
||||||
|
let reserved_pages = read_u64(path.join("resv_hugepages"));
|
||||||
|
let surplus_pages = read_u64(path.join("surplus_hugepages"));
|
||||||
|
|
||||||
|
pools.push(HugePagePool {
|
||||||
|
size_kb,
|
||||||
|
total_pages,
|
||||||
|
free_pages,
|
||||||
|
reserved_pages,
|
||||||
|
surplus_pages,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pools.sort_by_key(|pool| pool.size_kb);
|
||||||
|
Ok(pools)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_process_hugepage_usage(root: impl AsRef<Path>) -> UResult<Vec<ProcessHugeUsage>> {
|
||||||
|
let mut usages = Vec::new();
|
||||||
|
|
||||||
|
let Ok(entries) = fs::read_dir(&root) else {
|
||||||
|
return Ok(usages);
|
||||||
|
};
|
||||||
|
|
||||||
|
for entry in entries.flatten() {
|
||||||
|
let path = entry.path();
|
||||||
|
let Some(file_name) = path.file_name().and_then(|name| name.to_str()) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Ok(pid) = file_name.parse::<u32>() else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some((_, shared_hugetlb_kb, private_hugetlb_kb)) =
|
||||||
|
parse_smaps_rollup(path.join("smaps_rollup"))
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let total_kb = shared_hugetlb_kb + private_hugetlb_kb;
|
||||||
|
if total_kb == 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let command = fs::read_to_string(path.join("comm"))
|
||||||
|
.unwrap_or_else(|_| String::from("?"))
|
||||||
|
.trim()
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
usages.push(ProcessHugeUsage {
|
||||||
|
pid,
|
||||||
|
command,
|
||||||
|
anon_huge_kb: 0,
|
||||||
|
shared_hugetlb_kb,
|
||||||
|
private_hugetlb_kb,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(usages)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_hugepage_dir_name(name: &str) -> Option<u64> {
|
||||||
|
let prefix = "hugepages-";
|
||||||
|
let suffix = "kB";
|
||||||
|
|
||||||
|
if !name.starts_with(prefix) || !name.ends_with(suffix) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
name[prefix.len()..name.len() - suffix.len()]
|
||||||
|
.parse::<u64>()
|
||||||
|
.ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_smaps_rollup(path: impl AsRef<Path>) -> Option<SmapsRollupValues> {
|
||||||
|
let content = fs::read_to_string(&path).ok()?;
|
||||||
|
|
||||||
|
let mut anon_huge_kb = 0;
|
||||||
|
let mut shared_hugetlb_kb = 0;
|
||||||
|
let mut private_hugetlb_kb = 0;
|
||||||
|
|
||||||
|
for line in content.lines() {
|
||||||
|
if let Some(value) = parse_kb_field(line, "AnonHugePages:") {
|
||||||
|
anon_huge_kb = value;
|
||||||
|
} else if let Some(value) = parse_kb_field(line, "Shared_Hugetlb:") {
|
||||||
|
shared_hugetlb_kb = value;
|
||||||
|
} else if let Some(value) = parse_kb_field(line, "Private_Hugetlb:") {
|
||||||
|
private_hugetlb_kb = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some((anon_huge_kb, shared_hugetlb_kb, private_hugetlb_kb))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_kb_field(line: &str, field: &str) -> Option<u64> {
|
||||||
|
let value = line.strip_prefix(field)?.trim();
|
||||||
|
let number = value.split_whitespace().next()?;
|
||||||
|
number.parse::<u64>().ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_u64(path: impl AsRef<Path>) -> u64 {
|
||||||
|
fs::read_to_string(path)
|
||||||
|
.ok()
|
||||||
|
.and_then(|s| s.trim().parse::<u64>().ok())
|
||||||
|
.unwrap_or(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn parse_hugepage_name_works() {
|
||||||
|
assert_eq!(parse_hugepage_dir_name("hugepages-2048kB"), Some(2048));
|
||||||
|
assert_eq!(
|
||||||
|
parse_hugepage_dir_name("hugepages-1048576kB"),
|
||||||
|
Some(1_048_576)
|
||||||
|
);
|
||||||
|
assert_eq!(parse_hugepage_dir_name("hugepages-foo"), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn parse_smaps_rollup_works() {
|
||||||
|
let dir = tempfile::tempdir().unwrap();
|
||||||
|
let file = dir.path().join("smaps_rollup");
|
||||||
|
let mut f = fs::File::create(&file).unwrap();
|
||||||
|
writeln!(f, "AnonHugePages: 512 kB").unwrap();
|
||||||
|
writeln!(f, "Shared_Hugetlb: 64 kB").unwrap();
|
||||||
|
writeln!(f, "Private_Hugetlb: 32 kB").unwrap();
|
||||||
|
|
||||||
|
assert_eq!(parse_smaps_rollup(&file), Some((512, 64, 32)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn reads_pools_from_tree() {
|
||||||
|
let dir = tempfile::tempdir().unwrap();
|
||||||
|
let pool = dir.path().join("hugepages-2048kB");
|
||||||
|
fs::create_dir(&pool).unwrap();
|
||||||
|
fs::write(pool.join("nr_hugepages"), "10\n").unwrap();
|
||||||
|
fs::write(pool.join("free_hugepages"), "3\n").unwrap();
|
||||||
|
fs::write(pool.join("resv_hugepages"), "2\n").unwrap();
|
||||||
|
fs::write(pool.join("surplus_hugepages"), "1\n").unwrap();
|
||||||
|
|
||||||
|
let pools = read_hugepage_pools(dir.path()).unwrap();
|
||||||
|
assert_eq!(pools.len(), 1);
|
||||||
|
assert_eq!(pools[0].size_kb, 2048);
|
||||||
|
assert_eq!(pools[0].total_pages, 10);
|
||||||
|
assert_eq!(pools[0].free_pages, 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uucore::bin!(uu_hugetop);
|
||||||
@@ -0,0 +1,182 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
use uutests::new_ucmd;
|
||||||
|
|
||||||
|
// Basic functionality tests
|
||||||
|
#[test]
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn runs_successfully() {
|
||||||
|
new_ucmd!().succeeds();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lines option tests
|
||||||
|
#[test]
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn supports_lines_option() {
|
||||||
|
new_ucmd!().arg("-l").arg("1").succeeds();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn supports_lines_option_long() {
|
||||||
|
new_ucmd!().arg("--lines").arg("5").succeeds();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn supports_lines_option_zero() {
|
||||||
|
new_ucmd!().arg("-l").arg("0").succeeds();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn supports_lines_option_large() {
|
||||||
|
new_ucmd!().arg("-l").arg("1000").succeeds();
|
||||||
|
}
|
||||||
|
|
||||||
|
// NUMA option tests
|
||||||
|
#[test]
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn supports_numa_option() {
|
||||||
|
new_ucmd!().arg("-n").succeeds();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn supports_numa_option_long() {
|
||||||
|
new_ucmd!().arg("--numa").succeeds();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Human-readable format tests
|
||||||
|
#[test]
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn supports_human_option() {
|
||||||
|
new_ucmd!().arg("-H").succeeds();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn supports_human_option_long() {
|
||||||
|
new_ucmd!().arg("--human").succeeds();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Once option tests
|
||||||
|
#[test]
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn supports_once_option() {
|
||||||
|
new_ucmd!().arg("-o").succeeds();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn supports_once_option_long() {
|
||||||
|
new_ucmd!().arg("--once").succeeds();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delay option tests
|
||||||
|
#[test]
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn supports_delay_option_zero() {
|
||||||
|
new_ucmd!().arg("-d").arg("0").succeeds();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn supports_delay_option_long() {
|
||||||
|
new_ucmd!().arg("--delay").arg("0").succeeds();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Combined options tests
|
||||||
|
#[test]
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn combined_lines_and_human() {
|
||||||
|
new_ucmd!().arg("-l").arg("5").arg("-H").succeeds();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn combined_numa_and_human() {
|
||||||
|
new_ucmd!().arg("-n").arg("-H").succeeds();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn combined_lines_and_numa() {
|
||||||
|
new_ucmd!().arg("-l").arg("3").arg("-n").succeeds();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn combined_once_and_human() {
|
||||||
|
new_ucmd!().arg("-o").arg("-H").succeeds();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn combined_lines_human_and_once() {
|
||||||
|
new_ucmd!()
|
||||||
|
.arg("-l")
|
||||||
|
.arg("2")
|
||||||
|
.arg("-H")
|
||||||
|
.arg("-o")
|
||||||
|
.succeeds();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn combined_all_options() {
|
||||||
|
new_ucmd!()
|
||||||
|
.arg("-l")
|
||||||
|
.arg("5")
|
||||||
|
.arg("-H")
|
||||||
|
.arg("-n")
|
||||||
|
.arg("-o")
|
||||||
|
.succeeds();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn combined_delay_and_once() {
|
||||||
|
new_ucmd!().arg("-d").arg("0").arg("-o").succeeds();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Long form option combinations
|
||||||
|
#[test]
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn long_form_options() {
|
||||||
|
new_ucmd!()
|
||||||
|
.arg("--lines")
|
||||||
|
.arg("3")
|
||||||
|
.arg("--human")
|
||||||
|
.arg("--once")
|
||||||
|
.succeeds();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn long_form_with_numa_and_delay() {
|
||||||
|
new_ucmd!()
|
||||||
|
.arg("--lines")
|
||||||
|
.arg("4")
|
||||||
|
.arg("--numa")
|
||||||
|
.arg("--delay")
|
||||||
|
.arg("0")
|
||||||
|
.succeeds();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Help tests
|
||||||
|
#[test]
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn help_short_flag() {
|
||||||
|
new_ucmd!().arg("-h").succeeds();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn help_long_flag() {
|
||||||
|
new_ucmd!().arg("--help").succeeds();
|
||||||
|
}
|
||||||
@@ -23,6 +23,10 @@ mod test_pwdx;
|
|||||||
#[path = "by-util/test_free.rs"]
|
#[path = "by-util/test_free.rs"]
|
||||||
mod test_free;
|
mod test_free;
|
||||||
|
|
||||||
|
#[cfg(feature = "hugetop")]
|
||||||
|
#[path = "by-util/test_hugetop.rs"]
|
||||||
|
mod test_hugetop;
|
||||||
|
|
||||||
#[cfg(feature = "w")]
|
#[cfg(feature = "w")]
|
||||||
#[path = "by-util/test_w.rs"]
|
#[path = "by-util/test_w.rs"]
|
||||||
mod test_w;
|
mod test_w;
|
||||||
|
|||||||
Reference in New Issue
Block a user