monitoring kit
bat
| -rw-r--r-- | Cargo.toml | 4 | ||||
| -rw-r--r-- | battery/Cargo.toml | 23 | ||||
| -rw-r--r-- | battery/src/charge.rs | 96 | ||||
| -rw-r--r-- | gpu/src/intel.rs | 1 | ||||
| -rw-r--r-- | grapher/Cargo.toml | 4 | ||||
| -rw-r--r-- | grapher/src/lib.rs | 40 |
6 files changed, 163 insertions, 5 deletions
@@ -1,6 +1,6 @@ [workspace] -members = ["cpu", "grapher", "memory","gpu"] -resolver = "2" +members = ["cpu", "grapher", "memory", "gpu", "battery"] +resolver = "3" [profile.release] debug = 2 diff --git a/battery/Cargo.toml b/battery/Cargo.toml new file mode 100644 index 0000000..e454e17 --- /dev/null +++ b/battery/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "battery" +version = "1.0.0" +edition = "2021" +authors = ["bend-n <[email protected]>"] +repository = "https://github.com/bend-n/monitorkit" +license = "MIT" +rust-version = "1.85" + +[dependencies] +anyhow = "1.0.97" +collar = "1.0.1" +comat = "0.1.3" +parking_lot = "0.12.3" +termion = "4.0.4" +grapher = { path = "../grapher" } +memchr = "2.7.4" + + +[[bin]] +name = "charge" +doc = false +path = "src/charge.rs" diff --git a/battery/src/charge.rs b/battery/src/charge.rs new file mode 100644 index 0000000..ecba756 --- /dev/null +++ b/battery/src/charge.rs @@ -0,0 +1,96 @@ +#![feature( + generic_arg_infer, + import_trait_associated_functions, + string_deref_patterns, + let_chains, + iter_array_chunks, + array_chunks, + portable_simd, + iter_chain +)] +use anyhow::*; +use grapher::{truncwrite, Grapher}; +use std::array; +use std::fs::read_to_string as read; +use std::io::Write; +use std::io::{stdout, Read}; +use std::path::PathBuf; +use std::result::Result::Ok as Rok; +use std::sync::OnceLock; +use std::thread::sleep; +use std::time::Duration; +use termion::color::*; +use termion::cursor::Hide; +use termion::raw::IntoRawMode; +use termion::screen::IntoAlternateScreen; +use termion::{async_stdin, clear, cursor, style}; + +fn fetch<U, E: Into<anyhow::Error>>(find: &str, f: impl FnOnce(&str) -> Result<U, E>) -> Result<U> { + f(read(path.get().unwrap().join("uevent"))? + .lines() + .filter_map(|x| x.split_once('=')) + .find(|x| x.0 == find) + .ok_or(anyhow!("no power??"))? + .1) + .map_err(Into::into) +} + +static path: OnceLock<PathBuf> = OnceLock::new(); +fn main() -> Result<()> { + let x = std::fs::read_dir("/sys/class/power_supply")? + .filter_map(Result::ok) + .find(|x| matches!(read(x.path().join("type")), Rok(x) if x.trim_ascii() == "Battery")) + .ok_or(anyhow!("batless"))? + .path(); + path.set(x.clone()).unwrap(); + let manufacturer = fetch("POWER_SUPPLY_MANUFACTURER", |x| Ok(x.to_string()))?; + let model = fetch("POWER_SUPPLY_MODEL_NAME", |x| Ok(x.to_string()))?; + let cap = fetch("POWER_SUPPLY_ENERGY_FULL_DESIGN", str::parse::<f64>)? / 1e6; + + let charge = match &*std::env::args().nth(1).unwrap_or("charge".into()) { + x @ ("charge" | "usage") => x == "charge", + _ => bail!("arg must be {{usage, charge}}"), + }; + + let mut g = Grapher::new()?; + let mut d = 1; + + let mut stdout = stdout().into_raw_mode()?.into_alternate_screen()?; + let mut stdin = async_stdin(); + write!(stdout, "{}{}{}", Hide, clear::All, style::Reset).unwrap(); + let mut second = 0; + 'out: loop { + let mut key = 0; + while stdin.read(array::from_mut(&mut key)).unwrap() != 0 { + match key { + b'q' => break 'out, + b'+' => d = (d + 1).min(100), + b'-' if d > 1 => d -= 1, + b'-' => d = 1, + _ => (), + } + } + + g.draw(|_| None)?; + let current = fetch("POWER_SUPPLY_CAPACITY", str::parse::<f64>)?; + let whs = fetch("POWER_SUPPLY_ENERGY_NOW", str::parse::<f64>)? / 1e6; + let usage = fetch("POWER_SUPPLY_POWER_NOW", str::parse::<f64>)? / 1e6; + + write!(g.buffer, "{}{}", White.fg_str(), cursor::Goto(1, 1))?; + truncwrite!( + g.buffer, + " {d}s/f ──── {usage:.2}W → ── {current}% {whs:.1}Wh / {cap:.0}Wh ── {manufacturer} {model}" + )?; + write!(stdout, "{}", Rgb(40, 185, 119).fg_string())?; + stdout.write_all(&g.buffer)?; + stdout.flush()?; + if second % (d * 2) == 0 { + g.push_point(if charge { current / 1e2 } else { usage / 40. }); + } + + sleep(Duration::from_millis(500)); + second += 1; + } + println!("\x1B[?7l"); + Ok(()) +} diff --git a/gpu/src/intel.rs b/gpu/src/intel.rs index b9c7dc8..da71f72 100644 --- a/gpu/src/intel.rs +++ b/gpu/src/intel.rs @@ -69,7 +69,6 @@ unsafe extern "C" { fn igt_devices_print(opts: *const igt_devices_print_format); fn igt_device_find_integrated_card(card: *mut igt_device_card) -> bool; fn igt_device_get_pretty_name(card: *const igt_device_card, numeric: bool) -> *const c_char; - } fn uh() -> Result<()> { diff --git a/grapher/Cargo.toml b/grapher/Cargo.toml index 890709e..b03bb37 100644 --- a/grapher/Cargo.toml +++ b/grapher/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "grapher" version = "1.0.0" -edition = "2021" +edition = "2024" authors = ["bend-n <[email protected]>"] repository = "https://github.com/bend-n/monitorkit" license = "MIT" @@ -9,4 +9,6 @@ rust-version = "1.85" [dependencies] anyhow = "1.0.97" +itertools = "0.14.0" termion = "4.0.4" +unicode-width = "0.2.0" diff --git a/grapher/src/lib.rs b/grapher/src/lib.rs index 75bbb83..354e41d 100644 --- a/grapher/src/lib.rs +++ b/grapher/src/lib.rs @@ -1,4 +1,11 @@ -#![feature(let_chains, iter_array_chunks, array_chunks, portable_simd, iter_chain)] +#![feature( + let_chains, + iter_array_chunks, + array_chunks, + portable_simd, + iter_chain, + round_char_boundary +)] use anyhow::Result; use std::collections::VecDeque; use std::io::Write; @@ -6,6 +13,7 @@ use std::iter::zip; use std::simd::prelude::*; use termion::color::*; use termion::{clear, cursor}; +use unicode_width::*; pub struct Grapher { pub buffer: Vec<u8>, @@ -91,3 +99,33 @@ fn bl(x: u8) -> [u8; 3] { .encode_utf8(&mut b); b } + +pub fn truncate_to(x: &str, cols: u16) -> String { + if x.width() < cols as _ { + return x.to_string(); + } + let mut i = x.chars().scan(0, |length, x| { + *length += x.width().unwrap_or(0); + Some((x, *length)) + }); + use itertools::Itertools; + let mut o = i + .take_while_ref(|&(_, length)| length < cols as usize) + .map(|x| x.0) + .collect::<Vec<char>>(); + if i.next().is_some() { + *o.last_mut().unwrap() = '…'; + } + o.into_iter().collect::<String>() +} + +pub fn truncate(x: &str) -> anyhow::Result<String> { + let columns = termion::terminal_size()?.0; + Ok(truncate_to(x, columns)) +} +#[macro_export] +macro_rules! truncwrite { + ($into:expr, $x:literal $(, $args:expr)* $(,)?) => { + $into.write(grapher::truncate(&format!($x $(, $args)*))?.as_bytes()); + }; +} |