#[allow(dead_code, unused_imports, unused_macros)]
#[path = "common.rs"]
mod common;
use common::{any_fix_cfg_with, gen_random_kv, make_fixed_value, AnyOFixDb};
use commonware_runtime::{
tokio::{Config, Runner},
Runner as _, Supervisor as _,
};
use commonware_storage::{merkle::mmr::Family as Mmr, qmdb::any::traits::DbAny as _};
use commonware_utils::{NZUsize, NZU64};
use std::{
io::Write as _,
num::{NonZeroU64, NonZeroUsize},
time::{Duration, Instant},
};
const ITEMS_PER_BLOB: NonZeroU64 = NZU64!(1_000_000);
const PAGE_CACHE_SIZE: NonZeroUsize = NZUsize!(65536);
const COMMIT_FREQUENCY: u32 = 10_000;
const PRUNE_FREQUENCY: u32 = 100;
const KEY_ZIPF_EXPONENT: f64 = 1.0;
fn usage() {
eprintln!(
"usage:\n generate <folder> <keyspace> <num_updates> [zipf_exponent] build a database (omit exponent => zipf 1.0; 0 => uniform)\n bench <folder> reopen + time init at cache off / R/4 / R\n destroy <folder> delete the database"
);
}
fn main() {
let argv: Vec<String> = std::env::args().skip(1).collect();
match argv.first().map(String::as_str) {
Some("generate") => match (
argv.get(1),
argv.get(2).and_then(|a| a.parse().ok()),
argv.get(3).and_then(|a| a.parse().ok()),
) {
(Some(folder), Some(keyspace), Some(num_updates)) => {
let zipf_exponent = match argv.get(4).map(|a| a.parse::<f64>()) {
None => Some(KEY_ZIPF_EXPONENT),
Some(Ok(e)) if e > 0.0 => Some(e),
Some(Ok(_)) => None,
Some(Err(_)) => {
usage();
return;
}
};
generate(folder, keyspace, num_updates, zipf_exponent)
}
_ => usage(),
},
Some("bench") => match argv.get(1) {
Some(folder) => bench(folder),
None => usage(),
},
Some("destroy") => match argv.get(1) {
Some(folder) => destroy(folder),
None => usage(),
},
_ => usage(),
}
}
fn generate(folder: &str, keyspace: u64, num_updates: u64, zipf_exponent: Option<f64>) {
if keyspace == 0 {
eprintln!("keyspace must be > 0");
return;
}
if db_dir_nonempty(folder) {
eprintln!("{folder} already contains data; `destroy` it first or pick a new folder");
return;
}
let cfg = Config::default().with_storage_directory(folder);
let elapsed = Runner::new(cfg).start(|ctx| async move {
let mut db = AnyOFixDb::<Mmr>::init(
ctx.child("storage"),
any_fix_cfg_with(&ctx, ITEMS_PER_BLOB, PAGE_CACHE_SIZE),
)
.await
.unwrap();
let start = Instant::now();
gen_random_kv::<Mmr, _>(
&mut db,
0, num_updates,
Some(COMMIT_FREQUENCY),
None, Some(PRUNE_FREQUENCY),
zipf_exponent,
Some(keyspace),
make_fixed_value,
)
.await;
db.prune(db.sync_boundary()).await.unwrap();
db.sync().await.unwrap();
start.elapsed()
});
println!("generated {num_updates} updates over keyspace {keyspace} at {folder} in {elapsed:?}");
}
fn bench(folder: &str) {
if !db_dir_nonempty(folder) {
eprintln!(
"no database at {folder}; run `generate {folder} <keyspace> <num_updates>` first"
);
return;
}
let cfg = Config::default().with_storage_directory(folder);
let (baseline, region) = time_init(&cfg, None);
if region == 0 {
eprintln!(
"database at {folder} is empty; run `generate {folder} <keyspace> <num_updates>` first"
);
return;
}
println!("init_scale: {folder} (any::ordered::fixed::mmr)");
println!(" replay region R = {region} ops");
println!(" cache=off : {baseline:?}");
let _ = std::io::stdout().flush();
for cache_size in [
NonZeroUsize::new((region / 4) as usize),
NonZeroUsize::new(region as usize),
] {
let (elapsed, _) = time_init(&cfg, cache_size);
println!(" cache={cache_size:?}: {elapsed:?}");
let _ = std::io::stdout().flush();
}
}
fn destroy(folder: &str) {
match std::fs::remove_dir_all(folder) {
Ok(()) => println!("destroyed {folder}"),
Err(e) => eprintln!("failed to destroy {folder}: {e}"),
}
}
fn time_init(cfg: &Config, cache_size: Option<NonZeroUsize>) -> (Duration, u64) {
Runner::new(cfg.clone()).start(|ctx| async move {
let mut config = any_fix_cfg_with(&ctx, ITEMS_PER_BLOB, PAGE_CACHE_SIZE);
config.init_cache_size = cache_size;
let start = Instant::now();
let db = AnyOFixDb::<Mmr>::init(ctx.child("storage"), config)
.await
.unwrap();
let elapsed = start.elapsed();
let end: u64 = *db.bounds().end;
let floor: u64 = *db.inactivity_floor_loc();
(elapsed, end.saturating_sub(floor))
})
}
fn db_dir_nonempty(folder: &str) -> bool {
std::fs::read_dir(folder)
.map(|mut entries| entries.next().is_some())
.unwrap_or(false)
}