Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

top: implement SUMMARY display #306

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
236 changes: 236 additions & 0 deletions src/uu/top/src/header.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
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<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(arg: &ArgMatches) -> String {
format!(
"top - {time} {uptime}, {user}, {load_average}\n\
{task}\n\
{cpu}\n\
{memory}",
time = chrono::Local::now().format("%H:%M:%S"),
uptime = uptime(),
user = user(),
load_average = load_average(),
task = task(),
cpu = cpu(),
memory = memory(arg),
)
}

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(arg: &ArgMatches) -> String {
let binding = sysinfo().read().unwrap();
let (unit, unit_name) = match arg.get_one::<String>("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 => {
println!("none");
(bytesize::MIB, "MiB")
}
};

format!(
"{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.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.available_memory(), unit),
unit_name = unit_name
)
}
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
14 changes: 7 additions & 7 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 @@ -97,7 +102,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
table
};

println!("{}", header());
println!("{}", header(&matches));
println!("\n");

let cutter = {
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 Expand Up @@ -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 <SECS> "iterative delay as SECS [.TENTHS]"),
// arg!(-E --"scale-summary-mem" <SCALE> "set mem as: k,m,g,t,p,e for SCALE"),
arg!(-E --"scale-summary-mem" <SCALE> "set mem as: k,m,g,t,p,e for SCALE"),
// arg!(-e --"scale-task-mem" <SCALE> "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"),
Expand Down
Loading