use std::env;
use std::hint::black_box;
use std::mem;
use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
use tempfile::TempDir;
use tephra::event::{Event, EventType, Tag, Tags};
use tephra::log::set::{SegmentConfig, SegmentSet};
use tephra::query::{AppendCondition, Query, QueryItem};
use tephra::writer::{AppendError, WriteCoordinator, WriteHandle, WriterConfig};
const SEGMENT_SIZE: usize = 1 << 16; const MAX_BATCH_BYTES: usize = 1 << 14; const PAYLOAD: usize = 32;
const SIZES: [u64; 3] = [2_000, 8_000, 32_000];
const SENTINEL: &str = "sentinel:hit";
struct Store {
handle: WriteHandle,
coord: Option<WriteCoordinator>,
_dir: TempDir,
}
impl Store {
fn new(force_scan: bool, n: u64) -> 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,
condition_force_scan: force_scan,
..WriterConfig::default()
};
let (coord, handle) = WriteCoordinator::start(set, cfg).expect("start coordinator");
let payload = vec![0u8; PAYLOAD];
let per_batch = (MAX_BATCH_BYTES / (PAYLOAD + 128)).clamp(1, 200);
let mut batch: Vec<Event> = Vec::new();
for seq in 0..n {
batch.push(make_event(seq, seq == n - 1, &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, is_last: bool, payload: &[u8]) -> Event {
let mut picked = vec![Tag::new(format!("f:{}", seq % 64)).expect("valid tag")];
if is_last {
picked.push(Tag::new(SENTINEL).expect("valid tag"));
}
let tags = Tags::new(picked).expect("valid tag set");
Event::new(&event_type(), &tags, payload).expect("encode event")
}
fn sentinel_guard() -> AppendCondition {
let tag = Tag::new(SENTINEL).expect("valid tag");
AppendCondition::new(Query::item(QueryItem::with_tags(
Tags::new(vec![tag]).expect("tag set"),
)))
}
fn run_check(handle: &WriteHandle) {
let throwaway = Event::new(&event_type(), &Tags::empty(), b"x").expect("encode");
match handle.append(vec![throwaway], Some(sentinel_guard())) {
Err(AppendError::Conflict { .. }) => {}
other => panic!("expected a conflict, got {other:?}"),
}
}
fn bench_condition_check(c: &mut Criterion) {
let mut group = c.benchmark_group("condition_uniqueness_guard");
group.sample_size(20);
for &n in &SIZES {
group.throughput(Throughput::Elements(n));
let index_store = Store::new(false, n);
let scan_store = Store::new(true, n);
group.bench_with_input(BenchmarkId::new("index", n), &n, |b, _| {
b.iter(|| run_check(black_box(&index_store.handle)));
});
group.bench_with_input(BenchmarkId::new("scan", n), &n, |b, _| {
b.iter(|| run_check(black_box(&scan_store.handle)));
});
}
group.finish();
}
criterion_group!(benches, bench_condition_check);
criterion_main!(benches);