feb1f8ff6355e3506ac77049cb27b9a0f9e532fd / src/main.rs · 5793 bytes · raw
mod config;
mod presets;
mod report;
mod run;
mod ui;
mod workload;
fn main() {
let cfg = config::load();
let args: Vec<String> = 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 let Some(idx) = args.iter().position(|a| a == "--preset") {
if let Some(name) = args.get(idx + 1) {
match name.as_str() {
"stability" => {
ui::run_tui(presets::stability());
return;
}
"bench" => {
ui::run_tui(presets::bench());
return;
}
"core-cycle" => {
ui::run_tui(presets::stability_core_cycle(60));
return;
}
_ => {
eprintln!("unknown preset: {name} (available: stability, bench, core-cycle)");
std::process::exit(1);
}
}
}
}
if args.iter().any(|a| a == "--tui") {
ui::run_tui(cfg);
return;
}
// Default mode = TUI picker (lets the user choose a preset or custom).
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::<Vec<_>>()
.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}"),
}
}