Skip to content

Commit 4674776

Browse files
committed
top: implement SUMMARY display
todo: io wait(only Linux now) cpu load average for Windows cpu load for Macos active user count determine memory unit from `--scale-summary-mem`
1 parent ee7ae52 commit 4674776

File tree

6 files changed

+278
-5
lines changed

6 files changed

+278
-5
lines changed

Cargo.lock

Lines changed: 32 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: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ uucore = "0.0.29"
6363
walkdir = "2.5.0"
6464
windows = { version = "0.59.0" }
6565
xattr = "1.3.1"
66+
systemstat = "0.2.4"
6667

6768
[dependencies]
6869
clap = { workspace = true }

src/uu/top/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ nix = { workspace = true }
1919
prettytable-rs = { workspace = true }
2020
sysinfo = { workspace = true }
2121
chrono = { workspace = true }
22+
systemstat = { workspace = true }
23+
bytesize = { workspace = true }
2224

2325
[lib]
2426
path = "src/top.rs"

src/uu/top/src/header.rs

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
use crate::picker::{sysinfo, systemstat};
2+
use bytesize::ByteSize;
3+
use systemstat::Platform;
4+
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
5+
use {
6+
std::sync::{Mutex, OnceLock},
7+
systemstat::{CPULoad, DelayedMeasurement},
8+
};
9+
10+
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
11+
static LOAD_AVERAGE: OnceLock<Mutex<DelayedMeasurement<CPULoad>>> = OnceLock::new();
12+
13+
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
14+
pub(crate) fn cpu_load() -> CPULoad {
15+
match LOAD_AVERAGE.get() {
16+
None => {
17+
LOAD_AVERAGE.get_or_init(|| {
18+
Mutex::new(systemstat().read().unwrap().cpu_load_aggregate().unwrap())
19+
});
20+
systemstat()
21+
.read()
22+
.unwrap()
23+
.cpu_load_aggregate()
24+
.unwrap()
25+
.done()
26+
.unwrap()
27+
}
28+
Some(v) => {
29+
let mut v = v.lock().unwrap();
30+
let load = v.done().unwrap();
31+
*v = systemstat().read().unwrap().cpu_load_aggregate().unwrap();
32+
load
33+
}
34+
}
35+
}
36+
37+
pub(crate) fn header() -> String {
38+
format!(
39+
"top - {time} {uptime}, {user}, {load_average}\n\
40+
{task}\n\
41+
{cpu}\n\
42+
{memory}\n\
43+
{swap}",
44+
time = chrono::Local::now().format("%H:%M:%S"),
45+
uptime = uptime(),
46+
user = user(),
47+
load_average = load_average(),
48+
task = task(),
49+
cpu = cpu(),
50+
memory = memory(),
51+
swap = swap(),
52+
)
53+
}
54+
55+
fn todo() -> String {
56+
"TODO".into()
57+
}
58+
59+
fn format_memory(memory_b: u64, unit: u64) -> f64 {
60+
ByteSize::b(memory_b).0 as f64 / unit as f64
61+
}
62+
63+
fn uptime() -> String {
64+
let binding = systemstat().read().unwrap();
65+
66+
let up_seconds = binding.uptime().unwrap().as_secs();
67+
let up_minutes = (up_seconds % (60 * 60)) / 60;
68+
let up_hours = (up_seconds % (24 * 60 * 60)) / (60 * 60);
69+
let up_days = up_seconds / (24 * 60 * 60);
70+
71+
let mut res = String::from("up ");
72+
73+
if up_days > 0 {
74+
res.push_str(&format!(
75+
"{} day{}, ",
76+
up_days,
77+
if up_days > 1 { "s" } else { "" }
78+
));
79+
}
80+
if up_hours > 0 {
81+
res.push_str(&format!("{}:{:0>2}", up_hours, up_minutes));
82+
} else {
83+
res.push_str(&format!("{} min", up_minutes));
84+
}
85+
86+
res
87+
}
88+
89+
//TODO: Implement active user count
90+
fn user() -> String {
91+
todo()
92+
}
93+
94+
#[cfg(not(target_os = "windows"))]
95+
fn load_average() -> String {
96+
let binding = systemstat().read().unwrap();
97+
98+
let load_average = binding.load_average().unwrap();
99+
format!(
100+
"load average: {:.2}, {:.2}, {:.2}",
101+
load_average.one, load_average.five, load_average.fifteen
102+
)
103+
}
104+
105+
#[cfg(target_os = "windows")]
106+
fn load_average() -> String{
107+
todo()
108+
}
109+
110+
fn task() -> String {
111+
let binding = sysinfo().read().unwrap();
112+
113+
let process = binding.processes();
114+
let mut running_process = 0;
115+
let mut sleeping_process = 0;
116+
let mut stopped_process = 0;
117+
let mut zombie_process = 0;
118+
119+
for (_, process) in process.iter() {
120+
match process.status() {
121+
sysinfo::ProcessStatus::Run => running_process += 1,
122+
sysinfo::ProcessStatus::Sleep => sleeping_process += 1,
123+
sysinfo::ProcessStatus::Stop => stopped_process += 1,
124+
sysinfo::ProcessStatus::Zombie => zombie_process += 1,
125+
_ => {}
126+
};
127+
}
128+
129+
format!(
130+
"Tasks: {} total, {} running, {} sleeping, {} stopped, {} zombie",
131+
process.len(),
132+
running_process,
133+
sleeping_process,
134+
stopped_process,
135+
zombie_process,
136+
)
137+
}
138+
139+
#[cfg(target_os = "linux")]
140+
fn cpu() -> String {
141+
let file = std::fs::File::open(std::path::Path::new("/proc/stat")).unwrap();
142+
let content = std::io::read_to_string(file).unwrap();
143+
let load = content
144+
.lines()
145+
.next()
146+
.unwrap()
147+
.strip_prefix("cpu")
148+
.unwrap()
149+
.split(' ')
150+
.filter(|s| !s.is_empty())
151+
.collect::<Vec<_>>();
152+
let user = load[0].parse::<f64>().unwrap();
153+
let nice = load[1].parse::<f64>().unwrap();
154+
let system = load[2].parse::<f64>().unwrap();
155+
let idle = load[3].parse::<f64>().unwrap_or_default(); // since 2.5.41
156+
let io_wait = load[4].parse::<f64>().unwrap_or_default(); // since 2.5.41
157+
let hardware_interrupt = load[5].parse::<f64>().unwrap_or_default(); // since 2.6.0
158+
let software_interrupt = load[6].parse::<f64>().unwrap_or_default(); // since 2.6.0
159+
let steal_time = load[7].parse::<f64>().unwrap_or_default(); // since 2.6.11
160+
// GNU do not show guest and guest_nice
161+
let guest = load[8].parse::<f64>().unwrap_or_default(); // since 2.6.24
162+
let guest_nice = load[9].parse::<f64>().unwrap_or_default(); // since 2.6.33
163+
let total = user
164+
+ nice
165+
+ system
166+
+ idle
167+
+ io_wait
168+
+ hardware_interrupt
169+
+ software_interrupt
170+
+ steal_time
171+
+ guest
172+
+ guest_nice;
173+
174+
format!(
175+
"%Cpu(s): {:.1} us, {:.1} sy, {:.1} ni, {:.1} id, {:.1} wa, {:.1} hi, {:.1} si, {:.1} st",
176+
user / total * 100.0,
177+
system / total * 100.0,
178+
nice / total * 100.0,
179+
idle / total * 100.0,
180+
io_wait / total * 100.0,
181+
hardware_interrupt / total * 100.0,
182+
software_interrupt / total * 100.0,
183+
steal_time / total * 100.0,
184+
)
185+
}
186+
187+
//TODO: Implement io wait, hardware interrupt, software interrupt and steal time
188+
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
189+
fn cpu() -> String {
190+
let cpu = cpu_load();
191+
format!(
192+
"%Cpu(s): {:.1} us, {:.1} sy, {:.1} ni, {:.1} id",
193+
cpu.user * 100.0,
194+
cpu.system * 100.0,
195+
cpu.nice * 100.0,
196+
cpu.idle * 100.0
197+
)
198+
}
199+
200+
//TODO: Implement for macos
201+
#[cfg(target_os = "macos")]
202+
fn cpu() -> String {
203+
todo()
204+
}
205+
206+
fn memory() -> String {
207+
let binding = sysinfo().read().unwrap();
208+
//TODO: unit from argument
209+
let unit = bytesize::MIB;
210+
211+
format!(
212+
"MiB Mem : {:8.1} total, {:8.1} free, {:8.1} used, {:8.1} buff/cache",
213+
format_memory(binding.total_memory(), unit),
214+
format_memory(binding.free_memory(), unit),
215+
format_memory(binding.used_memory(), unit),
216+
format_memory(binding.total_memory() - binding.free_memory(), unit),
217+
)
218+
}
219+
220+
fn swap() -> String {
221+
let binding = sysinfo().read().unwrap();
222+
//TODO: unit from argument
223+
let unit = bytesize::MIB;
224+
225+
format!(
226+
"MiB Swap: {:8.1} total, {:8.1} free, {:8.1} used, {:8.1} avail Mem",
227+
format_memory(binding.total_swap(), unit),
228+
format_memory(binding.free_swap(), unit),
229+
format_memory(binding.used_swap(), unit),
230+
format_memory(binding.total_memory() - binding.free_memory(), unit),
231+
)
232+
}

