tegratop 0.2.1

TUI for monitoring Nvidia jetson boards
Documentation
use anyhow::Context;
use log::error;
use std::fs::File;
use std::io::Read;
use std::io::Seek;

use anyhow::Result;

use ratatui::{
    layout::{Constraint, Rect},
    style::{Style, Stylize},
    widgets::{Block, Borders, Cell, Padding, Row, Table},
    Frame,
};

#[derive(Debug, Default)]
pub struct System {
    loadavg: Option<Loadavg>,
    uptime: Option<Uptime>,
}

#[derive(Debug)]
pub struct Uptime {
    file: File,
    value: String,
}

impl Uptime {
    fn new() -> Result<Self> {
        let mut file = File::open("/proc/uptime").context("Failed to open /proc/uptime")?;
        let mut buffer = String::new();
        file.read_to_string(&mut buffer)?;

        let uptime_in_seconds = buffer.trim().split('.').next().unwrap().parse::<f64>()?;

        let days = (uptime_in_seconds / (60.0 * 60.0 * 24.0)) as u64;
        let hours = ((uptime_in_seconds / (60.0 * 60.0)) % 24.0) as u64;
        let minutes = ((uptime_in_seconds / 60.0) % 60.0) as u64;
        let seconds = (uptime_in_seconds % 60.0) as u64;

        let value = format!("{}days, {}h, {}min, {}s", days, hours, minutes, seconds);

        Ok(Self { file, value })
    }

    fn refresh(&mut self) -> Result<()> {
        self.file.seek(std::io::SeekFrom::Start(0))?;
        let mut buffer = String::new();
        self.file.read_to_string(&mut buffer)?;

        let uptime_in_seconds = buffer.trim().split('.').next().unwrap().parse::<f64>()?;

        let days = (uptime_in_seconds / (60.0 * 60.0 * 24.0)) as u64;
        let hours = ((uptime_in_seconds / (60.0 * 60.0)) % 24.0) as u64;
        let minutes = ((uptime_in_seconds / 60.0) % 60.0) as u64;
        let seconds = (uptime_in_seconds % 60.0) as u64;
        self.value = format!("{}days, {}h, {}min, {}s", days, hours, minutes, seconds);

        Ok(())
    }
}

#[derive(Debug)]
pub struct Loadavg {
    file: File,
    value: [f64; 3],
}

impl Loadavg {
    fn new() -> Result<Self> {
        let mut file = File::open("/proc/loadavg").context("Failed to open /proc/loadavg")?;
        let mut buffer = String::new();
        file.read_to_string(&mut buffer)?;

        let loadavg_values: Vec<&str> = buffer.split_whitespace().collect();

        let load_avg_1min: f64 = loadavg_values[0].parse()?;
        let load_avg_10min: f64 = loadavg_values[1].parse()?;
        let load_avg_15min: f64 = loadavg_values[2].parse()?;

        Ok(Self {
            file,
            value: [load_avg_1min, load_avg_10min, load_avg_15min],
        })
    }

    fn refresh(&mut self) -> Result<()> {
        self.file.seek(std::io::SeekFrom::Start(0))?;
        let mut buffer = String::new();
        self.file.read_to_string(&mut buffer).unwrap();

        let loadavg_values: Vec<&str> = buffer.split_whitespace().collect();

        let load_avg_1min: f64 = loadavg_values[0].parse()?;
        let load_avg_10min: f64 = loadavg_values[1].parse()?;
        let load_avg_15min: f64 = loadavg_values[2].parse()?;

        self.value = [load_avg_1min, load_avg_10min, load_avg_15min];
        Ok(())
    }
}

impl System {
    pub fn new() -> Self {
        let loadavg = Loadavg::new().map_or_else(
            |e| {
                error!("{}", e);
                None
            },
            Some,
        );

        let uptime = Uptime::new().map_or_else(
            |e| {
                error!("{}", e);
                None
            },
            Some,
        );

        Self { loadavg, uptime }
    }

    pub fn refresh(&mut self) {
        if let Some(load_avg) = &mut self.loadavg {
            if let Err(e) = load_avg.refresh() {
                error!("{}", e);
            }
        };

        if let Some(uptime) = &mut self.uptime {
            if let Err(e) = uptime.refresh() {
                error!("{}", e);
            }
        };
    }

    pub fn render(&self, frame: &mut Frame, block: Rect) {
        let rows: [Row; 3] = [
            Row::new(vec![
                Cell::new("Load avg").style(Style::default().bold()),
                Cell::new(match &self.loadavg {
                    Some(loadavg) => format!(
                        "{:.2} {:.2} {:.2}",
                        loadavg.value[0], loadavg.value[1], loadavg.value[2]
                    ),
                    None => " - ".to_string(),
                }),
            ]),
            Row::new(vec!["", ""]),
            Row::new(vec![
                Cell::new("Uptime").style(Style::default().bold()),
                Cell::new(match &self.uptime {
                    Some(uptime) => uptime.value.to_string(),
                    None => " - ".to_string(),
                }),
            ]),
        ];

        let widths = [Constraint::Length(10), Constraint::Fill(1)];

        let system = Table::new(rows, widths).block(
            Block::default()
                .title("System")
                .title_style(Style::new().bold())
                .borders(Borders::ALL)
                .padding(Padding::horizontal(1)),
        );
        frame.render_widget(system, block);
    }
}