mprober 0.11.0-beta.12

This program aims to collect Linux system information including hostname, kernel version, uptime, RTC time, load average, CPU, memory, network interfaces, block devices and processes. It can be used not only as a normal CLI tool, but also a web application with a front-end webpage and useful HTTP APIs.
Documentation
#[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; // should be smaller than 1000 milliseconds

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
                    // percentage_a == percentage_b hardly happens
                    {
                        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());

        // TODO: musl cannot directly handle dynamic users (with systemd). It causes `UserCache` returns `None`.
        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, " ")?; // 1
            width += 1;
        }

        write!(&mut stdout, "PID")?; // 3
        width += 3;

        if width + 1 + ppid_len > terminal_width {
            break;
        }

        for _ in 3..ppid_len {
            write!(&mut stdout, " ")?; // 1
            width += 1;
        }

        write!(&mut stdout, "PPID")?; // 4
        width += 4;

        if width + 5 > terminal_width {
            break;
        }

        write!(&mut stdout, "   PR")?; // 5
        width += 5;

        if width + 4 > terminal_width {
            break;
        }

        write!(&mut stdout, "  NI")?; // 4
        width += 4;

        if !only_information {
            if width + 5 > terminal_width {
                break;
            }

            write!(&mut stdout, " %CPU")?; // 5
            width += 5;
        }

        if width + 1 + vsz_len > terminal_width {
            break;
        }

        for _ in 2..vsz_len {
            write!(&mut stdout, " ")?; // 1
            width += 1;
        }

        write!(&mut stdout, "VSZ")?; // 3
        width += 3;

        if width + 1 + rss_len > terminal_width {
            break;
        }

        for _ in 2..rss_len {
            write!(&mut stdout, " ")?; // 1
            width += 1;
        }

        write!(&mut stdout, "RSS")?; // 3
        width += 3;

        if width + 1 + anon_len > terminal_width {
            break;
        }

        for _ in 3..anon_len {
            write!(&mut stdout, " ")?; // 1
            width += 1;
        }

        write!(&mut stdout, "ANON")?; // 4
        width += 4;

        if width + 1 + thd_len > terminal_width {
            break;
        }

        for _ in 2..thd_len {
            write!(&mut stdout, " ")?; // 1
            width += 1;
        }

        write!(&mut stdout, "THD")?; // 3
        width += 3;

        if width + 1 + tty_len > terminal_width {
            break;
        }

        write!(&mut stdout, " TTY")?; // 4
        width += 4;

        for _ in 3..tty_len {
            write!(&mut stdout, " ")?; // 1
            width += 1;
        }

        if width + 1 + user_len > terminal_width {
            break;
        }

        write!(&mut stdout, " USER")?; // 5
        width += 5;

        for _ in 4..user_len {
            write!(&mut stdout, " ")?; // 1
            width += 1;
        }

        if width + 1 + group_len > terminal_width {
            break;
        }

        write!(&mut stdout, " GROUP")?; // 6
        width += 6;

        for _ in 5..group_len {
            write!(&mut stdout, " ")?; // 1
            width += 1;
        }

        if width + 1 + program_len > terminal_width {
            break;
        }

        write!(&mut stdout, " PROGRAM")?; // 8
        width += 8;

        for _ in 7..program_len {
            write!(&mut stdout, " ")?; // 1
            width += 1;
        }

        if width + 1 + state_len > terminal_width {
            break;
        }

        write!(&mut stdout, " STATE")?; // 6
        width += 6;

        for _ in 5..state_len {
            write!(&mut stdout, " ")?; // 1
            width += 1;
        }

        if start_time {
            if width + 21 > terminal_width {
                break;
            }

            write!(&mut stdout, " START")?; // 6
            width += 6;

            for _ in 5..20 {
                write!(&mut stdout, " ")?; // 1
                width += 1;
            }
        }

        if width + 8 > terminal_width {
            break;
        }

        write!(&mut stdout, " COMMAND")?; // 8

        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, " ")?; // 1
            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, " ")?; // 1
            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, " ")?; // 1
            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, " ")?; // 1
            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, " ")?; // 1
            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, " ")?; // 1
        width += 1;

        stdout.write_all(tty.as_bytes())?;
        width += tty.len();

        for _ in 0..(tty_len - tty.len()) {
            write!(&mut stdout, " ")?; // 1
            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, " ")?; // 1
        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, "+")?; // 1
                width += truncate_inc;

                for _ in truncate_inc..4 {
                    write!(&mut stdout, " ")?; // 1
                    width += 1;
                }
            } else {
                stdout.write_all(s.as_bytes())?;
                width += s.len();

                for _ in 0..(user_len - s.len()) {
                    write!(&mut stdout, " ")?; // 1
                    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, " ")?; // 1
        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, "+")?; // 1
                width += truncate_inc;

                for _ in truncate_inc..5 {
                    write!(&mut stdout, " ")?; // 1
                    width += 1;
                }
            } else {
                stdout.write_all(s.as_bytes())?;
                width += s.len();

                for _ in 0..(group_len - s.len()) {
                    write!(&mut stdout, " ")?; // 1
                    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, " ")?; // 1
        width += 1;

        if program.len() > truncate_inc {
            stdout.write_all(program[..(truncate_inc - 1)].as_bytes())?;
            write!(&mut stdout, "+")?; // 1
            width += truncate_inc;

            for _ in truncate_inc..7 {
                write!(&mut stdout, " ")?; // 1
                width += 1;
            }
        } else {
            stdout.write_all(program.as_bytes())?;
            width += program.len();

            for _ in 0..(program_len - program.len()) {
                write!(&mut stdout, " ")?; // 1
                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, " ")?; // 1
        width += 1;

        stdout.write_all(state.as_bytes())?;
        width += state.len();

        for _ in 0..(state_len - state.len()) {
            write!(&mut stdout, " ")?; // 1
            width += 1;
        }

        if start_time {
            if width + 21 > terminal_width {
                stdout.set_color(&*COLOR_DEFAULT)?;
                writeln!(&mut stdout)?;

                continue;
            }

            write!(&mut stdout, " ")?; // 1

            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, " ")?; // 1
        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, "+")?; // 1
        } 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, " [")?; // 2

            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, "|")?; // 1
            }

            for _ in 0..(progress_max - progress_used) {
                write!(&mut stdout, " ")?; // 1
            }

            stdout.set_color(&*COLOR_NORMAL_TEXT)?;
            write!(&mut stdout, "] ")?; // 2

            for _ in 0..(volumes_used_len - used.len()) {
                write!(&mut stdout, " ")?; // 1
            }

            stdout.set_color(&*COLOR_BOLD_TEXT)?;
            stdout.write_all(used.as_bytes())?;

            stdout.set_color(&*COLOR_NORMAL_TEXT)?;
            write!(&mut stdout, " / ")?; // 3

            for _ in 0..(volumes_size_len - size.len()) {
                write!(&mut stdout, " ")?; // 1
            }

            stdout.set_color(&*COLOR_BOLD_TEXT)?;
            stdout.write_all(size.as_bytes())?;

            write!(&mut stdout, " (")?; // 2

            for _ in 0..(volumes_used_percentage_len - used_percentage.len()) {
                write!(&mut stdout, " ")?; // 1
            }

            stdout.write_all(used_percentage.as_bytes())?;

            write!(&mut stdout, ")")?; // 1

            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, " [")?; // 2

            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, "|")?; // 1
            }

            for _ in 0..(progress_max - progress_used) {
                write!(&mut stdout, " ")?; // 1
            }

            stdout.set_color(&*COLOR_NORMAL_TEXT)?;
            write!(&mut stdout, "] ")?; // 2

            for _ in 0..(volumes_used_len - used.len()) {
                write!(&mut stdout, " ")?; // 1
            }

            stdout.set_color(&*COLOR_BOLD_TEXT)?;
            stdout.write_all(used.as_bytes())?;

            stdout.set_color(&*COLOR_NORMAL_TEXT)?;
            write!(&mut stdout, " / ")?; // 3

            for _ in 0..(volumes_size_len - size.len()) {
                write!(&mut stdout, " ")?; // 1
            }

            stdout.set_color(&*COLOR_BOLD_TEXT)?;
            stdout.write_all(size.as_bytes())?;

            write!(&mut stdout, " (")?; // 2

            for _ in 0..(volumes_used_percentage_len - used_percentage.len()) {
                write!(&mut stdout, " ")?; // 1
            }

            stdout.write_all(used_percentage.as_bytes())?;

            write!(&mut stdout, ")")?; // 1

            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);

    // Memory

    stdout.set_color(&*COLOR_LABEL)?;
    write!(&mut stdout, "Memory")?; // 6

    stdout.set_color(&*COLOR_NORMAL_TEXT)?;
    write!(&mut stdout, " [")?; // 2

    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, "|")?; // 1
    }

    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, "$")?; // 1
        } else {
            write!(&mut stdout, "|")?; // 1
        }
    }

    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, "#")?; // 1
        } else {
            write!(&mut stdout, "|")?; // 1
        }
    }

    for _ in 0..(progress_max - progress_used - progress_cache - progress_buffers) {
        write!(&mut stdout, " ")?; // 1
    }

    stdout.set_color(&*COLOR_NORMAL_TEXT)?;
    write!(&mut stdout, "] ")?; // 2

    for _ in 0..(used_len - mem_used.len()) {
        write!(&mut stdout, " ")?; // 1
    }

    stdout.set_color(&*COLOR_BOLD_TEXT)?;
    stdout.write_all(mem_used.as_bytes())?;

    stdout.set_color(&*COLOR_NORMAL_TEXT)?;
    write!(&mut stdout, " / ")?; // 3

    for _ in 0..(total_len - mem_total.len()) {
        write!(&mut stdout, " ")?; // 1
    }

    stdout.set_color(&*COLOR_BOLD_TEXT)?;
    stdout.write_all(mem_total.as_bytes())?;

    write!(&mut stdout, " (")?; // 2

    for _ in 0..(percentage_len - mem_percentage.len()) {
        write!(&mut stdout, " ")?; // 1
    }

    stdout.write_all(mem_percentage.as_bytes())?;

    write!(&mut stdout, ")")?; // 1

    writeln!(&mut stdout)?;

    // Swap

    stdout.set_color(&*COLOR_LABEL)?;
    write!(&mut stdout, "Swap  ")?; // 6

    stdout.set_color(&*COLOR_NORMAL_TEXT)?;
    write!(&mut stdout, " [")?; // 2

    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, "|")?; // 1
    }

    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, "$")?; // 1
        } else {
            write!(&mut stdout, "|")?; // 1
        }
    }

    for _ in 0..(progress_max - progress_used - progress_cache) {
        write!(&mut stdout, " ")?; // 1
    }

    stdout.set_color(&*COLOR_NORMAL_TEXT)?;
    write!(&mut stdout, "] ")?; // 2

    for _ in 0..(used_len - swap_used.len()) {
        write!(&mut stdout, " ")?; // 1
    }

    stdout.set_color(&*COLOR_BOLD_TEXT)?;
    stdout.write_all(swap_used.as_bytes())?;

    stdout.set_color(&*COLOR_NORMAL_TEXT)?;
    write!(&mut stdout, " / ")?; // 3

    for _ in 0..(total_len - swap_total.len()) {
        write!(&mut stdout, " ")?; // 1
    }

    stdout.set_color(&*COLOR_BOLD_TEXT)?;
    stdout.write_all(swap_total.as_bytes())?;

    write!(&mut stdout, " (")?; // 2

    for _ in 0..(percentage_len - swap_percentage.len()) {
        write!(&mut stdout, " ")?; // 1
    }

    stdout.write_all(swap_percentage.as_bytes())?;

    write!(&mut stdout, ")")?; // 1

    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;

        // number of logical CPU cores

        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)?;

        // one

        stdout.set_color(&*COLOR_LABEL)?;
        write!(&mut stdout, "one    ")?; // 7

        stdout.set_color(&*COLOR_NORMAL_TEXT)?;
        write!(&mut stdout, " [")?; // 2

        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, "|")?; // 1
        }

        for _ in 0..(progress_max - progress_used) {
            write!(&mut stdout, " ")?; // 1
        }

        stdout.set_color(&*COLOR_NORMAL_TEXT)?;
        write!(&mut stdout, "] ")?; // 2

        for _ in 0..(load_average_len - one.len()) {
            write!(&mut stdout, " ")?; // 1
        }

        stdout.set_color(&*COLOR_BOLD_TEXT)?;
        stdout.write_all(one.as_bytes())?;

        write!(&mut stdout, " (")?; // 2

        for _ in 0..(percentage_len - one_percentage.len()) {
            write!(&mut stdout, " ")?; // 1
        }

        stdout.write_all(one_percentage.as_bytes())?;

        write!(&mut stdout, ")")?; // 1

        writeln!(&mut stdout)?;

        // five

        stdout.set_color(&*COLOR_LABEL)?;
        write!(&mut stdout, "five   ")?; // 7

        stdout.set_color(&*COLOR_NORMAL_TEXT)?;
        write!(&mut stdout, " [")?; // 2

        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, "|")?; // 1
        }

        for _ in 0..(progress_max - progress_used) {
            write!(&mut stdout, " ")?; // 1
        }

        stdout.set_color(&*COLOR_NORMAL_TEXT)?;
        write!(&mut stdout, "] ")?; // 2

        for _ in 0..(load_average_len - five.len()) {
            write!(&mut stdout, " ")?; // 1
        }

        stdout.set_color(&*COLOR_BOLD_TEXT)?;
        stdout.write_all(five.as_bytes())?;

        write!(&mut stdout, " (")?; // 2

        for _ in 0..(percentage_len - five_percentage.len()) {
            write!(&mut stdout, " ")?; // 1
        }

        stdout.write_all(five_percentage.as_bytes())?;

        write!(&mut stdout, ")")?; // 1

        writeln!(&mut stdout)?;

        // fifteen

        stdout.set_color(&*COLOR_LABEL)?;
        write!(&mut stdout, "fifteen")?; // 7

        stdout.set_color(&*COLOR_NORMAL_TEXT)?;
        write!(&mut stdout, " [")?; // 2

        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, "|")?; // 1
        }

        for _ in 0..(progress_max - progress_used) {
            write!(&mut stdout, " ")?; // 1
        }

        stdout.set_color(&*COLOR_NORMAL_TEXT)?;
        write!(&mut stdout, "] ")?; // 2

        for _ in 0..(load_average_len - fifteen.len()) {
            write!(&mut stdout, " ")?; // 1
        }

        stdout.set_color(&*COLOR_BOLD_TEXT)?;
        stdout.write_all(fifteen.as_bytes())?;

        write!(&mut stdout, " (")?; // 2

        for _ in 0..(percentage_len - fifteen_percentage.len()) {
            write!(&mut stdout, " ")?; // 1
        }

        stdout.write_all(fifteen_percentage.as_bytes())?;

        write!(&mut stdout, ")")?; // 1

        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();

            // The max length of `CPU<number> `.
            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, "[")?; // 1

                    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, "|")?; // 1
                    }

                    for _ in 0..(progress_max - progress_used) {
                        write!(&mut stdout, " ")?; // 1
                    }

                    stdout.set_color(&*COLOR_NORMAL_TEXT)?;
                    write!(&mut stdout, "] ")?; // 2

                    for _ in 0..(percentage_len - percentage_string.len()) {
                        write!(&mut stdout, " ")?; // 1
                    }

                    stdout.set_color(&*COLOR_BOLD_TEXT)?;
                    stdout.write_all(percentage_string.as_bytes())?;

                    write!(&mut stdout, " (")?; // 2

                    for _ in 0..(hz_string_len - hz_string.len()) {
                        write!(&mut stdout, " ")?; // 1
                    }

                    stdout.write_all(hz_string.as_bytes())?;

                    write!(&mut stdout, ")")?; // 1

                    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")?; // 3

            stdout.set_color(&*COLOR_NORMAL_TEXT)?;
            write!(&mut stdout, " [")?; // 2

            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, "|")?; // 1
            }

            for _ in 0..(progress_max - progress_used) {
                write!(&mut stdout, " ")?; // 1
            }

            stdout.set_color(&*COLOR_NORMAL_TEXT)?;
            write!(&mut stdout, "] ")?; // 2

            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),
    }
}