From 4674776948ac4bd5e10811c9a4924315b3db6d4b Mon Sep 17 00:00:00 2001 From: Bluemangoo Date: Fri, 10 Jan 2025 15:51:04 +0800 Subject: [PATCH 1/9] 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 aa86236a..a3e78061 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![ From 66bbf74cb613adb22bd5d4877d73d6dbd8c90619 Mon Sep 17 00:00:00 2001 From: Bluemangoo Date: Wed, 15 Jan 2025 14:38:36 +0800 Subject: [PATCH 2/9] top: implement `--scale-summary-mem` --- src/uu/top/src/header.rs | 47 +++++++++++++++++++++------------------- src/uu/top/src/top.rs | 4 ++-- 2 files changed, 27 insertions(+), 24 deletions(-) diff --git a/src/uu/top/src/header.rs b/src/uu/top/src/header.rs index 4c0d2b06..73101bbd 100644 --- a/src/uu/top/src/header.rs +++ b/src/uu/top/src/header.rs @@ -1,5 +1,6 @@ use crate::picker::{sysinfo, systemstat}; use bytesize::ByteSize; +use clap::ArgMatches; use systemstat::Platform; #[cfg(not(any(target_os = "macos", target_os = "linux")))] use { @@ -34,21 +35,19 @@ pub(crate) fn cpu_load() -> CPULoad { } } -pub(crate) fn header() -> String { +pub(crate) fn header(arg: &ArgMatches) -> String { format!( "top - {time} {uptime}, {user}, {load_average}\n\ {task}\n\ {cpu}\n\ - {memory}\n\ - {swap}", + {memory}", time = chrono::Local::now().format("%H:%M:%S"), uptime = uptime(), user = user(), load_average = load_average(), task = task(), cpu = cpu(), - memory = memory(), - swap = swap(), + memory = memory(arg), ) } @@ -103,7 +102,7 @@ fn load_average() -> String { } #[cfg(target_os = "windows")] -fn load_average() -> String{ +fn load_average() -> String { todo() } @@ -203,30 +202,34 @@ fn cpu() -> String { todo() } -fn memory() -> String { +fn memory(arg: &ArgMatches) -> String { let binding = sysinfo().read().unwrap(); - //TODO: unit from argument - let unit = bytesize::MIB; + let (unit, unit_name) = match arg.get_one::("scale-summary-mem") { + Some(scale) => match scale.as_str() { + "k" => (bytesize::KIB, "KiB"), + "m" => (bytesize::MIB, "MiB"), + "g" => (bytesize::GIB, "GiB"), + "t" => (bytesize::TIB, "TiB"), + "p" => (bytesize::PIB, "PiB"), + "e" => (1_152_921_504_606_846_976, "EiB"), + _ => (bytesize::MIB, "MiB"), + }, + None => { + (bytesize::MIB, "MiB") + } + }; format!( - "MiB Mem : {:8.1} total, {:8.1} free, {:8.1} used, {:8.1} buff/cache", + "{unit_name} Mem : {:8.1} total, {:8.1} free, {:8.1} used, {:8.1} buff/cache\n\ + {unit_name} Swap: {:8.1} total, {:8.1} free, {:8.1} used, {:8.1} avail Mem", 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.available_memory() - binding.free_memory(), unit), 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), + format_memory(binding.available_memory(), unit), + unit_name = unit_name ) } diff --git a/src/uu/top/src/top.rs b/src/uu/top/src/top.rs index b9e8c373..421a2c6d 100644 --- a/src/uu/top/src/top.rs +++ b/src/uu/top/src/top.rs @@ -102,7 +102,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { table }; - println!("{}", header()); + println!("{}", header(&matches)); println!("\n"); let cutter = { @@ -261,7 +261,7 @@ pub fn uu_app() -> Command { // arg!(-b --"batch-mode" "run in non-interactive batch mode"), // arg!(-c --"cmdline-toggle" "reverse last remembered 'c' state"), // arg!(-d --delay "iterative delay as SECS [.TENTHS]"), - // arg!(-E --"scale-summary-mem" "set mem as: k,m,g,t,p,e for SCALE"), + arg!(-E --"scale-summary-mem" "set mem as: k,m,g,t,p,e for SCALE"), // arg!(-e --"scale-task-mem" "set mem with: k,m,g,t,p for SCALE"), // arg!(-H --"threads-show" "show tasks plus all their threads"), // arg!(-i --"idle-toggle" "reverse last remembered 'i' state"), From ff78299c389365ef1b60e545ca82f4248e0e5d4e Mon Sep 17 00:00:00 2001 From: Bluemangoo Date: Thu, 23 Jan 2025 18:58:13 +0800 Subject: [PATCH 3/9] top: implement user count --- Cargo.lock | 25 +++++++++ src/uu/top/Cargo.toml | 6 ++ src/uu/top/src/header.rs | 116 +++++++++++++++++++++++++++++++++++++-- 3 files changed, 142 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a3e78061..42c37a70 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -92,6 +92,12 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +[[package]] +name = "build-env" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1522ac6ee801a11bf9ef3f80403f4ede6eb41291fac3dde3de09989679305f25" + [[package]] name = "bumpalo" version = "3.16.0" @@ -420,6 +426,17 @@ dependencies = [ "libc", ] +[[package]] +name = "libsystemd-sys" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed080163caa59cc29b34bce2209b737149a4bac148cd9a8b04e4c12822798119" +dependencies = [ + "build-env", + "libc", + "pkg-config", +] + [[package]] name = "linux-raw-sys" version = "0.3.8" @@ -564,6 +581,12 @@ dependencies = [ "siphasher", ] +[[package]] +name = "pkg-config" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + [[package]] name = "powerfmt" version = "0.2.0" @@ -1170,11 +1193,13 @@ dependencies = [ "chrono", "clap", "libc", + "libsystemd-sys", "nix", "prettytable-rs", "sysinfo", "systemstat", "uucore", + "windows 0.59.0", ] [[package]] diff --git a/src/uu/top/Cargo.toml b/src/uu/top/Cargo.toml index 8e4855e5..a82e2bc1 100644 --- a/src/uu/top/Cargo.toml +++ b/src/uu/top/Cargo.toml @@ -22,6 +22,12 @@ chrono = { workspace = true } systemstat = { workspace = true } bytesize = { workspace = true } +[target.'cfg(target_os="linux")'.dependencies] +libsystemd-sys = "0.9.3" + +[target.'cfg(target_os="windows")'.dependencies] +windows = { version = "0.59.0", features = ["Win32_System_RemoteDesktop"] } + [lib] path = "src/top.rs" diff --git a/src/uu/top/src/header.rs b/src/uu/top/src/header.rs index 73101bbd..634d7cac 100644 --- a/src/uu/top/src/header.rs +++ b/src/uu/top/src/header.rs @@ -51,6 +51,7 @@ pub(crate) fn header(arg: &ArgMatches) -> String { ) } +#[cfg(not(target_os = "linux"))] fn todo() -> String { "TODO".into() } @@ -85,9 +86,116 @@ fn uptime() -> String { res } -//TODO: Implement active user count +#[inline] +fn format_user(user: u64) -> String { + match user { + 0 => "0 user".to_string(), + 1 => "1 user".to_string(), + _ => format!("{} users", user), + } +} + +#[cfg(target_os = "windows")] fn user() -> String { - todo() + use windows::{core::*, Win32::System::RemoteDesktop::*}; + + let mut num_user = 0; + + unsafe { + let mut session_info_ptr = std::ptr::null_mut(); + let mut session_count = 0; + + WTSEnumerateSessionsW( + Some(WTS_CURRENT_SERVER_HANDLE), + 0, + 1, + &mut session_info_ptr, + &mut session_count, + ) + .unwrap(); + + let sessions = std::slice::from_raw_parts(session_info_ptr, session_count as usize); + + for session in sessions { + let mut buffer = PWSTR::null(); + let mut bytes_returned = 0; + + WTSQuerySessionInformationW( + Some(WTS_CURRENT_SERVER_HANDLE), + session.SessionId, + WTS_INFO_CLASS(5), + &mut buffer, + &mut bytes_returned, + ) + .unwrap(); + + let username = PWSTR(buffer.0).to_string().unwrap_or_default(); + if !username.is_empty() { + num_user += 1; + } + + WTSFreeMemory(buffer.0 as _); + } + + WTSFreeMemory(session_info_ptr as _); + } + + format_user(num_user) +} + +#[cfg(unix)] +// see: https://gitlab.com/procps-ng/procps/-/blob/4740a0efa79cade867cfc7b32955fe0f75bf5173/library/uptime.c#L63-L115 +fn user() -> String { + use uucore::utmpx::Utmpx; + + #[cfg(target_os = "linux")] + unsafe { + use libc::free; + use libsystemd_sys::daemon::sd_booted; + use libsystemd_sys::login::{sd_get_sessions, sd_session_get_class}; + use std::ffi::{c_char, c_void, CStr}; + use std::ptr; + // systemd + if sd_booted() > 0 { + let mut sessions_list: *mut *mut c_char = ptr::null_mut(); + let mut num_user = 0; + let sessions = sd_get_sessions(&mut sessions_list); // rust-systemd does not implement this + + if sessions > 0 { + for i in 0..sessions { + let mut class: *mut c_char = ptr::null_mut(); + + if sd_session_get_class( + *sessions_list.add(i as usize) as *const c_char, + &mut class, + ) < 0 + { + continue; + } + if CStr::from_ptr(class).to_str().unwrap().starts_with("user") { + num_user += 1; + } + free(class as *mut c_void); + } + } + + for i in 0..sessions { + free(*sessions_list.add(i as usize) as *mut c_void); + } + free(sessions_list as *mut c_void); + + return format_user(num_user); + } + } + + // utmpx + let mut num_user = 0; + Utmpx::iter_all_records().for_each(|ut| { + if ut.record_type() == 7 && !ut.user().is_empty() { + num_user += 1; + } + }); + format_user(num_user) } #[cfg(not(target_os = "windows"))] @@ -214,9 +322,7 @@ fn memory(arg: &ArgMatches) -> String { "e" => (1_152_921_504_606_846_976, "EiB"), _ => (bytesize::MIB, "MiB"), }, - None => { - (bytesize::MIB, "MiB") - } + None => (bytesize::MIB, "MiB"), }; format!( From aae5099cf287d0e8cb1b010e229e5514c37d9b0f Mon Sep 17 00:00:00 2001 From: Bluemangoo Date: Thu, 23 Jan 2025 20:37:53 +0800 Subject: [PATCH 4/9] top: add licence header --- src/uu/top/src/header.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/uu/top/src/header.rs b/src/uu/top/src/header.rs index 634d7cac..bced0dbe 100644 --- a/src/uu/top/src/header.rs +++ b/src/uu/top/src/header.rs @@ -1,3 +1,8 @@ +// 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 crate::picker::{sysinfo, systemstat}; use bytesize::ByteSize; use clap::ArgMatches; From b7f8e7c911c3fc34eb68a39e96176f56a93e4636 Mon Sep 17 00:00:00 2001 From: Bluemangoo Date: Sat, 25 Jan 2025 12:17:40 +0800 Subject: [PATCH 5/9] ci: install `libsystemd-dev` on ubuntu --- .github/workflows/ci.yml | 12 ++++++++++++ .github/workflows/code-quality.yml | 4 ++++ 2 files changed, 16 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0c2c0f69..18b98dd2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,6 +15,10 @@ jobs: steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable + - if: ${{ contains(matrix.os, 'ubuntu') }} + run: | + sudo apt-get update -y + sudo apt-get -yq --no-install-suggests --no-install-recommends install libsystemd-dev - run: cargo check test: @@ -26,6 +30,10 @@ jobs: steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable + - if: ${{ contains(matrix.os, 'ubuntu') }} + run: | + sudo apt-get update -y + sudo apt-get -yq --no-install-suggests --no-install-recommends install libsystemd-dev - run: cargo test --all fmt: @@ -71,6 +79,10 @@ jobs: uses: dtolnay/rust-toolchain@nightly with: components: llvm-tools-preview + - if: ${{ contains(matrix.job.os, 'ubuntu') }} + run: | + sudo apt-get update -y + sudo apt-get -yq --no-install-suggests --no-install-recommends install libsystemd-dev - name: Test run: cargo test --no-fail-fast env: diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index 2f35527b..485103db 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -81,6 +81,10 @@ jobs: - uses: Swatinem/rust-cache@v2 - name: Run sccache-cache uses: mozilla-actions/sccache-action@v0.0.7 + - if: ${{ contains(matrix.job.os, 'ubuntu') }} + run: | + sudo apt-get update -y + sudo apt-get -yq --no-install-suggests --no-install-recommends install libsystemd-dev - name: Initialize workflow variables id: vars shell: bash From 5093892d8ee37a5fcb2e2271eeffb17c143498a9 Mon Sep 17 00:00:00 2001 From: Bluemangoo Date: Sat, 25 Jan 2025 15:48:39 +0800 Subject: [PATCH 6/9] top: cpu time for windows --- src/uu/top/Cargo.toml | 2 +- src/uu/top/src/header.rs | 96 +++++++++++++++++++++++----------------- src/uu/top/src/top.rs | 3 -- 3 files changed, 57 insertions(+), 44 deletions(-) diff --git a/src/uu/top/Cargo.toml b/src/uu/top/Cargo.toml index a82e2bc1..5ddb2db9 100644 --- a/src/uu/top/Cargo.toml +++ b/src/uu/top/Cargo.toml @@ -26,7 +26,7 @@ bytesize = { workspace = true } libsystemd-sys = "0.9.3" [target.'cfg(target_os="windows")'.dependencies] -windows = { version = "0.59.0", features = ["Win32_System_RemoteDesktop"] } +windows = { version = "0.59.0", features = ["Win32_System_RemoteDesktop", "Wdk_System_SystemInformation"] } [lib] path = "src/top.rs" diff --git a/src/uu/top/src/header.rs b/src/uu/top/src/header.rs index bced0dbe..b1e9bc4b 100644 --- a/src/uu/top/src/header.rs +++ b/src/uu/top/src/header.rs @@ -7,38 +7,6 @@ use crate::picker::{sysinfo, systemstat}; use bytesize::ByteSize; use clap::ArgMatches; 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(arg: &ArgMatches) -> String { format!( @@ -296,16 +264,64 @@ fn cpu() -> String { ) } -//TODO: Implement io wait, hardware interrupt, software interrupt and steal time -#[cfg(not(any(target_os = "macos", target_os = "linux")))] +#[cfg(target_os = "windows")] fn cpu() -> String { - let cpu = cpu_load(); + use libc::malloc; + use windows::Wdk::System::SystemInformation::NtQuerySystemInformation; + + #[repr(C)] + #[derive(Debug)] + struct SystemProcessorPerformanceInformation { + idle_time: i64, // LARGE_INTEGER + kernel_time: i64, // LARGE_INTEGER + user_time: i64, // LARGE_INTEGER + dpc_time: i64, // LARGE_INTEGER + interrupt_time: i64, // LARGE_INTEGER + interrupt_count: u32, // ULONG + } + + let n_cpu = sysinfo().read().unwrap().cpus().len(); + let mut cpu_load = SystemProcessorPerformanceInformation { + idle_time: 0, + kernel_time: 0, + user_time: 0, + dpc_time: 0, + interrupt_time: 0, + interrupt_count: 0, + }; + unsafe { + let len = n_cpu * size_of::(); + let data = malloc(len); + let _ = NtQuerySystemInformation( + windows::Wdk::System::SystemInformation::SystemProcessorPerformanceInformation, + data, + (n_cpu * size_of::()) as u32, + std::ptr::null_mut(), + ); + for i in 0..n_cpu { + let cpu = data.add(i * size_of::()) + as *const SystemProcessorPerformanceInformation; + let cpu = cpu.as_ref().unwrap(); + cpu_load.idle_time += cpu.idle_time; + cpu_load.kernel_time += cpu.kernel_time; + cpu_load.user_time += cpu.user_time; + cpu_load.dpc_time += cpu.dpc_time; + cpu_load.interrupt_time += cpu.interrupt_time; + cpu_load.interrupt_count += cpu.interrupt_count; + } + } + let total = cpu_load.idle_time + + cpu_load.kernel_time + + cpu_load.user_time + + cpu_load.dpc_time + + cpu_load.interrupt_time; 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 + "%Cpu(s): {:.1} us, {:.1} sy, {:.1} id, {:.1} hi, {:.1} si", + cpu_load.user_time as f64 / total as f64 * 100.0, + cpu_load.kernel_time as f64 / total as f64 * 100.0, + cpu_load.idle_time as f64 / total as f64 * 100.0, + cpu_load.interrupt_time as f64 / total as f64 * 100.0, + cpu_load.dpc_time as f64 / total as f64 * 100.0, ) } diff --git a/src/uu/top/src/top.rs b/src/uu/top/src/top.rs index 421a2c6d..6955e631 100644 --- a/src/uu/top/src/top.rs +++ b/src/uu/top/src/top.rs @@ -55,9 +55,6 @@ 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(); From 3f19e07e8dae7a5d6656a7e40fe913b2afb854cf Mon Sep 17 00:00:00 2001 From: Bluemangoo Date: Sat, 25 Jan 2025 20:35:47 +0800 Subject: [PATCH 7/9] top: pass `scale-summary-mem` instead of args to summary --- src/uu/top/src/header.rs | 9 ++++----- src/uu/top/src/top.rs | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/uu/top/src/header.rs b/src/uu/top/src/header.rs index b1e9bc4b..aa28927f 100644 --- a/src/uu/top/src/header.rs +++ b/src/uu/top/src/header.rs @@ -5,10 +5,9 @@ use crate::picker::{sysinfo, systemstat}; use bytesize::ByteSize; -use clap::ArgMatches; use systemstat::Platform; -pub(crate) fn header(arg: &ArgMatches) -> String { +pub(crate) fn header(scale_summary_mem: Option<&String>) -> String { format!( "top - {time} {uptime}, {user}, {load_average}\n\ {task}\n\ @@ -20,7 +19,7 @@ pub(crate) fn header(arg: &ArgMatches) -> String { load_average = load_average(), task = task(), cpu = cpu(), - memory = memory(arg), + memory = memory(scale_summary_mem), ) } @@ -331,9 +330,9 @@ fn cpu() -> String { todo() } -fn memory(arg: &ArgMatches) -> String { +fn memory(scale_summary_mem: Option<&String>) -> String { let binding = sysinfo().read().unwrap(); - let (unit, unit_name) = match arg.get_one::("scale-summary-mem") { + let (unit, unit_name) = match scale_summary_mem { Some(scale) => match scale.as_str() { "k" => (bytesize::KIB, "KiB"), "m" => (bytesize::MIB, "MiB"), diff --git a/src/uu/top/src/top.rs b/src/uu/top/src/top.rs index 6955e631..885ebd19 100644 --- a/src/uu/top/src/top.rs +++ b/src/uu/top/src/top.rs @@ -99,7 +99,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { table }; - println!("{}", header(&matches)); + println!("{}", header(matches.get_one::("scale-summary-mem"))); println!("\n"); let cutter = { From d29e05ad441fe38238085d46247075d9af57f0e2 Mon Sep 17 00:00:00 2001 From: Bluemangoo Date: Sat, 8 Mar 2025 15:59:39 +0000 Subject: [PATCH 8/9] top: move codes to uu_core --- Cargo.lock | 112 +++++++++++++++------------- Cargo.toml | 2 +- src/uu/top/Cargo.toml | 15 ++-- src/uu/top/build.rs | 4 + src/uu/top/src/header.rs | 156 ++++++++++----------------------------- src/uu/top/src/picker.rs | 6 -- 6 files changed, 111 insertions(+), 184 deletions(-) create mode 100644 src/uu/top/build.rs diff --git a/Cargo.lock b/Cargo.lock index 363b8f67..0c3ba95a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -86,18 +86,18 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" -[[package]] -name = "build-env" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1522ac6ee801a11bf9ef3f80403f4ede6eb41291fac3dde3de09989679305f25" - [[package]] name = "bumpalo" version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytesize" version = "2.0.1" @@ -442,17 +442,6 @@ dependencies = [ "libc", ] -[[package]] -name = "libsystemd-sys" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed080163caa59cc29b34bce2209b737149a4bac148cd9a8b04e4c12822798119" -dependencies = [ - "build-env", - "libc", - "pkg-config", -] - [[package]] name = "linux-raw-sys" version = "0.4.14" @@ -477,12 +466,6 @@ 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" @@ -495,16 +478,6 @@ 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" @@ -719,7 +692,7 @@ checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" dependencies = [ "rand_chacha", "rand_core 0.9.0", - "zerocopy", + "zerocopy 0.8.14", ] [[package]] @@ -745,7 +718,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b08f3c9802962f7e1b25113931d94f43ed9725bebc59db9d0c3e9a23b67e15ff" dependencies = [ "getrandom 0.3.1", - "zerocopy", + "zerocopy 0.8.14", ] [[package]] @@ -943,20 +916,6 @@ 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.18.0" @@ -1107,6 +1066,30 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +[[package]] +name = "utmp-classic" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e24c654e19afaa6b8f3877ece5d3bed849c2719c56f6752b18ca7da4fcc6e85a" +dependencies = [ + "cfg-if", + "libc", + "thiserror 1.0.69", + "time", + "utmp-classic-raw", + "zerocopy 0.7.35", +] + +[[package]] +name = "utmp-classic-raw" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22c226537a3d6e01c440c1926ca0256dbee2d19b2229ede6fc4863a6493dd831" +dependencies = [ + "cfg-if", + "zerocopy 0.7.35", +] + [[package]] name = "uu_free" version = "0.0.1" @@ -1230,13 +1213,12 @@ dependencies = [ "chrono", "clap", "libc", - "libsystemd-sys", "nix", + "pkg-config", "prettytable-rs", "sysinfo", - "systemstat", "uucore", - "windows 0.59.0", + "windows-sys 0.59.0", ] [[package]] @@ -1273,9 +1255,12 @@ dependencies = [ "nix", "number_prefix", "os_display", + "thiserror 2.0.12", "time", + "utmp-classic", "uucore_procs", "wild", + "windows-sys 0.59.0", ] [[package]] @@ -1750,13 +1735,34 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive 0.7.35", +] + [[package]] name = "zerocopy" version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a367f292d93d4eab890745e75a778da40909cab4d6ff8173693812f79c4a2468" dependencies = [ - "zerocopy-derive", + "zerocopy-derive 0.8.14", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 084bd4a6..37cbb449 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,8 +62,8 @@ thiserror = "2.0.4" uucore = "0.0.30" walkdir = "2.5.0" windows = { version = "0.60.0" } +windows-sys = { version = "0.59.0", default-features = false } 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 5ddb2db9..f02dd87f 100644 --- a/src/uu/top/Cargo.toml +++ b/src/uu/top/Cargo.toml @@ -12,21 +12,22 @@ keywords = ["acl", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] [dependencies] -uucore = { workspace = true, features = ["utmpx"] } +uucore = { workspace = true, features = ["utmpx", "uptime"] } clap = { workspace = true } libc = { workspace = true } nix = { workspace = true } prettytable-rs = { workspace = true } sysinfo = { workspace = true } chrono = { workspace = true } -systemstat = { workspace = true } bytesize = { workspace = true } - -[target.'cfg(target_os="linux")'.dependencies] -libsystemd-sys = "0.9.3" - [target.'cfg(target_os="windows")'.dependencies] -windows = { version = "0.59.0", features = ["Win32_System_RemoteDesktop", "Wdk_System_SystemInformation"] } +windows-sys = { workspace = true, features = [ + "Win32_System_RemoteDesktop", + "Win32_System_SystemInformation", +] } + +[target.'cfg(target_os="linux")'.build-dependencies] +pkg-config = "0.3.31" [lib] path = "src/top.rs" diff --git a/src/uu/top/build.rs b/src/uu/top/build.rs new file mode 100644 index 00000000..8adf5b0a --- /dev/null +++ b/src/uu/top/build.rs @@ -0,0 +1,4 @@ +fn main() { + #[cfg(target_os = "linux")] + pkg_config::find_library("libsystemd").unwrap(); +} diff --git a/src/uu/top/src/header.rs b/src/uu/top/src/header.rs index aa28927f..b1f1423e 100644 --- a/src/uu/top/src/header.rs +++ b/src/uu/top/src/header.rs @@ -3,9 +3,11 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -use crate::picker::{sysinfo, systemstat}; +use crate::picker::sysinfo; use bytesize::ByteSize; -use systemstat::Platform; +use uucore::uptime::{ + get_formated_uptime, get_formatted_loadavg, get_formatted_nusers, get_formatted_time, +}; pub(crate) fn header(scale_summary_mem: Option<&String>) -> String { format!( @@ -13,7 +15,7 @@ pub(crate) fn header(scale_summary_mem: Option<&String>) -> String { {task}\n\ {cpu}\n\ {memory}", - time = chrono::Local::now().format("%H:%M:%S"), + time = get_formatted_time(), uptime = uptime(), user = user(), load_average = load_average(), @@ -23,7 +25,17 @@ pub(crate) fn header(scale_summary_mem: Option<&String>) -> String { ) } -#[cfg(not(target_os = "linux"))] +#[cfg(target_os = "linux")] +extern "C" { + pub fn sd_booted() -> libc::c_int; + pub fn sd_get_sessions(sessions: *mut *mut *mut libc::c_char) -> libc::c_int; + pub fn sd_session_get_class( + session: *const libc::c_char, + class: *mut *mut libc::c_char, + ) -> libc::c_int; +} + +#[cfg(target_os = "macos")] fn todo() -> String { "TODO".into() } @@ -33,105 +45,22 @@ fn format_memory(memory_b: u64, unit: u64) -> 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 -} - -#[inline] -fn format_user(user: u64) -> String { - match user { - 0 => "0 user".to_string(), - 1 => "1 user".to_string(), - _ => format!("{} users", user), - } -} - -#[cfg(target_os = "windows")] -fn user() -> String { - use windows::{core::*, Win32::System::RemoteDesktop::*}; - - let mut num_user = 0; - - unsafe { - let mut session_info_ptr = std::ptr::null_mut(); - let mut session_count = 0; - - WTSEnumerateSessionsW( - Some(WTS_CURRENT_SERVER_HANDLE), - 0, - 1, - &mut session_info_ptr, - &mut session_count, - ) - .unwrap(); - - let sessions = std::slice::from_raw_parts(session_info_ptr, session_count as usize); - - for session in sessions { - let mut buffer = PWSTR::null(); - let mut bytes_returned = 0; - - WTSQuerySessionInformationW( - Some(WTS_CURRENT_SERVER_HANDLE), - session.SessionId, - WTS_INFO_CLASS(5), - &mut buffer, - &mut bytes_returned, - ) - .unwrap(); - - let username = PWSTR(buffer.0).to_string().unwrap_or_default(); - if !username.is_empty() { - num_user += 1; - } - - WTSFreeMemory(buffer.0 as _); - } - - WTSFreeMemory(session_info_ptr as _); - } - - format_user(num_user) + get_formated_uptime(None).unwrap_or_default() } -#[cfg(unix)] -// see: https://gitlab.com/procps-ng/procps/-/blob/4740a0efa79cade867cfc7b32955fe0f75bf5173/library/uptime.c#L63-L115 -fn user() -> String { - use uucore::utmpx::Utmpx; +#[cfg(target_os = "linux")] +pub fn get_nusers_systemd() -> uucore::error::UResult { + use std::ffi::CStr; + use std::ptr; + use uucore::error::USimpleError; + use uucore::libc::*; - #[cfg(target_os = "linux")] unsafe { - use libc::free; - use libsystemd_sys::daemon::sd_booted; - use libsystemd_sys::login::{sd_get_sessions, sd_session_get_class}; - use std::ffi::{c_char, c_void, CStr}; - use std::ptr; // systemd if sd_booted() > 0 { let mut sessions_list: *mut *mut c_char = ptr::null_mut(); let mut num_user = 0; - let sessions = sd_get_sessions(&mut sessions_list); // rust-systemd does not implement this + let sessions = sd_get_sessions(&mut sessions_list); if sessions > 0 { for i in 0..sessions { @@ -156,34 +85,27 @@ fn user() -> String { } free(sessions_list as *mut c_void); - return format_user(num_user); + return Ok(num_user); } } - - // utmpx - let mut num_user = 0; - Utmpx::iter_all_records().for_each(|ut| { - if ut.record_type() == 7 && !ut.user().is_empty() { - num_user += 1; - } - }); - format_user(num_user) + Err(USimpleError::new( + 1, + "could not retrieve number of logged users", + )) } -#[cfg(not(target_os = "windows"))] -fn load_average() -> String { - let binding = systemstat().read().unwrap(); +// see: https://gitlab.com/procps-ng/procps/-/blob/4740a0efa79cade867cfc7b32955fe0f75bf5173/library/uptime.c#L63-L115 +fn user() -> String { + #[cfg(target_os = "linux")] + if let Ok(nusers) = get_nusers_systemd() { + return uucore::uptime::format_nusers(nusers); + } - let load_average = binding.load_average().unwrap(); - format!( - "load average: {:.2}, {:.2}, {:.2}", - load_average.one, load_average.five, load_average.fifteen - ) + get_formatted_nusers() } -#[cfg(target_os = "windows")] fn load_average() -> String { - todo() + get_formatted_loadavg().unwrap_or_default() } fn task() -> String { @@ -266,7 +188,7 @@ fn cpu() -> String { #[cfg(target_os = "windows")] fn cpu() -> String { use libc::malloc; - use windows::Wdk::System::SystemInformation::NtQuerySystemInformation; + use windows_sys::Wdk::System::SystemInformation::NtQuerySystemInformation; #[repr(C)] #[derive(Debug)] @@ -292,7 +214,7 @@ fn cpu() -> String { let len = n_cpu * size_of::(); let data = malloc(len); let _ = NtQuerySystemInformation( - windows::Wdk::System::SystemInformation::SystemProcessorPerformanceInformation, + windows_sys::Wdk::System::SystemInformation::SystemProcessorPerformanceInformation, data, (n_cpu * size_of::()) as u32, std::ptr::null_mut(), diff --git a/src/uu/top/src/picker.rs b/src/uu/top/src/picker.rs index b96828ae..f103e9b4 100644 --- a/src/uu/top/src/picker.rs +++ b/src/uu/top/src/picker.rs @@ -12,19 +12,13 @@ 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() From db8ecf5691876310e371f1df13353b08bf8e7ffd Mon Sep 17 00:00:00 2001 From: Bluemangoo Date: Mon, 10 Mar 2025 18:28:45 +0800 Subject: [PATCH 9/9] top: optimize --- src/uu/top/src/header.rs | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/src/uu/top/src/header.rs b/src/uu/top/src/header.rs index b1f1423e..1785e200 100644 --- a/src/uu/top/src/header.rs +++ b/src/uu/top/src/header.rs @@ -35,11 +35,6 @@ extern "C" { ) -> libc::c_int; } -#[cfg(target_os = "macos")] -fn todo() -> String { - "TODO".into() -} - fn format_memory(memory_b: u64, unit: u64) -> f64 { ByteSize::b(memory_b).0 as f64 / unit as f64 } @@ -55,6 +50,7 @@ pub fn get_nusers_systemd() -> uucore::error::UResult { use uucore::error::USimpleError; use uucore::libc::*; + // SAFETY: sd_booted to check if system is booted with systemd. unsafe { // systemd if sd_booted() > 0 { @@ -210,25 +206,28 @@ fn cpu() -> String { interrupt_time: 0, interrupt_count: 0, }; + // SAFETY: malloc is safe to use here. We free the memory after we are done with it. If action fails, all "time" will be 0. unsafe { let len = n_cpu * size_of::(); let data = malloc(len); - let _ = NtQuerySystemInformation( + let status = NtQuerySystemInformation( windows_sys::Wdk::System::SystemInformation::SystemProcessorPerformanceInformation, data, (n_cpu * size_of::()) as u32, std::ptr::null_mut(), ); - for i in 0..n_cpu { - let cpu = data.add(i * size_of::()) - as *const SystemProcessorPerformanceInformation; - let cpu = cpu.as_ref().unwrap(); - cpu_load.idle_time += cpu.idle_time; - cpu_load.kernel_time += cpu.kernel_time; - cpu_load.user_time += cpu.user_time; - cpu_load.dpc_time += cpu.dpc_time; - cpu_load.interrupt_time += cpu.interrupt_time; - cpu_load.interrupt_count += cpu.interrupt_count; + if status == 0 { + for i in 0..n_cpu { + let cpu = data.add(i * size_of::()) + as *const SystemProcessorPerformanceInformation; + let cpu = cpu.as_ref().unwrap(); + cpu_load.idle_time += cpu.idle_time; + cpu_load.kernel_time += cpu.kernel_time; + cpu_load.user_time += cpu.user_time; + cpu_load.dpc_time += cpu.dpc_time; + cpu_load.interrupt_time += cpu.interrupt_time; + cpu_load.interrupt_count += cpu.interrupt_count; + } } } let total = cpu_load.idle_time @@ -249,7 +248,7 @@ fn cpu() -> String { //TODO: Implement for macos #[cfg(target_os = "macos")] fn cpu() -> String { - todo() + "TODO".into() } fn memory(scale_summary_mem: Option<&String>) -> String {