#![allow(missing_docs, clippy::unwrap_used, clippy::expect_used)] use criterion::{criterion_group, criterion_main, Criterion}; use stemedb_storage::key_codec; use stemedb_storage::{HybridStore, KVStore}; use tokio::runtime::Runtime; fn sequential_put(c: &mut Criterion) { let rt = Runtime::new().expect("runtime"); let store = HybridStore::open_temp().expect("store"); c.bench_function("sequential_put_10k", |b| { b.iter(|| { rt.block_on(async { for i in 0..10_000u64 { let hash_hex = format!("bench_{}", i); let key = key_codec::assertion_key("Bench", &hash_hex); let value = format!("value_{}", i); store.put(&key, value.as_bytes()).await.unwrap(); } }) }) }); } fn random_get(c: &mut Criterion) { let rt = Runtime::new().expect("runtime"); let store = HybridStore::open_temp().expect("store"); // Pre-populate (read-heavy keys → redb via S: tag) rt.block_on(async { for i in 0..10_000u64 { let key = key_codec::subject_predicate_key("Bench", &format!("pred_{}", i)); let value = format!("value_{}", i); store.put(&key, value.as_bytes()).await.unwrap(); } }); c.bench_function("random_get_10k", |b| { b.iter(|| { rt.block_on(async { for i in 0..10_000u64 { let key = key_codec::subject_predicate_key("Bench", &format!("pred_{}", i)); let _ = store.get(&key).await.unwrap(); } }) }) }); } fn prefix_scan(c: &mut Criterion) { let rt = Runtime::new().expect("runtime"); let store = HybridStore::open_temp().expect("store"); // Pre-populate: 1K keys under "target", 9K under "other" rt.block_on(async { for i in 0..1_000u64 { let key = key_codec::subject_predicate_key("target", &format!("pred_{}", i)); store.put(&key, b"matching").await.unwrap(); } for i in 0..9_000u64 { let key = key_codec::subject_predicate_key("other", &format!("pred_{}", i)); store.put(&key, b"non_matching").await.unwrap(); } }); c.bench_function("prefix_scan_1k_of_10k", |b| { b.iter(|| { rt.block_on(async { let prefix = key_codec::subject_scan_prefix("target"); let results = store.scan_prefix(&prefix).await.unwrap(); assert_eq!(results.len(), 1_000); }) }) }); } fn atomic_increment(c: &mut Criterion) { let rt = Runtime::new().expect("runtime"); let store = HybridStore::open_temp().expect("store"); c.bench_function("atomic_increment_10k", |b| { b.iter(|| { rt.block_on(async { for i in 0..10_000u64 { let hash_hex = format!("counter_{}", i % 100); let key = key_codec::vote_count_key("Bench", &hash_hex); store.fetch_and_add_u64(&key, 1).await.unwrap(); } }) }) }); } fn mixed_workload(c: &mut Criterion) { let rt = Runtime::new().expect("runtime"); let store = HybridStore::open_temp().expect("store"); // Pre-populate read-heavy keys rt.block_on(async { for i in 0..1_000u64 { let key = key_codec::subject_predicate_key("mixed", &format!("pred_{}", i)); store.put(&key, b"initial_value").await.unwrap(); } }); c.bench_function("mixed_70r_20w_10s", |b| { b.iter(|| { rt.block_on(async { for i in 0..1_000u64 { match i % 10 { // 70% reads (redb path) 0..=6 => { let key = key_codec::subject_predicate_key( "mixed", &format!("pred_{}", i % 1000), ); let _ = store.get(&key).await.unwrap(); } // 20% writes (fjall path) 7 | 8 => { let key = key_codec::assertion_key("mixed", &format!("write_{}", i)); store.put(&key, b"new_value").await.unwrap(); } // 10% scans (redb path) _ => { let prefix = key_codec::subject_scan_prefix("mixed"); let _ = store.scan_prefix(&prefix).await.unwrap(); } } } }) }) }); } criterion_group!( benches, sequential_put, random_get, prefix_scan, atomic_increment, mixed_workload ); criterion_main!(benches);