mod config; mod report; mod run; mod ui; mod workload; fn main() { let cfg = config::load(); let args: Vec = std::env::args().collect(); if let Some(idx) = args.iter().position(|a| a == "--smoke-workload") { if let Some(name) = args.get(idx + 1) { smoke_workload(name, &cfg); return; } } if args.iter().any(|a| a == "--headless") { headless_run(cfg); return; } if args.iter().any(|a| a == "--tui") { ui::run_tui(cfg); return; } // Default mode = TUI (the real front-end). Args without a dedicated // mode fall through to the TUI so `adlerbench` with no args launches // the sweep with a live display. ui::run_tui(cfg); } /// `--headless` mode: drive the run loop without a TUI. Prints one line /// per workload (verdict + score) at the end. Useful for CI / scripts /// and for verifying run.rs end-to-end before the TUI is wired. fn headless_run(cfg: config::Config) { use std::thread; let mut run = run::Run::new(cfg); for (name, why) in run.skipped() { eprintln!("skipped: {name} ({why})"); } if run.total_workloads() == 0 { eprintln!("no workloads to run"); return; } let poll_ms = config::load().poll_ms; let poll = std::time::Duration::from_millis(poll_ms); let mut count = 0usize; loop { match run.tick() { Ok(run::TickOutcome::WorkloadStarted { idx, name, duration_secs }) => { println!("[{idx}] starting {name} ({duration_secs}s)"); } Ok(run::TickOutcome::WorkloadFinished { idx, name, verdict, score, report_path }) => { println!("[{idx}] finished {name}: {:?}", verdict); println!(" score: {}", format_score(&score)); println!(" report: {}", report_path.display()); count += 1; } Ok(run::TickOutcome::SweepDone) => { println!("sweep complete: {count} workloads"); break; } Ok(run::TickOutcome::Idle) => {} Err(e) => { eprintln!("run error: {e}"); break; } } thread::sleep(poll); } } /// One-line score formatter for the headless + smoke modes. Mirrors what /// `report::human_summary` does for the score, but standalone so the /// headless loop can print it without reconstructing a Report. fn format_score(s: &workload::Score) -> String { use workload::Score; match s { Score::None => "(no score)".to_string(), Score::SevenZip { rating_mips, r_u_mips, usage_pct, compress_mips, decompress_mips } => { format!( "rating={rating_mips} MIPS r/u={r_u_mips} usage={usage_pct}% \ compress={compress_mips} MIPS decompress={decompress_mips} MIPS" ) } Score::YCruncher { tests } => { if tests.is_empty() { return "(no tests recorded)".to_string(); } tests .iter() .map(|t| { let gbps = t.bits_per_sec / 1e9; format!("{}={} {:.2} Gb/s", t.tag, if t.passed { "pass" } else { "FAIL" }, gbps) }) .collect::>() .join(" ") } } } 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(); let score = w.score(); println!("smoke: verdict = {:?}", verdict); println!("smoke: score = {}", format_score(&score)); } Err(e) => println!("smoke: start failed: {e}"), } }