monitoring kit
add amd
| -rw-r--r-- | Cargo.toml | 11 | ||||
| -rw-r--r-- | cpu/Cargo.toml | 2 | ||||
| -rw-r--r-- | cpu/src/cpu.rs | 8 | ||||
| -rw-r--r-- | cpu/src/lib.rs | 91 | ||||
| -rw-r--r-- | cpu/src/temps.rs | 14 | ||||
| -rw-r--r-- | disk/Cargo.toml | 26 | ||||
| -rw-r--r-- | disk/src/disk.rs | 112 | ||||
| -rw-r--r-- | grapher/Cargo.toml | 2 | ||||
| -rw-r--r-- | grapher/src/lib.rs | 15 | ||||
| -rw-r--r-- | hwmons/Cargo.toml | 12 | ||||
| -rw-r--r-- | hwmons/src/lib.rs | 68 | ||||
| -rw-r--r-- | launch.py | 6 |
12 files changed, 293 insertions, 74 deletions
@@ -1,5 +1,14 @@ [workspace] -members = ["cpu", "grapher", "memory", "gpu", "battery", "network"] +members = [ + "hwmons", + "cpu", + "grapher", + "memory", + "gpu", + "battery", + "network", + "disk", +] resolver = "3" [profile.release] diff --git a/cpu/Cargo.toml b/cpu/Cargo.toml index 9fca4ee..d961a56 100644 --- a/cpu/Cargo.toml +++ b/cpu/Cargo.toml @@ -16,6 +16,8 @@ parking_lot = "0.12.3" regex = "1.11.1" termion = "4.0.4" grapher = { path = "../grapher" } +hwmons = { version = "1.0.0", path = "../hwmons" } +implicit-fn = "0.1.0" [[bin]] diff --git a/cpu/src/cpu.rs b/cpu/src/cpu.rs index 00feb75..9356bc6 100644 --- a/cpu/src/cpu.rs +++ b/cpu/src/cpu.rs @@ -9,7 +9,7 @@ use anyhow::*; use comat::cwrite; use cpu::*; -use grapher::Grapher; +use grapher::{truncwrite, Grapher}; use std::array; use std::convert::identity; use std::io::Write; @@ -37,7 +37,7 @@ fn main() -> Result<()> { ViewCore::One(x) => ensure!(x < info.count, "not enough cores"), _ => (), } - let mut t = Temps::load()?; + let mut t = temps()?; let mut g = Grapher::new()?; g.push_point(usage()?.max(0.01)); @@ -82,9 +82,9 @@ fn main() -> Result<()> { let speed = speed()?; let temp = t.read()?; let fps = (1f32 / d).round(); - cwrite!( + truncwrite!( g.buffer, - " {fps}fps ──── {name} {core} @ {speed:.2} GHz ──── {red}{temp}{reset} °C", + " {fps}fps ──── {name} {core} @ {speed:.2} GHz ──── \u{1b}[0;34;31m{temp:.0}\u{1b}[0m °C", )?; stdout.write_all(&g.buffer)?; stdout.flush()?; diff --git a/cpu/src/lib.rs b/cpu/src/lib.rs index 5ddb34c..5a9e899 100644 --- a/cpu/src/lib.rs +++ b/cpu/src/lib.rs @@ -9,6 +9,7 @@ use anyhow::*; use atools::prelude::*; use collar::CollectArray; +use hwmons::{hwmons, Hwmon}; use parking_lot::Mutex; use std::collections::HashMap; use std::fmt::Display; @@ -42,46 +43,21 @@ pub struct CpuInfo { pub count: u64, } -pub struct Temps(File); -impl Temps { - pub fn load() -> Result<Self> { - for hwmon in std::fs::read_dir("/sys/class/hwmon/")?.filter_map(Result::ok) { - if !read(Path::join(&hwmon.path(), "name"))?.starts_with("coretemp") { - continue; - } - for f in std::fs::read_dir(hwmon.path())?.filter_map(Result::ok) { - if let Some(n) = f.file_name().to_str() - && n.starts_with("temp") - && n.ends_with("input") - { - let i = n - .bytes() - .filter(u8::is_ascii_digit) - .fold(0, |acc, x| acc * 10 + (x - b'0') as u64); - - let name = read(Path::join(&hwmon.path(), format!("temp{i}_label"))) - .context("alas")?; - if let Some(c) = core() { - if !name.contains(&c.to_string()) { - continue; - } - } else if !name.contains("Package") { - continue; - } - - let f = File::open(f.path())?; - return Ok(Self(f)); - } - } +#[implicit_fn::implicit_fn] +pub fn temps() -> Result<Hwmon> { + if let x @ Some(_) = hwmons().find(_.contains("Package")) { + if let Some(c) = core() { + hwmons().find(_.contains(&c.to_string())) + } else { + x } - bail!("h") - } - pub fn read(&mut self) -> Result<f64> { - let mut o = String::default(); - self.0.seek(std::io::SeekFrom::Start(0))?; - self.0.read_to_string(&mut o)?; - Ok(o.trim().parse::<f64>().context("reading temps")? / 1000.0) + .ok_or(anyhow!("no intel hwmon"))? + } else { + hwmons() + .find(_.contains("Tccd")) + .ok_or(anyhow!("no amd hwmon"))? } + .load() } impl CpuInfo { @@ -98,21 +74,30 @@ impl CpuInfo { .lines() .filter_map(|l| l.split_once(":").map(|(a, b)| (a, b.trim()))) .collect::<HashMap<_, _>>(); - let name = x["Model name"] - .replace("Core", "") - .replace("(TM)", "") - .replace("Intel", "") - .replace("(R)", ""); - let name = regex::Regex::new(r"@ [0-9]+\.[0-9]+GHz") - .unwrap() - .replace(&name, ""); - let name = regex::Regex::new(r"[0-9]+th Gen") - .unwrap() - .replace(&name, "") - .replace(" ", " ") - .trim() - .replace(" ", " ") - .to_lowercase(); + let name = x["Model name"]; + let name = if name.contains("Intel") { + let name = name + .replace("Core", "") + .replace("(TM)", "") + .replace("Intel", "") + .replace("(R)", ""); + let name = regex::Regex::new(r"[0-9]+th Gen") + .unwrap() + .replace(&name, "") + .replace(" ", " ") + .trim() + .replace(" ", " "); + regex::Regex::new(r"@ [0-9]+\.[0-9]+GHz") + .unwrap() + .replace(&name, "") + .to_lowercase() + } else { + name.replace("AMD Ryzen", "Ryzen") + .replace("Processor", "") + .trim() + .to_owned() + }; + Ok(Self { name: name, speed: x["CPU max MHz"].parse::<f64>().context("cpu mhz??")? / 1000.0, diff --git a/cpu/src/temps.rs b/cpu/src/temps.rs index 485b427..81c7883 100644 --- a/cpu/src/temps.rs +++ b/cpu/src/temps.rs @@ -2,7 +2,7 @@ use anyhow::{ensure, Result}; use comat::cwrite; use cpu::*; -use grapher::Grapher; +use grapher::{truncwrite, Grapher}; use std::array; use std::convert::identity; use std::io::Write; @@ -30,7 +30,7 @@ fn main() -> Result<()> { ViewCore::One(x) => ensure!(x < info.count, "not enough cores"), _ => (), } - let mut t = Temps::load()?; + let mut t = temps()?; let mut g = Grapher::new()?; g.push_point(t.read()? / 105.0); @@ -75,10 +75,12 @@ fn main() -> Result<()> { let speed = speed()?; let temp = t.read()?; let fps = (1f32 / d).round(); - cwrite!( - g.buffer, - " {fps}fps ──── {name} {core} @ {speed:.2} GHz ──── {red}{temp}{reset} °C", - )?; + let core = if !name.contains("Ryzen") { + core.to_string() + " " + } else { + "".to_string() + }; + truncwrite!(g.buffer," {fps}fps ──── {name} {core}@ {speed:.2} GHz ──── \u{1b}[0;34;31m{temp:.0}\u{1b}[0m °C",)?; stdout.write_all(&g.buffer)?; stdout.flush()?; diff --git a/disk/Cargo.toml b/disk/Cargo.toml new file mode 100644 index 0000000..80c961d --- /dev/null +++ b/disk/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "disk" +version = "1.0.0" +edition = "2024" +authors = ["bend-n <[email protected]>"] +repository = "https://github.com/bend-n/monitorkit" +license = "MIT" +rust-version = "1.85" + +[dependencies] +anyhow = "1.0.97" +termion = "4.0.4" +grapher = { path = "../grapher" } +ping-rs = "0.1.2" +oneshot = { version = "0.1.11", default-features = false, features = ["std"] } +libc = "0.2.171" +atools = "0.1.6" +implicit-fn = "0.1.0" +collar = "1.0.1" +human_bytes = "0.4.3" + + +[[bin]] +name = "disk" +doc = false +path = "src/disk.rs" diff --git a/disk/src/disk.rs b/disk/src/disk.rs new file mode 100644 index 0000000..677fd1c --- /dev/null +++ b/disk/src/disk.rs @@ -0,0 +1,112 @@ +#![feature(portable_simd, string_deref_patterns, duration_millis_float)] +use anyhow::*; +use collar::CollectArray; +use grapher::{Grapher, truncwrite}; +use human_bytes::human_bytes; +use std::array; +use std::fs::read_to_string as read; +use std::io::Write; +use std::io::{Read, stdout}; +use std::process::exit; +use std::thread::sleep; +use std::time::{Duration, Instant}; +use termion::color::*; +use termion::cursor::Hide; +use termion::raw::IntoRawMode; +use termion::screen::IntoAlternateScreen; +use termion::{async_stdin, clear, cursor, style}; +#[implicit_fn::implicit_fn] +fn main() -> Result<()> { + let disk = match std::env::args().nth(1) { + None => { + let fs = std::fs::read_dir("/sys/block")? + .filter_map(Result::ok) + .filter_map(_.file_name().into_string().ok()) + .collect::<Vec<_>>(); + eprintln!("no args! must be one of {fs:?}"); + exit(1) + } + Some(x) => x, + }; + let write = match std::env::args().nth(2) { + Some("r") => false, + Some("w") => true, + Some(_) | None => { + eprintln!("requires r/w arg"); + exit(1) + } + }; + let drive = std::fs::read_dir("/sys/block")? + .filter_map(Result::ok) + .find(_.file_name().to_string_lossy().starts_with(&disk)) + .ok_or(anyhow!("no network"))? + .path(); + + let name = read(drive.join("device").join("model"))?; + let name = name.trim(); + + // https://www.kernel.org/doc/html/latest/block/stat.html + let ticks = || { + let [_rio, _rm, rs, _rt, _wio, _wm, ws, _wt] = read(drive.join("stat"))? + .split_whitespace() + .filter_map(_.parse::<u32>().ok()) + .collect_array(); + + Ok(write.then_some(ws).unwrap_or(rs)) + }; + + let mut g = Grapher::new()?; + let mut d = 0.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 max = 1f64; + let mut last = ticks()?; + + let mut i = Instant::now(); + 'out: loop { + sleep(Duration::from_secs_f32(d)); + + let pass = i.elapsed().as_millis_f64(); + let new = ticks()?; + i = Instant::now(); + + let δ = (new - last) * 512; + let r = δ as f64 / pass; + g.push_point(r); + max = max.max(r); + last = new; + + let mut key = 0; + while stdin.read(array::from_mut(&mut key)).unwrap() != 0 { + match key { + b'q' => break 'out, + b'+' => d = (d + 0.1f32).min(1.0), + b'-' if d >= 0.2 => d -= 0.1, + b'-' if d >= 0.02 => d -= 0.01, + b'-' => d = 0.00833, + _ => (), + } + } + + g.draw(|_| None, _ / max as f64)?; + write!(g.buffer, "{}{}", White.fg_str(), cursor::Goto(1, 1))?; + + let fps = (1f32 / d).round(); + truncwrite!( + g.buffer, + " {fps}fps ──── {name} ──── ceil {}/s ──── curr {}/s ─ {}", + human_bytes(max as f64 * 1e3), + human_bytes(r as f64 * 1e3), // 1e3 + ["read", "write"][write as usize], + )?; + + write!(stdout, "{}", Rgb(106, 186, 212).fg_string())?; + + stdout.write_all(&g.buffer)?; + stdout.flush()?; + } + println!("\x1B[?7l"); + return Ok(()); +} diff --git a/grapher/Cargo.toml b/grapher/Cargo.toml index b03bb37..6cbfdb7 100644 --- a/grapher/Cargo.toml +++ b/grapher/Cargo.toml @@ -9,6 +9,8 @@ rust-version = "1.85" [dependencies] anyhow = "1.0.97" +comat = "0.1.3" +implicit-fn = "0.1.0" 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 b6d2df2..6f98eed 100644 --- a/grapher/src/lib.rs +++ b/grapher/src/lib.rs @@ -104,7 +104,7 @@ fn bl(x: u8) -> [u8; 3] { .encode_utf8(&mut b); b } - +#[implicit_fn::implicit_fn] pub fn truncate_to(x: &str, cols: u16) -> String { if x.width() < cols as _ { return x.to_string(); @@ -114,14 +114,13 @@ pub fn truncate_to(x: &str, cols: u16) -> String { Some((x, *length)) }); use itertools::Itertools; - let mut o = i - .take_while_ref(|&(_, length)| length < cols as usize) - .map(|x| x.0) + let o = i + .take_while_ref(_.1 < cols as usize) + .map(_.0) .collect::<Vec<char>>(); - if i.next().is_some() { - *o.last_mut().unwrap() = '…'; - } - o.into_iter().collect::<String>() + o.into_iter() + .chain(i.next().map(|_| '…')) + .collect::<String>() } pub fn truncate(x: &str) -> anyhow::Result<String> { diff --git a/hwmons/Cargo.toml b/hwmons/Cargo.toml new file mode 100644 index 0000000..102f261 --- /dev/null +++ b/hwmons/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "hwmons" +version = "1.0.0" +edition = "2024" +authors = ["bend-n <[email protected]>"] +repository = "https://github.com/bend-n/monitorkit" +license = "MIT" +rust-version = "1.85" + +[dependencies] +anyhow = "1.0.97" +implicit-fn = "0.1.0" diff --git a/hwmons/src/lib.rs b/hwmons/src/lib.rs new file mode 100644 index 0000000..55ad3df --- /dev/null +++ b/hwmons/src/lib.rs @@ -0,0 +1,68 @@ +use anyhow::*; +use std::ffi::OsStr; +use std::fs::{File, read_to_string as read}; +use std::io::{Read, Seek}; +use std::ops::Deref; +use std::path::PathBuf; +#[derive(Debug)] +pub struct Hwmon(String, PathBuf, Option<File>); +impl Deref for Hwmon { + type Target = str; + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl Hwmon { + pub fn load(self) -> Result<Self> { + File::open(&self.1) + .map(|x| Self(self.0, self.1, Some(x))) + .context("couldnt load hwmon") + } + #[implicit_fn::implicit_fn] + pub fn read(&mut self) -> Result<f64> { + let mut o = String::default(); + let f = self.2.as_mut().unwrap(); + f.seek(std::io::SeekFrom::Start(0))?; + f.read_to_string(&mut o)?; + o.trim() + .parse::<f64>() + .map(_ / 1000.0) + .context("parsing hwmon") + } +} +#[implicit_fn::implicit_fn] +pub fn hwmons() -> impl Iterator<Item = Hwmon> { + std::fs::read_dir("/sys/class/hwmon/").into_iter().flat_map( + _.into_iter() + .filter_map(Result::ok) + .map(_.path()) + .map(std::fs::read_dir) + .filter_map(Result::ok) + .flatten() + .filter_map(Result::ok) + .map(_.path()) + .filter( + _.file_name() + .and_then(OsStr::to_str) + .is_some_and(|x| x.starts_with("temp") & x.ends_with("input")), + ) + .filter_map(|f| { + Some(Hwmon( + read(f.parent()?.join(format!("temp{}_label", + f.file_name()?.to_str()?.bytes().skip(4) + .take_while(u8::is_ascii_digit) + .fold(0, |acc, x| acc * 10 + (x - b'0') as u64)))) + .ok()? + .trim() + .to_string(), + f, + None, + )) + }), + ) +} + +#[test] +fn x() { + dbg!(hwmons().collect::<Vec<_>>()); +} @@ -1,5 +1,7 @@ for n in range(16): kitty ./.target/release/cpu @(n) & kitty ./.target/release/temps & -kitty sudo ./.target/release/intel & -./.target/release/memory
\ No newline at end of file +kitty ./.target/release/disk nvme r & +kitty ./.target/release/ping google.com & +# kitty sudo ./.target/release/intel & +./.target/release/memory |