terminal-info 1.4.2

An extensible terminal information CLI and developer toolbox
Documentation
use clap::ValueEnum;
use serde::Serialize;
use sysinfo::System;

use crate::builtins::{format_bytes, format_uptime};

#[derive(Copy, Clone, Debug, ValueEnum)]
pub enum ProcessSort {
    Cpu,
    Memory,
}

#[derive(Serialize)]
struct ProcessListView {
    sort: String,
    limit: usize,
    processes: Vec<ProcessView>,
}

#[derive(Serialize)]
struct ProcessView {
    pid: String,
    name: String,
    status: String,
    cpu_percent: f32,
    memory_bytes: u64,
    virtual_memory_bytes: u64,
    runtime_secs: u64,
}

pub fn show_processes(limit: usize, sort: ProcessSort) -> Result<(), String> {
    let mut system = System::new_all();
    system.refresh_all();

    let mut processes = system
        .processes()
        .iter()
        .map(|(pid, process)| ProcessView {
            pid: pid.to_string(),
            name: process.name().to_string_lossy().to_string(),
            status: format!("{:?}", process.status()),
            cpu_percent: process.cpu_usage(),
            memory_bytes: process.memory(),
            virtual_memory_bytes: process.virtual_memory(),
            runtime_secs: process.run_time(),
        })
        .collect::<Vec<_>>();

    match sort {
        ProcessSort::Cpu => {
            processes.sort_by(|left, right| right.cpu_percent.total_cmp(&left.cpu_percent))
        }
        ProcessSort::Memory => {
            processes.sort_by(|left, right| right.memory_bytes.cmp(&left.memory_bytes))
        }
    }
    processes.truncate(limit.max(1));

    let view = ProcessListView {
        sort: match sort {
            ProcessSort::Cpu => "cpu".to_string(),
            ProcessSort::Memory => "memory".to_string(),
        },
        limit,
        processes,
    };

    if crate::output::json_output() {
        println!(
            "{}",
            serde_json::to_string_pretty(&view).unwrap_or_else(|_| "{}".to_string())
        );
        return Ok(());
    }

    println!("Processes (sorted by {})", view.sort);
    if view.processes.is_empty() {
        println!("No process data available.");
        return Ok(());
    }
    println!(
        "{:<8} {:<24} {:>7} {:>10} {:>9} Status",
        "PID", "Name", "CPU%", "Memory", "Runtime"
    );
    for process in view.processes {
        println!(
            "{:<8} {:<24} {:>6.1} {:>10} {:>9} {}",
            process.pid,
            truncate_name(&process.name, 24),
            process.cpu_percent,
            format_bytes(process.memory_bytes),
            format_uptime(process.runtime_secs),
            process.status
        );
    }

    Ok(())
}

fn truncate_name(value: &str, width: usize) -> String {
    let mut truncated = value.chars().take(width).collect::<String>();
    if value.chars().count() > width {
        truncated.pop();
        truncated.push('~');
    }
    truncated
}