use std::path::Path;
use regolith::{Db, Options};
const VALUE_SIZE: usize = 128;
const DEFAULT_PUTS: usize = 20_000;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let args: Vec<String> = std::env::args().skip(1).collect();
let dir = args
.first()
.map(String::as_str)
.ok_or("usage: embedded_profile <db-dir> [embedded|wasm|default] [num-puts]")?;
let profile = args.get(1).map(String::as_str).unwrap_or("embedded");
let puts: usize = match args.get(2) {
Some(n) => n.parse()?,
None => DEFAULT_PUTS,
};
let opts = match profile {
"embedded" => Options::embedded(),
"wasm" => Options::wasm(),
"default" => Options::default(),
other => {
return Err(format!("unknown profile {other:?}; use embedded, wasm or default").into());
}
};
println!("regolith memory profile");
println!(" probe {}", Probe::describe());
println!(" profile {profile}");
println!(" writes {puts} x {VALUE_SIZE} B values, 16 B keys");
println!(" write_buffer {} KiB", opts.write_buffer_size / 1024);
println!(" block_cache {} KiB", opts.block_cache_size / 1024);
println!(" target_file {} KiB", opts.target_file_size / 1024);
println!(" bg_compactions {}", opts.max_background_compactions);
println!();
let mut report = Report::new();
report.record("baseline (before open)");
let db_dir = Path::new(dir).join("db");
let db = Db::open(&db_dir, opts.clone())?;
report.record("after Db::open");
let mut value = vec![0u8; VALUE_SIZE];
for i in 0..puts {
let tag = (i as u64).to_le_bytes();
value[..8].copy_from_slice(&tag);
db.put(&key(i), &value)?;
if i + 1 == 1_000 {
report.record("after 1k puts");
}
}
report.record(&format!("after {puts} puts"));
let mut found = 0usize;
for i in (0..puts).step_by(7) {
if db.get(&key(i))?.is_some() {
found += 1;
}
}
report.record("after sampled reads");
let scanned = db.scan_page(Some(&key(0)), None, 512)?.entries.len();
report.record("after a 512-row page scan");
db.compact_range(None, None)?;
report.record("after compact_range");
db.close()?;
drop(db);
report.record("after close (flush to SSTable)");
let reopened = Db::open(&db_dir, opts)?;
report.record("after reopen");
let probe_key = key(puts / 2);
let round_trip = reopened.get(&probe_key)?;
reopened.close()?;
drop(reopened);
report.record("after second close");
println!("{report}");
println!();
println!(" sampled reads found {found}");
println!(" scan returned {scanned} rows");
let round_trip_ok = round_trip.as_ref().is_some_and(|v| v.len() == VALUE_SIZE);
println!(
" round-trip after reopen {}",
match (&round_trip, round_trip_ok) {
(_, true) => "ok",
(Some(_), false) => "WRONG LENGTH",
(None, _) => "MISSING",
}
);
if round_trip_ok {
Ok(())
} else {
Err("lifecycle failed: the key written before close did not survive reopen".into())
}
}
fn key(i: usize) -> Vec<u8> {
format!("key{i:013}").into_bytes()
}
struct Phase {
label: String,
sample: Sample,
}
struct Report {
phases: Vec<Phase>,
baseline: Option<Sample>,
}
impl Report {
fn new() -> Self {
Self {
phases: Vec::new(),
baseline: None,
}
}
fn record(&mut self, label: &str) {
let sample = Probe::sample();
if self.baseline.is_none() {
self.baseline = Some(sample);
}
self.phases.push(Phase {
label: label.to_string(),
sample,
});
}
}
impl std::fmt::Display for Report {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(
f,
" {:<32} {:>12} {:>12} {:>12}",
"phase", "current", "delta", "peak"
)?;
writeln!(f, " {}", "-".repeat(70))?;
let base = self.baseline.and_then(|s| s.current_bytes);
for phase in &self.phases {
let delta = match (base, phase.sample.current_bytes) {
(Some(b), Some(c)) => format!("{:+}", (c as i64 - b as i64) / 1024),
_ => "-".to_string(),
};
writeln!(
f,
" {:<32} {:>12} {:>12} {:>12}",
phase.label,
kib(phase.sample.current_bytes),
delta,
kib(phase.sample.peak_bytes),
)?;
}
write!(f, " all figures in KiB")
}
}
fn kib(bytes: Option<u64>) -> String {
match bytes {
Some(b) => (b / 1024).to_string(),
None => "unavailable".to_string(),
}
}
#[derive(Clone, Copy)]
struct Sample {
current_bytes: Option<u64>,
peak_bytes: Option<u64>,
}
struct Probe;
#[cfg(target_family = "wasm")]
impl Probe {
fn describe() -> &'static str {
"wasm linear memory (memory_size * 64 KiB; never shrinks)"
}
fn sample() -> Sample {
let bytes = (core::arch::wasm32::memory_size(0) as u64) * 65536;
Sample {
current_bytes: Some(bytes),
peak_bytes: Some(bytes),
}
}
}
#[cfg(all(target_os = "linux", not(target_family = "wasm")))]
impl Probe {
fn describe() -> &'static str {
"Linux RSS (/proc/self/statm) and peak RSS (VmHWM)"
}
fn sample() -> Sample {
Sample {
current_bytes: rss_bytes(),
peak_bytes: vm_hwm_bytes(),
}
}
}
#[cfg(all(target_os = "linux", not(target_family = "wasm")))]
fn rss_bytes() -> Option<u64> {
let statm = std::fs::read_to_string("/proc/self/statm").ok()?;
let pages: u64 = statm.split_whitespace().nth(1)?.parse().ok()?;
Some(pages * 4096)
}
#[cfg(all(target_os = "linux", not(target_family = "wasm")))]
fn vm_hwm_bytes() -> Option<u64> {
let status = std::fs::read_to_string("/proc/self/status").ok()?;
let line = status.lines().find(|l| l.starts_with("VmHWM:"))?;
let kib: u64 = line.split_whitespace().nth(1)?.parse().ok()?;
Some(kib * 1024)
}
#[cfg(not(any(target_family = "wasm", target_os = "linux")))]
impl Probe {
fn describe() -> &'static str {
"no memory probe on this host"
}
fn sample() -> Sample {
Sample {
current_bytes: None,
peak_bytes: None,
}
}
}