use std::env;
use std::hint::black_box;
use std::mem;
use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
use smallvec::SmallVec;
use tempfile::TempDir;
use tephra::Position;
use tephra::event::{Event, EventType, Tag, Tags};
use tephra::log::set::{SegmentConfig, SegmentSet};
use tephra::query::{Query, QueryItem};
use tephra::read::ReadConfig;
use tephra::writer::{WriteCoordinator, WriteHandle, WriterConfig};
const N: u64 = 20_000;
const SEGMENT_SIZE: usize = 1 << 18; const MAX_BATCH_BYTES: usize = 1 << 16;
const DEFAULT_PAYLOAD: usize = 64;
const DENOMS: [u64; 4] = [1000, 100, 10, 2];
const FORCE_INDEX: u32 = 1;
const FORCE_SCAN: u32 = u32::MAX;
struct Store {
handle: WriteHandle,
coord: Option<WriteCoordinator>,
_dir: TempDir,
}
impl Store {
fn new(scan_bias: u32, payload: usize) -> Store {
let dir = match env::var_os("TEPHRA_BENCH_DIR") {
Some(base) => TempDir::new_in(base).expect("create scratch dir under TEPHRA_BENCH_DIR"),
None => TempDir::new().expect("create scratch dir"),
};
let set = SegmentSet::open(dir.path(), SegmentConfig::new(SEGMENT_SIZE))
.expect("open segment set");
let cfg = WriterConfig {
max_batch_bytes: MAX_BATCH_BYTES,
verify_tips: false,
read: ReadConfig { scan_bias },
..WriterConfig::default()
};
let (coord, handle) = WriteCoordinator::start(set, cfg).expect("start coordinator");
let payload = vec![0u8; payload];
let per_batch = (MAX_BATCH_BYTES / (payload.len() + 128)).clamp(1, 200);
let mut batch: Vec<Event> = Vec::new();
for seq in 0..N {
batch.push(make_event(seq, &payload));
if batch.len() == per_batch {
handle
.append(mem::take(&mut batch), None)
.expect("append batch");
}
}
if !batch.is_empty() {
handle.append(batch, None).expect("append tail batch");
}
Store {
handle,
coord: Some(coord),
_dir: dir,
}
}
}
impl Drop for Store {
fn drop(&mut self) {
if let Some(coord) = self.coord.take() {
coord.shutdown();
}
}
}
fn event_type() -> EventType {
EventType::new("Appended").expect("valid type")
}
fn make_event(seq: u64, payload: &[u8]) -> Event {
let picked: SmallVec<[Tag; 4]> = DENOMS
.iter()
.map(|d| Tag::new(format!("s{d}:{}", seq % d)).expect("valid tag"))
.collect();
let tags = Tags::new(picked).expect("valid tag set");
Event::new(&event_type(), &tags, payload).expect("encode event")
}
fn query_for(denom: u64) -> Query {
let tag = Tag::new(format!("s{denom}:0")).expect("valid tag");
Query::item(QueryItem::with_tags(Tags::new(vec![tag]).expect("tag set")))
}
fn run_read(handle: &WriteHandle, query: &Query, after: Position) -> u64 {
let mut reads = handle.read(query, after, None);
let mut count = 0u64;
while let Some(item) = reads.next() {
item.expect("read");
count += 1;
}
count
}
fn bench_selectivity(c: &mut Criterion) {
let mut group = c.benchmark_group("read_selectivity");
group.sample_size(20);
let index_store = Store::new(FORCE_INDEX, DEFAULT_PAYLOAD);
let scan_store = Store::new(FORCE_SCAN, DEFAULT_PAYLOAD);
for &denom in &DENOMS {
let matches = N / denom;
group.throughput(Throughput::Elements(matches));
let label = format!("1_over_{denom}");
group.bench_with_input(BenchmarkId::new("index", &label), &denom, |b, &denom| {
b.iter(|| {
black_box(run_read(
&index_store.handle,
&query_for(denom),
Position::ZERO,
))
});
});
group.bench_with_input(BenchmarkId::new("scan", &label), &denom, |b, &denom| {
b.iter(|| {
black_box(run_read(
&scan_store.handle,
&query_for(denom),
Position::ZERO,
))
});
});
}
group.finish();
}
fn bench_range_width(c: &mut Criterion) {
let mut group = c.benchmark_group("read_range_width");
group.sample_size(20);
const DENOM: u64 = 100;
let index_store = Store::new(FORCE_INDEX, DEFAULT_PAYLOAD);
let scan_store = Store::new(FORCE_SCAN, DEFAULT_PAYLOAD);
for &(label, after) in &[
("whole", 0u64),
("half", N / 2),
("recent_tenth", N * 9 / 10),
] {
let matches = (N - after) / DENOM;
group.throughput(Throughput::Elements(matches.max(1)));
let after = Position::new(after);
group.bench_with_input(BenchmarkId::new("index", label), &after, |b, &after| {
b.iter(|| black_box(run_read(&index_store.handle, &query_for(DENOM), after)));
});
group.bench_with_input(BenchmarkId::new("scan", label), &after, |b, &after| {
b.iter(|| black_box(run_read(&scan_store.handle, &query_for(DENOM), after)));
});
}
group.finish();
}
fn bench_payload_size(c: &mut Criterion) {
let mut group = c.benchmark_group("read_payload_size");
group.sample_size(20);
const DENOM: u64 = 20;
let matches = N / DENOM;
for &payload in &[64usize, 1024, 4096] {
group.throughput(Throughput::Elements(matches));
let index_store = Store::new(FORCE_INDEX, payload);
let scan_store = Store::new(FORCE_SCAN, payload);
group.bench_with_input(BenchmarkId::new("index", payload), &payload, |b, _| {
b.iter(|| {
black_box(run_read(
&index_store.handle,
&query_for(DENOM),
Position::ZERO,
))
});
});
group.bench_with_input(BenchmarkId::new("scan", payload), &payload, |b, _| {
b.iter(|| {
black_box(run_read(
&scan_store.handle,
&query_for(DENOM),
Position::ZERO,
))
});
});
}
group.finish();
}
criterion_group!(
benches,
bench_selectivity,
bench_range_width,
bench_payload_size
);
criterion_main!(benches);