//! VT parser throughput benchmark. //! //! Measures how quickly the terminal can process raw bytes from a PTY stream. //! Methodology is modelled on Kitty's `kitten __benchmark__` (without `--render`): //! 1. Generate representative synthetic data for each scenario. //! 2. Do one warmup pass. //! 3. Run `REPETITIONS` timed passes, report MB/s and elapsed time. //! //! Run with: //! cargo run --bin bench_process --release use std::time::Instant; use winiterm::terminal::Terminal; use winiterm::vt_parser::Parser; const REPETITIONS: usize = 100; // ─── Data generators ───────────────────────────────────────────────────────── const ASCII_PRINTABLE: &[u8] = b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ `~!@#$%^&*()_+-=[]{}\\|;:'\",<.>/?"; const CONTROL_CHARS: &[u8] = b"\n\t"; /// Simple linear-congruential PRNG (deterministic, no dependency). #[inline] fn lcg(rng: &mut u64) -> u64 { *rng = rng .wrapping_mul(6_364_136_223_846_793_005) .wrapping_add(1_442_695_040_888_963_407); *rng } /// Pick a random byte from `alphabet`. fn random_byte(rng: &mut u64, alphabet: &[u8]) -> u8 { alphabet[(lcg(rng) >> 33) as usize % alphabet.len()] } /// Generate `len` random bytes from the combined ASCII + control alphabet. fn random_text(len: usize, rng: &mut u64) -> Vec { let alphabet: Vec = ASCII_PRINTABLE .iter() .chain(CONTROL_CHARS.iter()) .copied() .collect(); (0..len).map(|_| random_byte(rng, &alphabet)).collect() } // ─── Benchmark runner ───────────────────────────────────────────────────────── fn run_benchmark(name: &str, data: &[u8], repetitions: usize, setup: F) where F: Fn() -> (Terminal, Parser), { // Warmup { let (mut term, mut parser) = setup(); parser.parse(data, &mut term); } // Timed passes let start = Instant::now(); for _ in 0..repetitions { let (mut term, mut parser) = setup(); parser.parse(data, &mut term); } let elapsed = start.elapsed(); let total_bytes = data.len() * repetitions; let mb = total_bytes as f64 / 1_048_576.0; let rate = mb / elapsed.as_secs_f64(); println!( " {:<30} : {:>6.2}s {:>8.1} MB/s ({} reps × {:.2} MB)", name, elapsed.as_secs_f64(), rate, repetitions, data.len() as f64 / 1_048_576.0, ); } fn make_terminal() -> (Terminal, Parser) { (Terminal::new(80, 25, 20_000), Parser::new()) } // ─── main ───────────────────────────────────────────────────────────────────── fn main() { println!("=== winiterm VT Parser Benchmark ==="); println!("(methodology matches Kitty's kitten __benchmark__ without --render)\n"); // ── 1. Plain ASCII text ─────────────────────────────────────────────────── println!("--- Plain ASCII text ---"); { let target = 1024 * 2048 + 13; let mut rng: u64 = 0xDEAD_BEEF; let data = random_text(target, &mut rng); run_benchmark("ascii_only", &data, REPETITIONS, make_terminal); } // ── 2. CSI escape codes interleaved with text ───────────────────────────── println!("\n--- CSI codes with text ---"); { let target = 1024 * 1024 + 17; let mut rng: u64 = 0x1234_5678; let mut data: Vec = Vec::with_capacity(target + 128); while data.len() < target { match (lcg(&mut rng) >> 33) % 10 { 0 => { // Plain text burst (1–72 chars) let len = ((lcg(&mut rng) >> 33) % 72 + 1) as usize; data.extend(random_text(len, &mut rng)); } 1 | 2 => { // Cursor movement + reset data.extend_from_slice(b"\x1b[m\x1b[?1h\x1b[H"); } 3 => { // SGR: bold + italic + colours data.extend_from_slice(b"\x1b[1;3;31;42m"); } 4 => { // SGR: 256-colour foreground + background data.extend_from_slice(b"\x1b[38;5;214;48;5;236m"); } 5 => { // SGR: true-colour (RGB) foreground data.extend_from_slice(b"\x1b[38;2;255;128;0m"); } 6 | 7 => { // Cursor movement + erase data.extend_from_slice(b"\x1b[m\x1b[5A\x1b[2K\x1b[1J"); } _ => { // SGR reset + misc cursor data.extend_from_slice(b"\x1b[0m\x1b[10;20H\x1b[?25h"); } } } data.extend_from_slice(b"\x1b[m"); run_benchmark("csi_with_text", &data, REPETITIONS, make_terminal); } // ── 3. Long OSC sequences (window titles) ──────────────────────────────── println!("\n--- Long OSC sequences ---"); { let title_body: String = (0..512) .map(|i| ASCII_PRINTABLE[i % ASCII_PRINTABLE.len()] as char) .collect(); let mut data: Vec = Vec::new(); for _ in 0..2048 { data.extend_from_slice(b"\x1b]2;"); data.extend_from_slice(title_body.as_bytes()); data.push(0x07); // BEL terminator } run_benchmark("osc_long_titles", &data, REPETITIONS, make_terminal); } // ── 4. Dense colour changes (typical syntax-highlighted code output) ────── println!("\n--- Dense colour output (syntax highlighting) ---"); { let mut data: Vec = Vec::new(); let words = ["fn", "main", "let", "mut", "if", "return", "use", "pub"]; let mut rng: u64 = 0xABCD_EF01; for _ in 0..8192 { // Random foreground colour (30–45) let fg = 30 + (lcg(&mut rng) >> 33) % 16; let word = words[(lcg(&mut rng) >> 33) as usize % words.len()]; let seq = format!("\x1b[{fg}m{word} \x1b[m"); data.extend_from_slice(seq.as_bytes()); } run_benchmark("dense_color_output", &data, REPETITIONS, make_terminal); } // ── 5. Mixed: scroll + insert/delete lines ──────────────────────────────── println!("\n--- Scroll-heavy output ---"); { let mut data: Vec = Vec::new(); let mut rng: u64 = 0xFEED_FACE; for _ in 0..4096 { data.extend(random_text(60, &mut rng)); data.push(b'\n'); // Occasionally insert/delete lines if (lcg(&mut rng) >> 33) % 8 == 0 { data.extend_from_slice(b"\x1b[1L"); // insert line } } run_benchmark("scroll_heavy", &data, REPETITIONS, make_terminal); } println!("\n=== Benchmark complete ==="); println!("Note: measures CPU parse + terminal state updates only (no GPU rendering)."); println!("Compare against Kitty: kitten __benchmark__ (without --render flag)"); } // ─── Minimal `write!` support for building data vecs ───────────────────────── use std::fmt::Write as FmtWrite; struct VecWriter<'a>(&'a mut Vec); impl FmtWrite for VecWriter<'_> { fn write_str(&mut self, s: &str) -> std::fmt::Result { self.0.extend_from_slice(s.as_bytes()); Ok(()) } } // Override the `write!` macro to work with our VecWriter. trait WriteVec { fn write_fmt(&mut self, args: std::fmt::Arguments<'_>); } impl WriteVec for Vec { fn write_fmt(&mut self, args: std::fmt::Arguments<'_>) { let _ = std::fmt::write(&mut VecWriter(self), args); } }