use std::time::Instant;
use crate::ansi;
use crate::replay::format;
use crate::replay::recorder::*;
use crate::terminal::TerminalState;
#[derive(Debug, Clone)]
pub struct HeadlessResult {
pub events_processed: u64,
pub total_bytes: u64,
pub parse_time_ms: f64,
pub wall_time_ms: f64,
pub parse_throughput_mbps: f64,
pub peak_dirty_rows: usize,
pub final_grid_hash: u64,
pub final_cursor: (usize, usize),
pub scrollback_lines: usize,
pub scrollback_hash: u64,
pub alt_screen_state: bool,
pub grid_update_time_ms: f64,
pub parser_latency_min_ms: f64,
pub parser_latency_max_ms: f64,
pub parser_latency_avg_ms: f64,
pub total_events_counted: u64,
pub errors: Vec<String>,
pub cell_writes: u64,
pub sgr_updates: u64,
pub line_advances: u64,
}
pub fn run_headless(events: &[ReplayEvent], cols: u16, rows: u16) -> HeadlessResult {
let mut terminal = TerminalState::new(cols as usize, rows as usize, 10_000);
let mut parser = ansi::Parser::new();
let mut result = HeadlessResult {
events_processed: 0,
total_bytes: 0,
parse_time_ms: 0.0,
wall_time_ms: 0.0,
parse_throughput_mbps: 0.0,
peak_dirty_rows: 0,
final_grid_hash: 0,
final_cursor: (0, 0),
scrollback_lines: 0,
scrollback_hash: 0,
alt_screen_state: false,
grid_update_time_ms: 0.0,
parser_latency_min_ms: f64::MAX,
parser_latency_max_ms: f64::MIN,
parser_latency_avg_ms: 0.0,
total_events_counted: 0,
errors: Vec::new(),
cell_writes: 0,
sgr_updates: 0,
line_advances: 0,
};
let mut total_parser_latency = 0.0;
let mut latency_count = 0u64;
let start = Instant::now();
for event in events {
match &event.kind {
ReplayEventKind::PtyBytes(data) => {
let t0 = Instant::now();
parser.advance(data, &mut terminal);
let dt = t0.elapsed().as_secs_f64() * 1000.0;
result.parse_time_ms += dt;
result.events_processed += 1;
result.total_bytes += data.len() as u64;
let p = &parser.last_profile;
result.cell_writes += p.cell_writes;
result.sgr_updates += p.sgr_updates;
result.line_advances += p.line_advances;
if dt < result.parser_latency_min_ms {
result.parser_latency_min_ms = dt;
}
if dt > result.parser_latency_max_ms {
result.parser_latency_max_ms = dt;
}
total_parser_latency += dt;
latency_count += 1;
let dc = terminal.grid.dirty_count();
if dc > result.peak_dirty_rows {
result.peak_dirty_rows = dc;
}
}
ReplayEventKind::Resize { cols: c, rows: r } => {
if *c as usize != terminal.grid.cols || *r as usize != terminal.grid.rows {
let t0 = Instant::now();
terminal.resize(*c as usize, *r as usize);
result.grid_update_time_ms += t0.elapsed().as_secs_f64() * 1000.0;
}
}
ReplayEventKind::Snapshot(_) => {}
}
}
let elapsed = start.elapsed().as_secs_f64();
result.wall_time_ms = elapsed * 1000.0;
let seconds = (result.parse_time_ms / 1000.0).max(f64::EPSILON);
result.parse_throughput_mbps = (result.total_bytes as f64 / seconds) / (1024.0 * 1024.0);
result.parser_latency_avg_ms = if latency_count > 0 {
total_parser_latency / latency_count as f64
} else {
0.0
};
if result.parser_latency_min_ms == f64::MAX {
result.parser_latency_min_ms = 0.0;
}
if result.parser_latency_max_ms == f64::MIN {
result.parser_latency_max_ms = 0.0;
}
result.final_grid_hash = format::grid_hash(&terminal.grid);
result.final_cursor = (terminal.grid.cursor_row(), terminal.grid.cursor_col());
result.scrollback_lines = terminal.scrollback.len();
result.scrollback_hash = format::scrollback_hash(&terminal.scrollback);
result.alt_screen_state = terminal.in_alt_screen();
result.total_events_counted = events.len() as u64;
result
}
pub fn print_headless(r: &HeadlessResult) {
println!("=== Headless Replay Report ===");
println!(" Events: {}", r.events_processed);
println!(
" Total bytes: {} ({:.2} KB)",
r.total_bytes,
r.total_bytes as f64 / 1024.0
);
println!(" Total parse time: {:.2} ms", r.parse_time_ms);
println!(" Wall time: {:.2} ms", r.wall_time_ms);
println!(" Grid update time: {:.2} ms", r.grid_update_time_ms);
println!(" Throughput: {:.2} MB/s", r.parse_throughput_mbps);
println!(" Peak dirty rows: {}", r.peak_dirty_rows);
println!(" Grid hash: {:016x}", r.final_grid_hash);
println!(
" Final cursor: ({}, {})",
r.final_cursor.0, r.final_cursor.1
);
println!(" Scrollback lines: {}", r.scrollback_lines);
println!(" Scrollback hash: {:016x}", r.scrollback_hash);
println!(
" Parser latency (ms): min={:.4} avg={:.4} max={:.4}",
r.parser_latency_min_ms, r.parser_latency_avg_ms, r.parser_latency_max_ms
);
if r.cell_writes > 0 {
println!("\n=== Parser Profile ===");
println!(" Cell writes: {}", r.cell_writes);
println!(" SGR updates: {}", r.sgr_updates);
println!(" Line advances: {}", r.line_advances);
}
if !r.errors.is_empty() {
println!("\n=== Errors ===");
for e in &r.errors {
println!(" {}", e);
}
}
}
pub fn print_headless_machine(r: &HeadlessResult) {
println!(
"HEADLESS_RESULT events={} bytes={} parse_ms={:.4} throughput_mbps={:.4} \
peak_dirty={} grid_hash={:016x} cursor_row={} cursor_col={} \
scrollback_lines={} scrollback_hash={:016x} \
parser_latency_min_ms={:.4} parser_latency_avg_ms={:.4} parser_latency_max_ms={:.4} \
grid_update_ms={:.4} \
cell_writes={} sgr_updates={} line_advances={} wall_ms={:.4}",
r.events_processed,
r.total_bytes,
r.parse_time_ms,
r.parse_throughput_mbps,
r.peak_dirty_rows,
r.final_grid_hash,
r.final_cursor.0,
r.final_cursor.1,
r.scrollback_lines,
r.scrollback_hash,
r.parser_latency_min_ms,
r.parser_latency_avg_ms,
r.parser_latency_max_ms,
r.grid_update_time_ms,
r.cell_writes,
r.sgr_updates,
r.line_advances,
r.wall_time_ms,
);
}