josie / alder-tools

//! adlermon — Alder Lake monitor.
//!
//! Currently a dump tool: prints a one-shot sensor snapshot to stdout. The
//! TUI consumes the same `sensors` layer once the vCore source spike is done.

mod config;
mod sensors;
mod ui;

use std::thread::sleep;
use std::time::{Duration, Instant};

use sensors::{cpu_frequencies, discover_chips, rapl_domains, rapl_energy_uj};

fn main() {
    let args: Vec<String> = std::env::args().collect();
    if args.iter().any(|a| a == "--vid") {
        vid_spike();
        return;
    }
    if args.iter().any(|a| a == "--tui") {
        ui::run_tui();
        return;
    }
    println!("== hwmon ==");
    for chip in discover_chips() {
        println!("[{}] {}", chip.name, chip.path.display());
        for (label, volts) in sensors::voltages(&chip) {
            println!("  {:>18}: {:8.3} V", label, volts);
        }
        for (label, temp) in sensors::temperatures(&chip) {
            println!("  {:>18}: {:7.1} °C", label, temp);
        }
        for (label, rpm) in sensors::fans(&chip) {
            println!("  {:>18}: {:7.0} RPM", label, rpm);
        }
    }

    println!("\n== cpu frequency (MHz) ==");
    for (cpu, khz) in cpu_frequencies() {
        println!("  cpu{:>2}: {:8.1} MHz", cpu, khz as f64 / 1000.0);
    }

    println!("\n== RAPL power (sampled over 500 ms) ==");
    let domains = rapl_domains();
    let first: Vec<Option<u64>> = domains.iter().map(|d| rapl_energy_uj(&d.id)).collect();
    let start = Instant::now();
    sleep(Duration::from_millis(500));
    let elapsed = start.elapsed().as_secs_f64();
    for (domain, first) in domains.iter().zip(first) {
        match (first, rapl_energy_uj(&domain.id)) {
            (Some(a), Some(b)) if b >= a => {
                let watts = (b - a) as f64 / 1_000_000.0 / elapsed;
                println!("  {:>18} ({}): {:6.2} W", domain.name, domain.id, watts);
            }
            _ => println!("  {:>18} ({}): unreadable", domain.name, domain.id),
        }
    }
}

/// VID cross-check spike: compare SIO in0 (delivered rail) with the CPU's
/// reported VID (IA32_PERF_STATUS) per sample. Requires root + msr module.
fn vid_spike() {
    println!("== VID cross-check (needs root: /dev/cpu/*/msr) ==");
    for round in 0..5 {
        let vcore = sensors::sio_vcore();
        let vids: Vec<Option<f64>> = (0..16).map(sensors::vid_volts).collect();
        println!("-- sample {} --", round);
        match vcore {
            Some(v) => println!("  SIO in0 (vCore): {:?}", v),
            None => println!("  SIO in0 (vCore): UNAVAILABLE"),
        }
        for (cpu, vid) in vids.iter().enumerate() {
            match vid {
                Some(v) => println!("  cpu{:>2} VID: {:?}", cpu, v),
                None => println!("  cpu{:>2} VID: unreadable", cpu),
            }
        }
        std::thread::sleep(std::time::Duration::from_millis(400));
    }
}