#[macro_use]
extern crate concat_with;
extern crate clap;
extern crate terminal_size;
extern crate mprober_lib;
extern crate validators;
extern crate byte_unit;
extern crate chrono;
extern crate regex;
extern crate users;
extern crate getch;
extern crate termcolor;
extern crate once_cell;
use std::cmp::Ordering;
use std::collections::BTreeMap;
use std::env;
use std::error::Error;
use std::io::{self, ErrorKind, Write};
use std::net::IpAddr;
use std::process as std_process;
use std::sync::Arc;
use std::thread;
use std::time::Duration;
use mprober::*;
use clap::{Arg, ArgMatches, Command};
use terminal_size::terminal_size;
use validators::prelude::*;
use byte_unit::{Byte, ByteUnit};
use chrono::SecondsFormat;
use regex::Regex;
use users::{Group, Groups, User, Users, UsersCache};
use getch::Getch;
use termcolor::{BufferWriter, Color, ColorChoice, ColorSpec, WriteColor};
use once_cell::sync::Lazy;
const APP_NAME: &str = "M Prober";
const CARGO_PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
const CARGO_PKG_AUTHORS: &str = env!("CARGO_PKG_AUTHORS");
const DEFAULT_TERMINAL_WIDTH: usize = 64;
const MIN_TERMINAL_WIDTH: usize = 60;
const DEFAULT_INTERVAL: u64 = 333;
const CYAN_COLOR: Color = Color::Rgb(0, 177, 177);
const WHITE_COLOR: Color = Color::Rgb(219, 219, 219);
const RED_COLOR: Color = Color::Rgb(255, 95, 0);
const YELLOW_COLOR: Color = Color::Rgb(216, 177, 0);
const SKY_CYAN_COLOR: Color = Color::Rgb(107, 200, 200);
const DARK_CYAN_COLOR: Color = Color::Rgb(0, 95, 95);
const BLACK_COLOR: Color = Color::Rgb(28, 28, 28);
const WINE_COLOR: Color = Color::Rgb(215, 0, 0);
const ORANGE_COLOR: Color = Color::Rgb(215, 135, 0);
const DARK_BLUE_COLOR: Color = Color::Rgb(0, 0, 95);
const CLEAR_SCREEN_DATA: [u8; 11] =
[0x1b, 0x5b, 0x33, 0x4a, 0x1b, 0x5b, 0x48, 0x1b, 0x5b, 0x32, 0x4a];
const ENV_LIGHT_MODE: &str = "MPROBER_LIGHT";
const ENV_FORCE_PLAIN: &str = "MPROBER_FORCE_PLAIN";
static mut LIGHT_MODE: bool = false;
static mut FORCE_PLAIN_MODE: bool = false;
static COLOR_DEFAULT: Lazy<ColorSpec> = Lazy::new(ColorSpec::new);
static COLOR_LABEL: Lazy<ColorSpec> = Lazy::new(|| {
let mut color_spec = ColorSpec::new();
if !unsafe { FORCE_PLAIN_MODE } {
if unsafe { LIGHT_MODE } {
color_spec.set_fg(Some(DARK_CYAN_COLOR));
} else {
color_spec.set_fg(Some(CYAN_COLOR));
}
}
color_spec
});
static COLOR_NORMAL_TEXT: Lazy<ColorSpec> = Lazy::new(|| {
let mut color_spec = ColorSpec::new();
if !unsafe { FORCE_PLAIN_MODE } {
if unsafe { LIGHT_MODE } {
color_spec.set_fg(Some(BLACK_COLOR));
} else {
color_spec.set_fg(Some(WHITE_COLOR));
}
}
color_spec
});
static COLOR_BOLD_TEXT: Lazy<ColorSpec> = Lazy::new(|| {
let mut color_spec = ColorSpec::new();
if !unsafe { FORCE_PLAIN_MODE } {
if unsafe { LIGHT_MODE } {
color_spec.set_fg(Some(BLACK_COLOR)).set_bold(true);
} else {
color_spec.set_fg(Some(WHITE_COLOR)).set_bold(true);
}
}
color_spec
});
static COLOR_USED: Lazy<ColorSpec> = Lazy::new(|| {
let mut color_spec = ColorSpec::new();
if !unsafe { FORCE_PLAIN_MODE } {
if unsafe { LIGHT_MODE } {
color_spec.set_fg(Some(WINE_COLOR));
} else {
color_spec.set_fg(Some(RED_COLOR));
}
}
color_spec
});
static COLOR_CACHE: Lazy<ColorSpec> = Lazy::new(|| {
let mut color_spec = ColorSpec::new();
if !unsafe { FORCE_PLAIN_MODE } {
if unsafe { LIGHT_MODE } {
color_spec.set_fg(Some(ORANGE_COLOR));
} else {
color_spec.set_fg(Some(YELLOW_COLOR));
}
}
color_spec
});
static COLOR_BUFFERS: Lazy<ColorSpec> = Lazy::new(|| {
let mut color_spec = ColorSpec::new();
if !unsafe { FORCE_PLAIN_MODE } {
if unsafe { LIGHT_MODE } {
color_spec.set_fg(Some(DARK_BLUE_COLOR));
} else {
color_spec.set_fg(Some(SKY_CYAN_COLOR));
}
}
color_spec
});
macro_rules! set_color_mode {
($sub_matches:ident) => {
unsafe {
if $sub_matches.is_present("PLAIN") {
FORCE_PLAIN_MODE = true;
} else {
match env::var_os(ENV_FORCE_PLAIN).map(|v| v.ne("0")) {
Some(b) => {
if b {
FORCE_PLAIN_MODE = true;
} else {
if $sub_matches.is_present("LIGHT") {
LIGHT_MODE = true;
} else {
LIGHT_MODE =
env::var_os(ENV_LIGHT_MODE).map(|v| v.ne("0")).unwrap_or(false);
}
}
}
None => {
if $sub_matches.is_present("LIGHT") {
LIGHT_MODE = true;
} else {
LIGHT_MODE =
env::var_os(ENV_LIGHT_MODE).map(|v| v.ne("0")).unwrap_or(false);
}
}
}
}
}
};
}
macro_rules! monitor_handler {
($monitor:expr, $s:stmt) => {
match $monitor {
Some(monitor) => {
thread::spawn(move || {
loop {
let key = Getch::new().getch().unwrap();
if let b'q' = key {
break;
}
}
std_process::exit(0);
});
let sleep_interval = monitor;
loop {
io::stdout().write_all(&CLEAR_SCREEN_DATA)?;
$s
thread::sleep(sleep_interval);
}
}
None => {
$s
}
}
return Ok(());
};
($monitor:expr, $monitor_interval_milli_secs:expr, $s:stmt) => {
if $monitor {
thread::spawn(move || {
loop {
let key = Getch::new().getch().unwrap();
if let b'q' = key {
break;
}
}
std_process::exit(0);
});
let sleep_interval = Duration::from_millis($monitor_interval_milli_secs);
loop {
io::stdout().write_all(&CLEAR_SCREEN_DATA)?;
$s
thread::sleep(sleep_interval);
}
} else {
$s
}
return Ok(());
};
($monitor:expr, $s:stmt, $si:stmt, $no_self_sleep:expr) => {
match $monitor {
Some(monitor) => {
thread::spawn(move || {
loop {
let key = Getch::new().getch().unwrap();
if let b'q' = key {
break;
}
}
std_process::exit(0);
});
io::stdout().write_all(&CLEAR_SCREEN_DATA)?;
$si
let sleep_interval = monitor;
loop {
if $no_self_sleep {
thread::sleep(sleep_interval);
}
io::stdout().write_all(&CLEAR_SCREEN_DATA)?;
$s
}
}
None => {
$s
}
}
return Ok(());
};
}
#[rocket::main]
async fn main() -> Result<(), Box<dyn Error>> {
let matches = get_matches();
if matches.subcommand_matches("hostname").is_some() {
handle_hostname()
} else if matches.subcommand_matches("kernel").is_some() {
handle_kernel()
} else if let Some(sub_matches) = matches.subcommand_matches("uptime") {
set_color_mode!(sub_matches);
let monitor = sub_matches.is_present("MONITOR");
let second = sub_matches.is_present("SECOND");
handle_uptime(monitor, second)
} else if let Some(sub_matches) = matches.subcommand_matches("time") {
set_color_mode!(sub_matches);
let monitor = sub_matches.is_present("MONITOR");
handle_time(monitor)
} else if let Some(sub_matches) = matches.subcommand_matches("cpu") {
set_color_mode!(sub_matches);
let monitor = get_monitor_duration(sub_matches)?;
let separate = sub_matches.is_present("SEPARATE");
let only_information = sub_matches.is_present("ONLY_INFORMATION");
handle_cpu(monitor, separate, only_information)
} else if let Some(sub_matches) = matches.subcommand_matches("memory") {
set_color_mode!(sub_matches);
let monitor = get_monitor_duration(sub_matches)?;
let unit = get_byte_unit(sub_matches)?;
handle_memory(monitor, unit)
} else if let Some(sub_matches) = matches.subcommand_matches("network") {
set_color_mode!(sub_matches);
let monitor = get_monitor_duration(sub_matches)?;
let unit = get_byte_unit(sub_matches)?;
handle_network(monitor, unit)
} else if let Some(sub_matches) = matches.subcommand_matches("volume") {
set_color_mode!(sub_matches);
let monitor = get_monitor_duration(sub_matches)?;
let unit = get_byte_unit(sub_matches)?;
let only_information = sub_matches.is_present("ONLY_INFORMATION");
let mounts = sub_matches.is_present("MOUNTS");
handle_volume(monitor, unit, only_information, mounts)
} else if let Some(sub_matches) = matches.subcommand_matches("process") {
set_color_mode!(sub_matches);
let monitor = get_monitor_duration(sub_matches)?;
let unit = get_byte_unit(sub_matches)?;
let only_information = sub_matches.is_present("ONLY_INFORMATION");
#[allow(clippy::manual_map)]
let top = match sub_matches.value_of("TOP") {
Some(top) => Some(top.parse::<usize>()?),
None => None,
};
let truncate = match sub_matches.value_of("TRUNCATE") {
Some(truncate) => {
let truncate = truncate.parse::<usize>()?;
if truncate > 0 {
Some(truncate)
} else {
None
}
}
None => None,
};
let start_time = sub_matches.is_present("START_TIME");
let user_filter = sub_matches.value_of("USER_FILTER");
let group_filter = sub_matches.value_of("GROUP_FILTER");
#[allow(clippy::manual_map)]
let program_filter = match sub_matches.value_of("PROGRAM_FILTER") {
Some(program_filter) => Some(Regex::new(program_filter)?),
None => None,
};
#[allow(clippy::manual_map)]
let tty_filter = match sub_matches.value_of("TTY_FILTER") {
Some(tty_filter) => Some(Regex::new(tty_filter)?),
None => None,
};
#[allow(clippy::manual_map)]
let pid_filter = match sub_matches.value_of("PID_FILTER") {
Some(pid_filter) => Some(pid_filter.parse::<u32>()?),
None => None,
};
handle_process(
monitor,
unit,
only_information,
top,
truncate,
start_time,
user_filter,
group_filter,
program_filter,
tty_filter,
pid_filter,
)
} else if let Some(sub_matches) = matches.subcommand_matches("web") {
let monitor = match sub_matches.value_of("MONITOR") {
Some(monitor) => {
let monitor = WebMonitorInterval::parse_str(monitor)
.map_err(|_| format!("`{}` is not a correct value for SECONDS", monitor))?;
Duration::from_secs(monitor.get_number())
}
None => unreachable!(),
};
let address = sub_matches.value_of("ADDRESS").unwrap().parse()?;
let listen_port = match sub_matches.value_of("LISTEN_PORT") {
Some(port) => {
let port: u16 = port
.parse()
.map_err(|_| format!("`{}` is not a correct value for LISTEN_PORT", port))?;
port
}
None => unreachable!(),
};
let auth_key = sub_matches.value_of("AUTH_KEY");
let only_api = sub_matches.is_present("ONLY_API");
handle_web(monitor, address, listen_port, auth_key, only_api).await
} else if let Some(sub_matches) = matches.subcommand_matches("benchmark") {
let warming_up_duration = match sub_matches.value_of("WARMING_UP_DURATION") {
Some(millisecond) => {
let millisecond: u64 = millisecond.parse().map_err(|_| {
format!("`{}` is not a correct value for MILLI_SECONDS", millisecond)
})?;
Duration::from_millis(millisecond)
}
None => unreachable!(),
};
let benchmark_duration = match sub_matches.value_of("BENCHMARK_DURATION") {
Some(millisecond) => {
let millisecond: u64 = millisecond.parse().map_err(|_| {
format!("`{}` is not a correct value for MILLI_SECONDS", millisecond)
})?;
Duration::from_millis(millisecond)
}
None => unreachable!(),
};
let print_out = if sub_matches.is_present("VERBOSE") {
benchmark::BenchmarkLog::Verbose
} else {
benchmark::BenchmarkLog::Normal
};
let disable_cpu = sub_matches.is_present("DISABLE_CPU");
let enable_cpu = sub_matches.is_present("ENABLE_CPU");
if enable_cpu && disable_cpu {
return Err("Cannot determine whether to enable benchmarking CPU or not.".into());
}
let disable_memory = sub_matches.is_present("DISABLE_MEMORY");
let enable_memory = sub_matches.is_present("ENABLE_MEMORY");
if disable_memory && enable_memory {
return Err("Cannot determine whether to enable benchmarking memory or not.".into());
}
let disable_volume = sub_matches.is_present("DISABLE_VOLUME");
let enable_volume = sub_matches.is_present("ENABLE_VOLUME");
if disable_volume && enable_volume {
return Err("Cannot determine whether to enable benchmarking volumes or not.".into());
}
let default = !(enable_cpu || enable_memory || enable_volume);
let cpu = if disable_cpu {
false
} else {
default || enable_cpu
};
let memory = if disable_memory {
false
} else {
default || enable_memory
};
let volume = if disable_volume {
false
} else {
default || enable_volume
};
handle_benchmark(warming_up_duration, benchmark_duration, print_out, cpu, memory, volume)
} else {
Err("Please input a subcommand. Use `help` to see how to use this program.".into())
}
}
#[inline]
fn handle_benchmark(
warming_up_duration: Duration,
benchmark_duration: Duration,
print_out: benchmark::BenchmarkLog,
cpu: bool,
memory: bool,
volume: bool,
) -> Result<(), Box<dyn Error>> {
let benchmark_config = benchmark::BenchmarkConfig {
warming_up_duration,
benchmark_duration,
print_out,
cpu,
memory,
volume,
};
benchmark::run_benchmark(&benchmark_config)?;
Ok(())
}
#[inline]
async fn handle_web(
monitor: Duration,
address: IpAddr,
listen_port: u16,
auth_key: Option<&str>,
only_api: bool,
) -> Result<(), Box<dyn Error>> {
rocket_mounts::launch(monitor, address, listen_port, auth_key.map(|s| s.to_string()), only_api)
.await?;
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn handle_process(
monitor: Option<Duration>,
unit: Option<ByteUnit>,
only_information: bool,
top: Option<usize>,
truncate: Option<usize>,
start_time: bool,
user_filter: Option<&str>,
group_filter: Option<&str>,
program_filter: Option<Regex>,
tty_filter: Option<Regex>,
pid_filter: Option<u32>,
) -> Result<(), Box<dyn Error>> {
let user_filter = user_filter;
let group_filter = group_filter;
let program_filter = program_filter.as_ref();
let tty_filter = tty_filter.as_ref();
monitor_handler!(
monitor,
draw_process(
top,
truncate,
unit,
only_information,
monitor,
start_time,
user_filter,
group_filter,
program_filter,
tty_filter,
pid_filter,
)?,
draw_process(
top,
truncate,
unit,
only_information,
Some(Duration::from_millis(DEFAULT_INTERVAL)),
start_time,
user_filter,
group_filter,
program_filter,
tty_filter,
pid_filter,
)?,
only_information
);
}
#[allow(clippy::too_many_arguments)]
fn draw_process(
mut top: Option<usize>,
truncate: Option<usize>,
unit: Option<ByteUnit>,
only_information: bool,
monitor: Option<Duration>,
start_time: bool,
user_filter: Option<&str>,
group_filter: Option<&str>,
program_filter: Option<&Regex>,
tty_filter: Option<&Regex>,
pid_filter: Option<u32>,
) -> Result<(), ScannerError> {
let output = if unsafe { FORCE_PLAIN_MODE } {
BufferWriter::stdout(ColorChoice::Never)
} else {
BufferWriter::stdout(ColorChoice::Always)
};
let mut stdout = output.buffer();
let terminal_width = match terminal_size() {
Some((width, height)) => {
if monitor.is_some() {
let height = (height.0 as usize).max(2) - 2;
top = match top {
Some(top) => Some(top.min(height)),
None => Some(height),
};
}
(width.0 as usize).max(MIN_TERMINAL_WIDTH)
}
None => DEFAULT_TERMINAL_WIDTH,
};
let user_cache = UsersCache::new();
let uid_filter = match user_filter {
Some(user_filter) => {
match user_cache.get_user_by_name(user_filter) {
Some(user) => Some(user.uid()),
None => {
return Err(ScannerError::IOError(io::Error::new(
ErrorKind::InvalidInput,
format!("Cannot find the user `{}`.", user_filter),
)));
}
}
}
None => None,
};
let gid_filter = match group_filter {
Some(group_filter) => {
match user_cache.get_group_by_name(group_filter) {
Some(group) => Some(group.gid()),
None => {
return Err(ScannerError::IOError(io::Error::new(
ErrorKind::InvalidInput,
format!("Cannot find the group `{}`.", group_filter),
)));
}
}
}
None => None,
};
let process_filter = process::ProcessFilter {
pid_filter,
uid_filter,
gid_filter,
program_filter,
tty_filter,
};
let (processes, percentage): (Vec<process::Process>, BTreeMap<u32, f64>) = if only_information {
let mut processes_with_stats = process::get_processes_with_stat(&process_filter)?;
processes_with_stats.sort_unstable_by(|(a, _), (b, _)| b.vsz.cmp(&a.vsz));
if let Some(top) = top {
if top < processes_with_stats.len() {
unsafe {
processes_with_stats.set_len(top);
}
}
}
(processes_with_stats.into_iter().map(|(process, _)| process).collect(), BTreeMap::new())
} else {
let mut processes_with_percentage =
process::get_processes_with_cpu_utilization_in_percentage(
&process_filter,
match monitor {
Some(monitor) => monitor,
None => Duration::from_millis(DEFAULT_INTERVAL),
},
)?;
processes_with_percentage.sort_unstable_by(
|(process_a, percentage_a), (process_b, percentage_b)| {
let percentage_a = *percentage_a;
let percentage_b = *percentage_b;
if percentage_a > 0.01 {
if percentage_a > percentage_b {
Ordering::Less
} else if percentage_b > 0.01
{
Ordering::Greater
} else {
process_b.vsz.cmp(&process_a.vsz)
}
} else if percentage_b > 0.01 {
if percentage_b > percentage_a {
Ordering::Greater
} else {
process_b.vsz.cmp(&process_a.vsz)
}
} else {
process_b.vsz.cmp(&process_a.vsz)
}
},
);
if let Some(top) = top {
if top < processes_with_percentage.len() {
unsafe {
processes_with_percentage.set_len(top);
}
}
}
let mut processes = Vec::with_capacity(processes_with_percentage.len());
let mut processes_percentage = BTreeMap::new();
for (process, percentage) in processes_with_percentage {
processes_percentage.insert(process.pid, percentage);
processes.push(process);
}
(processes, processes_percentage)
};
let processes_len = processes.len();
let mut pid: Vec<String> = Vec::with_capacity(processes_len);
let mut ppid: Vec<String> = Vec::with_capacity(processes_len);
let mut vsz: Vec<String> = Vec::with_capacity(processes_len);
let mut rss: Vec<String> = Vec::with_capacity(processes_len);
let mut anon: Vec<String> = Vec::with_capacity(processes_len);
let mut thd: Vec<String> = Vec::with_capacity(processes_len);
let mut tty: Vec<&str> = Vec::with_capacity(processes_len);
let mut user: Vec<Arc<User>> = Vec::with_capacity(processes_len);
let mut group: Vec<Arc<Group>> = Vec::with_capacity(processes_len);
let mut program: Vec<&str> = Vec::with_capacity(processes_len);
let mut state: Vec<&'static str> = Vec::with_capacity(processes_len);
for process in processes.iter() {
pid.push(process.pid.to_string());
ppid.push(process.ppid.to_string());
let (p_vsz, p_rss, p_anon) = (
Byte::from_bytes(process.vsz as u128),
Byte::from_bytes(process.rss as u128),
Byte::from_bytes(process.rss_anon as u128),
);
match unit {
Some(byte_unit) => {
vsz.push(p_vsz.get_adjusted_unit(byte_unit).format(1));
rss.push(p_rss.get_adjusted_unit(byte_unit).format(1));
anon.push(p_anon.get_adjusted_unit(byte_unit).format(1));
}
None => {
vsz.push(p_vsz.get_appropriate_unit(true).format(1));
rss.push(p_rss.get_appropriate_unit(true).format(1));
anon.push(p_anon.get_appropriate_unit(true).format(1));
}
}
tty.push(process.tty.as_deref().unwrap_or(""));
thd.push(process.threads.to_string());
user.push(
user_cache
.get_user_by_uid(process.effective_uid)
.unwrap_or_else(|| Arc::new(User::new(0, "systemd?", 0))),
);
group.push(
user_cache
.get_group_by_gid(process.effective_gid)
.unwrap_or_else(|| Arc::new(Group::new(0, "systemd?"))),
);
program.push(process.program.as_str());
state.push(process.state.as_str());
}
let truncate_inc = truncate.map(|t| t + 1).unwrap_or(usize::max_value());
let pid_len = pid.iter().map(|s| s.len()).max().map(|s| s.max(5)).unwrap_or(0);
let ppid_len = ppid.iter().map(|s| s.len()).max().map(|s| s.max(5)).unwrap_or(0);
let vsz_len = vsz.iter().map(|s| s.len()).max().map(|s| s.max(9)).unwrap_or(0);
let rss_len = rss.iter().map(|s| s.len()).max().map(|s| s.max(9)).unwrap_or(0);
let anon_len = anon.iter().map(|s| s.len()).max().map(|s| s.max(9)).unwrap_or(0);
let thd_len = thd.iter().map(|s| s.len()).max().map(|s| s.max(3)).unwrap_or(0);
let tty_len = tty.iter().map(|s| s.len()).max().map(|s| s.max(4)).unwrap_or(0);
let user_len = user
.iter()
.map(|user| user.name().len())
.max()
.map(|s| s.min(truncate_inc).max(4))
.unwrap_or(truncate_inc);
let group_len = group
.iter()
.map(|group| group.name().len())
.max()
.map(|s| s.min(truncate_inc).max(5))
.unwrap_or(truncate_inc);
let program_len = program
.iter()
.map(|s| s.len())
.max()
.map(|s| s.min(truncate_inc).max(7))
.unwrap_or(truncate_inc);
let state_len = state.iter().map(|s| s.len()).max().map(|s| s.max(5)).unwrap_or(0);
#[allow(clippy::never_loop)]
loop {
let mut width = 0;
stdout.set_color(&*COLOR_LABEL)?;
if width + pid_len > terminal_width {
break;
}
for _ in 3..pid_len {
write!(&mut stdout, " ")?; width += 1;
}
write!(&mut stdout, "PID")?; width += 3;
if width + 1 + ppid_len > terminal_width {
break;
}
for _ in 3..ppid_len {
write!(&mut stdout, " ")?; width += 1;
}
write!(&mut stdout, "PPID")?; width += 4;
if width + 5 > terminal_width {
break;
}
write!(&mut stdout, " PR")?; width += 5;
if width + 4 > terminal_width {
break;
}
write!(&mut stdout, " NI")?; width += 4;
if !only_information {
if width + 5 > terminal_width {
break;
}
write!(&mut stdout, " %CPU")?; width += 5;
}
if width + 1 + vsz_len > terminal_width {
break;
}
for _ in 2..vsz_len {
write!(&mut stdout, " ")?; width += 1;
}
write!(&mut stdout, "VSZ")?; width += 3;
if width + 1 + rss_len > terminal_width {
break;
}
for _ in 2..rss_len {
write!(&mut stdout, " ")?; width += 1;
}
write!(&mut stdout, "RSS")?; width += 3;
if width + 1 + anon_len > terminal_width {
break;
}
for _ in 3..anon_len {
write!(&mut stdout, " ")?; width += 1;
}
write!(&mut stdout, "ANON")?; width += 4;
if width + 1 + thd_len > terminal_width {
break;
}
for _ in 2..thd_len {
write!(&mut stdout, " ")?; width += 1;
}
write!(&mut stdout, "THD")?; width += 3;
if width + 1 + tty_len > terminal_width {
break;
}
write!(&mut stdout, " TTY")?; width += 4;
for _ in 3..tty_len {
write!(&mut stdout, " ")?; width += 1;
}
if width + 1 + user_len > terminal_width {
break;
}
write!(&mut stdout, " USER")?; width += 5;
for _ in 4..user_len {
write!(&mut stdout, " ")?; width += 1;
}
if width + 1 + group_len > terminal_width {
break;
}
write!(&mut stdout, " GROUP")?; width += 6;
for _ in 5..group_len {
write!(&mut stdout, " ")?; width += 1;
}
if width + 1 + program_len > terminal_width {
break;
}
write!(&mut stdout, " PROGRAM")?; width += 8;
for _ in 7..program_len {
write!(&mut stdout, " ")?; width += 1;
}
if width + 1 + state_len > terminal_width {
break;
}
write!(&mut stdout, " STATE")?; width += 6;
for _ in 5..state_len {
write!(&mut stdout, " ")?; width += 1;
}
if start_time {
if width + 21 > terminal_width {
break;
}
write!(&mut stdout, " START")?; width += 6;
for _ in 5..20 {
write!(&mut stdout, " ")?; width += 1;
}
}
if width + 8 > terminal_width {
break;
}
write!(&mut stdout, " COMMAND")?;
break;
}
stdout.set_color(&*COLOR_DEFAULT)?;
writeln!(&mut stdout)?;
let mut pid_iter = pid.into_iter();
let mut ppid_iter = ppid.into_iter();
let mut vsz_iter = vsz.into_iter();
let mut rss_iter = rss.into_iter();
let mut tty_iter = tty.into_iter();
let mut anon_iter = anon.into_iter();
let mut thd_iter = thd.into_iter();
let mut user_iter = user.into_iter();
let mut group_iter = group.into_iter();
let mut program_iter = program.into_iter();
let mut state_iter = state.into_iter();
for process in processes.iter() {
let mut width = 0;
if width + pid_len > terminal_width {
stdout.set_color(&*COLOR_DEFAULT)?;
writeln!(&mut stdout)?;
continue;
}
let pid = pid_iter.next().unwrap();
stdout.set_color(&*COLOR_BOLD_TEXT)?;
write!(&mut stdout, "{1:>0$}", pid_len, pid)?;
width += pid_len;
if width + 1 + ppid_len > terminal_width {
continue;
}
stdout.set_color(&*COLOR_NORMAL_TEXT)?;
let ppid = ppid_iter.next().unwrap();
for _ in 0..=(ppid_len - ppid.len()) {
write!(&mut stdout, " ")?; width += 1;
}
stdout.write_all(ppid.as_bytes())?;
width += ppid.len();
if width + 5 > terminal_width {
stdout.set_color(&*COLOR_DEFAULT)?;
writeln!(&mut stdout)?;
continue;
}
if let Some(real_time_priority) = process.real_time_priority {
write!(&mut stdout, "{:>5}", format!("*{}", real_time_priority))?;
} else {
write!(&mut stdout, "{:>5}", process.priority)?;
}
width += 5;
if width + 4 > terminal_width {
stdout.set_color(&*COLOR_DEFAULT)?;
writeln!(&mut stdout)?;
continue;
}
write!(&mut stdout, "{:>4}", process.nice)?;
width += 4;
if !only_information {
if width + 5 > terminal_width {
stdout.set_color(&*COLOR_DEFAULT)?;
writeln!(&mut stdout)?;
continue;
}
write!(&mut stdout, " {:>4.1}", percentage.get(&process.pid).unwrap() * 100.0)?;
width += 5;
}
if width + 1 + vsz_len > terminal_width {
stdout.set_color(&*COLOR_DEFAULT)?;
writeln!(&mut stdout)?;
continue;
}
let vsz = vsz_iter.next().unwrap();
for _ in 0..=(vsz_len - vsz.len()) {
write!(&mut stdout, " ")?; width += 1;
}
stdout.write_all(vsz.as_bytes())?;
width += vsz.len();
if width + 1 + rss_len > terminal_width {
stdout.set_color(&*COLOR_DEFAULT)?;
writeln!(&mut stdout)?;
continue;
}
let rss = rss_iter.next().unwrap();
for _ in 0..=(rss_len - rss.len()) {
write!(&mut stdout, " ")?; width += 1;
}
stdout.write_all(rss.as_bytes())?;
width += rss.len();
if width + 1 + anon_len > terminal_width {
stdout.set_color(&*COLOR_DEFAULT)?;
writeln!(&mut stdout)?;
continue;
}
let anon = anon_iter.next().unwrap();
for _ in 0..=(anon_len - anon.len()) {
write!(&mut stdout, " ")?; width += 1;
}
stdout.write_all(anon.as_bytes())?;
width += anon.len();
if width + 1 + thd_len > terminal_width {
stdout.set_color(&*COLOR_DEFAULT)?;
writeln!(&mut stdout)?;
continue;
}
let thd = thd_iter.next().unwrap();
for _ in 0..=(thd_len - thd.len()) {
write!(&mut stdout, " ")?; width += 1;
}
stdout.write_all(thd.as_bytes())?;
width += thd.len();
if width + 1 + tty_len > terminal_width {
stdout.set_color(&*COLOR_DEFAULT)?;
writeln!(&mut stdout)?;
continue;
}
let tty = tty_iter.next().unwrap();
write!(&mut stdout, " ")?; width += 1;
stdout.write_all(tty.as_bytes())?;
width += tty.len();
for _ in 0..(tty_len - tty.len()) {
write!(&mut stdout, " ")?; width += 1;
}
if width + 1 + user_len > terminal_width {
stdout.set_color(&*COLOR_DEFAULT)?;
writeln!(&mut stdout)?;
continue;
}
let user = user_iter.next().unwrap();
write!(&mut stdout, " ")?; width += 1;
{
let s = user.name().to_str().unwrap();
if s.len() > truncate_inc {
stdout.write_all(s[..(truncate_inc - 1)].as_bytes())?;
write!(&mut stdout, "+")?; width += truncate_inc;
for _ in truncate_inc..4 {
write!(&mut stdout, " ")?; width += 1;
}
} else {
stdout.write_all(s.as_bytes())?;
width += s.len();
for _ in 0..(user_len - s.len()) {
write!(&mut stdout, " ")?; width += 1;
}
}
}
if width + 1 + group_len > terminal_width {
stdout.set_color(&*COLOR_DEFAULT)?;
writeln!(&mut stdout)?;
continue;
}
let group = group_iter.next().unwrap();
write!(&mut stdout, " ")?; width += 1;
{
let s = group.name().to_str().unwrap();
if s.len() > truncate_inc {
stdout.write_all(s[..(truncate_inc - 1)].as_bytes())?;
write!(&mut stdout, "+")?; width += truncate_inc;
for _ in truncate_inc..5 {
write!(&mut stdout, " ")?; width += 1;
}
} else {
stdout.write_all(s.as_bytes())?;
width += s.len();
for _ in 0..(group_len - s.len()) {
write!(&mut stdout, " ")?; width += 1;
}
}
}
if width + 1 + program_len > terminal_width {
stdout.set_color(&*COLOR_DEFAULT)?;
writeln!(&mut stdout)?;
continue;
}
let program = program_iter.next().unwrap();
write!(&mut stdout, " ")?; width += 1;
if program.len() > truncate_inc {
stdout.write_all(program[..(truncate_inc - 1)].as_bytes())?;
write!(&mut stdout, "+")?; width += truncate_inc;
for _ in truncate_inc..7 {
write!(&mut stdout, " ")?; width += 1;
}
} else {
stdout.write_all(program.as_bytes())?;
width += program.len();
for _ in 0..(program_len - program.len()) {
write!(&mut stdout, " ")?; width += 1;
}
}
if width + 1 + state_len > terminal_width {
stdout.set_color(&*COLOR_DEFAULT)?;
writeln!(&mut stdout)?;
continue;
}
let state = state_iter.next().unwrap();
write!(&mut stdout, " ")?; width += 1;
stdout.write_all(state.as_bytes())?;
width += state.len();
for _ in 0..(state_len - state.len()) {
write!(&mut stdout, " ")?; width += 1;
}
if start_time {
if width + 21 > terminal_width {
stdout.set_color(&*COLOR_DEFAULT)?;
writeln!(&mut stdout)?;
continue;
}
write!(&mut stdout, " ")?;
stdout.write_all(
process.start_time.to_rfc3339_opts(SecondsFormat::Secs, true).as_bytes(),
)?;
width += 21;
}
if width + 8 > terminal_width {
stdout.set_color(&*COLOR_DEFAULT)?;
writeln!(&mut stdout)?;
continue;
}
write!(&mut stdout, " ")?; width += 1;
let remain_width = terminal_width - width;
if process.cmdline.len() > remain_width {
let cmdline =
String::from_utf8_lossy(&process.cmdline.as_bytes()[..(remain_width - 1)]);
stdout.write_all(cmdline.as_bytes())?;
write!(&mut stdout, "+")?; } else {
stdout.write_all(process.cmdline.as_bytes())?;
}
stdout.set_color(&*COLOR_DEFAULT)?;
writeln!(&mut stdout)?;
}
output.print(&stdout)?;
Ok(())
}
fn handle_volume(
monitor: Option<Duration>,
unit: Option<ByteUnit>,
only_information: bool,
mounts: bool,
) -> Result<(), Box<dyn Error>> {
monitor_handler!(
monitor,
draw_volume(unit, only_information, mounts, monitor)?,
draw_volume(unit, only_information, mounts, None)?,
only_information
);
}
fn draw_volume(
unit: Option<ByteUnit>,
only_information: bool,
mounts: bool,
monitor: Option<Duration>,
) -> Result<(), ScannerError> {
let output = if unsafe { FORCE_PLAIN_MODE } {
BufferWriter::stdout(ColorChoice::Never)
} else {
BufferWriter::stdout(ColorChoice::Always)
};
let mut stdout = output.buffer();
let terminal_width = terminal_size()
.map(|(width, _)| (width.0 as usize).max(MIN_TERMINAL_WIDTH))
.unwrap_or(DEFAULT_TERMINAL_WIDTH);
if only_information {
let volumes = volume::get_volumes()?;
let volumes_len = volumes.len();
debug_assert!(volumes_len > 0);
let mut volumes_size: Vec<String> = Vec::with_capacity(volumes_len);
let mut volumes_used: Vec<String> = Vec::with_capacity(volumes_len);
let mut volumes_used_percentage: Vec<String> = Vec::with_capacity(volumes_len);
let mut volumes_read_total: Vec<String> = Vec::with_capacity(volumes_len);
let mut volumes_write_total: Vec<String> = Vec::with_capacity(volumes_len);
for volume in volumes.iter() {
let size = Byte::from_bytes(u128::from(volume.size));
let used = Byte::from_bytes(u128::from(volume.used));
let used_percentage =
format!("{:.2}%", (volume.used * 100) as f64 / volume.size as f64);
let read_total = Byte::from_bytes(u128::from(volume.stat.read_bytes));
let write_total = Byte::from_bytes(u128::from(volume.stat.write_bytes));
let (size, used, read_total, write_total) = match unit {
Some(unit) => {
(
size.get_adjusted_unit(unit).to_string(),
used.get_adjusted_unit(unit).to_string(),
read_total.get_adjusted_unit(unit).to_string(),
write_total.get_adjusted_unit(unit).to_string(),
)
}
None => {
(
size.get_appropriate_unit(false).to_string(),
used.get_appropriate_unit(false).to_string(),
read_total.get_appropriate_unit(false).to_string(),
write_total.get_appropriate_unit(false).to_string(),
)
}
};
volumes_size.push(size);
volumes_used.push(used);
volumes_used_percentage.push(used_percentage);
volumes_read_total.push(read_total);
volumes_write_total.push(write_total);
}
let devices_len = volumes.iter().map(|volume| volume.device.len()).max().unwrap();
let devices_len_inc = devices_len + 1;
let volumes_size_len = volumes_size.iter().map(|size| size.len()).max().unwrap();
let volumes_used_len = volumes_used.iter().map(|used| used.len()).max().unwrap();
let volumes_used_percentage_len = volumes_used_percentage
.iter()
.map(|used_percentage| used_percentage.len())
.max()
.unwrap();
let volumes_read_total_len =
volumes_read_total.iter().map(|read_total| read_total.len()).max().unwrap().max(9);
let volumes_write_total_len =
volumes_write_total.iter().map(|write_total| write_total.len()).max().unwrap().max(12);
let progress_max = terminal_width
- devices_len
- 4
- volumes_used_len
- 3
- volumes_size_len
- 2
- volumes_used_percentage_len
- 1;
stdout.set_color(&*COLOR_LABEL)?;
write!(&mut stdout, "{1:>0$}", devices_len_inc + volumes_read_total_len, "Read Data")?;
stdout.set_color(&*COLOR_NORMAL_TEXT)?;
write!(&mut stdout, " | ")?;
stdout.set_color(&*COLOR_LABEL)?;
write!(&mut stdout, "{1:>0$}", volumes_write_total_len, "Written Data")?;
writeln!(&mut stdout)?;
let mut volumes_size_iter = volumes_size.into_iter();
let mut volumes_used_iter = volumes_used.into_iter();
let mut volumes_used_percentage_iter = volumes_used_percentage.into_iter();
let mut volumes_read_total_iter = volumes_read_total.into_iter();
let mut volumes_write_total_iter = volumes_write_total.into_iter();
for volume in volumes.into_iter() {
let size = volumes_size_iter.next().unwrap();
let used = volumes_used_iter.next().unwrap();
let used_percentage = volumes_used_percentage_iter.next().unwrap();
let read_total = volumes_read_total_iter.next().unwrap();
let write_total = volumes_write_total_iter.next().unwrap();
stdout.set_color(&*COLOR_LABEL)?;
write!(&mut stdout, "{1:<0$}", devices_len_inc, volume.device)?;
stdout.set_color(&*COLOR_BOLD_TEXT)?;
for _ in 0..(volumes_read_total_len - read_total.len()) {
write!(&mut stdout, " ")?;
}
stdout.write_all(read_total.as_bytes())?;
write!(&mut stdout, " ")?;
for _ in 0..(volumes_write_total_len - write_total.len()) {
write!(&mut stdout, " ")?;
}
stdout.write_all(write_total.as_bytes())?;
writeln!(&mut stdout)?;
stdout.set_color(&*COLOR_NORMAL_TEXT)?;
for _ in 0..devices_len {
write!(&mut stdout, " ")?;
}
write!(&mut stdout, " [")?;
let f = progress_max as f64 / volume.size as f64;
let progress_used = (volume.used as f64 * f).floor() as usize;
stdout.set_color(&*COLOR_USED)?;
for _ in 0..progress_used {
write!(&mut stdout, "|")?; }
for _ in 0..(progress_max - progress_used) {
write!(&mut stdout, " ")?; }
stdout.set_color(&*COLOR_NORMAL_TEXT)?;
write!(&mut stdout, "] ")?;
for _ in 0..(volumes_used_len - used.len()) {
write!(&mut stdout, " ")?; }
stdout.set_color(&*COLOR_BOLD_TEXT)?;
stdout.write_all(used.as_bytes())?;
stdout.set_color(&*COLOR_NORMAL_TEXT)?;
write!(&mut stdout, " / ")?;
for _ in 0..(volumes_size_len - size.len()) {
write!(&mut stdout, " ")?; }
stdout.set_color(&*COLOR_BOLD_TEXT)?;
stdout.write_all(size.as_bytes())?;
write!(&mut stdout, " (")?;
for _ in 0..(volumes_used_percentage_len - used_percentage.len()) {
write!(&mut stdout, " ")?; }
stdout.write_all(used_percentage.as_bytes())?;
write!(&mut stdout, ")")?;
stdout.set_color(&*COLOR_DEFAULT)?;
writeln!(&mut stdout)?;
if mounts {
stdout.set_color(&*COLOR_NORMAL_TEXT)?;
for point in volume.points {
for _ in 0..devices_len_inc {
write!(&mut stdout, " ")?;
}
stdout.write_all(point.as_bytes())?;
stdout.set_color(&*COLOR_DEFAULT)?;
writeln!(&mut stdout)?;
}
}
}
} else {
let volumes_with_speed = volume::get_volumes_with_speed(match monitor {
Some(monitor) => monitor,
None => Duration::from_millis(DEFAULT_INTERVAL),
})?;
let volumes_with_speed_len = volumes_with_speed.len();
debug_assert!(volumes_with_speed_len > 0);
let mut volumes_size: Vec<String> = Vec::with_capacity(volumes_with_speed_len);
let mut volumes_used: Vec<String> = Vec::with_capacity(volumes_with_speed_len);
let mut volumes_used_percentage: Vec<String> = Vec::with_capacity(volumes_with_speed_len);
let mut volumes_read: Vec<String> = Vec::with_capacity(volumes_with_speed_len);
let mut volumes_read_total: Vec<String> = Vec::with_capacity(volumes_with_speed_len);
let mut volumes_write: Vec<String> = Vec::with_capacity(volumes_with_speed_len);
let mut volumes_write_total: Vec<String> = Vec::with_capacity(volumes_with_speed_len);
for (volume, volume_speed) in volumes_with_speed.iter() {
let size = Byte::from_bytes(u128::from(volume.size));
let used = Byte::from_bytes(u128::from(volume.used));
let used_percentage =
format!("{:.2}%", (volume.used * 100) as f64 / volume.size as f64);
let read = Byte::from_unit(volume_speed.read, ByteUnit::B).unwrap();
let read_total = Byte::from_bytes(u128::from(volume.stat.read_bytes));
let write = Byte::from_unit(volume_speed.write, ByteUnit::B).unwrap();
let write_total = Byte::from_bytes(u128::from(volume.stat.write_bytes));
let (size, used, mut read, read_total, mut write, write_total) = match unit {
Some(unit) => {
(
size.get_adjusted_unit(unit).to_string(),
used.get_adjusted_unit(unit).to_string(),
read.get_adjusted_unit(unit).to_string(),
read_total.get_adjusted_unit(unit).to_string(),
write.get_adjusted_unit(unit).to_string(),
write_total.get_adjusted_unit(unit).to_string(),
)
}
None => {
(
size.get_appropriate_unit(false).to_string(),
used.get_appropriate_unit(false).to_string(),
read.get_appropriate_unit(false).to_string(),
read_total.get_appropriate_unit(false).to_string(),
write.get_appropriate_unit(false).to_string(),
write_total.get_appropriate_unit(false).to_string(),
)
}
};
read.push_str("/s");
write.push_str("/s");
volumes_size.push(size);
volumes_used.push(used);
volumes_used_percentage.push(used_percentage);
volumes_read.push(read);
volumes_read_total.push(read_total);
volumes_write.push(write);
volumes_write_total.push(write_total);
}
let devices_len =
volumes_with_speed.iter().map(|(volume, _)| volume.device.len()).max().unwrap();
let devices_len_inc = devices_len + 1;
let volumes_size_len = volumes_size.iter().map(|size| size.len()).max().unwrap();
let volumes_used_len = volumes_used.iter().map(|used| used.len()).max().unwrap();
let volumes_used_percentage_len = volumes_used_percentage
.iter()
.map(|used_percentage| used_percentage.len())
.max()
.unwrap();
let volumes_read_len = volumes_read.iter().map(|read| read.len()).max().unwrap().max(12);
let volumes_read_total_len =
volumes_read_total.iter().map(|read_total| read_total.len()).max().unwrap().max(9);
let volumes_write_len =
volumes_write.iter().map(|write| write.len()).max().unwrap().max(12);
let volumes_write_total_len =
volumes_write_total.iter().map(|write_total| write_total.len()).max().unwrap().max(12);
let progress_max = terminal_width
- devices_len
- 4
- volumes_used_len
- 3
- volumes_size_len
- 2
- volumes_used_percentage_len
- 1;
stdout.set_color(&*COLOR_LABEL)?;
write!(&mut stdout, "{1:>0$}", devices_len_inc + volumes_read_len, "Reading Rate")?;
stdout.set_color(&*COLOR_NORMAL_TEXT)?;
write!(&mut stdout, " | ")?;
stdout.set_color(&*COLOR_LABEL)?;
write!(&mut stdout, "{1:>0$}", volumes_read_total_len, "Read Data")?;
stdout.set_color(&*COLOR_NORMAL_TEXT)?;
write!(&mut stdout, " | ")?;
stdout.set_color(&*COLOR_LABEL)?;
write!(&mut stdout, "{1:>0$}", volumes_write_len, "Writing Rate")?;
stdout.set_color(&*COLOR_NORMAL_TEXT)?;
write!(&mut stdout, " | ")?;
stdout.set_color(&*COLOR_LABEL)?;
write!(&mut stdout, "{1:>0$}", volumes_write_total_len, "Written Data")?;
writeln!(&mut stdout)?;
let mut volumes_size_iter = volumes_size.into_iter();
let mut volumes_used_iter = volumes_used.into_iter();
let mut volumes_used_percentage_iter = volumes_used_percentage.into_iter();
let mut volumes_read_iter = volumes_read.into_iter();
let mut volumes_read_total_iter = volumes_read_total.into_iter();
let mut volumes_write_iter = volumes_write.into_iter();
let mut volumes_write_total_iter = volumes_write_total.into_iter();
for (volume, _) in volumes_with_speed.into_iter() {
let size = volumes_size_iter.next().unwrap();
let used = volumes_used_iter.next().unwrap();
let used_percentage = volumes_used_percentage_iter.next().unwrap();
let read = volumes_read_iter.next().unwrap();
let read_total = volumes_read_total_iter.next().unwrap();
let write = volumes_write_iter.next().unwrap();
let write_total = volumes_write_total_iter.next().unwrap();
stdout.set_color(&*COLOR_LABEL)?;
write!(&mut stdout, "{1:<0$}", devices_len_inc, volume.device)?;
stdout.set_color(&*COLOR_BOLD_TEXT)?;
for _ in 0..(volumes_read_len - read.len()) {
write!(&mut stdout, " ")?;
}
stdout.write_all(read.as_bytes())?;
write!(&mut stdout, " ")?;
for _ in 0..(volumes_read_total_len - read_total.len()) {
write!(&mut stdout, " ")?;
}
stdout.write_all(read_total.as_bytes())?;
write!(&mut stdout, " ")?;
for _ in 0..(volumes_write_len - write.len()) {
write!(&mut stdout, " ")?;
}
stdout.write_all(write.as_bytes())?;
write!(&mut stdout, " ")?;
for _ in 0..(volumes_write_total_len - write_total.len()) {
write!(&mut stdout, " ")?;
}
stdout.write_all(write_total.as_bytes())?;
writeln!(&mut stdout)?;
stdout.set_color(&*COLOR_NORMAL_TEXT)?;
for _ in 0..devices_len {
write!(&mut stdout, " ")?;
}
write!(&mut stdout, " [")?;
let f = progress_max as f64 / volume.size as f64;
let progress_used = (volume.used as f64 * f).floor() as usize;
stdout.set_color(&*COLOR_USED)?;
for _ in 0..progress_used {
write!(&mut stdout, "|")?; }
for _ in 0..(progress_max - progress_used) {
write!(&mut stdout, " ")?; }
stdout.set_color(&*COLOR_NORMAL_TEXT)?;
write!(&mut stdout, "] ")?;
for _ in 0..(volumes_used_len - used.len()) {
write!(&mut stdout, " ")?; }
stdout.set_color(&*COLOR_BOLD_TEXT)?;
stdout.write_all(used.as_bytes())?;
stdout.set_color(&*COLOR_NORMAL_TEXT)?;
write!(&mut stdout, " / ")?;
for _ in 0..(volumes_size_len - size.len()) {
write!(&mut stdout, " ")?; }
stdout.set_color(&*COLOR_BOLD_TEXT)?;
stdout.write_all(size.as_bytes())?;
write!(&mut stdout, " (")?;
for _ in 0..(volumes_used_percentage_len - used_percentage.len()) {
write!(&mut stdout, " ")?; }
stdout.write_all(used_percentage.as_bytes())?;
write!(&mut stdout, ")")?;
stdout.set_color(&*COLOR_DEFAULT)?;
writeln!(&mut stdout)?;
if mounts {
stdout.set_color(&*COLOR_NORMAL_TEXT)?;
for point in volume.points {
for _ in 0..devices_len_inc {
write!(&mut stdout, " ")?;
}
stdout.write_all(point.as_bytes())?;
stdout.set_color(&*COLOR_DEFAULT)?;
writeln!(&mut stdout)?;
}
}
}
}
output.print(&stdout)?;
Ok(())
}
fn handle_network(monitor: Option<Duration>, unit: Option<ByteUnit>) -> Result<(), Box<dyn Error>> {
monitor_handler!(monitor, draw_network(unit, monitor)?, draw_network(unit, None)?, false);
}
fn draw_network(unit: Option<ByteUnit>, monitor: Option<Duration>) -> Result<(), ScannerError> {
let networks_with_speed = network::get_networks_with_speed(match monitor {
Some(monitor) => monitor,
None => Duration::from_millis(DEFAULT_INTERVAL),
})?;
let networks_with_speed_len = networks_with_speed.len();
let output = if unsafe { FORCE_PLAIN_MODE } {
BufferWriter::stdout(ColorChoice::Never)
} else {
BufferWriter::stdout(ColorChoice::Always)
};
let mut stdout = output.buffer();
debug_assert!(networks_with_speed_len > 0);
let mut uploads: Vec<String> = Vec::with_capacity(networks_with_speed_len);
let mut uploads_total: Vec<String> = Vec::with_capacity(networks_with_speed_len);
let mut downloads: Vec<String> = Vec::with_capacity(networks_with_speed_len);
let mut downloads_total: Vec<String> = Vec::with_capacity(networks_with_speed_len);
for (network, network_speed) in networks_with_speed.iter() {
let upload = Byte::from_unit(network_speed.transmit, ByteUnit::B).unwrap();
let upload_total = Byte::from_bytes(u128::from(network.stat.transmit_bytes));
let download = Byte::from_unit(network_speed.receive, ByteUnit::B).unwrap();
let download_total = Byte::from_bytes(u128::from(network.stat.receive_bytes));
let (mut upload, upload_total, mut download, download_total) = match unit {
Some(unit) => {
(
upload.get_adjusted_unit(unit).to_string(),
upload_total.get_adjusted_unit(unit).to_string(),
download.get_adjusted_unit(unit).to_string(),
download_total.get_adjusted_unit(unit).to_string(),
)
}
None => {
(
upload.get_appropriate_unit(false).to_string(),
upload_total.get_appropriate_unit(false).to_string(),
download.get_appropriate_unit(false).to_string(),
download_total.get_appropriate_unit(false).to_string(),
)
}
};
upload.push_str("/s");
download.push_str("/s");
uploads.push(upload);
uploads_total.push(upload_total);
downloads.push(download);
downloads_total.push(download_total);
}
let interface_len =
networks_with_speed.iter().map(|(network, _)| network.interface.len()).max().unwrap();
let interface_len_inc = interface_len + 1;
let upload_len = uploads.iter().map(|upload| upload.len()).max().unwrap().max(11);
let upload_total_len =
uploads_total.iter().map(|upload_total| upload_total.len()).max().unwrap().max(13);
let download_len = downloads.iter().map(|download| download.len()).max().unwrap().max(13);
let download_total_len =
downloads_total.iter().map(|download_total| download_total.len()).max().unwrap().max(15);
stdout.set_color(&*COLOR_LABEL)?;
write!(&mut stdout, "{1:>0$}", interface_len_inc + upload_len, "Upload Rate")?;
stdout.set_color(&*COLOR_NORMAL_TEXT)?;
write!(&mut stdout, " | ")?;
stdout.set_color(&*COLOR_LABEL)?;
write!(&mut stdout, "{1:>0$}", upload_total_len, "Uploaded Data")?;
stdout.set_color(&*COLOR_NORMAL_TEXT)?;
write!(&mut stdout, " | ")?;
stdout.set_color(&*COLOR_LABEL)?;
write!(&mut stdout, "{1:>0$}", download_len, "Download Rate")?;
stdout.set_color(&*COLOR_NORMAL_TEXT)?;
write!(&mut stdout, " | ")?;
stdout.set_color(&*COLOR_LABEL)?;
write!(&mut stdout, "{1:>0$}", download_total_len, "Downloaded Data")?;
writeln!(&mut stdout)?;
let mut uploads_iter = uploads.into_iter();
let mut uploads_total_iter = uploads_total.into_iter();
let mut downloads_iter = downloads.into_iter();
let mut downloads_total_iter = downloads_total.into_iter();
for (network, _) in networks_with_speed.into_iter() {
let upload = uploads_iter.next().unwrap();
let upload_total = uploads_total_iter.next().unwrap();
let download = downloads_iter.next().unwrap();
let download_total = downloads_total_iter.next().unwrap();
stdout.set_color(&*COLOR_LABEL)?;
write!(&mut stdout, "{1:<0$}", interface_len_inc, network.interface)?;
stdout.set_color(&*COLOR_BOLD_TEXT)?;
for _ in 0..(upload_len - upload.len()) {
write!(&mut stdout, " ")?;
}
stdout.write_all(upload.as_bytes())?;
write!(&mut stdout, " ")?;
for _ in 0..(upload_total_len - upload_total.len()) {
write!(&mut stdout, " ")?;
}
stdout.write_all(upload_total.as_bytes())?;
write!(&mut stdout, " ")?;
for _ in 0..(download_len - download.len()) {
write!(&mut stdout, " ")?;
}
stdout.write_all(download.as_bytes())?;
write!(&mut stdout, " ")?;
for _ in 0..(download_total_len - download_total.len()) {
write!(&mut stdout, " ")?;
}
stdout.write_all(download_total.as_bytes())?;
stdout.set_color(&*COLOR_DEFAULT)?;
writeln!(&mut stdout)?;
}
output.print(&stdout)?;
Ok(())
}
fn handle_memory(monitor: Option<Duration>, unit: Option<ByteUnit>) -> Result<(), Box<dyn Error>> {
monitor_handler!(monitor, draw_memory(unit)?);
}
fn draw_memory(unit: Option<ByteUnit>) -> Result<(), ScannerError> {
let free = memory::free()?;
let output = if unsafe { FORCE_PLAIN_MODE } {
BufferWriter::stdout(ColorChoice::Never)
} else {
BufferWriter::stdout(ColorChoice::Always)
};
let mut stdout = output.buffer();
let (mem_used, mem_total, swap_used, swap_total) = {
let (mem_used, mem_total, swap_used, swap_total) = (
Byte::from_bytes(free.mem.used as u128),
Byte::from_bytes(free.mem.total as u128),
Byte::from_bytes(free.swap.used as u128),
Byte::from_bytes(free.swap.total as u128),
);
match unit {
Some(unit) => {
(
mem_used.get_adjusted_unit(unit).to_string(),
mem_total.get_adjusted_unit(unit).to_string(),
swap_used.get_adjusted_unit(unit).to_string(),
swap_total.get_adjusted_unit(unit).to_string(),
)
}
None => {
(
mem_used.get_appropriate_unit(true).to_string(),
mem_total.get_appropriate_unit(true).to_string(),
swap_used.get_appropriate_unit(true).to_string(),
swap_total.get_appropriate_unit(true).to_string(),
)
}
}
};
let used_len = mem_used.len().max(swap_used.len());
let total_len = mem_total.len().max(swap_total.len());
let mem_percentage = format!("{:.2}%", free.mem.used as f64 * 100f64 / free.mem.total as f64);
let swap_percentage =
format!("{:.2}%", free.swap.used as f64 * 100f64 / free.swap.total as f64);
let percentage_len = mem_percentage.len().max(swap_percentage.len());
let terminal_width = terminal_size()
.map(|(width, _)| (width.0 as usize).max(MIN_TERMINAL_WIDTH))
.unwrap_or(DEFAULT_TERMINAL_WIDTH);
stdout.set_color(&*COLOR_LABEL)?;
write!(&mut stdout, "Memory")?;
stdout.set_color(&*COLOR_NORMAL_TEXT)?;
write!(&mut stdout, " [")?;
let progress_max = terminal_width - 10 - used_len - 3 - total_len - 2 - percentage_len - 1;
let f = progress_max as f64 / free.mem.total as f64;
let progress_used = (free.mem.used as f64 * f).floor() as usize;
stdout.set_color(&*COLOR_USED)?;
for _ in 0..progress_used {
write!(&mut stdout, "|")?; }
let progress_cache = (free.mem.cache as f64 * f).floor() as usize;
stdout.set_color(&*COLOR_CACHE)?;
for _ in 0..progress_cache {
if unsafe { FORCE_PLAIN_MODE } {
write!(&mut stdout, "$")?; } else {
write!(&mut stdout, "|")?; }
}
let progress_buffers = (free.mem.buffers as f64 * f).floor() as usize;
stdout.set_color(&*COLOR_BUFFERS)?;
for _ in 0..progress_buffers {
if unsafe { FORCE_PLAIN_MODE } {
write!(&mut stdout, "#")?; } else {
write!(&mut stdout, "|")?; }
}
for _ in 0..(progress_max - progress_used - progress_cache - progress_buffers) {
write!(&mut stdout, " ")?; }
stdout.set_color(&*COLOR_NORMAL_TEXT)?;
write!(&mut stdout, "] ")?;
for _ in 0..(used_len - mem_used.len()) {
write!(&mut stdout, " ")?; }
stdout.set_color(&*COLOR_BOLD_TEXT)?;
stdout.write_all(mem_used.as_bytes())?;
stdout.set_color(&*COLOR_NORMAL_TEXT)?;
write!(&mut stdout, " / ")?;
for _ in 0..(total_len - mem_total.len()) {
write!(&mut stdout, " ")?; }
stdout.set_color(&*COLOR_BOLD_TEXT)?;
stdout.write_all(mem_total.as_bytes())?;
write!(&mut stdout, " (")?;
for _ in 0..(percentage_len - mem_percentage.len()) {
write!(&mut stdout, " ")?; }
stdout.write_all(mem_percentage.as_bytes())?;
write!(&mut stdout, ")")?;
writeln!(&mut stdout)?;
stdout.set_color(&*COLOR_LABEL)?;
write!(&mut stdout, "Swap ")?;
stdout.set_color(&*COLOR_NORMAL_TEXT)?;
write!(&mut stdout, " [")?;
let f = progress_max as f64 / free.swap.total as f64;
let progress_used = (free.swap.used as f64 * f).floor() as usize;
stdout.set_color(&*COLOR_USED)?;
for _ in 0..progress_used {
write!(&mut stdout, "|")?; }
let progress_cache = (free.swap.cache as f64 * f).floor() as usize;
stdout.set_color(&*COLOR_CACHE)?;
for _ in 0..progress_cache {
if unsafe { FORCE_PLAIN_MODE } {
write!(&mut stdout, "$")?; } else {
write!(&mut stdout, "|")?; }
}
for _ in 0..(progress_max - progress_used - progress_cache) {
write!(&mut stdout, " ")?; }
stdout.set_color(&*COLOR_NORMAL_TEXT)?;
write!(&mut stdout, "] ")?;
for _ in 0..(used_len - swap_used.len()) {
write!(&mut stdout, " ")?; }
stdout.set_color(&*COLOR_BOLD_TEXT)?;
stdout.write_all(swap_used.as_bytes())?;
stdout.set_color(&*COLOR_NORMAL_TEXT)?;
write!(&mut stdout, " / ")?;
for _ in 0..(total_len - swap_total.len()) {
write!(&mut stdout, " ")?; }
stdout.set_color(&*COLOR_BOLD_TEXT)?;
stdout.write_all(swap_total.as_bytes())?;
write!(&mut stdout, " (")?;
for _ in 0..(percentage_len - swap_percentage.len()) {
write!(&mut stdout, " ")?; }
stdout.write_all(swap_percentage.as_bytes())?;
write!(&mut stdout, ")")?;
stdout.set_color(&*COLOR_DEFAULT)?;
writeln!(&mut stdout)?;
output.print(&stdout)?;
Ok(())
}
fn handle_cpu(
monitor: Option<Duration>,
separate: bool,
only_information: bool,
) -> Result<(), Box<dyn Error>> {
monitor_handler!(
monitor,
draw_cpu_info(separate, only_information, monitor)?,
draw_cpu_info(separate, only_information, None)?,
only_information
);
}
fn draw_cpu_info(
separate: bool,
only_information: bool,
monitor: Option<Duration>,
) -> Result<(), ScannerError> {
let output = if unsafe { FORCE_PLAIN_MODE } {
BufferWriter::stdout(ColorChoice::Never)
} else {
BufferWriter::stdout(ColorChoice::Always)
};
let mut stdout = output.buffer();
let terminal_width = terminal_size()
.map(|(width, _)| (width.0 as usize).max(MIN_TERMINAL_WIDTH))
.unwrap_or(DEFAULT_TERMINAL_WIDTH);
let mut draw_load_average = |cpus: &[cpu::CPU]| -> Result<(), ScannerError> {
let load_average = load_average::get_load_average()?;
let logical_cores_number: usize = cpus.iter().map(|cpu| cpu.siblings).sum();
let logical_cores_number_f64 = logical_cores_number as f64;
let one = format!("{:.2}", load_average.one);
let five = format!("{:.2}", load_average.five);
let fifteen = format!("{:.2}", load_average.fifteen);
let load_average_len = one.len().max(five.len()).max(fifteen.len());
let one_percentage =
format!("{:.2}%", load_average.one * 100f64 / logical_cores_number_f64);
let five_percentage =
format!("{:.2}%", load_average.five * 100f64 / logical_cores_number_f64);
let fifteen_percentage =
format!("{:.2}%", load_average.fifteen * 100f64 / logical_cores_number_f64);
let percentage_len =
one_percentage.len().max(five_percentage.len()).max(fifteen_percentage.len());
let progress_max = terminal_width - 11 - load_average_len - 2 - percentage_len - 1;
stdout.set_color(&*COLOR_NORMAL_TEXT)?;
if logical_cores_number > 1 {
write!(&mut stdout, "There are ")?;
stdout.set_color(&*COLOR_BOLD_TEXT)?;
write!(&mut stdout, "{}", logical_cores_number)?;
stdout.set_color(&*COLOR_NORMAL_TEXT)?;
write!(&mut stdout, " logical CPU cores.")?;
} else {
write!(&mut stdout, "There is only one logical CPU core.")?;
}
writeln!(&mut stdout)?;
stdout.set_color(&*COLOR_LABEL)?;
write!(&mut stdout, "one ")?;
stdout.set_color(&*COLOR_NORMAL_TEXT)?;
write!(&mut stdout, " [")?;
let f = progress_max as f64 / logical_cores_number_f64;
let progress_used = ((load_average.one * f).floor() as usize).min(progress_max);
stdout.set_color(&*COLOR_USED)?;
for _ in 0..progress_used {
write!(&mut stdout, "|")?; }
for _ in 0..(progress_max - progress_used) {
write!(&mut stdout, " ")?; }
stdout.set_color(&*COLOR_NORMAL_TEXT)?;
write!(&mut stdout, "] ")?;
for _ in 0..(load_average_len - one.len()) {
write!(&mut stdout, " ")?; }
stdout.set_color(&*COLOR_BOLD_TEXT)?;
stdout.write_all(one.as_bytes())?;
write!(&mut stdout, " (")?;
for _ in 0..(percentage_len - one_percentage.len()) {
write!(&mut stdout, " ")?; }
stdout.write_all(one_percentage.as_bytes())?;
write!(&mut stdout, ")")?;
writeln!(&mut stdout)?;
stdout.set_color(&*COLOR_LABEL)?;
write!(&mut stdout, "five ")?;
stdout.set_color(&*COLOR_NORMAL_TEXT)?;
write!(&mut stdout, " [")?;
let f = progress_max as f64 / logical_cores_number_f64;
let progress_used = ((load_average.five * f).floor() as usize).min(progress_max);
stdout.set_color(&*COLOR_USED)?;
for _ in 0..progress_used {
write!(&mut stdout, "|")?; }
for _ in 0..(progress_max - progress_used) {
write!(&mut stdout, " ")?; }
stdout.set_color(&*COLOR_NORMAL_TEXT)?;
write!(&mut stdout, "] ")?;
for _ in 0..(load_average_len - five.len()) {
write!(&mut stdout, " ")?; }
stdout.set_color(&*COLOR_BOLD_TEXT)?;
stdout.write_all(five.as_bytes())?;
write!(&mut stdout, " (")?;
for _ in 0..(percentage_len - five_percentage.len()) {
write!(&mut stdout, " ")?; }
stdout.write_all(five_percentage.as_bytes())?;
write!(&mut stdout, ")")?;
writeln!(&mut stdout)?;
stdout.set_color(&*COLOR_LABEL)?;
write!(&mut stdout, "fifteen")?;
stdout.set_color(&*COLOR_NORMAL_TEXT)?;
write!(&mut stdout, " [")?;
let f = progress_max as f64 / logical_cores_number_f64;
let progress_used = ((load_average.fifteen * f).floor() as usize).min(progress_max);
stdout.set_color(&*COLOR_USED)?;
for _ in 0..progress_used {
write!(&mut stdout, "|")?; }
for _ in 0..(progress_max - progress_used) {
write!(&mut stdout, " ")?; }
stdout.set_color(&*COLOR_NORMAL_TEXT)?;
write!(&mut stdout, "] ")?;
for _ in 0..(load_average_len - fifteen.len()) {
write!(&mut stdout, " ")?; }
stdout.set_color(&*COLOR_BOLD_TEXT)?;
stdout.write_all(fifteen.as_bytes())?;
write!(&mut stdout, " (")?;
for _ in 0..(percentage_len - fifteen_percentage.len()) {
write!(&mut stdout, " ")?; }
stdout.write_all(fifteen_percentage.as_bytes())?;
write!(&mut stdout, ")")?;
writeln!(&mut stdout)?;
writeln!(&mut stdout)?;
Ok(())
};
if separate {
let all_percentage: Vec<f64> = if only_information {
Vec::new()
} else {
cpu::get_all_cpu_utilization_in_percentage(false, match monitor {
Some(monitor) => monitor,
None => Duration::from_millis(DEFAULT_INTERVAL),
})?
};
let cpus = cpu::get_cpus()?;
draw_load_average(&cpus)?;
let mut i = 0;
let cpus_len_dec = cpus.len() - 1;
for (cpu_index, cpu) in cpus.into_iter().enumerate() {
stdout.set_color(&*COLOR_NORMAL_TEXT)?;
stdout.write_all(cpu.model_name.as_bytes())?;
write!(&mut stdout, " ")?;
stdout.set_color(&*COLOR_BOLD_TEXT)?;
write!(&mut stdout, "{}C/{}T", cpu.cpu_cores, cpu.siblings)?;
writeln!(&mut stdout)?;
let mut hz_string: Vec<String> = Vec::with_capacity(cpu.siblings);
for cpu_mhz in cpu.cpus_mhz.iter().copied() {
let cpu_hz =
Byte::from_unit(cpu_mhz, ByteUnit::MB).unwrap().get_appropriate_unit(false);
hz_string.push(format!(
"{:.2} {}Hz",
cpu_hz.get_value(),
&cpu_hz.get_unit().as_str()[..1]
));
}
let hz_string_len = hz_string.iter().map(|s| s.len()).max().unwrap();
let d = {
let mut n = cpu.siblings;
let mut d = 1;
while n > 10 {
n /= 10;
d += 1;
}
d + 4
};
if only_information {
for (i, hz_string) in hz_string.into_iter().enumerate() {
stdout.set_color(&*COLOR_LABEL)?;
write!(&mut stdout, "{1:<0$}", d, format_args!("CPU{}", i))?;
stdout.set_color(&*COLOR_BOLD_TEXT)?;
write!(&mut stdout, "{1:>0$}", hz_string_len, hz_string)?;
stdout.set_color(&*COLOR_DEFAULT)?;
writeln!(&mut stdout)?;
}
} else {
let mut percentage_string: Vec<String> = Vec::with_capacity(cpu.siblings);
for p in all_percentage[i..].iter().copied().take(cpu.siblings) {
percentage_string.push(format!("{:.2}%", p * 100f64));
}
let percentage_len = percentage_string.iter().map(|s| s.len()).max().unwrap();
let progress_max = terminal_width - d - 3 - percentage_len - 2 - hz_string_len - 1;
let mut percentage_string_iter = percentage_string.into_iter();
let mut hz_string_iter = hz_string.into_iter();
for (i, p) in all_percentage[i..].iter().take(cpu.siblings).enumerate() {
let percentage_string = percentage_string_iter.next().unwrap();
let hz_string = hz_string_iter.next().unwrap();
stdout.set_color(&*COLOR_LABEL)?;
write!(&mut stdout, "{1:<0$}", d, format_args!("CPU{}", i))?;
stdout.set_color(&*COLOR_NORMAL_TEXT)?;
write!(&mut stdout, "[")?;
let f = progress_max as f64;
let progress_used = (p * f).floor() as usize;
stdout.set_color(&*COLOR_USED)?;
for _ in 0..progress_used {
write!(&mut stdout, "|")?; }
for _ in 0..(progress_max - progress_used) {
write!(&mut stdout, " ")?; }
stdout.set_color(&*COLOR_NORMAL_TEXT)?;
write!(&mut stdout, "] ")?;
for _ in 0..(percentage_len - percentage_string.len()) {
write!(&mut stdout, " ")?; }
stdout.set_color(&*COLOR_BOLD_TEXT)?;
stdout.write_all(percentage_string.as_bytes())?;
write!(&mut stdout, " (")?;
for _ in 0..(hz_string_len - hz_string.len()) {
write!(&mut stdout, " ")?; }
stdout.write_all(hz_string.as_bytes())?;
write!(&mut stdout, ")")?;
stdout.set_color(&*COLOR_DEFAULT)?;
writeln!(&mut stdout)?;
}
i += cpu.siblings;
}
if cpu_index != cpus_len_dec {
writeln!(&mut stdout)?;
}
}
} else {
let (average_percentage, average_percentage_string) = if only_information {
(0f64, "".to_string())
} else {
let average_percentage =
cpu::get_average_cpu_utilization_in_percentage(match monitor {
Some(monitor) => monitor,
None => Duration::from_millis(DEFAULT_INTERVAL),
})?;
let average_percentage_string = format!("{:.2}%", average_percentage * 100f64);
(average_percentage, average_percentage_string)
};
let cpus = cpu::get_cpus()?;
draw_load_average(&cpus)?;
for cpu in cpus {
stdout.set_color(&*COLOR_NORMAL_TEXT)?;
stdout.write_all(cpu.model_name.as_bytes())?;
write!(&mut stdout, " ")?;
stdout.set_color(&*COLOR_BOLD_TEXT)?;
write!(&mut stdout, "{}C/{}T", cpu.cpu_cores, cpu.siblings)?;
write!(&mut stdout, " ")?;
let cpu_mhz: f64 = cpu.cpus_mhz.iter().sum::<f64>() / cpu.cpus_mhz.len() as f64;
let cpu_hz =
Byte::from_unit(cpu_mhz, ByteUnit::MB).unwrap().get_appropriate_unit(false);
write!(&mut stdout, "{:.2}{}Hz", cpu_hz.get_value(), &cpu_hz.get_unit().as_str()[..1])?;
stdout.set_color(&*COLOR_DEFAULT)?;
writeln!(&mut stdout)?;
}
if !only_information {
let progress_max = terminal_width - 7 - average_percentage_string.len();
stdout.set_color(&*COLOR_LABEL)?;
write!(&mut stdout, "CPU")?;
stdout.set_color(&*COLOR_NORMAL_TEXT)?;
write!(&mut stdout, " [")?;
let f = progress_max as f64;
let progress_used = (average_percentage * f).floor() as usize;
stdout.set_color(&*COLOR_USED)?;
for _ in 0..progress_used {
write!(&mut stdout, "|")?; }
for _ in 0..(progress_max - progress_used) {
write!(&mut stdout, " ")?; }
stdout.set_color(&*COLOR_NORMAL_TEXT)?;
write!(&mut stdout, "] ")?;
stdout.set_color(&*COLOR_BOLD_TEXT)?;
stdout.write_all(average_percentage_string.as_bytes())?;
stdout.set_color(&*COLOR_DEFAULT)?;
writeln!(&mut stdout)?;
}
}
output.print(&stdout)?;
Ok(())
}
fn handle_time(monitor: bool) -> Result<(), Box<dyn Error>> {
monitor_handler!(monitor, 1000, draw_time()?);
}
fn draw_time() -> Result<(), ScannerError> {
let rtc_date_time = rtc_time::get_rtc_date_time()?;
let output = if unsafe { FORCE_PLAIN_MODE } {
BufferWriter::stdout(ColorChoice::Never)
} else {
BufferWriter::stdout(ColorChoice::Always)
};
let mut stdout = output.buffer();
stdout.set_color(&*COLOR_LABEL)?;
write!(&mut stdout, "RTC Date")?;
write!(&mut stdout, " ")?;
stdout.set_color(&*COLOR_BOLD_TEXT)?;
write!(&mut stdout, "{}", rtc_date_time.date())?;
writeln!(&mut stdout)?;
stdout.set_color(&*COLOR_LABEL)?;
write!(&mut stdout, "RTC Time")?;
write!(&mut stdout, " ")?;
stdout.set_color(&*COLOR_BOLD_TEXT)?;
write!(&mut stdout, "{}", rtc_date_time.time())?;
stdout.set_color(&*COLOR_DEFAULT)?;
writeln!(&mut stdout)?;
output.print(&stdout)?;
Ok(())
}
fn handle_uptime(monitor: bool, second: bool) -> Result<(), Box<dyn Error>> {
monitor_handler!(monitor, 1000, draw_uptime(second)?);
}
fn draw_uptime(second: bool) -> Result<(), ScannerError> {
let uptime = uptime::get_uptime()?.total_uptime;
let output = if unsafe { FORCE_PLAIN_MODE } {
BufferWriter::stdout(ColorChoice::Never)
} else {
BufferWriter::stdout(ColorChoice::Always)
};
let mut stdout = output.buffer();
stdout.set_color(&*COLOR_NORMAL_TEXT)?;
write!(&mut stdout, "This computer has been up for ")?;
if second {
let uptime_sec = uptime.as_secs();
stdout.set_color(&*COLOR_BOLD_TEXT)?;
write!(&mut stdout, "{} second", uptime_sec)?;
if uptime_sec > 1 {
write!(&mut stdout, "s")?;
}
} else {
let s = format_duration(uptime);
stdout.set_color(&*COLOR_BOLD_TEXT)?;
stdout.write_all(s.as_bytes())?;
}
stdout.set_color(&*COLOR_NORMAL_TEXT)?;
write!(&mut stdout, ".")?;
stdout.set_color(&*COLOR_DEFAULT)?;
writeln!(&mut stdout)?;
output.print(&stdout)?;
Ok(())
}
#[inline]
fn handle_hostname() -> Result<(), Box<dyn Error>> {
let hostname = hostname::get_hostname()?;
println!("{}", hostname);
Ok(())
}
#[inline]
fn handle_kernel() -> Result<(), Box<dyn Error>> {
let kernel_version = kernel::get_kernel_version()?;
println!("{}", kernel_version);
Ok(())
}
fn get_matches() -> ArgMatches {
Command::new(APP_NAME)
.term_width(terminal_size().map(|(width, _)| width.0 as usize).unwrap_or(0))
.version(CARGO_PKG_VERSION)
.author(CARGO_PKG_AUTHORS)
.about(concat!("M Prober is a free and simple probe utility for Linux.\n\nEXAMPLES:\n", concat_line!(prefix "mprober ",
"hostname # Show the hostname",
"kernel # Show the kernel version",
"uptime # Show the uptime",
"uptime -m # Show the uptime and refresh every second",
"uptime -p # Show the uptime without colors",
"uptime -l # Show the uptime with darker colors (fitting in with light themes)",
"uptime -s # Show the uptime in seconds",
"time # Show the RTC (UTC) date and time",
"time -m # Show the RTC (UTC) date and time and refresh every second",
"time -p # Show the RTC (UTC) date and time without colors",
"time -l # Show the RTC (UTC) date and time with darker colors (fitting in with light themes)",
"cpu # Show load average and current CPU stats on average",
"cpu -m 1000 # Show load average and CPU stats on average and refresh every 1000 milliseconds",
"cpu -p # Show load average and current CPU stats on average without colors",
"cpu -l # Show load average and current CPU stats on average with darker colors (fitting in with light themes)",
"cpu -s # Show load average and current stats of CPU cores separately",
"cpu -i # Only show CPU information",
"memory # Show current memory stats",
"memory -m 1000 # Show memory stats and refresh every 1000 milliseconds",
"memory -p # Show current memory stats without colors",
"memory -l # Show current memory stats with darker colors (fitting in with light themes)",
"memory -u kb # Show current memory stats in KB",
"network # Show current network stats",
"network -m 1000 # Show network stats and refresh every 1000 milliseconds",
"network -p # Show current network stats without colors",
"network -l # Show current network stats with darker colors (fitting in with light themes)",
"network -u kb # Show current network stats in KB",
"volume # Show current volume stats",
"volume -m 1000 # Show current volume stats and refresh every 1000 milliseconds",
"volume -p # Show current volume stats without colors",
"volume -l # Show current volume stats without colors",
"volume -u kb # Show current volume stats in KB",
"volume -i # Only show volume information without I/O rates",
"volume --mounts # Show current volume stats including mount points",
"process # Show a snapshot of the current processes",
"process -m 1000 # Show a snapshot of the current processes and refresh every 1000 milliseconds",
"process -p # Show a snapshot of the current processes without colors",
"process -l # Show a snapshot of the current processes with darker colors (fitting in with light themes)",
"process -i # Show a snapshot of the current processes but not including CPU usage",
"process -u kb # Show a snapshot of the current processes. Information about memory size is in KB",
"process --truncate 10 # Show a snapshot of the current processes with a specific truncation length to truncate user, group, program's names",
"process --top 10 # Show a snapshot of current top-10 (ordered by CPU and memory usage) processes",
"process -t # Show a snapshot of the current processes with the start time of each process",
"process --pid-filter 3456 # Show a snapshot of the current processes which are related to a specific PID",
"process --user-filter user1 # Show a snapshot of the current processes which are related to a specific user",
"process --group-filter gp1 # Show a snapshot of the current processes which are related to a specific group",
"process --tty-filter tty # Show a snapshot of the current processes which are related to specific tty names matched by a regex",
"process --program-filter ab # Show a snapshot of the current processes which are related to specific program names or commands matched by a regex",
"web # Start a HTTP service on port 8000 to monitor this computer. The default time interval is 3 seconds",
"web -m 2 # Start a HTTP service on port 8000 to monitor this computer. The time interval is set to 2 seconds",
"web -p 7777 # Start a HTTP service on port 7777 to monitor this computer",
"web --addr 127.0.0.1 # Start a HTTP service on 127.0.0.1:8000 to monitor this computer",
"web -a auth_key # Start a HTTP service on port 8000 to monitor this computer. APIs need to be invoked with an auth key",
"web --only-api # Start a HTTP service on port 8000 to serve only HTTP APIs",
"benchmark # Run benchmarks",
"benchmark --disable-cpu # Run benchmarks except for benchmarking CPU",
"benchmark --enable-memory # Benchmark the memory",
)))
.subcommand(
Command::new("hostname")
.aliases(&["h", "host", "name", "servername"])
.about("Show the hostname")
.display_order(0)
.after_help("Enjoy it! https://magiclen.org"),
)
.subcommand(
Command::new("kernel")
.aliases(&["k", "l", "linux"])
.about("Show the kernel version")
.display_order(1)
.after_help("Enjoy it! https://magiclen.org"),
)
.subcommand(
Command::new("uptime")
.aliases(&["u", "up", "utime", "ut"])
.about("Show the uptime")
.display_order(2)
.arg(
Arg::new("MONITOR")
.long("monitor")
.short('m')
.help("Show the uptime and refresh every second"),
)
.arg(Arg::new("PLAIN").long("plain").short('p').help("No colors"))
.arg(Arg::new("LIGHT").long("light").short('l').help("Darker colors"))
.arg(
Arg::new("SECOND")
.long("second")
.short('s')
.help("Show the uptime in seconds"),
)
.after_help("Enjoy it! https://magiclen.org"),
)
.subcommand(
Command::new("time")
.aliases(&[
"t", "systime", "stime", "st", "utc", "utctime", "rtc", "rtctime", "date",
])
.about("Show the RTC (UTC) date and time")
.display_order(3)
.arg(
Arg::new("MONITOR")
.long("monitor")
.short('m')
.help("Show the RTC (UTC) date and time, and refresh every second"),
)
.arg(Arg::new("PLAIN").long("plain").short('p').help("No colors"))
.arg(Arg::new("LIGHT").long("light").short('l').help("Darker colors"))
.after_help("Enjoy it! https://magiclen.org"),
)
.subcommand(
Command::new("cpu")
.aliases(&["c", "cpus", "core", "cores", "load", "processor", "processors"])
.about("Show CPU stats")
.display_order(4)
.arg(
Arg::new("MONITOR")
.long("monitor")
.short('m')
.help("Show CPU stats and refresh every N milliseconds")
.takes_value(true)
.value_name("MILLI_SECONDS"),
)
.arg(Arg::new("PLAIN").long("plain").short('p').help("No colors"))
.arg(Arg::new("LIGHT").long("light").short('l').help("Darker colors"))
.arg(
Arg::new("SEPARATE")
.long("separate")
.short('s')
.help("Separates each CPU"),
)
.arg(
Arg::new("ONLY_INFORMATION")
.long("only-information")
.short('i')
.help("Show only information about CPUs"),
)
.after_help("Enjoy it! https://magiclen.org"),
)
.subcommand(
Command::new("memory")
.aliases(&[
"m", "mem", "f", "free", "memories", "swap", "ram", "dram", "ddr", "cache",
"buffer", "buffers", "buf", "buff",
])
.about("Show memory stats")
.display_order(5)
.arg(
Arg::new("MONITOR")
.long("monitor")
.short('m')
.help("Show memory stats and refresh every N milliseconds")
.takes_value(true)
.value_name("MILLI_SECONDS"),
)
.arg(Arg::new("PLAIN").long("plain").short('p').help("No colors"))
.arg(Arg::new("LIGHT").long("light").short('l').help("Darker colors"))
.arg(
Arg::new("UNIT")
.long("unit")
.short('u')
.help("Forces to use a fixed unit")
.takes_value(true),
)
.after_help("Enjoy it! https://magiclen.org"),
)
.subcommand(
Command::new("network")
.aliases(&["n", "net", "networks", "bandwidth", "traffic"])
.about("Show network stats")
.display_order(6)
.arg(
Arg::new("MONITOR")
.long("monitor")
.short('m')
.help("Show network stats and refresh every N milliseconds")
.takes_value(true)
.value_name("MILLI_SECONDS"),
)
.arg(Arg::new("PLAIN").long("plain").short('p').help("No colors"))
.arg(Arg::new("LIGHT").long("light").short('l').help("Darker colors"))
.arg(
Arg::new("UNIT")
.long("unit")
.short('u')
.help("Forces to use a fixed unit")
.takes_value(true),
)
.after_help("Enjoy it! https://magiclen.org"),
)
.subcommand(
Command::new("volume")
.aliases(&[
"v", "storage", "volumes", "d", "disk", "disks", "blk", "block", "blocks",
"mount", "mounts", "ssd", "hdd",
])
.about("Show volume stats")
.display_order(7)
.arg(
Arg::new("MONITOR")
.long("monitor")
.short('m')
.help("Show volume stats and refresh every N milliseconds")
.takes_value(true)
.value_name("MILLI_SECONDS"),
)
.arg(Arg::new("PLAIN").long("plain").short('p').help("No colors"))
.arg(Arg::new("LIGHT").long("light").short('l').help("Darker colors"))
.arg(
Arg::new("UNIT")
.long("unit")
.short('u')
.help("Forces to use a fixed unit")
.takes_value(true),
)
.arg(
Arg::new("ONLY_INFORMATION")
.long("only-information")
.short('i')
.help("Show only information about volumes without I/O rates"),
)
.arg(
Arg::new("MOUNTS")
.long("mounts")
.aliases(&["mount", "point", "points"])
.help("Also shows mount points"),
)
.after_help("Enjoy it! https://magiclen.org"),
)
.subcommand(
Command::new("process")
.aliases(&["p", "ps"])
.about("Show process stats")
.display_order(8)
.arg(
Arg::new("MONITOR")
.long("monitor")
.short('m')
.help("Show volume stats and refresh every N milliseconds")
.takes_value(true)
.value_name("MILLI_SECONDS"),
)
.arg(Arg::new("PLAIN").long("plain").short('p').help("No colors"))
.arg(Arg::new("LIGHT").long("light").short('l').help("Darker colors"))
.arg(
Arg::new("UNIT")
.long("unit")
.short('u')
.help("Forces to use a fixed unit")
.takes_value(true),
)
.arg(
Arg::new("ONLY_INFORMATION")
.long("only-information")
.short('i')
.help("Show only information about processes without CPU usage"),
)
.arg(
Arg::new("TOP")
.long("top")
.help("Sets the max number of processes shown on the screen")
.takes_value(true)
.value_name("MAX_NUMBER_OF_PROCESSES"),
)
.arg(
Arg::new("TRUNCATE")
.long("truncate")
.help("Truncate the user name, the group name and the program name of processes")
.takes_value(true)
.value_name("LENGTH")
.default_value("7"),
)
.arg(
Arg::new("START_TIME")
.long("start-time")
.short('t')
.alias("time")
.help("Show when the progresses start")
.takes_value(false),
)
.arg(
Arg::new("USER_FILTER")
.long("user-filter")
.alias("filter-user")
.help("Show only processes which are related to a specific user")
.takes_value(true)
.value_name("USER_NAME"),
)
.arg(
Arg::new("GROUP_FILTER")
.long("group-filter")
.alias("filter-group")
.help("Show only processes which are related to a specific group")
.takes_value(true)
.value_name("GROUP_NAME"),
)
.arg(
Arg::new("PROGRAM_FILTER")
.long("program-filter")
.alias("filter-program")
.help("Show only processes which are related to specific programs or commands matched by a regex")
.takes_value(true)
.value_name("REGEX"),
)
.arg(
Arg::new("TTY_FILTER")
.long("tty-filter")
.alias("filter-tty")
.help("Show only processes which are run on specific TTY/PTS matched by a regex")
.takes_value(true)
.value_name("REGEX"),
)
.arg(
Arg::new("PID_FILTER")
.long("pid-filter")
.alias("filter-pid")
.help("Show only processes which are related to a specific PID")
.takes_value(true)
.value_name("PID"),
)
.after_help("Enjoy it! https://magiclen.org"),
)
.subcommand(
Command::new("web")
.aliases(&["w", "server", "http"])
.about("Start a HTTP service to monitor this computer")
.display_order(9)
.arg(
Arg::new("MONITOR")
.long("monitor")
.short('m')
.help("Automatically refresh every N seconds")
.takes_value(true)
.value_name("SECONDS")
.default_value("3"),
)
.arg(
Arg::new("ADDRESS")
.long("address")
.visible_alias("addr")
.help("Assign the address that M Prober binds.")
.takes_value(true)
.default_value(if cfg!(debug_assertions) {
"127.0.0.1"
} else {
"0.0.0.0"
}),
)
.arg(
Arg::new("LISTEN_PORT")
.long("listen-port")
.visible_alias("port")
.short('p')
.help("Assign a TCP port for the HTTP service")
.takes_value(true)
.default_value("8000"),
)
.arg(
Arg::new("AUTH_KEY")
.long("auth-key")
.short('a')
.help("Assign an auth key")
.takes_value(true),
)
.arg(
Arg::new("ONLY_API")
.long("only-api")
.aliases(&["only-apis"])
.help("Disable the web page"),
)
.after_help("Enjoy it! https://magiclen.org"),
)
.subcommand(
Command::new("benchmark")
.aliases(&["b", "bench", "performance"])
.about("Run benchmarks to measure the performance of this environment")
.display_order(10)
.arg(
Arg::new("WARMING_UP_DURATION")
.display_order(0)
.long("warming-up-duration")
.help("Assign a duration for warming up")
.takes_value(true)
.value_name("MILLI_SECONDS")
.default_value("3000"),
)
.arg(
Arg::new("BENCHMARK_DURATION")
.display_order(1)
.long("benchmark-duration")
.help("Assign a duration for each benchmarking")
.takes_value(true)
.value_name("MILLI_SECONDS")
.default_value("5000"),
)
.arg(
Arg::new("VERBOSE")
.display_order(2)
.long("verbose")
.short('v')
.help("Show more information in stderr"),
)
.arg(
Arg::new("DISABLE_CPU")
.display_order(10)
.long("disable-cpu")
.aliases(&["disabled-cpu", "disable-cpus", "disabled-cpus"])
.help("Not to benchmark CPUs"),
)
.arg(
Arg::new("ENABLE_CPU")
.display_order(100)
.long("enable-cpu")
.aliases(&["enabled-cpu", "enable-cpus", "enabled-cpus"])
.help("Allow to benchmark CPUs (disables others by default)"),
)
.arg(
Arg::new("DISABLE_MEMORY")
.display_order(11)
.long("disable-memory")
.aliases(&["disabled-memory"])
.help("Not to benchmark memory"),
)
.arg(
Arg::new("ENABLE_MEMORY")
.display_order(101)
.long("enable-memory")
.aliases(&["enabled-memory"])
.help("Allow to benchmark memory (disables others by default)"),
)
.arg(
Arg::new("DISABLE_VOLUME")
.display_order(13)
.long("disable-volume")
.aliases(&["disabled-volume", "disable-volumes", "disabled-volumes"])
.help("Not to benchmark volumes"),
)
.arg(
Arg::new("ENABLE_VOLUME")
.display_order(103)
.long("enable-volume")
.aliases(&["enabled-volume", "enable-volumes", "enabled-volumes"])
.help("Allow to benchmark volumes (disables others by default)"),
)
.after_help("Enjoy it! https://magiclen.org"),
)
.after_help("Enjoy it! https://magiclen.org")
.get_matches()
}
#[inline]
fn get_monitor_duration(matches: &ArgMatches) -> Result<Option<Duration>, Box<dyn Error>> {
match matches.value_of("MONITOR") {
Some(monitor) => {
let monitor = MonitorInterval::parse_str(monitor)
.map_err(|_| format!("`{}` is not a correct value for MILLI_SECONDS", monitor))?
.get_number();
Ok(Some(Duration::from_secs_f64(monitor / 1000f64)))
}
None => Ok(None),
}
}
#[inline]
fn get_byte_unit(matches: &ArgMatches) -> Result<Option<ByteUnit>, Box<dyn Error>> {
match matches.value_of("UNIT") {
Some(unit) => {
let unit = ByteUnit::from_str(unit)
.map_err(|_| format!("`{}` is not a correct value for UNIT", unit))?;
Ok(Some(unit))
}
None => Ok(None),
}
}