use std::time::{Duration, Instant};
use rusqlite::Connection;
use tempfile::TempDir;
use llm_kernel::graph::recall::smart_recall;
use llm_kernel::graph::schema::init_graph_schema;
use llm_kernel::graph::search::search_nodes;
use llm_kernel::graph::store::{append_edges, upsert_node};
use llm_kernel::graph::traversal::graph_neighbors;
use llm_kernel::graph::types::{GraphEdge, GraphNode};
const DEGREE: usize = 8;
const SCALES: &[usize] = &[10_000, 100_000, 1_000_000];
fn synth_edges(n: usize) -> Vec<GraphEdge> {
let mut out = Vec::with_capacity(n * DEGREE);
for i in 0..n {
for j in 0..DEGREE {
let tgt = (i + 1 + j * 7) % n;
out.push(GraphEdge {
id: format!("e-{i}-{j}"),
source: format!("n{i}"),
target: format!("n{tgt}"),
relation: "cites".to_string(),
weight: 1.0,
ts: "2026-01-01T00:00:00Z".to_string(),
});
}
}
out
}
fn synth_nodes(n: usize) -> Vec<GraphNode> {
(0..n)
.map(|i| GraphNode {
id: format!("n{i}"),
node_type: "concept".to_string(),
title: format!("Node {i}"),
body: format!("ownership borrowing lifetime concept {i} keyword{}", i % 16),
tags: vec![format!("band{}", i % 4)],
projects: vec![],
agents: vec![],
created: "2026-01-01T00:00:00Z".to_string(),
updated: "2026-01-01T00:00:00Z".to_string(),
importance: 0.5,
access_count: 0,
accessed_at: String::new(),
..Default::default()
})
.collect()
}
fn fresh_db() -> (TempDir, Connection) {
let dir = tempfile::Builder::new().tempdir().unwrap();
let conn = Connection::open(dir.path().join("scale.db")).unwrap();
init_graph_schema(&conn).unwrap();
conn.execute_batch("PRAGMA journal_mode = WAL; PRAGMA synchronous = NORMAL;")
.unwrap();
(dir, conn)
}
fn secs(d: Duration) -> String {
format!("{:.3}s", d.as_secs_f64())
}
fn main() {
eprintln!("axis B scale characterization (SQLite/WAL, DEGREE={DEGREE})\n");
println!(
"{:>9} {:>10} {:>11} {:>11} {:>9} {:>9} {:>10}",
"nodes", "edges", "node_insert", "edge_ingest", "search", "recall", "neighbors"
);
for &n in SCALES {
let (dir, conn) = fresh_db();
let nodes = synth_nodes(n);
let t = Instant::now();
for node in &nodes {
upsert_node(&conn, node).unwrap();
}
let node_insert = t.elapsed();
let edges = synth_edges(n);
let t = Instant::now();
append_edges(&conn, &edges).unwrap();
let edge_ingest = t.elapsed();
let t = Instant::now();
let hits = search_nodes(&conn, "ownership", 10).unwrap();
let search = t.elapsed();
let t = Instant::now();
let recalled = smart_recall(&conn, None, Some("ownership"), 10).unwrap();
let recall = t.elapsed();
let seeds = vec![format!("n{}", n / 2)];
let t = Instant::now();
let nbs = graph_neighbors(&conn, &seeds);
let neighbors = t.elapsed();
println!(
"{:>9} {:>10} {:>11} {:>11} {:>9} {:>9} {:>10}",
n,
n * DEGREE,
secs(node_insert),
secs(edge_ingest),
secs(search),
secs(recall),
secs(neighbors),
);
eprintln!(
" n={} → search hits={}, recall hits={}, neighbor seeds-expanded={}",
n,
hits.len(),
recalled.len(),
nbs.len(),
);
drop(conn);
drop(dir);
}
}