use std::hint::black_box;
use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main};
use mako_engine::{
deadline::{Deadline, DeadlineStore as _, InMemoryDeadlineStore},
envelope::{EventEnvelope, NewEvent},
event_store::{EventStore, ExpectedVersion, InMemoryEventStore},
ids::{ConversationId, CorrelationId, EventId, ProcessId, StreamId, TenantId},
outbox::{InMemoryOutboxStore, OutboxMessage, OutboxStore as _},
projection::{GlobalProjectionCheckpoint, Projection, ProjectionRunner},
version::WorkflowId,
};
use tokio::runtime::Runtime;
fn make_rt() -> Runtime {
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
}
fn wid() -> WorkflowId {
WorkflowId::new("gpke-lf-anmeldung", "FV2025-10-01")
}
fn make_outbox_message() -> OutboxMessage {
OutboxMessage::new(
StreamId::new("bench/outbox-stream"),
ProcessId::new(),
TenantId::new(),
CorrelationId::new(),
ConversationId::new(),
EventId::new(),
"UTILMD",
"9900357000004",
serde_json::json!({
"message_type": "UTILMD",
"pid": 55001,
"sender": "4012345000023",
"receiver": "9900357000004"
}),
)
}
fn make_deadline(past: time::OffsetDateTime) -> Deadline {
Deadline::new(
StreamId::new("bench/dl-stream"),
ProcessId::new(),
TenantId::new(),
wid(),
"gpke-response-window",
past,
)
}
fn make_event() -> NewEvent {
NewEvent::new(
CorrelationId::new(),
None,
ConversationId::new(),
ProcessId::new(),
TenantId::new(),
wid(),
"SupplierChangeInitiated",
1,
serde_json::json!({
"pruefidentifikator": 55001,
"sender": "4012345000023",
"receiver": "9900357000004"
}),
)
}
fn seeded_outbox(rt: &Runtime, depth: usize) -> InMemoryOutboxStore {
let store = InMemoryOutboxStore::new();
let msgs: Vec<OutboxMessage> = (0..depth).map(|_| make_outbox_message()).collect();
rt.block_on(async {
store.enqueue(&msgs).await.expect("seeded enqueue");
});
store
}
fn seeded_deadlines_overdue(rt: &Runtime, count: usize) -> InMemoryDeadlineStore {
let store = InMemoryDeadlineStore::new();
let past = time::OffsetDateTime::now_utc() - time::Duration::hours(1);
rt.block_on(async {
for _ in 0..count {
store
.register(&make_deadline(past))
.await
.expect("seeded register");
}
});
store
}
fn seeded_events(rt: &Runtime, n: usize) -> (InMemoryEventStore, StreamId) {
let store = InMemoryEventStore::new();
let stream = StreamId::new("bench/projection-stream");
let events: Vec<NewEvent> = (0..n).map(|_| make_event()).collect();
rt.block_on(async {
store
.append(&stream, ExpectedVersion::NoStream, &events)
.await
.expect("seeded append");
});
(store, stream)
}
#[derive(Default)]
struct NopProjection {
event_count: u64,
last_seq: Option<u64>,
}
impl Projection for NopProjection {
fn name(&self) -> &'static str {
"bench-nop"
}
fn handle_event(&mut self, envelope: &EventEnvelope) {
self.event_count += 1;
self.last_seq = Some(envelope.sequence_number);
}
fn last_sequence(&self) -> Option<u64> {
self.last_seq
}
}
fn bench_outbox_pending_now(c: &mut Criterion) {
let rt = make_rt();
let batch_size = 50usize;
let mut group = c.benchmark_group("outbox/pending_now");
for depth in [10, 100, 500, 1_000] {
let store = seeded_outbox(&rt, depth);
group.bench_with_input(BenchmarkId::new("depth", depth), &depth, |b, _depth| {
b.to_async(&rt).iter(|| async {
let batch = store
.pending_now(black_box(batch_size))
.await
.expect("pending_now");
black_box(batch)
});
});
}
group.finish();
}
fn bench_deadline_due_now(c: &mut Criterion) {
let rt = make_rt();
let batch_size = 100usize;
let mut group = c.benchmark_group("deadline/due_now");
for count in [10, 100, 500, 1_000] {
let store = seeded_deadlines_overdue(&rt, count);
group.bench_with_input(BenchmarkId::new("overdue", count), &count, |b, _count| {
b.to_async(&rt).iter(|| async {
let result = store.due_now(black_box(batch_size)).await.expect("due_now");
black_box(result)
});
});
}
group.finish();
}
fn bench_projection_catchup(c: &mut Criterion) {
let rt = make_rt();
let mut group = c.benchmark_group("projection/catch_up_matching_streams");
for n_events in [10, 100, 500, 1_000] {
let (store, _stream) = seeded_events(&rt, n_events);
group.bench_with_input(BenchmarkId::new("events", n_events), &n_events, |b, _n| {
b.to_async(&rt).iter(|| async {
let mut proj = NopProjection::default();
let checkpoint = GlobalProjectionCheckpoint::new();
let result = ProjectionRunner::catch_up_matching_streams(
&mut proj,
&store,
None, &checkpoint, )
.await
.expect("catch_up_matching_streams");
black_box(result)
});
});
}
group.finish();
}
criterion_group!(
workers,
bench_outbox_pending_now,
bench_deadline_due_now,
bench_projection_catchup,
);
criterion_main!(workers);