use core_api::{default_max_edges, GraphDb, IngestOptions, Predicate, RuleDef};
use std::collections::BTreeMap;
use std::time::Instant;
const N_NODES: usize = 10_000;
const N_QUERY_RUNS: usize = 50;
const QUERY: &str =
"MATCH (a:Item)-[:NEAR]->(b:Item)-[:NEAR]->(c:Item) WHERE a.id = 'item-0' RETURN c.id LIMIT 5";
const FE_SCALE: usize = 5_000;
fn main() {
let db_path = std::env::temp_dir().join(format!("mushroomdb_ci_bench_{}", std::process::id()));
std::fs::create_dir_all(&db_path).expect("create bench dir");
let _guard = DirCleanup(db_path.clone());
let json = build_nodes_json(N_NODES);
let mut db = GraphDb::open(&db_path).expect("open");
let t_ingest = Instant::now();
db.ingest_json("Item", &json, &IngestOptions::default())
.expect("ingest");
let ingest_wall_s = t_ingest.elapsed().as_secs_f64();
let score_near = Predicate::NumericWithin {
field: "score".into(),
tolerance: 10.0,
};
let tag_match = Predicate::Overlap {
field: "tags".into(),
min: 0.5,
};
let t_backfill = Instant::now();
db.create_rule(RuleDef {
name: "score_near".into(),
src_label: "Item".into(),
dst_label: "Item".into(),
predicate: score_near.clone(),
edge_type: "NEAR".into(),
weight_prop: Some("score".into()),
max_edges: Some(default_max_edges(&score_near)),
approximate: false,
via_label: None,
via_edge: None,
via_dir: None,
namespace: None,
})
.expect("create score_near rule");
db.create_rule(RuleDef {
name: "tag_match".into(),
src_label: "Item".into(),
dst_label: "Item".into(),
predicate: tag_match.clone(),
edge_type: "TAG_MATCH".into(),
weight_prop: Some("score".into()),
max_edges: Some(default_max_edges(&tag_match)),
approximate: false,
via_label: None,
via_edge: None,
via_dir: None,
namespace: None,
})
.expect("create tag_match rule");
let rule_backfill_wall_s = t_backfill.elapsed().as_secs_f64();
let t_snap_write = Instant::now();
db.snapshot().expect("snapshot");
let snapshot_write_s = t_snap_write.elapsed().as_secs_f64();
drop(db);
let t_snap_open = Instant::now();
let db2 = GraphDb::open(&db_path).expect("reopen");
let snapshot_open_s = t_snap_open.elapsed().as_secs_f64();
let params = BTreeMap::new();
let mut timings_ms: Vec<f64> = (0..N_QUERY_RUNS)
.map(|_| {
let t = Instant::now();
db2.query(QUERY, ¶ms).expect("query");
t.elapsed().as_secs_f64() * 1000.0
})
.collect();
timings_ms.sort_by(|a, b| a.partial_cmp(b).unwrap());
let query_p50_ms = (timings_ms[N_QUERY_RUNS / 2 - 1] + timings_ms[N_QUERY_RUNS / 2]) / 2.0;
let fe_db_path =
std::env::temp_dir().join(format!("mushroomdb_ci_bench_fe_{}", std::process::id()));
std::fs::create_dir_all(&fe_db_path).expect("create fe bench dir");
let _guard_fe = DirCleanup(fe_db_path.clone());
let src_json = build_field_equal_json(FE_SCALE, "src");
let dst_json = build_field_equal_json(FE_SCALE, "dst");
let mut fe_db = GraphDb::open(&fe_db_path).expect("open fe");
fe_db
.ingest_json("Src", &src_json, &IngestOptions::default())
.expect("ingest src");
fe_db
.ingest_json("Dst", &dst_json, &IngestOptions::default())
.expect("ingest dst");
let t_fe = Instant::now();
fe_db
.create_rule(RuleDef {
name: "shared_group".into(),
src_label: "Src".into(),
dst_label: "Dst".into(),
predicate: Predicate::FieldEqual {
field: "group".into(),
},
edge_type: "GROUPED".into(),
weight_prop: None,
max_edges: Some(5),
approximate: false,
via_label: None,
via_edge: None,
via_dir: None,
namespace: None,
})
.expect("create field_equal rule");
let backfill_field_equal_5k_wall_s = t_fe.elapsed().as_secs_f64();
println!(
"{{\n \"ingest_wall_s\": {ingest_wall_s:.6},\n \"rule_backfill_wall_s\": {rule_backfill_wall_s:.6},\n \"snapshot_write_s\": {snapshot_write_s:.6},\n \"snapshot_open_s\": {snapshot_open_s:.6},\n \"query_p50_ms\": {query_p50_ms:.6},\n \"backfill_field_equal_5k_wall_s\": {backfill_field_equal_5k_wall_s:.6}\n}}"
);
}
struct DirCleanup(std::path::PathBuf);
impl Drop for DirCleanup {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.0);
}
}
fn build_nodes_json(n: usize) -> String {
let mut out = String::with_capacity(n * 64);
out.push('[');
for i in 0..n {
if i > 0 {
out.push(',');
}
let score = i % 100;
let tag = i % 20;
out.push_str(&format!(
r#"{{"id":"item-{i}","score":{score},"tags":["g{tag}"]}}"#
));
}
out.push(']');
out
}
fn build_field_equal_json(n: usize, prefix: &str) -> String {
let mut out = String::with_capacity(n * 48);
out.push('[');
for i in 0..n {
if i > 0 {
out.push(',');
}
out.push_str(&format!(r#"{{"id":"{prefix}-{i}","group":"g0"}}"#));
}
out.push(']');
out
}