use std::time::Instant;
use grit_core::{Budget, Grit, Options, Query, Traversal};
use uuid::Uuid;
struct Rng(u64);
impl Rng {
fn next(&mut self) -> u64 {
self.0 ^= self.0 << 13;
self.0 ^= self.0 >> 7;
self.0 ^= self.0 << 17;
self.0
}
fn below(&mut self, n: usize) -> usize {
(self.next() % n as u64) as usize
}
}
const WORDS: &[&str] = &[
"yoneda",
"functor",
"kernel",
"image",
"exact",
"sequence",
"lemma",
"sheaf",
"topos",
"adjoint",
"limit",
"colimit",
"monad",
"algebra",
"module",
"tensor",
"homology",
"cohomology",
"spectral",
"fibration",
"bundle",
"manifold",
"scheme",
"variety",
"ideal",
"ring",
"field",
"group",
"torsion",
"lattice",
];
fn percentiles(mut samples: Vec<f64>) -> (f64, f64, f64) {
samples.sort_by(f64::total_cmp);
let pick = |q: f64| samples[((samples.len() - 1) as f64 * q) as usize];
(pick(0.50), pick(0.95), *samples.last().unwrap())
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let path = std::env::args()
.nth(1)
.expect("usage: readbench <bench.db>");
let mut rng = Rng(0xDEAD_BEEF_CAFE_F00D);
let node_ids: Vec<Uuid> = {
let conn = rusqlite::Connection::open_with_flags(
&path,
rusqlite::OpenFlags::SQLITE_OPEN_READ_ONLY,
)?;
let mut stmt = conn.prepare("SELECT id FROM nodes")?;
stmt.query_map([], |r| r.get::<_, String>(0))?
.filter_map(|r| r.ok().and_then(|s| Uuid::parse_str(&s).ok()))
.collect::<Vec<_>>()
};
println!("{} nodes available as seeds", node_ids.len());
let g = Grit::open(&path, Options::new("readbench"))?;
const ITERS: usize = 200;
let mut samples = Vec::with_capacity(ITERS);
let mut reached = 0usize;
for _ in 0..ITERS {
let seed = node_ids[rng.below(node_ids.len())];
let t = Instant::now();
let sub = g.traverse(&[seed], &Traversal::default().depth(3))?;
samples.push(t.elapsed().as_secs_f64() * 1e3);
reached += sub.nodes.len();
}
let (p50, p95, max) = percentiles(samples);
println!(
"traverse depth-3: p50 {p50:7.2}ms p95 {p95:7.2}ms max {max:7.2}ms \
(avg {} nodes; target p95 ≤ 10ms)",
reached / ITERS
);
let mut samples = Vec::with_capacity(ITERS);
let mut hits_total = 0usize;
for _ in 0..ITERS {
let (w1, w2) = (WORDS[rng.below(WORDS.len())], WORDS[rng.below(WORDS.len())]);
let t = Instant::now();
let hits = g.search(Query::text(format!("{w1} {w2}")).budget(Budget::items(20)))?;
samples.push(t.elapsed().as_secs_f64() * 1e3);
hits_total += hits.len();
}
let (p50, p95, max) = percentiles(samples);
println!(
"hybrid search: p50 {p50:7.2}ms p95 {p95:7.2}ms max {max:7.2}ms \
(avg {} hits; target p95 ≤ 50ms)",
hits_total / ITERS
);
let mut samples = Vec::with_capacity(ITERS);
for _ in 0..ITERS {
let id = node_ids[rng.below(node_ids.len())];
let t = Instant::now();
g.node_history(id)?;
samples.push(t.elapsed().as_secs_f64() * 1e3);
}
let (p50, p95, max) = percentiles(samples);
println!("node_history: p50 {p50:7.2}ms p95 {p95:7.2}ms max {max:7.2}ms");
Ok(())
}