ashwin-fetch 0.0.1

Similar to `neofetch` but written with rust.
use chrono::Duration;
use std::collections::HashMap;
use std::fmt::Debug;
use std::process::ExitCode;
use sysinfo::Motherboard;
use sysinfo::System;
use wgpu::Backends;
use wgpu::Instance;
use wgpu::InstanceDescriptor;

const LOGO_HEIGHT: usize = 9;
const LOGO_WIDTH: usize = 32;
const LOGO: [&str; LOGO_HEIGHT] = [
    "       :#.                      ",
    "       :#-:****************+    ",
    "         -::::::::.......:::    ",
    "   .#*               -**=:.     ",
    "    #-::            =%%=:.      ",
    "     --::.        :*%#::        ",
    "       -:::.    .=%%-:          ",
    "         :::=######:.           ",
    "          .::::::..             ",
];

struct CpuInfo {
    num_cores: usize,
    avg_usage: f64,
    max_frequency_mhz: f64,
}

impl Debug for CpuInfo {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("CpuInfo")
            .field("num_cores", &self.num_cores)
            .field("avg_usage", &self.avg_usage)
            .field("max_frequency_mhz", &self.max_frequency_mhz)
            .finish()
    }
}

struct GpuInfo {
    device_index: usize,
    gpu_name: String,
}

impl Debug for GpuInfo {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("GpuInfo")
            .field("device_index", &self.device_index)
            .field("gpu_name", &self.gpu_name)
            .finish()
    }
}

struct OutputInfo<'a> {
    username: String,
    hostname: String,
    os: String,
    serial_number: String,
    kernel: String,
    uptime: usize,
    cpu: HashMap<&'a str, CpuInfo>,
    gpu: Vec<GpuInfo>,
    memory_used_mb: usize,
    memory_total_mb: usize,
}

fn get_username() -> String {
    return whoami::username();
}

fn get_hostname() -> String {
    return whoami::fallible::hostname().unwrap_or(String::from("unknown"));
}

fn get_os_name() -> String {
    return whoami::distro();
}

fn get_serial_number() -> String {
    return Motherboard::new()
        .and_then(|x| x.serial_number())
        .unwrap_or("xxxxxxxxxx".to_string());
}

fn kernel() -> String {
    return System::kernel_long_version();
}

fn get_uptime() -> usize {
    return System::uptime() as usize;
}

fn get_cpu_info<'a>(sys: &'a System) -> HashMap<&'a str, CpuInfo> {
    let mut cpu_info_map = HashMap::<&'a str, CpuInfo>::new();
    for cpu in sys.cpus() {
        let entry = cpu_info_map.entry(cpu.brand()).or_insert(CpuInfo {
            num_cores: 0,
            avg_usage: 0.0 as f64,
            max_frequency_mhz: 0.0 as f64,
        });
        entry.num_cores += 1;
        entry.avg_usage += cpu.cpu_usage() as f64;
        if cpu.frequency() as f64 > entry.max_frequency_mhz {
            entry.max_frequency_mhz = cpu.frequency() as f64;
        }
    }
    for (_, val) in &mut cpu_info_map {
        val.avg_usage /= val.num_cores as f64;
    }
    return cpu_info_map;
}

fn get_gpu_info() -> Vec<GpuInfo> {
    let mut instance_descriptor = InstanceDescriptor::default();
    instance_descriptor.backends = Backends::all();
    let instance = Instance::new(&instance_descriptor);
    let adapters = instance.enumerate_adapters(Backends::all());
    let mut gpu_infos = vec![];
    for adapter in adapters {
        let info = adapter.get_info();
        gpu_infos.push(GpuInfo {
            device_index: info.device as usize,
            gpu_name: info.name,
        });
    }
    gpu_infos.sort_by(|x, y| x.device_index.cmp(&y.device_index));
    return gpu_infos;
}

fn get_used_memory(sys: &System) -> usize {
    return sys.used_memory() as usize;
}

fn get_total_memory(sys: &System) -> usize {
    return sys.total_memory() as usize;
}

fn convert_unix_to_human_string(unix_time: usize) -> String {
    let duration = Duration::seconds(unix_time as i64);
    let days = duration.num_days();
    let hours = duration.num_hours() % 24;
    let minutes = duration.num_minutes() % 60;

    if days > 0 {
        return format!("{}d {}h {}m", days, hours, minutes);
    } else if hours > 0 {
        return format!("{}h {}m", hours, minutes);
    } else {
        return format!("{}m", minutes);
    }
}

fn print_all_info(output_info: &OutputInfo) {
    let mut output_info_vec = vec![
        format!("{}@{}", output_info.username, output_info.hostname),
        format!("{}", "-".repeat(output_info.username.len() + output_info.hostname.len() + 1)),
        format!("OS:        {}", output_info.os),
        format!("Serial:    {}", output_info.serial_number),
        format!("Kernel:    {}", output_info.kernel),
        format!("Uptime:    {}", convert_unix_to_human_string(output_info.uptime)),
    ];
    for (cpu_brand, cpu_info) in &output_info.cpu {
        output_info_vec.push(format!(
            "CPU:       {} - {} cores, {:.2}% avg, {:.2} MHz",
            cpu_brand, cpu_info.num_cores, cpu_info.avg_usage, cpu_info.max_frequency_mhz
        ));
    }
    for gpu_info in &output_info.gpu {
        output_info_vec.push(format!(
            "GPU {}:     {}",
            gpu_info.device_index, gpu_info.gpu_name
        ));
    }
    output_info_vec.push(format!(
        "Memory:    {}/{} MB used",
        output_info.memory_used_mb, output_info.memory_total_mb
    ));
    println!();
    for (idx, line) in output_info_vec.iter().enumerate() {
        if idx < LOGO_HEIGHT {
            println!("{}{}", LOGO[idx], line);
        } else {
            println!("{}{}", " ".repeat(LOGO_WIDTH), line);
        }
    }
    if output_info_vec.len() < LOGO_HEIGHT {
        for i in output_info_vec.len()..LOGO_HEIGHT {
            println!("{}", LOGO[i]);
        }
    }
    println!();
}

fn main() -> ExitCode {
    if !sysinfo::IS_SUPPORTED_SYSTEM {
        println!("System not supported. Aborting.");
        return ExitCode::from(1);
    }

    let mut sys = System::new_all();
    std::thread::sleep(sysinfo::MINIMUM_CPU_UPDATE_INTERVAL);
    sys.refresh_cpu_all();

    let output_info = OutputInfo {
        username: get_username(),
        hostname: get_hostname(),
        os: get_os_name(),
        serial_number: get_serial_number(),
        kernel: kernel(),
        uptime: get_uptime(),
        cpu: get_cpu_info(&sys),
        gpu: get_gpu_info(),
        memory_used_mb: get_used_memory(&sys) / 1024 / 1024,
        memory_total_mb: get_total_memory(&sys) / 1024 / 1024,
    };

    print_all_info(&output_info);

    return ExitCode::from(0);
}