use std::hint::black_box;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Barrier};
use std::thread;
use std::time::{Duration, Instant};
use criterion::{
BenchmarkId, Criterion, SamplingMode, 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::{WriteCoordinator, WriteHandle, WriterConfig};
const SEGMENT_SIZE: usize = 1024 * 1024 * 1024;
const DEFAULT_PAYLOAD: usize = 128;
struct Harness {
handle: WriteHandle,
coord: Option<WriteCoordinator>,
_dir: TempDir,
}
impl Harness {
fn new() -> Harness {
let dir = match std::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 {
verify_tips: false,
..WriterConfig::default()
};
let (coord, handle) = WriteCoordinator::start(set, cfg).expect("start coordinator");
Harness {
handle,
coord: Some(coord),
_dir: dir,
}
}
}
impl Drop for Harness {
fn drop(&mut self) {
if let Some(coord) = self.coord.take() {
coord.shutdown();
}
}
}
static SEQ: AtomicU64 = AtomicU64::new(0);
fn next_seq() -> u64 {
SEQ.fetch_add(1, Ordering::Relaxed)
}
fn event_type() -> EventType {
EventType::new("Appended").expect("valid type")
}
fn make_event(seq: u64, payload: &[u8]) -> Event {
let tags = Tags::new(vec![
Tag::new(format!("entity:{}", seq & 0xFFF)).expect("valid tag"),
])
.expect("valid tag set");
Event::new(&event_type(), &tags, payload).expect("encode event")
}
fn bench_append_latency(c: &mut Criterion) {
let mut group = c.benchmark_group("append_latency");
group.throughput(Throughput::Elements(1));
group.sample_size(50);
group.measurement_time(Duration::from_secs(10));
let harness = Harness::new();
let payload = vec![0u8; DEFAULT_PAYLOAD];
group.bench_function("single_event", |b| {
b.iter(|| {
let ev = make_event(next_seq(), &payload);
let range = harness.handle.append(vec![ev], None).expect("append");
black_box(range);
});
});
group.finish();
}
fn bench_batch_size(c: &mut Criterion) {
let mut group = c.benchmark_group("batch_size");
group.sample_size(50);
group.measurement_time(Duration::from_secs(10));
let harness = Harness::new();
let payload = vec![0u8; DEFAULT_PAYLOAD];
for &batch in &[1usize, 8, 64, 512] {
group.throughput(Throughput::Elements(batch as u64));
let template = make_event(next_seq(), &payload);
group.bench_with_input(BenchmarkId::from_parameter(batch), &batch, |b, &batch| {
b.iter(|| {
let events = vec![template.clone(); batch];
let range = harness.handle.append(events, None).expect("append");
black_box(range);
});
});
}
group.finish();
}
fn bench_payload_size(c: &mut Criterion) {
let mut group = c.benchmark_group("payload_size");
group.sample_size(50);
group.measurement_time(Duration::from_secs(10));
for &size in &[64usize, 256, 1024, 4096, 16384] {
group.throughput(Throughput::Bytes(size as u64));
let harness = Harness::new();
let payload = vec![0u8; size];
group.bench_with_input(BenchmarkId::from_parameter(size), &size, |b, _| {
b.iter(|| {
let ev = make_event(next_seq(), &payload);
let range = harness.handle.append(vec![ev], None).expect("append");
black_box(range);
});
});
}
group.finish();
}
fn bench_group_commit(c: &mut Criterion) {
let mut group = c.benchmark_group("group_commit");
group.sampling_mode(SamplingMode::Flat);
group.sample_size(20);
group.measurement_time(Duration::from_secs(15));
const CHUNK: u64 = 256;
let payload = Arc::new(vec![0u8; DEFAULT_PAYLOAD]);
for &threads in &[1usize, 2, 4, 8, 16] {
group.throughput(Throughput::Elements(threads as u64 * CHUNK));
let harness = Harness::new();
group.bench_with_input(
BenchmarkId::from_parameter(threads),
&threads,
|b, &threads| {
b.iter_custom(|iters| {
let mut elapsed = Duration::ZERO;
for _ in 0..iters {
let barrier = Arc::new(Barrier::new(threads));
let mut workers = Vec::with_capacity(threads);
for _ in 0..threads {
let handle = harness.handle.clone();
let barrier = barrier.clone();
let payload = payload.clone();
workers.push(thread::spawn(move || {
barrier.wait();
let start = Instant::now();
for _ in 0..CHUNK {
let ev = make_event(next_seq(), &payload);
handle.append(vec![ev], None).expect("append");
}
start.elapsed()
}));
}
let mut slowest = Duration::ZERO;
for w in workers {
slowest = slowest.max(w.join().expect("writer thread"));
}
elapsed += slowest;
}
elapsed
});
},
);
}
group.finish();
}
fn bench_conditional_append(c: &mut Criterion) {
let mut group = c.benchmark_group("conditional_append");
group.throughput(Throughput::Elements(1));
group.sample_size(50);
group.measurement_time(Duration::from_secs(10));
let payload = vec![0u8; DEFAULT_PAYLOAD];
let harness = Harness::new();
group.bench_function("unconditional", |b| {
b.iter(|| {
let ev = make_event(next_seq(), &payload);
let range = harness.handle.append(vec![ev], None).expect("append");
black_box(range);
});
});
drop(harness);
let harness = Harness::new();
group.bench_function("unique_guard", |b| {
b.iter(|| {
let n = next_seq();
let tag = Tag::new(format!("unique:{n}")).expect("valid tag");
let tags = Tags::new(vec![tag]).expect("valid tag set");
let ev = Event::new(&event_type(), &tags, &payload).expect("encode event");
let condition = AppendCondition::new(Query::item(QueryItem::with_tags(tags.clone())));
let range = harness
.handle
.append(vec![ev], Some(condition))
.expect("append");
black_box(range);
});
});
group.finish();
}
criterion_group!(
benches,
bench_append_latency,
bench_batch_size,
bench_payload_size,
bench_group_commit,
bench_conditional_append,
);
criterion_main!(benches);