#![allow(clippy::unwrap_used)] //! Benchmarks for the session layer: signal writes, snapshot reads, //! and retrieve queries with active session context. use std::collections::HashMap; use std::time::Duration; use criterion::{Criterion, black_box, criterion_group, criterion_main}; use tidaldb::TidalDb; use tidaldb::query::retrieve::{ProfileRef, RetrieveBuilder}; use tidaldb::schema::{ AgentPolicy, DecaySpec, EntityId, EntityKind, SchemaBuilder, Timestamp, Window, }; fn session_schema() -> tidaldb::schema::Schema { let mut builder = SchemaBuilder::new(); let _ = builder .signal( "reward", EntityKind::Item, DecaySpec::Exponential { half_life: Duration::from_secs(7 * 24 * 3600), }, ) .windows(&[Window::OneHour, Window::TwentyFourHours]) .velocity(false) .add(); let _ = builder.session_policy( "bench_policy", AgentPolicy { allowed_signals: vec!["reward".to_string()], denied_signals: vec![], max_session_duration: Duration::from_secs(3600), max_signals_per_session: 1_000_000, }, ); builder.build().unwrap() } /// Benchmark: `session_signal()` write throughput. /// Target: < 200µs per call. fn bench_session_signal(c: &mut Criterion) { let db = TidalDb::builder() .ephemeral() .with_schema(session_schema()) .open() .unwrap(); let mut meta = HashMap::new(); meta.insert("title".to_string(), "bench-item".to_string()); db.write_item_with_metadata(EntityId::new(1), &meta) .unwrap(); let handle = db .start_session(1, "bench-agent", "bench_policy", HashMap::new()) .unwrap(); let entity = EntityId::new(1); let ts = Timestamp::now(); c.bench_function("session_signal", |b| { b.iter(|| { db.session_signal( black_box(&handle), black_box("reward"), black_box(entity), black_box(1.0_f64), black_box(ts), black_box(None), ) .unwrap(); }); }); } /// Benchmark: `session_snapshot()` on an active session with 100 signals. /// Target: < 50µs per call. fn bench_session_snapshot(c: &mut Criterion) { let db = TidalDb::builder() .ephemeral() .with_schema(session_schema()) .open() .unwrap(); let mut meta = HashMap::new(); meta.insert("title".to_string(), "bench-item".to_string()); db.write_item_with_metadata(EntityId::new(1), &meta) .unwrap(); let handle = db .start_session(2, "bench-agent", "bench_policy", HashMap::new()) .unwrap(); let session_id = handle.id; let entity = EntityId::new(1); let ts = Timestamp::now(); // Pre-load 100 signals. for _ in 0..100 { db.session_signal(&handle, "reward", entity, 1.0, ts, None) .unwrap(); } c.bench_function("session_snapshot_100_signals", |b| { b.iter(|| db.session_snapshot(black_box(session_id)).unwrap()); }); } /// Benchmark: `retrieve()` with FOR SESSION vs without, over 1K items. /// Measures the overhead of session context in ranking. /// Target: < 5ms overhead vs without session. fn bench_retrieve_with_session(c: &mut Criterion) { let db = TidalDb::builder() .ephemeral() .with_schema(session_schema()) .open() .unwrap(); // Write 1K items. for i in 1u64..=1000 { let mut meta = HashMap::new(); meta.insert("title".to_string(), format!("item-{i}")); db.write_item_with_metadata(EntityId::new(i), &meta) .unwrap(); } let handle = db .start_session(3, "bench-agent", "bench_policy", HashMap::new()) .unwrap(); let session_id = handle.id; let ts = Timestamp::now(); // Signal 10 entities to create a non-trivial session context. for i in 1u64..=10 { db.session_signal(&handle, "reward", EntityId::new(i), 1.0, ts, None) .unwrap(); } let query_with = RetrieveBuilder::new(EntityKind::Item, ProfileRef::new("hot")) .limit(20) .for_session(session_id) .build() .unwrap(); let query_without = RetrieveBuilder::new(EntityKind::Item, ProfileRef::new("hot")) .limit(20) .build() .unwrap(); let mut group = c.benchmark_group("retrieve_1k_items"); group.bench_function("without_session", |b| { b.iter(|| db.retrieve(black_box(&query_without)).unwrap()); }); group.bench_function("with_session", |b| { b.iter(|| db.retrieve(black_box(&query_with)).unwrap()); }); group.finish(); } criterion_group!( benches, bench_session_signal, bench_session_snapshot, bench_retrieve_with_session ); criterion_main!(benches);