use motedb::{DBConfig, Database};
use std::time::Instant;
use tempfile::TempDir;
fn rss_kb() -> usize {
#[cfg(target_os = "macos")]
{
let pid = std::process::id();
let out = std::process::Command::new("ps")
.args(["-o", "rss", "-p", &pid.to_string()])
.output()
.ok();
if let Some(o) = out {
let s = String::from_utf8_lossy(&o.stdout);
for line in s.lines().skip(1) {
if let Ok(v) = line.trim().parse::<usize>() {
return v;
}
}
}
0
}
#[cfg(not(target_os = "macos"))]
{
0
}
}
fn mb(kb: usize) -> f64 {
kb as f64 / 1024.0
}
fn fmt_us(us: u64) -> String {
if us < 1_000 {
format!("{}µs", us)
} else if us < 1_000_000 {
format!("{:.2}ms", us as f64 / 1000.0)
} else {
format!("{:.2}s", us as f64 / 1_000_000.0)
}
}
#[allow(dead_code)]
fn fmt_dur(d: std::time::Duration) -> String {
fmt_us(d.as_micros() as u64)
}
fn dir_size(path: &std::path::Path) -> u64 {
let mut total = 0;
if let Ok(entries) = std::fs::read_dir(path) {
for e in entries.flatten() {
let p = e.path();
if p.is_dir() {
total += dir_size(&p);
} else if let Ok(m) = e.metadata() {
total += m.len();
}
}
}
total
}
fn mote_dir(dir: &TempDir) -> std::path::PathBuf {
let p = dir.path();
let parent = p.parent().unwrap_or(std::path::Path::new("/tmp"));
let name = p
.file_name()
.map(|s| s.to_string_lossy().into_owned())
.unwrap_or_default();
parent.join(format!("{}.mote", name))
}
fn latency_stats(us: &[u64]) -> (u64, u64, u64, u64) {
if us.is_empty() {
return (0, 0, 0, 0);
}
let mut s = us.to_vec();
s.sort_unstable();
let n = s.len();
(
s[n / 2], s[n * 95 / 100], s[n * 99 / 100], s[n - 1], )
}
struct Results {
rows: usize,
insert_ms: u128,
insert_rss_mb: f64,
insert_throughput: f64,
disk_mb: f64,
bytes_per_row: f64,
count_all_us: u64,
count_where_us: u64,
like_prefix_us: u64,
agg_multi_us: u64,
group_by_us: u64,
order_by_us: u64,
point_query_p50_us: u64,
point_query_p95_us: u64,
point_query_p99_us: u64,
update_pk_us: u64,
delete_pk_us: u64,
steady_rss_mb: f64,
peak_rss_mb: f64,
}
impl Results {
fn print_table_row(&self) {
println!(
" {:>8} | {:>7.1} | {:>10.0} | {:>6.1} | {:>6.1} | {:>6.1} | {:>6.1} | {:>6.1} | {:>7.1} | {:>7.1}",
self.rows as f64 / 1000.0,
self.insert_ms as f64 / 1000.0,
self.insert_throughput,
self.disk_mb,
self.bytes_per_row,
self.count_where_us as f64 / 1000.0,
self.like_prefix_us as f64 / 1000.0,
self.agg_multi_us as f64 / 1000.0,
self.group_by_us as f64 / 1000.0,
self.order_by_us as f64 / 1000.0,
);
}
}
fn bench_scale(n: usize) -> Results {
let dir = TempDir::new().unwrap();
let mote = mote_dir(&dir);
let db = Database::create_with_config(dir.path(), DBConfig::for_edge()).unwrap();
let _rss_base = rss_kb();
db.execute(
"CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT, region TEXT, score FLOAT, qty INT)",
)
.unwrap();
let t0 = Instant::now();
for i in 1..=n as i64 {
let region = ["north", "south", "east", "west", "central"][(i % 5) as usize];
db.execute(&format!(
"INSERT INTO t VALUES ({}, 'item_{}', '{}', {:.2}, {})",
i,
i,
region,
(i as f64) * 1.7,
i % 1000,
))
.unwrap();
}
let insert_ms = t0.elapsed().as_millis();
let insert_rss = rss_kb();
let insert_throughput = n as f64 / (insert_ms as f64 / 1000.0);
db.checkpoint().ok();
std::thread::sleep(std::time::Duration::from_millis(200));
let disk = dir_size(&mote);
let steady_rss = rss_kb();
let _ = db.execute("SELECT COUNT(*) FROM t");
let _ = db.execute("SELECT COUNT(*) FROM t WHERE region = 'north'");
let iters = 20;
let _ = db.execute("SELECT COUNT(*) FROM t");
let t = Instant::now();
for _ in 0..iters {
let _ = db.execute("SELECT COUNT(*) FROM t");
}
let count_all_us = t.elapsed().as_micros() as u64 / iters;
let _ = db.execute("SELECT COUNT(*) FROM t WHERE region = 'north'");
let t = Instant::now();
for _ in 0..iters {
let _ = db.execute("SELECT COUNT(*) FROM t WHERE region = 'north'");
}
let count_where_us = t.elapsed().as_micros() as u64 / iters;
let _ = db.execute("SELECT COUNT(*) FROM t WHERE name LIKE 'item_1%'");
let t = Instant::now();
for _ in 0..iters {
let _ = db.execute("SELECT COUNT(*) FROM t WHERE name LIKE 'item_1%'");
}
let like_prefix_us = t.elapsed().as_micros() as u64 / iters;
let sql_agg = "SELECT SUM(qty), AVG(score), MIN(score), MAX(score), COUNT(*) FROM t";
let _ = db.execute(sql_agg);
let t = Instant::now();
for _ in 0..10 {
let _ = db.execute(sql_agg);
}
let agg_multi_us = t.elapsed().as_micros() as u64 / 10;
let sql_gb = "SELECT region, COUNT(*), SUM(qty), AVG(score) FROM t GROUP BY region";
let _ = db.execute(sql_gb);
let t = Instant::now();
for _ in 0..10 {
let _ = db.execute(sql_gb);
}
let group_by_us = t.elapsed().as_micros() as u64 / 10;
let sql_ob = "SELECT id FROM t ORDER BY score DESC LIMIT 10";
let _ = db.execute(sql_ob);
let t = Instant::now();
for _ in 0..iters {
let _ = db.execute(sql_ob);
}
let order_by_us = t.elapsed().as_micros() as u64 / iters;
let mut latencies = Vec::with_capacity(200);
for i in 0..200 {
let pid = (n as i64 / 2 + i) % n as i64;
let t = Instant::now();
let _ = db.execute(&format!("SELECT * FROM t WHERE id = {}", pid));
latencies.push(t.elapsed().as_micros() as u64);
}
let (pq_p50, pq_p95, pq_p99, _) = latency_stats(&latencies);
let peak_rss = rss_kb();
let update_target = n as i64 / 2;
let _ = db.execute(&format!(
"UPDATE t SET qty = 999 WHERE id = {}",
update_target
));
let t = Instant::now();
for i in 0..50 {
let _ = db.execute(&format!(
"UPDATE t SET qty = {} WHERE id = {}",
i,
update_target + i
));
}
let update_pk_us = t.elapsed().as_micros() as u64 / 50;
let del_target = n as i64 / 3;
let t = Instant::now();
let _ = db.execute(&format!("DELETE FROM t WHERE id = {}", del_target));
let delete_pk_us = t.elapsed().as_micros() as u64;
let _ = db.execute(&format!(
"INSERT INTO t VALUES ({}, 'item_{}', 'north', 42.0, 100)",
del_target, del_target
));
let final_rss = rss_kb();
Results {
rows: n,
insert_ms,
insert_rss_mb: mb(insert_rss),
insert_throughput,
disk_mb: disk as f64 / 1_048_576.0,
bytes_per_row: disk as f64 / n as f64,
count_all_us,
count_where_us,
like_prefix_us,
agg_multi_us,
group_by_us,
order_by_us,
point_query_p50_us: pq_p50,
point_query_p95_us: pq_p95,
point_query_p99_us: pq_p99,
update_pk_us,
delete_pk_us,
steady_rss_mb: mb(steady_rss),
peak_rss_mb: mb(peak_rss.max(final_rss)),
}
}
#[test]
#[ignore = "release benchmark: run with --ignored"]
fn bench_release_report() {
println!(
"\n{}",
"╔══════════════════════════════════════════════════════════════════════════╗"
);
println!("║ MoteDB v0.5.2 — Comprehensive Performance Report ║");
println!("║ Release Build · for_edge() config · macOS arm64 ║");
println!("╚══════════════════════════════════════════════════════════════════════════╝");
let scales = [100_000, 500_000, 1_000_000, 2_000_000];
let mut all_results = Vec::new();
for &n in &scales {
println!("\n{}", "─".repeat(80));
println!(" Scale: {}K rows", n / 1000);
println!("{}", "─".repeat(80));
let r = bench_scale(n);
println!(
" INSERT: {:>8.2}s ({:>8.0} rows/sec)",
r.insert_ms as f64 / 1000.0,
r.insert_throughput
);
println!(" RSS after INSERT: {:>8.1} MB", r.insert_rss_mb);
println!(
" Disk: {:>8.1} MB ({:.1} bytes/row)",
r.disk_mb, r.bytes_per_row
);
println!(" Steady RSS: {:>8.1} MB", r.steady_rss_mb);
println!(" Peak RSS: {:>8.1} MB", r.peak_rss_mb);
println!();
println!(" ── Query Latency (per query) ──");
println!(" COUNT(*): {}", fmt_us(r.count_all_us));
println!(" COUNT(*) WHERE eq: {}", fmt_us(r.count_where_us));
println!(" LIKE prefix: {}", fmt_us(r.like_prefix_us));
println!(" SUM/AVG/MIN/MAX: {}", fmt_us(r.agg_multi_us));
println!(" GROUP BY (5 groups): {}", fmt_us(r.group_by_us));
println!(" ORDER BY LIMIT 10: {}", fmt_us(r.order_by_us));
println!();
println!(" ── Point Query (PK lookup) latency ──");
println!(
" p50: {} p95: {} p99: {}",
fmt_us(r.point_query_p50_us),
fmt_us(r.point_query_p95_us),
fmt_us(r.point_query_p99_us)
);
println!();
println!(" ── Write latency ──");
println!(" UPDATE by PK: {}", fmt_us(r.update_pk_us));
println!(" DELETE by PK: {}", fmt_us(r.delete_pk_us));
all_results.push(r);
}
println!("\n{}", "═".repeat(80));
println!(" SUMMARY TABLE");
println!("{}", "═".repeat(80));
println!();
println!(" Scale (K) | INSERT | rows/sec | Disk | B/row | WHERE | LIKE | AGG | GROUP | ORDER ");
println!(" ---------|---------|-----------|--------|-------|--------|--------|--------|--------|--------");
for r in &all_results {
r.print_table_row();
}
println!();
println!(" ── Memory Scaling (does RSS grow with data?) ──");
println!(" Scale | Insert RSS | Steady RSS | Peak RSS | Disk");
println!(" --------|------------|------------|----------|--------");
for r in &all_results {
println!(
" {:>5}K | {:>8.1} MB | {:>8.1} MB | {:>6.1} MB | {:>6.1} MB",
r.rows / 1000,
r.insert_rss_mb,
r.steady_rss_mb,
r.peak_rss_mb,
r.disk_mb,
);
}
println!();
println!(" ── Point Query (PK) Latency Distribution ──");
println!(" Scale | p50 | p95 | p99");
println!(" --------|----------|----------|----------");
for r in &all_results {
println!(
" {:>5}K | {:>8} | {:>8} | {:>8}",
r.rows / 1000,
fmt_us(r.point_query_p50_us),
fmt_us(r.point_query_p95_us),
fmt_us(r.point_query_p99_us),
);
}
println!("\n{}", "═".repeat(80));
println!();
}