src/uu/top/src/picker.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,19 @@ use std::{
1212
sync::{OnceLock, RwLock},
1313
};
1414
use sysinfo::{Pid, System, Users};
15+
use systemstat::Platform;
1516

1617
static SYSINFO: OnceLock<RwLock<System>> = OnceLock::new();
18+
static SYSTEMSTAT: OnceLock<RwLock<systemstat::System>> = OnceLock::new();
1719

1820
pub fn sysinfo() -> &'static RwLock<System> {
1921
SYSINFO.get_or_init(|| RwLock::new(System::new_all()))
2022
}
2123

24+
pub fn systemstat() -> &'static RwLock<systemstat::System> {
25+
SYSTEMSTAT.get_or_init(|| RwLock::new(systemstat::System::new()))
26+
}
27+
2228
pub(crate) fn pickers(fields: &[String]) -> Vec<Box<dyn Fn(u32) -> String>> {
2329
fields
2430
.iter()

src/uu/top/src/top.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
// file that was distributed with this source code.
55

66
use clap::{arg, crate_version, value_parser, ArgAction, ArgGroup, ArgMatches, Command};
7+
use header::header;
78
use picker::pickers;
89
use picker::sysinfo;
910
use prettytable::{format::consts::FORMAT_CLEAN, Row, Table};
@@ -18,6 +19,7 @@ const ABOUT: &str = help_about!("top.md");
1819
const USAGE: &str = help_usage!("top.md");
1920

2021
mod field;
22+
mod header;
2123
mod picker;
2224

2325
#[allow(unused)]
@@ -53,6 +55,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
5355
// Must refresh twice.
5456
// https://docs.rs/sysinfo/0.31.2/sysinfo/struct.System.html#method.refresh_cpu_usage
5557
picker::sysinfo().write().unwrap().refresh_all();
58+
// Similar to the above.
59+
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
60+
crate::header::cpu_load();
5661
sleep(Duration::from_millis(200));
5762
picker::sysinfo().write().unwrap().refresh_all();
5863

@@ -157,11 +162,6 @@ where
157162
}
158163
}
159164

160-
// TODO: Implement information collecting.
161-
fn header() -> String {
162-
"TODO".into()
163-
}
164-
165165
// TODO: Implement fields selecting
166166
fn selected_fields() -> Vec<String> {
167167
vec![

0 commit comments

Comments
 (0)