use std::fs::File;
use std::io::{BufRead, BufReader, BufWriter, Cursor, Stderr};
use std::time::Duration;
use clap::Parser as _;
use criterion::{BatchSize, Criterion, Throughput, criterion_group, criterion_main};
use ratatui::backend::TestBackend;
use ratatui::prelude::CrosstermBackend;
use skim::prelude::*;
const SMALL_ITEMS: &[&str] = &[
"src/main.rs",
"src/lib.rs",
"src/options.rs",
"src/skim.rs",
"benches/partial.rs",
"tests/common/insta.rs",
"Cargo.toml",
"README.md",
];
const FIXTURE_DEFAULT: &str = "benches/fixtures/default.txt";
const FIXTURE_100K: &str = "benches/fixtures/100K.txt";
fn load_fixture(path: &str) -> Vec<String> {
let f = File::open(path).unwrap_or_else(|e| panic!("cannot open fixture {path}: {e}"));
BufReader::new(f)
.lines()
.map_while(Result::ok)
.filter(|l| !l.is_empty())
.collect()
}
fn items_to_bytes(items: &[impl AsRef<str>]) -> Vec<u8> {
let mut buf = Vec::new();
for item in items {
buf.extend_from_slice(item.as_ref().as_bytes());
buf.push(b'\n');
}
buf
}
fn make_receiver(items: &[impl AsRef<str>], options: &SkimOptions) -> SkimItemReceiver {
let reader_opts = SkimItemReaderOption::from_options(options);
let item_reader = SkimItemReader::new(reader_opts);
item_reader.of_bufread(Cursor::new(items_to_bytes(items)))
}
fn wait_until_done(skim: &mut Skim<TestBackend>, timeout: Duration) {
let start = std::time::Instant::now();
while !skim.reader_done() {
assert!(start.elapsed() < timeout, "timeout waiting for reader");
skim.check_reader();
std::thread::sleep(Duration::from_millis(1));
}
skim.check_reader();
while !skim.app().matcher_control.stopped() {
assert!(start.elapsed() < timeout, "timeout waiting for matcher");
std::thread::sleep(Duration::from_millis(1));
}
}
fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("parse_options", |b| {
b.iter(|| SkimOptions::parse_from(Vec::<&str>::new()));
});
c.bench_function("options_from_env", |b| {
b.iter(SkimOptions::from_env);
});
c.bench_function("options_build", |b| {
b.iter_batched(
|| SkimOptions::parse_from(Vec::<&str>::new()),
|opts| opts.build(),
BatchSize::SmallInput,
);
});
c.bench_function("item_reader_option_from_options", |b| {
let opts = SkimOptions::default().build();
b.iter(|| SkimItemReaderOption::from_options(&opts));
});
c.bench_function("item_reader_new", |b| {
b.iter_batched(
|| {
let opts = SkimOptions::default().build();
SkimItemReaderOption::from_options(&opts)
},
SkimItemReader::new,
BatchSize::SmallInput,
);
});
c.bench_function("of_bufread_setup", |b| {
let opts = SkimOptions::default().build();
let data = items_to_bytes(&load_fixture(FIXTURE_DEFAULT));
b.iter_batched(
|| {
let reader_opts = SkimItemReaderOption::from_options(&opts);
(SkimItemReader::new(reader_opts), data.clone())
},
|(reader, bytes)| {
let _rx = reader.of_bufread(Cursor::new(bytes));
},
BatchSize::SmallInput,
);
});
c.bench_function("init", |b| {
b.iter_batched(
|| SkimOptions::default().build(),
|options: SkimOptions| Skim::<CrosstermBackend<BufWriter<Stderr>>>::init(options, None),
BatchSize::SmallInput,
);
});
c.bench_function("init_with_source", |b| {
b.iter_batched(
|| {
let (_tx, rx) = bounded(8);
(SkimOptions::default().build(), rx)
},
|input: (SkimOptions, SkimItemReceiver)| {
Skim::<CrosstermBackend<BufWriter<Stderr>>>::init(input.0, Some(input.1))
},
BatchSize::SmallInput,
);
});
c.bench_function("start", |b| {
b.iter_batched(
|| Skim::<CrosstermBackend<BufWriter<Stderr>>>::init(SkimOptions::default().build(), None).unwrap(),
|mut skim: Skim| skim.start(),
BatchSize::SmallInput,
);
});
c.bench_function("ingest_and_match_small", |b| {
b.iter_batched(
|| {
let opts = SkimOptions::default().build();
let rx = make_receiver(SMALL_ITEMS, &opts);
Skim::<TestBackend>::init(opts, Some(rx)).unwrap()
},
|mut skim: Skim<TestBackend>| {
skim.start();
wait_until_done(&mut skim, Duration::from_secs(5));
},
BatchSize::SmallInput,
);
});
{
let items = load_fixture(FIXTURE_DEFAULT);
let n = items.len() as u64;
let mut group = c.benchmark_group("ingest_and_match_default_fixture");
group.throughput(Throughput::Elements(n));
group.bench_function("ingest_and_match", |b| {
b.iter_batched(
|| {
let opts = SkimOptions::default().build();
let rx = make_receiver(&items, &opts);
Skim::<TestBackend>::init(opts, Some(rx)).unwrap()
},
|mut skim: Skim<TestBackend>| {
skim.start();
wait_until_done(&mut skim, Duration::from_secs(10));
},
BatchSize::SmallInput,
);
});
group.finish();
}
if std::path::Path::new(FIXTURE_100K).exists() {
let items = load_fixture(FIXTURE_100K);
let n = items.len() as u64;
let mut group = c.benchmark_group("ingest_and_match_100k_fixture");
group.throughput(Throughput::Elements(n));
group.sample_size(10);
group.bench_function("ingest_and_match", |b| {
b.iter_batched(
|| {
let opts = SkimOptions::default().build();
let rx = make_receiver(&items, &opts);
Skim::<TestBackend>::init(opts, Some(rx)).unwrap()
},
|mut skim: Skim<TestBackend>| {
skim.start();
wait_until_done(&mut skim, Duration::from_secs(30));
},
BatchSize::SmallInput,
);
});
group.finish();
}
c.bench_function("full_setup", |b| {
b.iter(|| {
let mut options = SkimOptions::default().build();
if let Some(ref filter_query) = options.filter
&& options.query.is_none()
{
options.query = Some(filter_query.clone());
}
let mut skim = Skim::init(options, None).unwrap();
skim.start();
if skim.should_enter() {
skim.init_tui().unwrap();
}
});
});
}
criterion_group!(
name = benches;
config = Criterion::default().sample_size(100);
targets = criterion_benchmark
);
criterion_main!(benches);