#![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::>(); 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::().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(()); }