Skip to content

Commit 575272b

Browse files
hugetop: initial linux support
1 parent e6261fa commit 575272b

File tree

9 files changed

+282
-5
lines changed

9 files changed

+282
-5
lines changed

Cargo.lock

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ feat_common_core = [
4848
"vmstat",
4949
"w",
5050
"watch",
51+
"hugetop",
5152
]
5253

5354
[workspace.dependencies]
@@ -106,6 +107,7 @@ top = { optional = true, version = "0.0.1", package = "uu_top", path = "src/uu/t
106107
vmstat = { optional = true, version = "0.0.1", package = "uu_vmstat", path = "src/uu/vmstat" }
107108
w = { optional = true, version = "0.0.1", package = "uu_w", path = "src/uu/w" }
108109
watch = { optional = true, version = "0.0.1", package = "uu_watch", path = "src/uu/watch" }
110+
hugetop = {optional = true, version = "0.0.1", package = "uu_hugetop", path = "src/uu/hugetop"}
109111

110112
[dev-dependencies]
111113
chrono = { workspace = true }

src/uu/hugetop/Cargo.toml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
[package]
2+
name = "uu_hugetop"
3+
version = "0.0.1"
4+
edition = "2021"
5+
authors = ["uutils developers"]
6+
license = "MIT"
7+
description = "hugetop ~ (uutils) Report huge page information"
8+
9+
homepage = "https://github.com/uutils/procps"
10+
repository = "https://github.com/uutils/procps/tree/main/src/uu/hugetop"
11+
keywords = ["acl", "uutils", "cross-platform", "cli", "utility"]
12+
categories = ["command-line-utilities"]
13+
14+
[dependencies]
15+
bytesize = { workspace = true }
16+
clap = { workspace = true }
17+
sysinfo = { workspace = true }
18+
uucore = { workspace = true }
19+
uu_pmap = { path = "../pmap" }
20+
uu_top= { path = "../top" }
21+
22+
#[target.'cfg(target_os="windows")'.dependencies]
23+
# windows = { workspace = true, features = ["Wdk_System_SystemInformation", "Win32_System_ProcessStatus", "Win32_System_SystemInformation"] }
24+
25+
[lib]
26+
path = "src/hugetop.rs"
27+
28+
[[bin]]
29+
name = "hugetop"
30+
path = "src/main.rs"

src/uu/hugetop/hugetop.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# free
2+
3+
```
4+
free [options]
5+
```
6+
7+
Display amount of free and used memory in the system

src/uu/hugetop/src/hugetop.rs

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
// This file is part of the uutils procps package.
2+
//
3+
// For the full copyright and license information, please view the LICENSE
4+
// file that was distributed with this source code.
5+
6+
use clap::{arg, crate_version, ArgAction, Command};
7+
use std::env;
8+
use std::fs;
9+
use std::io::Error;
10+
use std::path::Path;
11+
use std::process;
12+
use uu_pmap::smaps_format_parser::parse_smap_entries;
13+
use uu_pmap::smaps_format_parser::SmapEntry;
14+
use uu_top::header;
15+
use uucore::uptime::get_formatted_time;
16+
use uucore::{error::UResult, format_usage, help_about, help_usage};
17+
18+
const ABOUT: &str = help_about!("hugetop.md");
19+
const USAGE: &str = help_usage!("hugetop.md");
20+
21+
#[derive(Debug)]
22+
struct ProcessHugepageInfo {
23+
pid: u32,
24+
name: String,
25+
entries: Vec<SmapEntry>,
26+
}
27+
28+
#[derive(Default, Debug)]
29+
struct HugePageSizeInfo {
30+
size_kb: u64,
31+
free: u64,
32+
total: u64,
33+
}
34+
35+
impl std::fmt::Display for HugePageSizeInfo {
36+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37+
let size_str = match self.size_kb {
38+
2048 => "2Mi",
39+
1048576 => "1Gi",
40+
_ => panic!("{}", self.size_kb),
41+
};
42+
43+
write!(f, "{} - {}/{}", size_str, self.free, self.total)
44+
}
45+
}
46+
47+
fn parse_hugepage() -> Result<Vec<HugePageSizeInfo>, Error> {
48+
let parse_hugepage_value = |p: &Path| -> Result<u64, Error> {
49+
fs::read_to_string(p)?.trim().parse().map_err(|_| {
50+
std::io::Error::new(
51+
std::io::ErrorKind::InvalidData,
52+
"Invalid memory info format",
53+
)
54+
})
55+
};
56+
57+
let info_dir = fs::read_dir("/sys/kernel/mm/hugepages")?;
58+
59+
let mut sizes = Vec::new();
60+
61+
for entry in info_dir {
62+
let entry = entry?;
63+
64+
let mut info = HugePageSizeInfo::default();
65+
66+
info.total = parse_hugepage_value(&entry.path().join("nr_hugepages"))?;
67+
info.free = parse_hugepage_value(&entry.path().join("free_hugepages"))?;
68+
info.size_kb = entry
69+
.file_name()
70+
.into_string()
71+
.unwrap()
72+
.split("-")
73+
.nth(1)
74+
.unwrap()
75+
.replace("kB", "")
76+
.parse()
77+
.map_err(|_| {
78+
std::io::Error::new(
79+
std::io::ErrorKind::InvalidData,
80+
"Invalid memory info format",
81+
)
82+
})?;
83+
84+
sizes.push(info);
85+
}
86+
87+
Ok(sizes)
88+
}
89+
90+
fn parse_process_info(p: &fs::DirEntry) -> Option<ProcessHugepageInfo> {
91+
let pid_str = p.file_name().into_string().unwrap_or_default();
92+
93+
// Skip non-PID directories
94+
let pid = pid_str.parse::<u32>().ok()?;
95+
96+
// Parse name
97+
let name = fs::read_to_string(p.path().join("status"))
98+
.ok()?
99+
.lines()
100+
.nth(0)
101+
.unwrap_or_default()
102+
.split(":")
103+
.nth(1)
104+
.unwrap_or_default()
105+
.trim()
106+
.to_string();
107+
108+
let contents = fs::read_to_string(p.path().join("smaps")).ok()?;
109+
let smap_entries = parse_smap_entries(&contents).ok()?;
110+
let smap_entries: Vec<_> = smap_entries
111+
.into_iter()
112+
.filter(|entry| entry.kernel_page_size_in_kb >= 2024)
113+
.collect();
114+
115+
if smap_entries.is_empty() {
116+
return None;
117+
}
118+
119+
Some(ProcessHugepageInfo {
120+
name,
121+
pid,
122+
entries: smap_entries,
123+
})
124+
}
125+
126+
#[cfg(target_os = "linux")]
127+
fn parse_process_hugepages() -> Result<Vec<ProcessHugepageInfo>, Error> {
128+
let mut processes = Vec::new();
129+
let proc_dir = fs::read_dir("/proc")?;
130+
131+
for entry in proc_dir {
132+
let entry = entry?;
133+
if let Some(info) = parse_process_info(&entry) {
134+
processes.push(info);
135+
}
136+
}
137+
138+
Ok(processes)
139+
}
140+
141+
#[uucore::main]
142+
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
143+
match parse_hugepage() {
144+
Ok(sys_info) => match parse_process_hugepages() {
145+
Ok(p_info) => {
146+
print!("{}", construct_str(sys_info, &p_info,));
147+
}
148+
Err(e) => {
149+
eprintln!("hugetop: failed to read process hugepage info: {}", e);
150+
process::exit(1);
151+
}
152+
},
153+
Err(e) => {
154+
eprintln!("hugetop: failed to read hugepage info: {}", e);
155+
process::exit(1);
156+
}
157+
}
158+
159+
Ok(())
160+
}
161+
162+
pub fn uu_app() -> Command {
163+
Command::new(uucore::util_name())
164+
.version(crate_version!())
165+
.about(ABOUT)
166+
.override_usage(format_usage(USAGE))
167+
.args_override_self(true)
168+
.infer_long_args(true)
169+
.disable_help_flag(true)
170+
.arg(arg!(--help "display this help and exit").action(ArgAction::SetTrue))
171+
}
172+
173+
fn construct_str(sys: Vec<HugePageSizeInfo>, processes: &[ProcessHugepageInfo]) -> String {
174+
let mut output = String::new();
175+
176+
output.push_str(&construct_system_str(sys));
177+
output.push_str(&format_process_str(processes));
178+
179+
output
180+
}
181+
182+
fn format_process_str(processes: &[ProcessHugepageInfo]) -> String {
183+
let mut output = String::new();
184+
let header = format!(
185+
"{:<8} {:<12} {:<12} {:<12}\n",
186+
"PID", "Private", "Shared", "Process"
187+
);
188+
189+
output.push_str(&header);
190+
191+
for process in processes {
192+
for smap_entry in &process.entries {
193+
output.push_str(&format!(
194+
"{:<8} {:<12} {:<12} {:<12}\n",
195+
process.pid,
196+
smap_entry.private_hugetlb_in_kb,
197+
smap_entry.shared_hugetlb_in_kb,
198+
process.name
199+
));
200+
}
201+
}
202+
203+
output
204+
}
205+
206+
fn construct_system_str(sys: Vec<HugePageSizeInfo>) -> String {
207+
let mut output = String::new();
208+
output.push_str(&format!(
209+
"top - {time} {uptime}, {user}\n",
210+
time = get_formatted_time(),
211+
uptime = header::uptime(),
212+
user = header::user(),
213+
));
214+
215+
for (i, info) in sys.iter().enumerate() {
216+
if i < sys.len() - 1 {
217+
output.push_str(&format!("{}, ", info.to_string()));
218+
} else {
219+
output.push_str(&info.to_string());
220+
output.push('\n');
221+
}
222+
}
223+
224+
output
225+
}

