mod config; mod sensors; mod workload; fn main() { let cfg = config::load(); let args: Vec = std::env::args().collect(); // `adlerbench --dump` = one-shot sensor snapshot (spike/verification // tool, mirrors adlermon's dump mode). Default = config summary. if args.iter().any(|a| a == "--dump") { let s = sensors::snapshot(None, 0.0); println!("vCore : {}", opt_volts(s.vcore)); println!("Pkg temp : {}", opt_temp(s.pkg_temp)); println!("Pkg power : {} (None on first tick — no delta yet)", opt_watts(s.pkg_power)); println!("Peak clock : {}", opt_khz(s.peak_clock_khz)); println!("RAPL energy: {}", opt_uj(s.energy_uj())); println!("Cores : {} logical", s.core_freqs.len()); return; } // `adlerbench --smoke-workload ` = run one workload for 3s and // print its verdict. Verifies the Workload trait end-to-end without // needing the run loop / TUI. if let Some(idx) = args.iter().position(|a| a == "--smoke-workload") { if let Some(name) = args.get(idx + 1) { smoke_workload(name, &cfg); return; } } println!("adlerbench — {} workloads configured", cfg.workloads.len()); for w in &cfg.workloads { println!(" {} {}s cores={}", w.name, w.duration_secs, w.cores); } } fn smoke_workload(name: &str, cfg: &config::Config) { use std::thread; use std::time::Duration; // Build a 3-second spec for the requested workload, regardless of the // configured duration — this is a smoke test, not a real run. let spec = config::WorkloadSpec { name: name.to_string(), duration_secs: 3, cores: "0-15".to_string(), }; let Some(mut w) = workload::from_spec(&spec, &cfg.bin_dir) else { eprintln!("unknown or not-yet-wired workload: {name}"); return; }; println!("smoke: {} params={}", w.name(), w.params()); match w.start() { Ok(()) => { // Poll is_running every 250ms; stop after the spec's duration // plus a 1s grace (some workloads self-exit right at the // boundary; the grace avoids a Stopped verdict when the // workload actually finished on its own). let ticks = spec.duration_secs * 4 + 4; let mut elapsed = 0u64; while elapsed < ticks { thread::sleep(Duration::from_millis(250)); elapsed += 1; if !w.is_running() { println!("smoke: exited after {:.1}s", elapsed as f64 / 4.0); break; } } if w.is_running() { println!("smoke: still running — calling stop()"); let _ = w.stop(); } let verdict = w.wait(); println!("smoke: verdict = {:?}", verdict); } Err(e) => println!("smoke: start failed: {e}"), } } fn opt_volts(v: Option) -> String { match v { Some(x) => format!("{:.3} V", x), None => "unreadable".to_string(), } } fn opt_temp(v: Option) -> String { match v { Some(x) => format!("{:.1} C", x), None => "unreadable".to_string(), } } fn opt_watts(v: Option) -> String { match v { Some(x) => format!("{:.1} W", x), None => "unreadable".to_string(), } } fn opt_khz(v: Option) -> String { match v { Some(x) => format!("{:.3} GHz", x as f64 / 1_000_000.0), None => "unreadable".to_string(), } } fn opt_uj(v: Option) -> String { match v { Some(x) => format!("{} uJ", x), None => "unreadable (root-only without adlermon udev rule + adm group)".to_string(), } }