From dbb2b7392cc2f6f591a9bd4021377f4043d1d258 Mon Sep 17 00:00:00 2001 From: Bluemangoo Date: Fri, 10 Jan 2025 15:51:04 +0800 Subject: [PATCH] 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` --- Cargo.lock | 32 ++++++ Cargo.toml | 1 + src/uu/top/Cargo.toml | 2 + src/uu/top/src/header.rs | 232 +++++++++++++++++++++++++++++++++++++++ src/uu/top/src/picker.rs | 6 + src/uu/top/src/top.rs | 10 +- 6 files changed, 278 insertions(+), 5 deletions(-) create mode 100644 src/uu/top/src/header.rs diff --git a/Cargo.lock b/Cargo.lock index 21285747..2813bfa2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -444,6 +444,12 @@ version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "nix" version = "0.29.0" @@ -456,6 +462,16 @@ dependencies = [ "libc", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "ntapi" version = "0.4.1" @@ -864,6 +880,20 @@ dependencies = [ "windows 0.57.0", ] +[[package]] +name = "systemstat" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668a4db78b439df482c238f559e4ea869017f9e62ef0a059c8bfcd841a4df544" +dependencies = [ + "bytesize", + "lazy_static", + "libc", + "nom", + "time", + "winapi", +] + [[package]] name = "tempfile" version = "3.14.0" @@ -1136,12 +1166,14 @@ dependencies = [ name = "uu_top" version = "0.0.1" dependencies = [ + "bytesize", "chrono", "clap", "libc", "nix", "prettytable-rs", "sysinfo", + "systemstat", "uucore", ] diff --git a/Cargo.toml b/Cargo.toml index 02975cd8..0186f298 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 } diff --git a/src/uu/top/Cargo.toml b/src/uu/top/Cargo.toml index 800a2fc3..8e4855e5 100644 --- a/src/uu/top/Cargo.toml +++ b/src/uu/top/Cargo.toml @@ -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" diff --git a/src/uu/top/src/header.rs b/src/uu/top/src/header.rs new file mode 100644 index 00000000..4c0d2b06 --- /dev/null +++ b/src/uu/top/src/header.rs @@ -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>> = 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::>(); + let user = load[0].parse::().unwrap(); + let nice = load[1].parse::().unwrap(); + let system = load[2].parse::().unwrap(); + let idle = load[3].parse::().unwrap_or_default(); // since 2.5.41 + let io_wait = load[4].parse::().unwrap_or_default(); // since 2.5.41 + let hardware_interrupt = load[5].parse::().unwrap_or_default(); // since 2.6.0 + let software_interrupt = load[6].parse::().unwrap_or_default(); // since 2.6.0 + let steal_time = load[7].parse::().unwrap_or_default(); // since 2.6.11 + // GNU do not show guest and guest_nice + let guest = load[8].parse::().unwrap_or_default(); // since 2.6.24 + let guest_nice = load[9].parse::().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), + ) +} diff --git a/src/uu/top/src/picker.rs b/src/uu/top/src/picker.rs index f103e9b4..b96828ae 100644 --- a/src/uu/top/src/picker.rs +++ b/src/uu/top/src/picker.rs @@ -12,13 +12,19 @@ use std::{ sync::{OnceLock, RwLock}, }; use sysinfo::{Pid, System, Users}; +use systemstat::Platform; static SYSINFO: OnceLock> = OnceLock::new(); +static SYSTEMSTAT: OnceLock> = OnceLock::new(); pub fn sysinfo() -> &'static RwLock { SYSINFO.get_or_init(|| RwLock::new(System::new_all())) } +pub fn systemstat() -> &'static RwLock { + SYSTEMSTAT.get_or_init(|| RwLock::new(systemstat::System::new())) +} + pub(crate) fn pickers(fields: &[String]) -> Vec String>> { fields .iter() diff --git a/src/uu/top/src/top.rs b/src/uu/top/src/top.rs index 6c4a12cc..b9e8c373 100644 --- a/src/uu/top/src/top.rs +++ b/src/uu/top/src/top.rs @@ -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}; @@ -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)] @@ -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(); @@ -157,11 +162,6 @@ where } } -// TODO: Implement information collecting. -fn header() -> String { - "TODO".into() -} - // TODO: Implement fields selecting fn selected_fields() -> Vec { vec![