#![allow(clippy::all)]
use criterion::{BenchmarkId, Criterion, black_box, criterion_group, criterion_main};
use qmd::Store;
use tempfile::TempDir;
const SAMPLE_ENTRIES: &[&str] = &[
"# Session Summary\n\nFixed authentication bug in login flow. The JWT token was not being refreshed properly when the session expired. Updated the middleware to check token expiry before each API call.\n\n## Files Modified\n- src/auth/middleware.rs\n- src/auth/token.rs\n- tests/auth_test.rs",
"# Session Summary\n\nRefactored database connection pooling. Replaced manual connection management with sqlx pool. Added retry logic for transient connection failures.\n\n## Key Decisions\n- Using sqlx instead of diesel for async support\n- Pool size set to 10 connections\n- Retry up to 3 times with exponential backoff",
"# Session Summary\n\nImplemented Discord bot integration. Added message handling, slash commands, and channel management. The bot uses serenity framework with gateway intents for message content.\n\n## Architecture\n- Event handler pattern for incoming messages\n- Command registry for slash commands\n- Rate limiting per user per channel",
"# Session Summary\n\nDebugged memory leak in TUI rendering. The issue was caused by accumulating terminal buffers that were never freed. Fixed by implementing proper cleanup in the render loop.\n\n## Root Cause\n- Crossterm alternate screen buffers not being dropped\n- Fixed by adding explicit flush and cleanup on exit",
"# Session Summary\n\nAdded vector search capability using qmd crate. The FTS5 engine provides BM25 ranking for full-text search. Integrated as a library dependency replacing the CLI subprocess approach.\n\n## Performance\n- Sub-millisecond search latency\n- SHA-256 content hashing for dedup\n- Background reindex on startup",
"# Session Summary\n\nOptimized context compaction algorithm. Reduced token usage by 40% through smarter summarization prompts. The compaction now preserves code snippets and error messages while condensing narrative.\n\n## Changes\n- New compaction prompt template\n- Preserve code blocks during summarization\n- Track token savings metrics",
"# Session Summary\n\nImplemented Slack integration with OAuth2 flow. Added workspace installation, event subscriptions, and interactive message components. Uses slack-morphism crate.\n\n## Features\n- Slash command /opencrabs for invoking the agent\n- Thread-based conversations\n- File upload support for code review",
"# Session Summary\n\nFixed CI pipeline failures. Updated GitHub Actions workflow to use latest Rust nightly for portable_simd. Added caching for Cargo dependencies to speed up builds.\n\n## Issues Resolved\n- nightly-2025-12 broke portable_simd API\n- Added wacore-binary patch for compatibility\n- Build time reduced from 12min to 4min",
"# Session Summary\n\nAdded WhatsApp integration via whatsapp-rust crate. Implemented QR code pairing, message routing, and media handling. Uses SQLite-backed session store for persistence.\n\n## Components\n- QR code display in terminal\n- Message handler with conversation context\n- Media download and upload support",
"# Session Summary\n\nRefactored tool system to use trait-based registry. Each tool implements the Tool trait with name, description, schema, and execute methods. Tools are registered at startup and dispatched by name.\n\n## Design\n- ToolCapability enum for permission model\n- Async execute with structured ToolResult\n- Approval flow for dangerous operations",
];
fn setup_store(n: usize) -> (Store, TempDir) {
let dir = TempDir::new().expect("tempdir");
let db = dir.path().join("bench.db");
let store = Store::open(&db).expect("store open");
let now = "2026-01-01T00:00:00";
for i in 0..n {
let body = if i < SAMPLE_ENTRIES.len() {
SAMPLE_ENTRIES[i].to_string()
} else {
format!(
"# Session {i}\n\nAutogenerated benchmark entry number {i}. \
Contains keywords like authentication, database, refactor, \
performance, and integration for search testing."
)
};
let hash = Store::hash_content(&body);
let title = Store::extract_title(&body);
let path = format!("entry-{i}.md");
store.insert_content(&hash, &body, now).unwrap();
store
.insert_document("memory", &path, &title, &hash, now, now)
.unwrap();
}
(store, dir)
}
fn bench_store_open(c: &mut Criterion) {
c.bench_function("store_open", |b| {
b.iter(|| {
let dir = TempDir::new().unwrap();
let db = dir.path().join("bench.db");
black_box(Store::open(&db).unwrap());
});
});
}
fn bench_index_file(c: &mut Criterion) {
use std::sync::atomic::{AtomicU64, Ordering};
static COUNTER: AtomicU64 = AtomicU64::new(0);
let body = SAMPLE_ENTRIES[0];
let now = "2026-01-01T00:00:00";
c.bench_function("index_file", |b| {
let dir = TempDir::new().unwrap();
let db = dir.path().join("bench.db");
let store = Store::open(&db).unwrap();
b.iter(|| {
let i = COUNTER.fetch_add(1, Ordering::Relaxed);
let body = format!("{body}\n<!-- iter {i} -->");
let hash = Store::hash_content(&body);
let title = Store::extract_title(&body);
let path = format!("bench-{i}.md");
store.insert_content(&hash, &body, now).unwrap();
black_box(
store
.insert_document("memory", &path, &title, &hash, now, now)
.unwrap(),
);
});
});
}
fn bench_hash_skip(c: &mut Criterion) {
let dir = TempDir::new().unwrap();
let db = dir.path().join("bench.db");
let store = Store::open(&db).unwrap();
let body = SAMPLE_ENTRIES[0];
let hash = Store::hash_content(body);
let title = Store::extract_title(body);
let now = "2026-01-01T00:00:00";
store.insert_content(&hash, body, now).unwrap();
store
.insert_document("memory", "existing.md", &title, &hash, now, now)
.unwrap();
c.bench_function("hash_skip", |b| {
b.iter(|| {
let hash = Store::hash_content(body);
let found = store.find_active_document("memory", "existing.md").unwrap();
black_box(match found {
Some((_id, existing_hash, _title)) if existing_hash == hash => true,
_ => false,
});
});
});
drop(store);
}
fn bench_search_fts(c: &mut Criterion) {
let mut group = c.benchmark_group("search_fts");
for &corpus_size in &[10, 50, 100, 500] {
group.bench_with_input(
BenchmarkId::from_parameter(corpus_size),
&corpus_size,
|b, &n| {
let (store, _dir) = setup_store(n);
b.iter(|| {
black_box(
store
.search_fts("\"authentication\" \"database\"", 5, Some("memory"))
.unwrap(),
);
});
},
);
}
group.finish();
}
fn bench_search_single_term(c: &mut Criterion) {
let (store, _dir) = setup_store(50);
c.bench_function("search_single_term", |b| {
b.iter(|| {
black_box(
store
.search_fts("\"authentication\"", 5, Some("memory"))
.unwrap(),
);
});
});
}
fn bench_search_all_collections(c: &mut Criterion) {
let (store, _dir) = setup_store(50);
c.bench_function("search_all_collections", |b| {
b.iter(|| {
black_box(store.search_fts("\"integration\"", 10, None).unwrap());
});
});
}
fn bench_bulk_index(c: &mut Criterion) {
let mut group = c.benchmark_group("bulk_index");
for &count in &[10, 50] {
group.bench_with_input(BenchmarkId::from_parameter(count), &count, |b, &n| {
b.iter(|| {
let dir = TempDir::new().unwrap();
let db = dir.path().join("bench.db");
let store = Store::open(&db).unwrap();
let now = "2026-01-01T00:00:00";
for i in 0..n {
let body = if i < SAMPLE_ENTRIES.len() {
SAMPLE_ENTRIES[i].to_string()
} else {
format!("# Entry {i}\n\nBenchmark content {i}.")
};
let hash = Store::hash_content(&body);
let title = Store::extract_title(&body);
let path = format!("entry-{i}.md");
store.insert_content(&hash, &body, now).unwrap();
store
.insert_document("memory", &path, &title, &hash, now, now)
.unwrap();
}
black_box(&store);
});
});
}
group.finish();
}
fn bench_deactivate(c: &mut Criterion) {
use std::sync::atomic::{AtomicU64, Ordering};
static COUNTER: AtomicU64 = AtomicU64::new(0);
c.bench_function("deactivate_document", |b| {
let dir = TempDir::new().unwrap();
let db = dir.path().join("bench.db");
let store = Store::open(&db).unwrap();
let body = SAMPLE_ENTRIES[0];
let hash = Store::hash_content(body);
let title = Store::extract_title(body);
let now = "2026-01-01T00:00:00";
store.insert_content(&hash, body, now).unwrap();
b.iter(|| {
let i = COUNTER.fetch_add(1, Ordering::Relaxed);
let path = format!("deactivate-{i}.md");
store
.insert_document("memory", &path, &title, &hash, now, now)
.unwrap();
black_box(store.deactivate_document("memory", &path).unwrap());
});
});
}
fn setup_store_with_embeddings(n: usize) -> (Store, TempDir) {
let (store, dir) = setup_store(n);
store.ensure_vector_table(768).unwrap();
let now = "2026-01-01T00:00:00";
for i in 0..n {
let body = if i < SAMPLE_ENTRIES.len() {
SAMPLE_ENTRIES[i].to_string()
} else {
format!(
"# Session {i}\n\nAutogenerated benchmark entry number {i}. \
Contains keywords like authentication, database, refactor, \
performance, and integration for search testing."
)
};
let hash = Store::hash_content(&body);
let mut emb = vec![0.0f32; 768];
emb[i % 768] = 1.0;
emb[(i * 7) % 768] = 0.5;
emb[(i * 13) % 768] = 0.3;
store
.insert_embedding(&hash, 0, 0, &emb, "bench-model", now)
.unwrap();
}
(store, dir)
}
fn bench_search_vec(c: &mut Criterion) {
let mut group = c.benchmark_group("search_vec");
for &corpus_size in &[10, 50, 100] {
group.bench_with_input(
BenchmarkId::from_parameter(corpus_size),
&corpus_size,
|b, &n| {
let (store, _dir) = setup_store_with_embeddings(n);
let mut query = vec![0.0f32; 768];
query[0] = 0.9;
query[1] = 0.5;
b.iter(|| {
black_box(store.search_vec(&query, 5, Some("memory")).unwrap());
});
},
);
}
group.finish();
}
fn bench_hybrid_rrf(c: &mut Criterion) {
use qmd::hybrid_search_rrf;
let (store, _dir) = setup_store_with_embeddings(50);
let mut query_emb = vec![0.0f32; 768];
query_emb[0] = 0.9;
query_emb[1] = 0.5;
c.bench_function("hybrid_rrf_50", |b| {
b.iter(|| {
let fts = store
.search_fts("\"authentication\" \"database\"", 10, Some("memory"))
.unwrap();
let vec = store.search_vec(&query_emb, 10, Some("memory")).unwrap();
let fts_tuples: Vec<_> = fts
.iter()
.map(|r| {
(
r.doc.path.clone(),
r.doc.path.clone(),
r.doc.title.clone(),
String::new(),
)
})
.collect();
let vec_tuples: Vec<_> = vec
.iter()
.map(|r| {
(
r.doc.path.clone(),
r.doc.path.clone(),
r.doc.title.clone(),
String::new(),
)
})
.collect();
black_box(hybrid_search_rrf(fts_tuples, vec_tuples, 60));
});
});
}
fn bench_insert_embedding(c: &mut Criterion) {
let dir = TempDir::new().unwrap();
let db = dir.path().join("bench.db");
let store = Store::open(&db).unwrap();
store.ensure_vector_table(768).unwrap();
use std::sync::atomic::{AtomicU64, Ordering};
static COUNTER: AtomicU64 = AtomicU64::new(0);
let now = "2026-01-01T00:00:00";
let emb = vec![0.1f32; 768];
c.bench_function("insert_embedding", |b| {
b.iter(|| {
let i = COUNTER.fetch_add(1, Ordering::Relaxed);
let body = format!("# Bench {i}\nContent for embedding benchmark {i}");
let hash = Store::hash_content(&body);
store.insert_content(&hash, &body, now).unwrap();
store
.insert_document("memory", &format!("emb-{i}.md"), "Bench", &hash, now, now)
.unwrap();
black_box(
store
.insert_embedding(&hash, 0, 0, &emb, "bench-model", now)
.unwrap(),
);
});
});
}
criterion_group!(
benches,
bench_store_open,
bench_index_file,
bench_hash_skip,
bench_search_fts,
bench_search_single_term,
bench_search_all_collections,
bench_bulk_index,
bench_deactivate,
bench_search_vec,
bench_hybrid_rrf,
bench_insert_embedding,
);
criterion_main!(benches);