monitoring kit
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
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(());
}