Files
ember-tune-rs/src/main.rs
2026-02-27 17:04:47 +01:00

282 lines
10 KiB
Rust

use miette::{Result, IntoDiagnostic, Diagnostic, Report};
use thiserror::Error;
use std::sync::mpsc;
use std::thread;
use std::time::{Duration, Instant};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::io;
use clap::Parser;
use tracing::{info, debug, error};
use crossterm::{
event::{self, Event, KeyCode},
execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use ratatui::{backend::CrosstermBackend, Terminal};
use ember_tune_rs::cli::Cli;
use ember_tune_rs::mediator::{TelemetryState, UiCommand, BenchmarkPhase};
use ember_tune_rs::sal::traits::{AuditError, PlatformSal};
use ember_tune_rs::sal::mock::MockSal;
use ember_tune_rs::sal::heuristic::engine::HeuristicEngine;
use ember_tune_rs::sal::heuristic::discovery::SystemFactSheet;
use ember_tune_rs::load::{StressNg};
use ember_tune_rs::orchestrator::BenchmarkOrchestrator;
use ember_tune_rs::ui::dashboard::{draw_dashboard, DashboardState};
use ember_tune_rs::engine::OptimizationResult;
use owo_colors::OwoColorize;
#[derive(Error, Diagnostic, Debug)]
#[error("Multiple pre-flight audit failures occurred.")]
struct MultiAuditError {
#[related]
errors: Vec<AuditError>,
}
fn print_summary_report(result: &OptimizationResult) {
println!();
let header = format!("{}", " 🔥 ember-tune optimization complete! ".bold().on_red().white());
println!("╭──────────────────────────────────────────────────╮");
println!("{:<48}", header);
println!("├──────────────────────────────────────────────────┤");
if result.is_partial {
println!("{}", " ⚠ WARNING: PARTIAL RESULTS (INTERRUPTED) ".yellow().bold());
println!("├──────────────────────────────────────────────────┤");
}
let knee_label = "Silicon Knee:".cyan();
println!("{:<28} {:>5.1} W / {:>3.0}°C │", knee_label, result.silicon_knee_watts, result.max_temp_c);
let res_label = "Thermal Resistance (Rθ):".cyan();
println!("{:<28} {:>8.3} K/W │", res_label, result.thermal_resistance_kw);
println!("│ │");
println!("{}", "Recommended Limits:".bold().green());
println!("│ Sustained (PL1): {:>5.1} W │", result.recommended_pl1);
println!("│ Burst (PL2): {:>5.1} W │", result.recommended_pl2);
println!("│ │");
println!("{}", "Apply these to your system:".bold().magenta());
for (id, path) in &result.config_paths {
println!("{:<10}: {:<34}", id, path.display());
}
println!("╰──────────────────────────────────────────────────╯");
println!();
}
fn setup_logging(verbose: bool) -> tracing_appender::non_blocking::WorkerGuard {
let file_appender = tracing_appender::rolling::never("/var/log", "ember-tune.log");
let (non_blocking, guard) = tracing_appender::non_blocking(file_appender);
let level = if verbose { tracing::Level::DEBUG } else { tracing::Level::INFO };
tracing_subscriber::fmt()
.with_max_level(level)
.with_writer(non_blocking)
.with_ansi(false)
.init();
guard
}
fn main() -> Result<()> {
// 1. Diagnostics & CLI Initialization
let args = Cli::parse();
let _log_guard = setup_logging(args.verbose);
// Set panic hook to restore terminal state
std::panic::set_hook(Box::new(|panic_info| {
let _ = disable_raw_mode();
let mut stdout = io::stdout();
let _ = execute!(stdout, LeaveAlternateScreen, crossterm::cursor::Show);
eprintln!("\n\x1b[1;31mFATAL ERROR: ember-tune Panicked\x1b[0m");
eprintln!("----------------------------------------");
eprintln!("{}", panic_info);
eprintln!("----------------------------------------\n");
}));
info!("ember-tune starting with args: {:?}", args);
let ctx = ember_tune_rs::sal::traits::EnvironmentCtx::production();
// 2. Platform Detection & Audit
let (sal_box, facts): (Box<dyn PlatformSal>, SystemFactSheet) = if args.mock {
(Box::new(MockSal::new()), SystemFactSheet::default())
} else {
HeuristicEngine::detect_and_build(ctx)?
};
let sal: Arc<dyn PlatformSal> = sal_box.into();
println!("{}", console::style("─── Pre-flight System Audit ───").bold().cyan());
let mut audit_failures = Vec::new();
for step in sal.audit() {
print!(" Checking {:<40} ", step.description);
io::Write::flush(&mut io::stdout()).into_diagnostic()?;
match step.outcome {
Ok(_) => { println!("{}", console::style("[✓]").green()); }
Err(e) => {
println!("{}", console::style("[✗]").red());
audit_failures.push(e);
}
}
}
if !audit_failures.is_empty() {
println!();
return Err(Report::new(MultiAuditError { errors: audit_failures }));
}
if args.audit_only {
println!("{}", console::style("✓ All pre-flight audits passed.").green().bold());
return Ok(());
}
// 3. Terminal Setup
enable_raw_mode().into_diagnostic()?;
let mut stdout = io::stdout();
execute!(stdout, EnterAlternateScreen).into_diagnostic()?;
let backend_stdout = io::stdout();
let backend_term = CrosstermBackend::new(backend_stdout);
let mut terminal = Terminal::new(backend_term).into_diagnostic()?;
// 4. State & Communication Setup
let running = Arc::new(AtomicBool::new(true));
let r = running.clone();
let (telemetry_tx, telemetry_rx) = mpsc::channel::<TelemetryState>();
let (command_tx, command_rx) = mpsc::channel::<UiCommand>();
let c_tx = command_tx.clone();
ctrlc::set_handler(move || {
let _ = c_tx.send(UiCommand::Abort);
r.store(false, Ordering::SeqCst);
}).expect("Error setting Ctrl-C handler");
// 5. Spawn Backend Orchestrator
let sal_backend = sal.clone();
let facts_backend = facts.clone();
let config_out = args.config_out.clone();
let backend_handle = thread::spawn(move || {
let workload = Box::new(StressNg::new());
let mut orchestrator = BenchmarkOrchestrator::new(
sal_backend,
facts_backend,
workload,
telemetry_tx,
command_rx,
config_out,
);
orchestrator.run()
});
// 6. Frontend Event Loop
let mut ui_state = DashboardState::new();
let mut last_telemetry = TelemetryState {
cpu_model: "Loading...".to_string(),
total_ram_gb: 0,
tick: 0,
cpu_temp: 0.0,
power_w: 0.0,
current_freq: 0.0,
fans: Vec::new(),
governor: "detecting".to_string(),
pl1_limit: 0.0,
pl2_limit: 0.0,
fan_tier: "auto".to_string(),
is_throttling: false,
phase: BenchmarkPhase::Auditing,
history_watts: Vec::new(),
history_temp: Vec::new(),
history_mhz: Vec::new(),
log_event: None,
metadata: std::collections::HashMap::new(),
is_emergency: false,
emergency_reason: None,
};
let tick_rate = Duration::from_millis(100);
let mut last_tick = Instant::now();
while running.load(Ordering::SeqCst) {
terminal.draw(|f| {
draw_dashboard(f, f.area(), &last_telemetry, &ui_state);
}).into_diagnostic()?;
let timeout = tick_rate
.checked_sub(last_tick.elapsed())
.unwrap_or(Duration::from_secs(0));
if event::poll(timeout).into_diagnostic()? {
if let Event::Key(key) = event::read().into_diagnostic()? {
match key.code {
KeyCode::Char('q') | KeyCode::Esc => {
let _ = command_tx.send(UiCommand::Abort);
running.store(false, Ordering::SeqCst);
}
_ => {}
}
}
}
while let Ok(new_state) = telemetry_rx.try_recv() {
if let Some(log) = &new_state.log_event {
ui_state.add_log(log.clone());
debug!("Backend Log: {}", log);
} else {
ui_state.update(&new_state);
last_telemetry = new_state;
}
}
if last_tick.elapsed() >= tick_rate { last_tick = Instant::now(); }
if backend_handle.is_finished() { break; }
}
// 7. Terminal Restoration
let _ = disable_raw_mode();
let _ = execute!(terminal.backend_mut(), LeaveAlternateScreen);
let _ = terminal.show_cursor();
// 8. Final Report & Hardware Restoration
let join_res = backend_handle.join();
// Explicit hardware restoration
info!("Restoring hardware state...");
if let Err(e) = sal.restore() {
error!("Failed to restore hardware state: {}", e);
}
match join_res {
Ok(Ok(result)) => {
print_summary_report(&result);
}
Ok(Err(e)) => {
let err_str = e.to_string();
if err_str == "ABORTED" {
println!("{}", "Benchmark aborted by user.".yellow());
} else if err_str.contains("EMERGENCY_ABORT") {
println!();
println!("{}", " 🚨 EMERGENCY ABORT TRIGGERED ".bold().on_red().white());
println!("Reason: {}", err_str.replace("EMERGENCY_ABORT: ", "").red().bold());
println!("{}", "Hardware state has been restored to safe defaults.".yellow());
println!();
} else {
error!("Orchestrator encountered error: {}", e);
eprintln!("{} {}", "Error:".red().bold(), e);
}
}
Err(_) => {
error!("Backend thread panicked!");
}
}
info!("ember-tune exited gracefully.");
Ok(())
}