src/uu/hugetop/src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
uucore::bin!(uu_hugetop);

src/uu/pmap/src/pmap.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@ use uucore::error::{set_exit_code, UResult};
1414
use uucore::{format_usage, help_about, help_usage};
1515

1616
mod maps_format_parser;
17-
mod pmap_config;
18-
mod smaps_format_parser;
17+
pub mod smaps_format_parser;
1918

2019
const ABOUT: &str = help_about!("pmap.md");
2120
const USAGE: &str = help_usage!("pmap.md");

src/uu/top/src/header.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ fn format_memory(memory_b: u64, unit: u64) -> f64 {
3939
}
4040

4141
#[inline]
42-
fn uptime() -> String {
42+
pub fn uptime() -> String {
4343
get_formatted_uptime_procps().unwrap_or_default()
4444
}
4545

@@ -91,7 +91,7 @@ pub fn get_nusers_systemd() -> uucore::error::UResult<usize> {
9191
}
9292

9393
// see: https://gitlab.com/procps-ng/procps/-/blob/4740a0efa79cade867cfc7b32955fe0f75bf5173/library/uptime.c#L63-L115
94-
fn user() -> String {
94+
pub fn user() -> String {
9595
#[cfg(target_os = "linux")]
9696
if let Ok(nusers) = get_nusers_systemd() {
9797
return uucore::uptime::format_nusers(nusers);

src/uu/top/src/top.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const ABOUT: &str = help_about!("top.md");
1919
const USAGE: &str = help_usage!("top.md");
2020

2121
mod field;
22-
mod header;
22+
pub mod header;
2323
mod picker;
2424

2525
#[allow(unused)]

0 commit comments

Comments
 (0)