#![allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
use std::time::{Duration, Instant};
use grafeo_common::types::Value;
use grafeo_engine::GrafeoDB;
#[cfg(debug_assertions)]
mod scale {
pub const SMALL_SCALE: usize = 100;
pub const MEDIUM_SCALE: usize = 500;
pub const LARGE_SCALE: usize = 1_000;
pub const NODE_COUNT: usize = 100;
pub const TRAVERSAL_NODES: usize = 500;
pub const FILTER_NODES: usize = 1_000;
pub const LOOKUP_NODES: usize = 1_000;
pub const CONCURRENT_NODES: usize = 1_000;
pub const PATTERN_NODES: usize = 200;
pub const MIXED_NODES: usize = 500;
pub const AGGREGATION_NODES: usize = 1_000;
pub const EDGE_NODES: usize = 100;
}
#[cfg(not(debug_assertions))]
mod scale {
pub const SMALL_SCALE: usize = 500;
pub const MEDIUM_SCALE: usize = 2_000;
pub const LARGE_SCALE: usize = 5_000;
pub const NODE_COUNT: usize = 5_000;
pub const TRAVERSAL_NODES: usize = 1_000;
pub const FILTER_NODES: usize = 2_000;
pub const LOOKUP_NODES: usize = 1_000;
pub const CONCURRENT_NODES: usize = 2_000;
pub const PATTERN_NODES: usize = 500;
pub const MIXED_NODES: usize = 1_000;
pub const AGGREGATION_NODES: usize = 2_000;
pub const EDGE_NODES: usize = 500;
}
use scale::*;
const EDGE_MULTIPLIER: usize = 10;
fn format_rate(count: usize, duration: Duration) -> String {
let rate = count as f64 / duration.as_secs_f64();
if rate >= 1_000_000.0 {
format!("{:.2}M/sec", rate / 1_000_000.0)
} else if rate >= 1_000.0 {
format!("{:.2}K/sec", rate / 1_000.0)
} else {
format!("{:.2}/sec", rate)
}
}
fn format_duration(duration: Duration) -> String {
if duration.as_secs() > 0 {
format!("{:.2}s", duration.as_secs_f64())
} else if duration.as_millis() > 0 {
format!("{:.2}ms", duration.as_secs_f64() * 1000.0)
} else {
format!("{:.2}us", duration.as_secs_f64() * 1_000_000.0)
}
}
fn print_result(name: &str, count: usize, duration: Duration) {
println!(
" {:<40} {:>10} in {:>10} ({:>12})",
name,
count,
format_duration(duration),
format_rate(count, duration)
);
}
fn print_header(section: &str) {
println!("\n{}", "=".repeat(80));
println!(" {}", section);
println!("{}", "=".repeat(80));
}
#[test]
#[ignore = "heavy benchmark: 100K+ nodes in debug mode"]
fn bench_bulk_node_insertion() {
print_header("BULK NODE INSERTION BENCHMARKS");
let db = GrafeoDB::new_in_memory();
let session = db.session();
for i in 0..1000 {
let query = format!("INSERT (:Warmup {{id: {}}})", i);
session.execute(&query).unwrap();
}
let start = Instant::now();
for i in 0..NODE_COUNT {
let query = format!("INSERT (:Person {{id: {}}})", i);
session.execute(&query).unwrap();
}
let duration = start.elapsed();
print_result("Simple nodes (1 property)", NODE_COUNT, duration);
let db2 = GrafeoDB::new_in_memory();
let session2 = db2.session();
let start = Instant::now();
for i in 0..NODE_COUNT {
let query = format!(
"INSERT (:Person {{id: {}, name: 'User{}', age: {}, active: true, score: {}}})",
i,
i,
20 + (i % 60),
(i % 1000) as f64 / 10.0
);
session2.execute(&query).unwrap();
}
let duration = start.elapsed();
print_result("Nodes with 5 properties", NODE_COUNT, duration);
let db3 = GrafeoDB::new_in_memory();
let session3 = db3.session();
let start = Instant::now();
for i in 0..NODE_COUNT {
let labels = match i % 4 {
0 => "Person",
1 => "Employee",
2 => "Customer",
_ => "Admin",
};
let query = format!("INSERT (:{} {{id: {}}})", labels, i);
session3.execute(&query).unwrap();
}
let duration = start.elapsed();
print_result("Nodes with varying labels", NODE_COUNT, duration);
}
#[test]
#[ignore = "heavy benchmark: bulk edge creation on 10K nodes"]
fn bench_bulk_edge_insertion() {
print_header("BULK EDGE INSERTION BENCHMARKS");
let db = GrafeoDB::new_in_memory();
let session = db.session();
let node_count = EDGE_NODES; let edge_count = node_count * EDGE_MULTIPLIER;
println!(" Setting up {} nodes...", node_count);
for i in 0..node_count {
let query = format!("INSERT (:Person {{id: {}}})", i);
session.execute(&query).unwrap();
}
let start = Instant::now();
for i in 0..edge_count {
let src = i % node_count;
let dst = (i * 7 + 13) % node_count; if src != dst {
let query = format!(
"MATCH (a:Person {{id: {}}}), (b:Person {{id: {}}}) INSERT (a)-[:KNOWS]->(b)",
src, dst
);
let _ = session.execute(&query); }
}
let duration = start.elapsed();
print_result("Simple edges (KNOWS)", edge_count, duration);
let db2 = GrafeoDB::new_in_memory();
let session2 = db2.session();
for i in 0..node_count {
let query = format!("INSERT (:Person {{id: {}}})", i);
session2.execute(&query).unwrap();
}
let start = Instant::now();
for i in 0..edge_count {
let src = i % node_count;
let dst = (i * 11 + 17) % node_count;
if src != dst {
let query = format!(
"MATCH (a:Person {{id: {}}}), (b:Person {{id: {}}}) INSERT (a)-[:FRIENDS {{since: {}, weight: {}}}]->(b)",
src,
dst,
2000 + (i % 24),
(i % 100) as f64 / 100.0
);
let _ = session2.execute(&query);
}
}
let duration = start.elapsed();
print_result("Edges with 2 properties", edge_count, duration);
}
#[test]
fn bench_graph_traversals() {
print_header("GRAPH TRAVERSAL BENCHMARKS");
let db = GrafeoDB::new_in_memory();
let session = db.session();
let node_count = TRAVERSAL_NODES;
let edge_count = node_count * 5;
println!(
" Setting up graph: {} nodes, ~{} edges...",
node_count, edge_count
);
for i in 0..node_count {
let query = format!(
"INSERT (:Person {{id: {}, name: 'User{}', age: {}}})",
i,
i,
20 + (i % 50)
);
session.execute(&query).unwrap();
}
for i in 0..edge_count {
let src = i % node_count;
let dst = ((i * 37) % (src.max(1))) % node_count;
if src != dst {
let query = format!(
"MATCH (a:Person {{id: {}}}), (b:Person {{id: {}}}) INSERT (a)-[:KNOWS]->(b)",
src, dst
);
let _ = session.execute(&query);
}
}
println!(" Graph setup complete. Running traversal benchmarks...\n");
let count_result = session.execute("MATCH (n:Person) RETURN COUNT(n)").unwrap();
assert_eq!(
count_result.rows().len(),
1,
"COUNT query should return one row"
);
let node_total = match &count_result.rows()[0][0] {
Value::Int64(n) => *n as usize,
other => panic!("Expected integer count, got {:?}", other),
};
assert_eq!(
node_total, node_count,
"Should have inserted exactly {} Person nodes",
node_count
);
let iterations = 1000;
let start = Instant::now();
for i in 0..iterations {
let node_id = i % node_count;
let query = format!(
"MATCH (n:Person {{id: {}}})-[:KNOWS]->(friend) RETURN friend.name",
node_id
);
let _ = session.execute(&query);
}
let duration = start.elapsed();
print_result("1-hop traversal (neighbors)", iterations, duration);
let iterations = 500;
let start = Instant::now();
for i in 0..iterations {
let node_id = i % node_count;
let query = format!(
"MATCH (n:Person {{id: {}}})-[:KNOWS]->()-[:KNOWS]->(fof) RETURN DISTINCT fof.id",
node_id
);
let _ = session.execute(&query);
}
let duration = start.elapsed();
print_result("2-hop traversal (friends of friends)", iterations, duration);
let iterations = 100;
let start = Instant::now();
for i in 0..iterations {
let node_id = i % node_count;
let query = format!(
"MATCH (n:Person {{id: {}}})-[:KNOWS]->()-[:KNOWS]->()-[:KNOWS]->(target) RETURN DISTINCT target.id",
node_id
);
let _ = session.execute(&query);
}
let duration = start.elapsed();
print_result("3-hop traversal", iterations, duration);
let iterations = 1000;
let start = Instant::now();
for i in 0..iterations {
let node_id = i % node_count;
let query = format!(
"MATCH (n:Person {{id: {}}})-[:KNOWS]-(neighbor) RETURN neighbor.id",
node_id
);
let _ = session.execute(&query);
}
let duration = start.elapsed();
print_result("1-hop bidirectional", iterations, duration);
let edge_check = session
.execute("MATCH (a:Person)-[:KNOWS]->(b:Person) RETURN COUNT(a)")
.unwrap();
assert_eq!(
edge_check.rows().len(),
1,
"Edge count query should return one row"
);
let total_edges = match &edge_check.rows()[0][0] {
Value::Int64(n) => *n as usize,
other => panic!("Expected integer edge count, got {:?}", other),
};
assert!(
total_edges > 0,
"Graph should have at least one KNOWS edge after setup"
);
}
#[test]
fn bench_filtering() {
print_header("FILTERING AND PREDICATE BENCHMARKS");
let db = GrafeoDB::new_in_memory();
let session = db.session();
let node_count = FILTER_NODES;
println!(
" Setting up {} nodes with varied properties...",
node_count
);
for i in 0..node_count {
let age = 18 + (i % 62);
let score = (i % 1000) as f64 / 10.0;
let active = i % 3 != 0;
let category = match i % 5 {
0 => "A",
1 => "B",
2 => "C",
3 => "D",
_ => "E",
};
let query = format!(
"INSERT (:User {{id: {}, age: {}, score: {}, active: {}, category: '{}'}})",
i, age, score, active, category
);
session.execute(&query).unwrap();
}
println!(" Setup complete. Running filter benchmarks...\n");
let count_result = session.execute("MATCH (u:User) RETURN COUNT(u)").unwrap();
assert_eq!(
count_result.rows().len(),
1,
"COUNT query should return one row"
);
let user_total = match &count_result.rows()[0][0] {
Value::Int64(n) => *n as usize,
other => panic!("Expected integer count, got {:?}", other),
};
assert_eq!(
user_total, node_count,
"Should have inserted exactly {} User nodes",
node_count
);
let iterations = 100;
let start = Instant::now();
for _ in 0..iterations {
let query = "MATCH (u:User) WHERE u.category = 'A' RETURN u.id";
let _ = session.execute(query);
}
let duration = start.elapsed();
print_result("Equality filter (20% selectivity)", iterations, duration);
let iterations = 100;
let start = Instant::now();
for _ in 0..iterations {
let query = "MATCH (u:User) WHERE u.age > 75 RETURN u.id";
let _ = session.execute(query);
}
let duration = start.elapsed();
print_result("Range filter (2% selectivity)", iterations, duration);
let iterations = 100;
let start = Instant::now();
for _ in 0..iterations {
let query = "MATCH (u:User) WHERE u.age > 48 RETURN u.id";
let _ = session.execute(query);
}
let duration = start.elapsed();
print_result("Range filter (50% selectivity)", iterations, duration);
let iterations = 100;
let start = Instant::now();
for _ in 0..iterations {
let query = "MATCH (u:User) WHERE u.active = true RETURN u.id";
let _ = session.execute(query);
}
let duration = start.elapsed();
print_result("Boolean filter (67% selectivity)", iterations, duration);
let iterations = 100;
let start = Instant::now();
for _ in 0..iterations {
let query = "MATCH (u:User) WHERE u.age > 30 AND u.active = true RETURN u.id";
let _ = session.execute(query);
}
let duration = start.elapsed();
print_result("Compound filter (AND)", iterations, duration);
let iterations = 100;
let start = Instant::now();
for _ in 0..iterations {
let query = "MATCH (u:User) WHERE u.age < 25 OR u.age > 70 RETURN u.id";
let _ = session.execute(query);
}
let duration = start.elapsed();
print_result("Compound filter (OR)", iterations, duration);
let check = session
.execute("MATCH (u:User) WHERE u.category = 'A' RETURN u.id")
.unwrap();
let expected_category_a = node_count / 5; assert_eq!(
check.rows().len(),
expected_category_a,
"Category 'A' filter should return exactly 1/5 of nodes"
);
}
#[test]
#[ignore = "heavy benchmark: aggregations over 50K nodes"]
fn bench_aggregations() {
print_header("AGGREGATION BENCHMARKS");
let db = GrafeoDB::new_in_memory();
let session = db.session();
let node_count = AGGREGATION_NODES;
println!(" Setting up {} nodes...", node_count);
for i in 0..node_count {
let department = match i % 10 {
0 => "Engineering",
1 => "Sales",
2 => "Marketing",
3 => "Support",
4 => "HR",
5 => "Finance",
6 => "Legal",
7 => "Operations",
8 => "Research",
_ => "Other",
};
let salary = 50000 + (i % 100) * 1000;
let query = format!(
"INSERT (:Employee {{id: {}, department: '{}', salary: {}, years: {}}})",
i,
department,
salary,
i % 30
);
session.execute(&query).unwrap();
}
println!(" Setup complete. Running aggregation benchmarks...\n");
let iterations = 100;
let start = Instant::now();
for _ in 0..iterations {
let query = "MATCH (e:Employee) RETURN COUNT(e)";
let _ = session.execute(query);
}
let duration = start.elapsed();
print_result("COUNT(*)", iterations, duration);
let iterations = 100;
let start = Instant::now();
for _ in 0..iterations {
let query = "MATCH (e:Employee) WHERE e.salary > 100000 RETURN COUNT(e)";
let _ = session.execute(query);
}
let duration = start.elapsed();
print_result("COUNT with filter", iterations, duration);
let iterations = 50;
let start = Instant::now();
for _ in 0..iterations {
let query = "MATCH (e:Employee) RETURN e.department, COUNT(e)";
let _ = session.execute(query);
}
let duration = start.elapsed();
print_result("GROUP BY department", iterations, duration);
let iterations = 50;
let start = Instant::now();
for _ in 0..iterations {
let query = "MATCH (e:Employee) RETURN SUM(e.salary)";
let _ = session.execute(query);
}
let duration = start.elapsed();
print_result("SUM(salary)", iterations, duration);
let iterations = 50;
let start = Instant::now();
for _ in 0..iterations {
let query = "MATCH (e:Employee) RETURN AVG(e.salary)";
let _ = session.execute(query);
}
let duration = start.elapsed();
print_result("AVG(salary)", iterations, duration);
let iterations = 50;
let start = Instant::now();
for _ in 0..iterations {
let query = "MATCH (e:Employee) RETURN MIN(e.salary), MAX(e.salary)";
let _ = session.execute(query);
}
let duration = start.elapsed();
print_result("MIN/MAX", iterations, duration);
}
#[test]
fn bench_point_lookups() {
print_header("POINT LOOKUP BENCHMARKS");
let db = GrafeoDB::new_in_memory();
let session = db.session();
let node_count = LOOKUP_NODES;
println!(" Setting up {} nodes...", node_count);
for i in 0..node_count {
let query = format!(
"INSERT (:Record {{id: {}, uuid: 'uuid-{}', data: 'data-{}'}})",
i, i, i
);
session.execute(&query).unwrap();
}
println!(" Setup complete. Running lookup benchmarks...\n");
let count_result = session.execute("MATCH (r:Record) RETURN COUNT(r)").unwrap();
assert_eq!(
count_result.rows().len(),
1,
"COUNT query should return one row"
);
let record_total = match &count_result.rows()[0][0] {
Value::Int64(n) => *n as usize,
other => panic!("Expected integer count, got {:?}", other),
};
assert_eq!(
record_total, node_count,
"Should have inserted exactly {} Record nodes",
node_count
);
let iterations = 10_000;
let start = Instant::now();
for i in 0..iterations {
let query = format!("MATCH (r:Record {{id: {}}}) RETURN r", i);
let _ = session.execute(&query);
}
let duration = start.elapsed();
print_result("ID lookup (sequential)", iterations, duration);
let iterations = 10_000;
let start = Instant::now();
for i in 0..iterations {
let id = (i * 7919) % node_count; let query = format!("MATCH (r:Record {{id: {}}}) RETURN r", id);
let _ = session.execute(&query);
}
let duration = start.elapsed();
print_result("ID lookup (random access)", iterations, duration);
let iterations = 1_000;
let start = Instant::now();
for i in 0..iterations {
let query = format!("MATCH (r:Record {{uuid: 'uuid-{}'}}) RETURN r", i);
let _ = session.execute(&query);
}
let duration = start.elapsed();
print_result("String property lookup", iterations, duration);
let iterations = 10_000;
let start = Instant::now();
for i in 0..iterations {
let id = (i * 7919) % node_count;
let query = format!("MATCH (r:Record {{id: {}}}) RETURN COUNT(r)", id);
let _ = session.execute(&query);
}
let duration = start.elapsed();
print_result("Existence check", iterations, duration);
let check = session
.execute("MATCH (r:Record {id: 0}) RETURN r.uuid")
.unwrap();
assert_eq!(
check.rows().len(),
1,
"Point lookup for id=0 should return exactly one row"
);
assert_eq!(
check.rows()[0][0],
Value::String("uuid-0".into()),
"Record 0 should have uuid 'uuid-0'"
);
}
#[test]
#[ignore = "heavy benchmark: dense graph pattern matching"]
fn bench_pattern_matching() {
print_header("PATTERN MATCHING BENCHMARKS");
let db = GrafeoDB::new_in_memory();
let session = db.session();
let node_count = PATTERN_NODES;
let edge_count = node_count * 8;
println!(
" Setting up graph: {} nodes, ~{} edges...",
node_count, edge_count
);
for i in 0..node_count {
let query = format!("INSERT (:TestNode {{id: {}}})", i);
session.execute(&query).unwrap();
}
for i in 0..edge_count {
let src = i % node_count;
let dst = (src + 1 + (i % 10)) % node_count;
if src != dst {
let query = format!(
"MATCH (a:TestNode {{id: {}}}), (b:TestNode {{id: {}}}) INSERT (a)-[:LINK]->(b)",
src, dst
);
let _ = session.execute(&query);
}
}
println!(" Setup complete. Running pattern matching benchmarks...\n");
let iterations = 10;
let start = Instant::now();
for i in 0..iterations {
let query = format!(
"MATCH (a:TestNode {{id: {}}})-[:LINK]->(b)-[:LINK]->(c)-[:LINK]->(a) RETURN a.id, b.id, c.id LIMIT 100",
i
);
let _ = session.execute(&query);
}
let duration = start.elapsed();
print_result("Triangle detection", iterations, duration);
let iterations = 50;
let start = Instant::now();
for i in 0..iterations {
let query = format!(
"MATCH (a:TestNode {{id: {}}})-[:LINK]->(b)-[:LINK]->(c)-[:LINK]->(d) RETURN d.id LIMIT 100",
i % node_count
);
let _ = session.execute(&query);
}
let duration = start.elapsed();
print_result("4-node path pattern", iterations, duration);
let iterations = 100;
let start = Instant::now();
for i in 0..iterations {
let query = format!(
"MATCH (hub:TestNode {{id: {}}})-[:LINK]->(n1), (hub)-[:LINK]->(n2) WHERE n1.id <> n2.id RETURN n1.id, n2.id LIMIT 50",
i % node_count
);
let _ = session.execute(&query);
}
let duration = start.elapsed();
print_result("Star pattern (2 branches)", iterations, duration);
}
#[test]
#[ignore = "heavy benchmark: 10K mixed OLTP operations"]
fn bench_mixed_workload() {
print_header("MIXED WORKLOAD BENCHMARK (OLTP-like)");
let db = GrafeoDB::new_in_memory();
let session = db.session();
let initial_nodes = MIXED_NODES;
println!(" Setting up {} initial nodes...", initial_nodes);
for i in 0..initial_nodes {
let query = format!(
"INSERT (:Account {{id: {}, balance: {}}})",
i,
1000 + (i % 10000)
);
session.execute(&query).unwrap();
}
for i in 0..initial_nodes * 2 {
let src = i % initial_nodes;
let dst = (i * 7 + 3) % initial_nodes;
if src != dst {
let query = format!(
"MATCH (a:Account {{id: {}}}), (b:Account {{id: {}}}) INSERT (a)-[:TRANSFER {{amount: {}}}]->(b)",
src,
dst,
(i % 500) + 10
);
let _ = session.execute(&query);
}
}
println!(" Setup complete. Running mixed workload...\n");
let total_ops = 10_000;
let mut reads = 0usize;
let mut writes = 0usize;
let mut node_counter = initial_nodes;
let start = Instant::now();
for i in 0..total_ops {
match i % 10 {
0..=2 => {
let query = format!(
"INSERT (:Account {{id: {}, balance: {}}})",
node_counter,
1000 + (i % 5000)
);
let _ = session.execute(&query);
node_counter += 1;
writes += 1;
}
3..=4 => {
let id = i % initial_nodes;
let query = format!(
"MATCH (a:Account {{id: {}}}) SET a.balance = {}",
id,
2000 + (i % 8000)
);
let _ = session.execute(&query);
writes += 1;
}
_ => {
match i % 5 {
0 => {
let id = (i * 7919) % initial_nodes;
let query = format!("MATCH (a:Account {{id: {}}}) RETURN a.balance", id);
let _ = session.execute(&query);
}
1 => {
let query = "MATCH (a:Account) WHERE a.balance > 5000 RETURN COUNT(a)";
let _ = session.execute(query);
}
2 => {
let id = i % initial_nodes;
let query = format!(
"MATCH (a:Account {{id: {}}})-[:TRANSFER]->(b) RETURN b.id",
id
);
let _ = session.execute(&query);
}
3 => {
let query = "MATCH (a:Account) RETURN AVG(a.balance)";
let _ = session.execute(query);
}
_ => {
let id = i % initial_nodes;
let query = format!(
"MATCH (a:Account {{id: {}}})-[:TRANSFER]->()-[:TRANSFER]->(c) RETURN DISTINCT c.id LIMIT 10",
id
);
let _ = session.execute(&query);
}
}
reads += 1;
}
}
}
let duration = start.elapsed();
println!(" Total operations: {}", total_ops);
println!(" - Reads: {} ({}%)", reads, reads * 100 / total_ops);
println!(" - Writes: {} ({}%)", writes, writes * 100 / total_ops);
print_result("Mixed workload throughput", total_ops, duration);
}
#[test]
fn bench_concurrent_reads() {
use std::sync::Arc;
use std::thread;
print_header("CONCURRENT READ BENCHMARKS");
let db = Arc::new(GrafeoDB::new_in_memory());
let node_count = CONCURRENT_NODES;
println!(" Setting up {} nodes...", node_count);
{
let session = db.session();
for i in 0..node_count {
let query = format!("INSERT (:Item {{id: {}, value: {}}})", i, i * 10);
session.execute(&query).unwrap();
}
}
println!(" Setup complete. Running concurrent read benchmarks...\n");
{
let session = db.session();
let count_result = session
.execute("MATCH (item:Item) RETURN COUNT(item)")
.unwrap();
assert_eq!(
count_result.rows().len(),
1,
"COUNT query should return one row"
);
let item_total = match &count_result.rows()[0][0] {
Value::Int64(n) => *n as usize,
other => panic!("Expected integer count, got {:?}", other),
};
assert_eq!(
item_total, node_count,
"Should have inserted exactly {} Item nodes",
node_count
);
}
for num_threads in [1, 2, 4, 8] {
let ops_per_thread = 5_000;
let total_ops = ops_per_thread * num_threads;
let start = Instant::now();
let handles: Vec<_> = (0..num_threads)
.map(|t| {
let db = Arc::clone(&db);
thread::spawn(move || {
let session = db.session();
for i in 0..ops_per_thread {
let id = (t * 1000 + i) % node_count;
let query = format!("MATCH (item:Item {{id: {}}}) RETURN item.value", id);
let _ = session.execute(&query);
}
})
})
.collect();
for handle in handles {
handle.join().unwrap();
}
let duration = start.elapsed();
print_result(
&format!("Concurrent reads ({} threads)", num_threads),
total_ops,
duration,
);
}
}
#[test]
fn bench_summary() {
print_header("GRAPHOS BENCHMARK SUMMARY");
println!("\n Configuration:");
println!(" - Small scale: {} nodes", SMALL_SCALE);
println!(" - Medium scale: {} nodes", MEDIUM_SCALE);
println!(" - Large scale: {} nodes", LARGE_SCALE);
println!(" - Edge multiplier: {}x", EDGE_MULTIPLIER);
println!("\n Run individual benchmarks with:");
println!(" cargo test -p grafeo-engine --release -- bench_ --nocapture");
println!("\n Run fast benchmarks only (default):");
println!(" cargo test -p grafeo-engine --release -- graph_benchmarks --nocapture");
println!("\n Run ALL benchmarks including heavy ones:");
println!(
" cargo test -p grafeo-engine --release -- graph_benchmarks --nocapture --include-ignored"
);
}