tegratop 0.2.1

TUI for monitoring Nvidia jetson boards
Documentation
use anyhow::{Context, Result};
use log::error;
use ratatui::{
    layout::{Constraint, Direction, Layout, Rect},
    style::{Style, Stylize},
    text::Line,
    widgets::{Bar, BarChart, BarGroup, Block, Borders, Cell, Padding, Row, Table},
    Frame,
};

use std::{
    fs::{self, File},
    io::{Read, Seek},
};

#[derive(Debug, Default)]
pub struct Memory {
    pub mem: Option<Mem>,
    pub emc: Option<EMC>,
}

#[derive(Debug)]
pub struct Mem {
    fd: File,
    pub total_ram: f64,
    pub used_ram: f64,
    pub total_swap: f64,
    pub used_swap: f64,
}

#[derive(Debug)]
pub struct EMC {
    max_frequency_fd: File,
    current_frequency: usize,
    max_frequency: usize,
}

impl EMC {
    fn new() -> Result<Self> {
        let current_frequency = fs::read_to_string("/sys/kernel/debug/clk/emc/clk_rate")
            .context("Failed to read from /sys/kernel/debug/clk/emc/clk_rate")?;

        let current_frequency = current_frequency.trim().parse::<usize>()? / 1_000_000;

        let mut max_frequency_fd = File::open("/sys/kernel/debug/clk/emc/clk_max_rate")
            .context("Failed to open /sys/kernel/debug/clk/emc/clk_max_rate")?;

        let mut buffer = String::new();
        max_frequency_fd.read_to_string(&mut buffer)?;
        let max_frequency = buffer.trim().parse::<usize>()? / 1_000_000;

        Ok(Self {
            max_frequency_fd,
            current_frequency,
            max_frequency,
        })
    }

    fn refresh(&mut self) -> Result<()> {
        self.max_frequency_fd.seek(std::io::SeekFrom::Start(0))?;
        let mut buffer = String::new();
        self.max_frequency_fd.read_to_string(&mut buffer)?;
        self.max_frequency = buffer.trim().parse::<usize>()? / 1_000_000;

        let current_frequency = fs::read_to_string("/sys/kernel/debug/clk/emc/clk_rate")
            .context("Failed to read from /sys/kernel/debug/clk/emc/clk_rate")?;
        self.current_frequency = current_frequency.trim().parse::<usize>()? / 1_000_000;

        Ok(())
    }
}

impl Mem {
    pub fn new() -> Result<Self> {
        let mut memory_file = File::open("/proc/meminfo").context("Faile to open /proc/meminfo")?;
        let mut buffer = String::new();
        memory_file.read_to_string(&mut buffer)?;

        let mut total = 0.0;
        let mut free = 0.0;
        let mut buffered = 0.0;
        let mut cached = 0.0;
        let mut sreclaimable = 0.0;
        let mut shmem = 0.0;
        let mut total_swap = 0.0;
        let mut free_swap = 0.0;

        for line in buffer.lines() {
            let mut parts: Vec<&str> = line.split_whitespace().collect();
            parts.truncate(2);

            match parts.as_slice() {
                ["MemTotal:", v] => {
                    total = v.parse::<f64>()?;
                }
                ["MemFree:", v] => {
                    free = v.parse::<f64>()?;
                }
                ["Buffers:", v] => {
                    buffered = v.parse::<f64>()?;
                }
                ["Cached:", v] => {
                    cached = v.parse::<f64>()?;
                }
                ["Shmem:", v] => {
                    shmem = v.parse::<f64>()?;
                }
                ["SReclaimable:", v] => {
                    sreclaimable = v.parse::<f64>()?;
                }
                ["SwapTotal:", v] => {
                    total_swap = v.parse::<f64>()?;
                }
                ["SwapFree:", v] => {
                    free_swap = v.parse::<f64>()?;
                }

                _ => (),
            }
        }

        let total_used_memory = total - free;
        let cached_memory = cached + sreclaimable - shmem;

        let total_ram = (total / 1024.0).round();
        let used_ram = ((total_used_memory - (buffered + cached_memory)) / 1024.0).round();

        let total_swap = ((total_swap) / 1024.0).round();
        let used_swap = ((total_swap - free_swap) / 1024.0).round();

        Ok(Self {
            fd: memory_file,
            total_ram,
            used_ram,
            total_swap,
            used_swap,
        })
    }

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

