Skip to content

Commit

Permalink
top: implement SUMMARY display
Browse files Browse the repository at this point in the history
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`
  • Loading branch information
Bluemangoo committed Jan 22, 2025
1 parent f296162 commit dbb2b73
Show file tree
Hide file tree
Showing 6 changed files with 278 additions and 5 deletions.
32 changes: 32 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ uucore = "0.0.29"
walkdir = "2.5.0"
windows = { version = "0.59.0" }
xattr = "1.3.1"
systemstat = "0.2.4"

[dependencies]
clap = { workspace = true }
Expand Down
2 changes: 2 additions & 0 deletions src/uu/top/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ nix = { workspace = true }
prettytable-rs = { workspace = true }
sysinfo = { workspace = true }
chrono = { workspace = true }
systemstat = { workspace = true }
bytesize = { workspace = true }

[lib]
path = "src/top.rs"
Expand Down
232 changes: 232 additions & 0 deletions src/uu/top/src/header.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
use crate::picker::{sysinfo, systemstat};
use bytesize::ByteSize;
use systemstat::Platform;
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
use {
std::sync::{Mutex, OnceLock},
systemstat::{CPULoad, DelayedMeasurement},
};

#[cfg(not(any(target_os = "macos", target_os = "linux")))]
static LOAD_AVERAGE: OnceLock<Mutex<DelayedMeasurement<CPULoad>>> = OnceLock::new();

#[cfg(not(any(target_os = "macos", target_os = "linux")))]
pub(crate) fn cpu_load() -> CPULoad {
match LOAD_AVERAGE.get() {
None => {
LOAD_AVERAGE.get_or_init(|| {
Mutex::new(systemstat().read().unwrap().cpu_load_aggregate().unwrap())
});
systemstat()
.read()
.unwrap()
.cpu_load_aggregate()
.unwrap()
.done()
.unwrap()
}
Some(v) => {
let mut v = v.lock().unwrap();
let load = v.done().unwrap();
*v = systemstat().read().unwrap().cpu_load_aggregate().unwrap();
load
}
}
}

pub(crate) fn header() -> String {
format!(
"top - {time} {uptime}, {user}, {load_average}\n\
{task}\n\
{cpu}\n\
{memory}\n\
{swap}",
time = chrono::Local::now().format("%H:%M:%S"),
uptime = uptime(),
user = user(),
load_average = load_average(),
task = task(),
cpu = cpu(),
memory = memory(),
swap = swap(),
)
}

fn todo() -> String {
"TODO".into()
}

fn format_memory(memory_b: u64, unit: u64) -> f64 {
ByteSize::b(memory_b).0 as f64 / unit as f64
}

fn uptime() -> String {
let binding = systemstat().read().unwrap();

let up_seconds = binding.uptime().unwrap().as_secs();
let up_minutes = (up_seconds % (60 * 60)) / 60;
let up_hours = (up_seconds % (24 * 60 * 60)) / (60 * 60);
let up_days = up_seconds / (24 * 60 * 60);

let mut res = String::from("up ");

if up_days > 0 {
res.push_str(&format!(
"{} day{}, ",
up_days,
if up_days > 1 { "s" } else { "" }
));
}
if up_hours > 0 {
res.push_str(&format!("{}:{:0>2}", up_hours, up_minutes));
} else {
res.push_str(&format!("{} min", up_minutes));
}

res
}

//TODO: Implement active user count
fn user() -> String {
todo()
}

#[cfg(not(target_os = "windows"))]
fn load_average() -> String {
let binding = systemstat().read().unwrap();

let load_average = binding.load_average().unwrap();
format!(
"load average: {:.2}, {:.2}, {:.2}",
load_average.one, load_average.five, load_average.fifteen
)
}

#[cfg(target_os = "windows")]
fn load_average() -> String{
todo()
}

fn task() -> String {
let binding = sysinfo().read().unwrap();

let process = binding.processes();
let mut running_process = 0;
let mut sleeping_process = 0;
let mut stopped_process = 0;
let mut zombie_process = 0;

for (_, process) in process.iter() {
match process.status() {
sysinfo::ProcessStatus::Run => running_process += 1,
sysinfo::ProcessStatus::Sleep => sleeping_process += 1,
sysinfo::ProcessStatus::Stop => stopped_process += 1,
sysinfo::ProcessStatus::Zombie => zombie_process += 1,
_ => {}
};
}

format!(
"Tasks: {} total, {} running, {} sleeping, {} stopped, {} zombie",
process.len(),
running_process,
sleeping_process,
stopped_process,
zombie_process,
)
}

#[cfg(target_os = "linux")]
fn cpu() -> String {
let file = std::fs::File::open(std::path::Path::new("/proc/stat")).unwrap();
let content = std::io::read_to_string(file).unwrap();
let load = content
.lines()
.next()
.unwrap()
.strip_prefix("cpu")
.unwrap()
.split(' ')
.filter(|s| !s.is_empty())
.collect::<Vec<_>>();
let user = load[0].parse::<f64>().unwrap();
let nice = load[1].parse::<f64>().unwrap();
let system = load[2].parse::<f64>().unwrap();
let idle = load[3].parse::<f64>().unwrap_or_default(); // since 2.5.41
let io_wait = load[4].parse::<f64>().unwrap_or_default(); // since 2.5.41
let hardware_interrupt = load[5].parse::<f64>().unwrap_or_default(); // since 2.6.0
let software_interrupt = load[6].parse::<f64>().unwrap_or_default(); // since 2.6.0
let steal_time = load[7].parse::<f64>().unwrap_or_default(); // since 2.6.11
// GNU do not show guest and guest_nice
let guest = load[8].parse::<f64>().unwrap_or_default(); // since 2.6.24
let guest_nice = load[9].parse::<f64>().unwrap_or_default(); // since 2.6.33
let total = user
+ nice
+ system
+ idle
+ io_wait
+ hardware_interrupt
+ software_interrupt
+ steal_time
+ guest
+ guest_nice;

format!(
"%Cpu(s): {:.1} us, {:.1} sy, {:.1} ni, {:.1} id, {:.1} wa, {:.1} hi, {:.1} si, {:.1} st",
user / total * 100.0,
system / total * 100.0,
nice / total * 100.0,
idle / total * 100.0,
io_wait / total * 100.0,
hardware_interrupt / total * 100.0,
software_interrupt / total * 100.0,
steal_time / total * 100.0,
)
}

//TODO: Implement io wait, hardware interrupt, software interrupt and steal time
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
fn cpu() -> String {
let cpu = cpu_load();
format!(
"%Cpu(s): {:.1} us, {:.1} sy, {:.1} ni, {:.1} id",
cpu.user * 100.0,
cpu.system * 100.0,
cpu.nice * 100.0,
cpu.idle * 100.0
)
}

//TODO: Implement for macos
#[cfg(target_os = "macos")]
fn cpu() -> String {
todo()
}

fn memory() -> String {
let binding = sysinfo().read().unwrap();
//TODO: unit from argument
let unit = bytesize::MIB;

format!(
"MiB Mem : {:8.1} total, {:8.1} free, {:8.1} used, {:8.1} buff/cache",
format_memory(binding.total_memory(), unit),
format_memory(binding.free_memory(), unit),
format_memory(binding.used_memory(), unit),
format_memory(binding.total_memory() - binding.free_memory(), unit),
)
}

fn swap() -> String {
let binding = sysinfo().read().unwrap();
//TODO: unit from argument
let unit = bytesize::MIB;

format!(
"MiB Swap: {:8.1} total, {:8.1} free, {:8.1} used, {:8.1} avail Mem",
format_memory(binding.total_swap(), unit),
format_memory(binding.free_swap(), unit),
format_memory(binding.used_swap(), unit),
format_memory(binding.total_memory() - binding.free_memory(), unit),
)
}
6 changes: 6 additions & 0 deletions src/uu/top/src/picker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,19 @@ use std::{
sync::{OnceLock, RwLock},
};
use sysinfo::{Pid, System, Users};
use systemstat::Platform;

static SYSINFO: OnceLock<RwLock<System>> = OnceLock::new();
static SYSTEMSTAT: OnceLock<RwLock<systemstat::System>> = OnceLock::new();

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

pub fn systemstat() -> &'static RwLock<systemstat::System> {
SYSTEMSTAT.get_or_init(|| RwLock::new(systemstat::System::new()))
}

pub(crate) fn pickers(fields: &[String]) -> Vec<Box<dyn Fn(u32) -> String>> {
fields
.iter()
Expand Down
10 changes: 5 additions & 5 deletions src/uu/top/src/top.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
// file that was distributed with this source code.

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

mod field;
mod header;
mod picker;

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

Expand Down Expand Up @@ -157,11 +162,6 @@ where
}
}

// TODO: Implement information collecting.
fn header() -> String {
"TODO".into()
}

// TODO: Implement fields selecting
fn selected_fields() -> Vec<String> {
vec![
Expand Down

0 comments on commit dbb2b73

Please sign in to comment.