tegratop 0.2.1

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

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

#[derive(Debug, Default)]
pub struct Thermal {
    pub sensors: Vec<Sensor>,
}
#[derive(Debug)]
pub struct Sensor {
    file: File,
    name: String,
    temperature: f32,
}

impl Sensor {
    pub 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)?;
        self.temperature = buffer.trim().parse::<f32>()? / 1000.0;
        Ok(())
    }
}

impl Thermal {
    pub fn new() -> Self {
        let sensors = match Thermal::init() {
            Ok(sensors) => sensors,
            Err(e) => {
                error!("{}", e);
                Vec::new()
            }
        };

        Self { sensors }
    }

    pub fn init() -> Result<Vec<Sensor>> {
        let mut sensors: Vec<Sensor> = Vec::new();
        let entries = fs::read_dir("/sys/devices/virtual/thermal/")
            .context("Failed to read from the directory /sys/devices/virtual/thermal")?;

        for entry in entries {
            let entry = entry?;
            let entry_path = entry.path();

            if let Some(file_name) = entry_path.file_name() {
                if let Some(name) = file_name.to_str() {
                    if name.starts_with("thermal_zone") && entry_path.is_dir() {
                        let name = match fs::read_to_string(entry_path.join("type")) {
                            Ok(v) => v.split('-').next().unwrap().trim().to_string(),
                            Err(_) => continue,
                        };

                        let temperature_path = &entry_path.join("temp");
                        let mut temperature_file = match File::open(temperature_path) {
                            Ok(f) => f,
                            Err(e) => {
                                error!("{}", e);
                                continue;
                            }
                        };

                        let mut buffer = String::new();
                        if let Err(e) = temperature_file.read_to_string(&mut buffer) {
                            error!("{}", e);
                            continue;
                        }

                        let temperature = buffer.trim().parse::<f32>()? / 1000.0;

                        let sensor = Sensor {
                            file: temperature_file,
                            name,
                            temperature,
                        };
                        sensors.push(sensor);
                    }
                }
            }
        }

        Ok(sensors)
    }

    pub fn refresh(&mut self) {
        for sensor in &mut self.sensors {
            if let Err(e) = sensor.refresh() {
                error!("{}", e);
            }
        }
    }

    pub fn render(&self, frame: &mut Frame, block: Rect) {
        let rows: Vec<Row> = self
            .sensors
            .iter()
            .map(|sensor| {
                let temperature = match sensor.temperature {
                    t if t < -25.0 => " - ".to_string(),
                    t => format!("{:.1} C", t),
                };

                Row::new(vec![sensor.name.to_owned(), temperature])
            })
            .collect();

        let widths = [Constraint::Length(8), Constraint::Length(11)];

        let table = Table::new(rows, widths)
            .header(Row::new(vec!["Sensor", "Temperature"]).style(Style::new().bold()))
            .block(
                Block::default()
                    .title("Thermal")
                    .title_style(Style::new().bold())
                    .borders(Borders::ALL)
                    .padding(Padding::horizontal(1)),
            );
        frame.render_widget(table, block);
    }
}