        let mut total = 0.0;
        let mut free = 0.0;
        let mut buffered = 0.0;
        let mut cached = 0.0;
        let mut sreclaimable = 0.0;
        let mut shmem = 0.0;
        let mut total_swap = 0.0;
        let mut free_swap = 0.0;

        for line in buffer.lines() {
            let mut parts: Vec<&str> = line.split_whitespace().collect();
            parts.truncate(2);

            match parts.as_slice() {
                ["MemTotal:", v] => {
                    total = v.parse::<f64>()?;
                }
                ["MemFree:", v] => {
                    free = v.parse::<f64>()?;
                }
                ["Buffers:", v] => {
                    buffered = v.parse::<f64>()?;
                }
                ["Cached:", v] => {
                    cached = v.parse::<f64>()?;
                }
                ["Shmem:", v] => {
                    shmem = v.parse::<f64>()?;
                }
                ["SReclaimable:", v] => {
                    sreclaimable = v.parse::<f64>()?;
                }
                ["SwapTotal:", v] => {
                    total_swap = v.parse::<f64>()?;
                }
                ["SwapFree:", v] => {
                    free_swap = v.parse::<f64>()?;
                }

                _ => (),
            }
        }

        let total_used_memory = total - free;
        let cached_memory = cached + sreclaimable - shmem;

        self.total_ram = (total / 1024.0).round();
        self.used_ram = ((total_used_memory - (buffered + cached_memory)) / 1024.0).round();

        self.total_swap = ((total_swap) / 1024.0).round();
        self.used_swap = ((total_swap - free_swap) / 1024.0).round();

        Ok(())
    }
}

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

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

        Self { mem, emc }
    }

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

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

    pub fn render(&self, frame: &mut Frame, block: Rect) {
        let container = Block::default()
            .borders(Borders::ALL)
            .title("Memory")
            .padding(Padding::horizontal(1))
            .title_style(Style::new().bold());

        let inside_container = container.inner(block);

        let (ram_block, swap_block, emc_block) = {
            let chunks = Layout::default()
                .direction(Direction::Vertical)
                .constraints([
                    Constraint::Length(2),
                    Constraint::Length(2),
                    Constraint::Length(2),
                ])
                .split(inside_container);

            (chunks[0], chunks[1], chunks[2])
        };

        let ram = BarChart::default()
            .block(Block::default())
            .bar_width(1)
            .bar_gap(1)
            .group_gap(0)
            .direction(Direction::Horizontal)
            .data(
                BarGroup::default().bars(&[match &self.mem {
                    Some(mem) => Bar::default()
                        .label(Line::styled("RAM ", Style::default().bold()))
                        .value(mem.used_ram as u64)
                        .text_value(format!("{}MB / {}MB ", mem.used_ram, mem.total_ram)),

                    None => Bar::default()
                        .label(Line::styled("RAM ", Style::default().bold()))
                        .value(0),
                }]),
            )
            .max(match &self.mem {
                Some(mem) => mem.total_ram as u64,
                None => 0,
            });

        let swap = BarChart::default()
            .block(Block::default())
            .bar_width(1)
            .bar_gap(1)
            .group_gap(0)
            .direction(Direction::Horizontal)
            .data(
                BarGroup::default().bars(&[match &self.mem {
                    Some(mem) => Bar::default()
                        .label(Line::styled("Swap", Style::default().bold()))
                        .value(mem.used_swap as u64)
                        .text_value(format!("{}MB / {}MB ", mem.used_swap, mem.total_swap)),

                    None => Bar::default()
                        .label(Line::styled("Swap", Style::default().bold()))
                        .value(0),
                }]),
            )
            .max(match &self.mem {
                Some(mem) => mem.total_swap as u64,
                None => 0,
            });

        let emc = Table::new(
            [Row::new(vec![
                Cell::new("EMC").style(Style::default().bold()),
                Cell::new(match &self.emc {
                    Some(emc) => format!("{}MHz / {}MHz", emc.current_frequency, emc.max_frequency),
                    None => " - ".to_string(),
                }),
            ])],
            [Constraint::Length(4), Constraint::Length(20)],
        )
        .block(Block::default());

        frame.render_widget(container, block);
        frame.render_widget(ram, ram_block);
        frame.render_widget(swap, swap_block);
        frame.render_widget(emc, emc_block);
    }
}