src/cli/command/bench.rs

//! `ripgrab bench` — micro-benchmark the filter pipeline without Criterion.
//!
//! This is a debugging aid for profiling, not a rigorous benchmark. Use
//! `cargo bench` (see `benches/`) for real numbers.

use std::time::Instant;

use anyhow::Result;
use tokio::fs::File;
use tokio::io::{AsyncBufReadExt, BufReader};

use crate::cli::args::BenchArgs;
use crate::filter::FilterSet;

pub async fn run(args: BenchArgs) -> Result<()> {
    let lines = load_lines(&args.input).await?;
    let filter = FilterSet::compile(&["error|warn|fatal".to_string()], &[], None)?;
    let start = Instant::now();
    let mut hits = 0u64;
    for _ in 0..args.iterations {
        for line in &lines {
            if matches!(filter.apply(line), crate::filter::MatchOutcome::Plain) {
                hits += 1;
            }
        }
    }
    let elapsed = start.elapsed();
    let total = (args.iterations as u64) * lines.len() as u64;
    let per_sec = (total as f64) / elapsed.as_secs_f64();
    println!(
        "bench: {} lines across {} iterations = {} total in {:.2?} ({:.0} lines/s, {} hits)",
        lines.len(),
        args.iterations,
        total,
        elapsed,
        per_sec,
        hits,
    );
    Ok(())
}

async fn load_lines(path: &std::path::Path) -> Result<Vec<String>> {
    let file = File::open(path).await?;
    let mut reader = BufReader::new(file);
    let mut buf = String::new();
    let mut lines = Vec::new();
    loop {
        buf.clear();
        let read = reader.read_line(&mut buf).await?;
        if read == 0 {
            break;
        }
        while buf.ends_with('\n') || buf.ends_with('\r') {
            buf.pop();
        }
        lines.push(buf.clone());
    }
    Ok(lines)
}