monitoring kit
-rw-r--r--Cargo.toml4
-rw-r--r--battery/Cargo.toml23
-rw-r--r--battery/src/charge.rs96
-rw-r--r--gpu/src/intel.rs1
-rw-r--r--grapher/Cargo.toml4
-rw-r--r--grapher/src/lib.rs40
6 files changed, 163 insertions, 5 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 2e2c600..643d3ad 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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());
+ };
+}