diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md new file mode 100644 index 0000000..0d75701 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.md @@ -0,0 +1,25 @@ +--- +name: Bug +about: Create a report to help us improve +title: '' +labels: bug +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: + +**Install method** +git? +cargo install? +from source? + +**contents of you nvtop.log** +If applicable, add screenshots to help explain your problem. + +**GPU info** + - NVIDIA RTX Titan diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..42a51bd --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,17 @@ +--- +name: Feature request +about: Suggest an idea for nvtop +title: "[FEAT]" +labels: enhancement +assignees: '' + +--- + +**Describe the solution you'd like** +A clear and concise description of what you want to happen, pictures don't hurt! + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/Cargo.lock b/Cargo.lock index 92a33ad..7080cd1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,30 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "aho-corasick" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea5d730647d4fadd988536d06fecce94b7b4f2a7efdae548f1cf4b63205518ab" -dependencies = [ - "memchr", -] - -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - [[package]] name = "anstream" version = "0.6.4" @@ -98,47 +74,18 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" -[[package]] -name = "bumpalo" -version = "3.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" - [[package]] name = "cassowary" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" -[[package]] -name = "cc" -version = "1.0.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" -dependencies = [ - "libc", -] - [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "chrono" -version = "0.4.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" -dependencies = [ - "android-tzdata", - "iana-time-zone", - "js-sys", - "num-traits", - "wasm-bindgen", - "windows-targets", -] - [[package]] name = "clap" version = "4.4.6" @@ -185,12 +132,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" -[[package]] -name = "core-foundation-sys" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" - [[package]] name = "crossterm" version = "0.27.0" @@ -257,49 +198,6 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" -[[package]] -name = "env_logger" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" -dependencies = [ - "humantime", - "is-terminal", - "log", - "regex", - "termcolor", -] - -[[package]] -name = "errno" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "add4f07d43996f76ef320709726a556a9d4f965d9410d8d0271132d2f8293480" -dependencies = [ - "errno-dragonfly", - "libc", - "windows-sys", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "fern" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9f0c14694cbd524c8720dd69b0e3179344f04ebb5f90f2e4a440c6ea3b2f1ee" -dependencies = [ - "log", -] - [[package]] name = "fnv" version = "1.0.7" @@ -312,41 +210,6 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" -[[package]] -name = "hermit-abi" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" - -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - -[[package]] -name = "iana-time-zone" -version = "0.1.57" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "windows", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - [[package]] name = "ident_case" version = "1.0.1" @@ -359,17 +222,6 @@ version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8" -[[package]] -name = "is-terminal" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" -dependencies = [ - "hermit-abi", - "rustix", - "windows-sys", -] - [[package]] name = "itertools" version = "0.11.0" @@ -379,15 +231,6 @@ dependencies = [ "either", ] -[[package]] -name = "js-sys" -version = "0.3.64" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" -dependencies = [ - "wasm-bindgen", -] - [[package]] name = "libc" version = "0.2.149" @@ -404,12 +247,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "linux-raw-sys" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3852614a3bd9ca9804678ba6be5e3b8ce76dfc902cae004e3e0c44051b6e88db" - [[package]] name = "lock_api" version = "0.4.10" @@ -426,12 +263,6 @@ version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" -[[package]] -name = "memchr" -version = "2.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" - [[package]] name = "mio" version = "0.8.8" @@ -444,15 +275,6 @@ dependencies = [ "windows-sys", ] -[[package]] -name = "num-traits" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" -dependencies = [ - "autocfg", -] - [[package]] name = "nvml-wrapper" version = "0.9.0" @@ -478,26 +300,16 @@ dependencies = [ [[package]] name = "nvtop" -version = "0.1.1" +version = "0.1.2" dependencies = [ "anyhow", - "chrono", "clap", "crossterm", - "fern", - "log", "nvml-wrapper", - "pretty_env_logger", "ratatui", "thiserror", ] -[[package]] -name = "once_cell" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" - [[package]] name = "parking_lot" version = "0.12.1" @@ -527,16 +339,6 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" -[[package]] -name = "pretty_env_logger" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "865724d4dbe39d9f3dd3b52b88d859d66bcb2d6a0acfd5ea68a65fb66d4bdc1c" -dependencies = [ - "env_logger", - "log", -] - [[package]] name = "proc-macro2" version = "1.0.68" @@ -581,48 +383,6 @@ dependencies = [ "bitflags 1.3.2", ] -[[package]] -name = "regex" -version = "1.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebee201405406dbf528b8b672104ae6d6d63e6d118cb10e4d51abbc7b58044ff" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59b23e92ee4318893fa3fe3e6fb365258efbfe6ac6ab30f090cdcbb7aa37efa9" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" - -[[package]] -name = "rustix" -version = "0.38.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f25469e9ae0f3d0047ca8b93fc56843f38e6774f0914a107ff8b41be8be8e0b7" -dependencies = [ - "bitflags 2.4.0", - "errno", - "libc", - "linux-raw-sys", - "windows-sys", -] - [[package]] name = "rustversion" version = "1.0.14" @@ -733,15 +493,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "termcolor" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64" -dependencies = [ - "winapi-util", -] - [[package]] name = "thiserror" version = "1.0.49" @@ -792,60 +543,6 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" -[[package]] -name = "wasm-bindgen" -version = "0.2.87" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" -dependencies = [ - "cfg-if", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.87" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" -dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2", - "quote", - "syn 2.0.38", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.87" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.87" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.38", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.87" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" - [[package]] name = "winapi" version = "0.3.9" @@ -862,30 +559,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -[[package]] -name = "winapi-util" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" -dependencies = [ - "winapi", -] - [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" -dependencies = [ - "windows-targets", -] - [[package]] name = "windows-sys" version = "0.48.0" diff --git a/Cargo.toml b/Cargo.toml index e225e18..1475e49 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nvtop" -version = "0.1.1" +version = "0.1.2" edition = "2021" description = """ nvtop: An NVIDIA SMI'esk GPU Monitoring tool for your terminal. """ keywords = [ @@ -11,7 +11,7 @@ keywords = [ "nvidia-smi", "Command-line tool", ] -authors = ["jer, alphastrata@gmail.com"] +authors = ["jer, alphastrata@gmail.com", "camuward, cameronuward@gmail.com"] documentation = "https://www.github.com/alphastrata/nvtop" license = "MIT" homepage = "https://www.jeremyfwebb.ninja" @@ -19,12 +19,8 @@ publish = true [dependencies] anyhow = "1.0.75" -chrono = "0.4.31" clap = { version = "4.4.6", features = ["derive"] } crossterm = "0.27.0" -fern = "0.6.2" -log = "0.4.20" nvml-wrapper = "0.9.0" -pretty_env_logger = "0.5.0" ratatui = "0.23.0" thiserror = "1.0.49" diff --git a/README.md b/README.md index e55bbff..fbbf064 100644 --- a/README.md +++ b/README.md @@ -25,16 +25,27 @@ ______________________________________________________________________ # Usage: +- Control the rate at which you're polling the GPU(s) for info: + ```shell # Monitor the GPU and system with a 1-second update interval nvtop --delay 1000 ``` +- Go with the default 1s update speed : + ```shell # 1-second just so happens to be the default so, if you're happy with that you can just run: nvtop ``` +- If you're having trouble, send us a log! + +```shell +# The app can log debug info +nvtop --log +``` + ______________________________________________________________________ ### Prerequisites @@ -126,11 +137,23 @@ is **boring**, and this: is **fun!** +______________________________________________________________________ +# Troubleshooting: + +- If something ain't working please feel free to open an issue, before doing so however, the app has the ability to do some verbose logging (to disk): `nvtop --log` +- This, by default will make an `nvtop.log` wherever your binary is, include that with your bug report (there's Issue templates). ______________________________________________________________________ -# Troubleshooting: -If something ain't working please feel free to open an issue, before doing so however, the app has the ability to do some verbose logging (to disk) -- please enable that like so: -```nvtop --logging``` +# Contributing: + +- All are welcome, I'm not really too fussy about coding standards etc (when I'm not at work :p) + +### Advice for contributors: + +- if you touch the readme, please format it with `mdformat` (`pip install mdformat`). +- if you touch python scrpits, please format them with `black` (`pip install black`). +- always run these `cargo test`, `cargo check`, `cargo clippy` -- please don't make PRs until any issues those tools flag are resolved. +- if this is your first time contributing to open source, _wow! thank you_. diff --git a/src/app.rs b/src/app.rs index ea40c79..6a9c7fa 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,4 +1,3 @@ -use log::{debug, trace}; use nvml_wrapper::enum_wrappers::device::{Clock, ClockId}; use nvml_wrapper::error::NvmlError; use nvml_wrapper::struct_wrappers::device::PciInfo; @@ -15,19 +14,29 @@ use std::time::Duration; use crate::errors::NvTopError; use crate::stylers::calculate_severity; +use crate::termite::LoggingHandle; use crate::{errors, gpu::GpuInfo}; pub type Frame<'a> = ratatui::Frame<'a, CrosstermBackend>; -pub fn run(nvml: nvml_wrapper::Nvml, delay: Duration) -> anyhow::Result<(), errors::NvTopError> { +pub fn run( + nvml: nvml_wrapper::Nvml, + delay: Duration, + lh: &LoggingHandle, +) -> anyhow::Result<(), errors::NvTopError> { crossterm::terminal::enable_raw_mode()?; crossterm::execute!(std::io::stderr(), crossterm::terminal::EnterAlternateScreen)?; let mut terminal = Terminal::new(CrosstermBackend::new(std::io::stderr()))?; - trace!("crossterm initialisation successful"); + lh.debug("crossterm initialisation successful"); - let mut gpu_list = crate::gpu::list_available_gpus(&nvml)?; + let mut gpu_list = crate::gpu::try_init_gpus(&nvml, lh)?; let mut selected_gpu: usize = 0; + let mut have_fans: bool = gpu_list + .iter() + .any(|gpu| gpu.inner.num_fans().map_or(0, |fc| fc) != 0); + + lh.debug(&format!("GPU has fans = {}", have_fans)); loop { _ = terminal.draw(|f| { @@ -40,7 +49,14 @@ pub fn run(nvml: nvml_wrapper::Nvml, delay: Duration) -> anyhow::Result<(), erro .constraints([Constraint::Min(0), Constraint::Length(1)]) .split(f.size()); - f.render_widget(Paragraph::new("q to quit, p to rescan devices"), layout[1]); + #[cfg(target_os = "linux")] + f.render_widget( + Paragraph::new("q to quit, p to rescan devices").alignment(Alignment::Right), + layout[1], + ); + + #[cfg(target_os = "windows")] + f.render_widget(Paragraph::new("q to quit"), layout[1]); layout[0] } else { @@ -134,9 +150,11 @@ pub fn run(nvml: nvml_wrapper::Nvml, delay: Duration) -> anyhow::Result<(), erro let temp_gauge = draw_gpu_die_temp(gpu); f.render_widget(temp_gauge, chunks[1]); - // Fanspeed: - let fan_gauge = draw_fan_speed(gpu); - f.render_widget(fan_gauge, chunks[2]); + // Fan speed: + if have_fans { + let gauge = draw_fan_speed(gpu); + f.render_widget(gauge, chunks[2]); + } } })?; @@ -149,6 +167,8 @@ pub fn run(nvml: nvml_wrapper::Nvml, delay: Duration) -> anyhow::Result<(), erro KeyCode::F(n) if (1..=gpu_list.len()).contains(&n.into()) => { selected_gpu = usize::from(n - 1) } + + #[cfg(target_os = "linux")] KeyCode::Char('p') => { // re-scan pci tree to let driver discover new devices (only works as sudo) match nvml.discover_gpus(PciInfo { @@ -159,15 +179,21 @@ pub fn run(nvml: nvml_wrapper::Nvml, delay: Duration) -> anyhow::Result<(), erro pci_device_id: 0, pci_sub_system_id: Some(0), }) { - Ok(()) => debug!("Re-scanned PCI tree"), + Ok(()) => { + have_fans = gpu_list + .iter() + .any(|gpu| gpu.inner.num_fans().map_or(0, |fc| fc) != 0); + + lh.debug(&format!("GPU has fans = {}", have_fans)); + lh.debug("Re-scanned PCI tree"); + } Err(e @ (NvmlError::OperatingSystem | NvmlError::NoPermission)) => { - debug!("Failed to re-scan PCI tree: {e}"); + lh.debug(&format!("Failed to re-scan PCI tree: {e}")); } Err(e) => return Err(e.into()), } - // re-scan for devices - gpu_list = crate::gpu::list_available_gpus(&nvml)?; + gpu_list = crate::gpu::try_init_gpus(&nvml, lh)?; if selected_gpu >= gpu_list.len() { selected_gpu = 0; } @@ -188,14 +214,18 @@ pub fn run(nvml: nvml_wrapper::Nvml, delay: Duration) -> anyhow::Result<(), erro } fn draw_fan_speed<'d>(gpu: &GpuInfo<'d>) -> Gauge<'d> { - let temps = gpu.inner.num_fans().map_or(0, |fc| fc); + let temps = gpu + .inner + .num_fans() + .expect("This should be impossible as we never call this without having earlier checked."); let avg = (0..temps as usize) .flat_map(|v| gpu.inner.fan_speed(v as u32)) .map(|u| u as f64) .sum::() / temps as f64; - let percentage = (avg / 100.).clamp(0., 1.0); + let percentage = (avg / 100.).clamp(0.0, 1.0); + let label = format!("{:.1}%", avg); let spanned_label = Span::styled(label, Style::new().white().bold().bg(Color::Black)); @@ -215,7 +245,7 @@ fn draw_gpu_die_temp<'d>(gpu: &GpuInfo<'d>) -> Gauge<'d> { let label = format!("{:.2}°C", gpu_die_temperature); let spanned_label = Span::styled(label, Style::new().white().bold().bg(Color::Black)); - let temp_ratio = (gpu_die_temperature as f64 / 100.).clamp(0., 1.0); + let temp_ratio = (gpu_die_temperature as f64 / 100.).clamp(0.0, 1.0); Gauge::default() .block(Block::default().borders(Borders::ALL).title("Temp")) diff --git a/src/errors.rs b/src/errors.rs index ab1945e..6b5c3a2 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -6,7 +6,6 @@ use thiserror::Error; pub enum NvTopError { Nvml(#[from] nvml_wrapper::error::NvmlError), Io(#[from] std::io::Error), - Fern(#[from] fern::InitError), } impl Display for NvTopError { diff --git a/src/gpu.rs b/src/gpu.rs index b63ade7..42929cf 100644 --- a/src/gpu.rs +++ b/src/gpu.rs @@ -4,14 +4,13 @@ use std::{ ops::Deref, }; -use log::{error, trace}; use nvml_wrapper::{ enum_wrappers::device::{Clock, ClockId, TemperatureSensor}, error::NvmlError, Device, Nvml, }; -use crate::errors::NvTopError; +use crate::{errors::NvTopError, termite::LoggingHandle}; #[derive(Debug)] pub struct GpuInfo<'d> { @@ -39,14 +38,6 @@ impl<'d> GpuInfo<'d> { driver_version, cuda_version / 1000.0 ); - trace!("Setting misc = {misc}"); - - // dbg!( - // dev.max_clock_info(Clock::Graphics)?, - // dev.max_clock_info(Clock::Video)?, - // dev.max_clock_info(Clock::SM)?, - // dev.max_clock_info(Clock::Memory)?, - // ); Ok(GpuInfo { max_memory_clock: device.max_clock_info(Clock::Memory)?, @@ -102,11 +93,10 @@ impl fmt::Display for GpuInfo<'_> { .unwrap_or_default() } Err(err) => { - let formatted = format!( + let _formatted = format!( "clock_type={:?}\t\tclock_id={:?} {}", clock_type, clock_id, err, ); - log::error!("{formatted}") } } }); @@ -115,7 +105,10 @@ impl fmt::Display for GpuInfo<'_> { } } -pub fn list_available_gpus(nvml: &Nvml) -> Result>, NvTopError> { +pub fn try_init_gpus<'n>( + nvml: &'n Nvml, + lh: &LoggingHandle, +) -> Result>, NvTopError> { let count = nvml.device_count()?; let mut gpu_list = Vec::with_capacity(count as usize); @@ -123,16 +116,16 @@ pub fn list_available_gpus(nvml: &Nvml) -> Result>, NvTopError> match nvml.device_by_index(i) { Ok(dev) => { let gpu = GpuInfo::from_device(i, dev)?; - trace!("Compatible GPU found at [{i}]: {gpu}"); + lh.error(&format!("Compatible GPU found at [{i}]: {gpu}")); gpu_list.push(gpu); } Err( - err @ (NvmlError::InsufficientPower + _err @ (NvmlError::InsufficientPower | NvmlError::NoPermission | NvmlError::IrqIssue | NvmlError::GpuLost), ) => { - error!("Failed to init device [{i}]: {err}"); + lh.error("Failed to init device [{i}]: {err}"); continue; // carry on } Err(e) => return Err(e.into()), diff --git a/src/main.rs b/src/main.rs index c3808c4..42e406e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,28 +1,29 @@ -use std::time::Duration; +use std::{path::PathBuf, time::Duration}; use anyhow::Result; use clap::Parser; -use log::{error, trace}; use nvml_wrapper::Nvml; -use nvtop::{app::run, errors::NvTopError, nvtop_args, termite::setup_logger}; +use nvtop::{app::run, errors::NvTopError, nvtop_args, termite::LoggingHandle}; fn main() -> Result<(), NvTopError> { let args = nvtop_args::Cli::parse(); - // If they've used the --log arg we write all logs to disk. + let mut lh = LoggingHandle::empty(); if args.log.is_some() { - setup_logger(args.log)?; - } else { - pretty_env_logger::init(); // If they've got RUST_LOG=trace on the TUI is ruined. + let log_path = match args.log { + Some(lp) => lp, + None => PathBuf::from("nvtop.log"), + }; + lh = LoggingHandle::init(log_path); } // Init the GPU management-layer let nvml = Nvml::init()?; - trace!("Nvml init success"); + lh.debug("Nvml init success"); - if let Err(e) = run(nvml, Duration::from_millis(args.delay)) { - error!("app::run() -> {e}"); + if let Err(e) = run(nvml, Duration::from_millis(args.delay), &lh) { + lh.error(&format!("app::run() -> {e}")); } Ok(()) diff --git a/src/termite.rs b/src/termite.rs index d87b7bb..aa878a8 100644 --- a/src/termite.rs +++ b/src/termite.rs @@ -1,53 +1,105 @@ -use std::{env, path::PathBuf}; - -/// Termite is a simple logging implementation, powered by the fern crate. -#[allow(unused_imports)] -use log::{debug, error, info, trace, warn}; - -/// Initialise termite.log, after initilisation you'll find it works EXACTLY as most of the logging -/// crates you're used to. -// -/// #Examples: -/// // it works exactly like the std library's log crate. -/// use fern; -/// use log::{debug, error, info, trace, warn}; -/// -/// info!("Info you'd like to log"); -/// warn!("Warning you'd like to log"); -/// error!("Error you'd like to log"); -/// trace!("Thing you'd like to trace"); -pub fn setup_logger(p: Option) -> Result<(), fern::InitError> { - match env::var("RUST_LOG") { - Ok(_) => { - std::env::set_var("RUST_LOG", "trace"); - warn!("$RUST_LOG was not set in $ENV"); - info!("$RUST_LOG set to 'trace'") - } - Err(e) => { - error!("Unable to get, or set $RUST_LOG\n{e}"); - } - }; - - let termite_path = match p { - Some(p) => p, - None => std::path::PathBuf::from("nvtop.log"), - }; - - fern::Dispatch::new() - .format(|out, message, record| { - out.finish(format_args!( - "{}[{}][{}][{}] {}", - chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"), - record.level(), // info!() or error!() etc. - record.target(), // The file that spawned this entry into the log. - record.line().unwrap_or(0), // The line number in said file. - message - )) - }) - .chain(std::io::stdout()) - .chain(fern::log_file(termite_path)?) - .apply()?; - - trace!("Logger setup complete."); - Ok(()) +use std::{ + fs::OpenOptions, + io::Write, + path::{Path, PathBuf}, + sync::mpsc::{self, Sender}, +}; + +pub enum LogType { + /// What it says on the can + Error(String), + /// What it says on the can + Info(String), + /// What it says on the can + Debug(String), + /// What it says on the can + Warn(String), + + /// Send this to hangup the logger (clean it up) + HangUp, +} + +/// An abstraction to allow logging to a file, as opposed to stdout, which is hard when developing a TUI app. +pub struct LoggingHandle { + /// The channel you send LogType(Some string) across + pub sender: Sender, +} + +impl LoggingHandle { + /// Gives you back a LoggingHandle + pub fn init(log_path: PathBuf) -> LoggingHandle { + let (tx, rx) = mpsc::channel(); + + std::thread::spawn(move || { + while let Ok(msg) = rx.recv() { + match msg { + LogType::Error(msg) => Self::log_error(log_path.as_ref(), &msg), + LogType::Debug(msg) => Self::log_debug(log_path.as_ref(), &msg), + LogType::Info(msg) => Self::log_info(log_path.as_ref(), &msg), + LogType::Warn(msg) => Self::log_warn(log_path.as_ref(), &msg), + LogType::HangUp => { + drop(rx); + break; + } + } + } + }); + + LoggingHandle { sender: tx } + } + + /// Creates a useless, empty (as the receiver is hung up before you get Self back), a nice dummy for when the app runs without logging. + pub fn empty() -> LoggingHandle { + let (tx, rx) = mpsc::channel(); + drop(rx); + LoggingHandle { sender: tx } + } + + pub fn error(&self, msg: &str) { + _ = self.sender.send(LogType::Error(msg.to_string())); + } + pub fn info(&self, msg: &str) { + _ = self.sender.send(LogType::Info(msg.to_string())); + } + pub fn debug(&self, msg: &str) { + _ = self.sender.send(LogType::Debug(msg.to_string())); + } + pub fn warn(&self, msg: &str) { + _ = self.sender.send(LogType::Warn(msg.to_string())); + } + + #[inline(always)] + fn log_error(log_path: &Path, msg: &str) { + let log_line = format!(" ERROR {}\n", msg); + Self::write_to_log(log_path, &log_line); + } + #[inline(always)] + fn log_info(log_path: &Path, msg: &str) { + let log_line = format!(" INFO {}\n", msg); + Self::write_to_log(log_path, &log_line); + } + + #[inline(always)] + fn log_debug(log_path: &Path, msg: &str) { + let log_line = format!(" DEBUG {}\n", msg); + Self::write_to_log(log_path, &log_line); + } + + #[inline(always)] + fn log_warn(log_path: &Path, msg: &str) { + let log_line = format!(" WARN {}\n", msg); + Self::write_to_log(log_path, &log_line); + } + + fn write_to_log(log_path: &Path, msg: &str) { + // Open the log file in append mode, creating it if it doesn't exist + let mut file = OpenOptions::new() + .create(true) + .append(true) + .open(log_path).unwrap_or_else(|_| panic!("Unable to create {}.\nAs a result of this we cannot write logs. So the app will crash.", log_path.display())); + + // Write the message to the log file + file.write_all(msg.as_bytes()) + .expect("Unable to write log to file -- We chose to exit the app when this happens."); + } }