use std::collections::BTreeMap;
use std::io::{self, Write};
use std::path::PathBuf;
use subms::{SubMsFeatureManifest, SubMsP99Source, SubMsPerfHarness, classify_feature, summarize};
use subms_cuckoo_filter::CuckooFilter;
const SIZES: [usize; 3] = [4_096, 32_768, 262_144];
const SNAPSHOT_REPS: usize = 32;
const SNAPSHOT_WARM: usize = 16;
fn keys(n: usize) -> Vec<String> {
(0..n).map(|i| format!("key-{i}")).collect()
}
fn stage_stats(h: &SubMsPerfHarness, name: &str) -> (u64, u64) {
summarize(h)
.stages
.iter()
.find(|s| s.name == name)
.map_or((0, 0), |s| (s.p50_ns, s.p99_ns))
}
fn keyed(ks: &[String], mut op: impl FnMut(&str)) -> (u64, u64) {
let mut h = SubMsPerfHarness::new("cuckoo-feature", "rust");
{
let st = h.stage("op", ks.len());
for k in ks {
st.time(|| op(k));
}
}
stage_stats(&h, "op")
}
fn main() -> io::Result<()> {
let canon = SIZES[SIZES.len() - 1];
let canon_keys = keys(canon);
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("..")
.join(".subms")
.join("features")
.join("rust.json");
let existing = std::fs::read_to_string(&path).unwrap_or_default();
let mut manifest = SubMsFeatureManifest::load_str("rust", &existing);
let (source, instance) = SubMsP99Source::from_env();
manifest.set_p99_source(source, instance.as_deref());
let base_p50 = {
let mut f = CuckooFilter::with_capacity(canon);
for k in &canon_keys {
f.insert(k);
}
let (p50, _) = keyed(&canon_keys, |k| {
let _ = f.contains(k);
});
p50
};
#[cfg(feature = "variable-fingerprint")]
{
use subms_cuckoo_filter::{FingerprintWidth, VariableFpCuckooFilter};
let sweep: Vec<(usize, u64)> = SIZES
.iter()
.map(|&n| {
let ks = keys(n);
let mut f = VariableFpCuckooFilter::new(n, FingerprintWidth::Sixteen);
for k in &ks {
f.insert(k);
}
let (p50, _) = keyed(&ks, |k| {
let _ = f.contains(k);
});
(n, p50)
})
.collect();
let (cat, reason) = classify_feature(&sweep, Some(base_p50), None);
let mut f = VariableFpCuckooFilter::new(canon, FingerprintWidth::Sixteen);
let (_, insert99) = keyed(&canon_keys, |k| {
f.insert(k);
});
let (_, lookup99) = keyed(&canon_keys, |k| {
let _ = f.contains(k);
});
let (_, delete99) = keyed(&canon_keys, |k| {
f.delete(k);
});
let mut p99 = BTreeMap::new();
p99.insert("insert".to_string(), insert99);
p99.insert("lookup".to_string(), lookup99);
p99.insert("delete".to_string(), delete99);
manifest.set_feature("variable-fingerprint", cat, &p99, &reason);
}
#[cfg(feature = "dynamic")]
{
use subms_cuckoo_filter::DynamicCuckooFilter;
let sweep: Vec<(usize, u64)> = SIZES
.iter()
.map(|&n| {
let ks = keys(n);
let mut f = DynamicCuckooFilter::new(n);
for k in &ks {
f.insert(k);
}
let (p50, _) = keyed(&ks, |k| {
let _ = f.contains(k);
});
(n, p50)
})
.collect();
let (cat, reason) = classify_feature(&sweep, Some(base_p50), None);
let mut f = DynamicCuckooFilter::new(canon);
let (_, insert99) = keyed(&canon_keys, |k| {
f.insert(k);
});
let (_, lookup99) = keyed(&canon_keys, |k| {
let _ = f.contains(k);
});
let (_, delete99) = keyed(&canon_keys, |k| {
f.delete(k);
});
let mut p99 = BTreeMap::new();
p99.insert("insert".to_string(), insert99);
p99.insert("lookup".to_string(), lookup99);
p99.insert("delete".to_string(), delete99);
manifest.set_feature("dynamic", cat, &p99, &reason);
}
#[cfg(feature = "concurrent-reads")]
{
use subms_cuckoo_filter::CuckooSnapshot;
let sweep: Vec<(usize, u64)> = SIZES
.iter()
.map(|&n| {
let ks = keys(n);
let mut src = CuckooFilter::with_capacity(n);
for k in &ks {
src.insert(k);
}
for _ in 0..SNAPSHOT_WARM {
let _ = CuckooSnapshot::capture(&src);
}
let mut h = SubMsPerfHarness::new("cuckoo-feature", "rust");
{
let st = h.stage("op", SNAPSHOT_REPS);
for _ in 0..SNAPSHOT_REPS {
st.time(|| {
let _ = CuckooSnapshot::capture(&src);
});
}
}
let (p50, _) = stage_stats(&h, "op");
(n, p50)
})
.collect();
let (cat, reason) =
classify_feature(&sweep, None, Some(subms::SubMsFeatureCategory::Structural));
let mut src = CuckooFilter::with_capacity(canon);
for k in &canon_keys {
src.insert(k);
}
for _ in 0..SNAPSHOT_WARM {
let _ = CuckooSnapshot::capture(&src);
}
let mut h = SubMsPerfHarness::new("cuckoo-feature", "rust");
let snap = {
let st = h.stage("op", SNAPSHOT_REPS);
for _ in 0..SNAPSHOT_REPS - 1 {
st.time(|| {
let _ = CuckooSnapshot::capture(&src);
});
}
st.time(|| CuckooSnapshot::capture(&src))
};
let (_, snap99) = stage_stats(&h, "op");
let (_, lookup99) = keyed(&canon_keys, |k| {
let _ = snap.contains(k);
});
let mut p99 = BTreeMap::new();
p99.insert("snapshot".to_string(), snap99);
p99.insert("lookup_on_snapshot".to_string(), lookup99);
manifest.set_feature("concurrent-reads", cat, &p99, &reason);
}
#[cfg(feature = "compressed-buckets")]
{
use subms_cuckoo_filter::CompressedCuckooFilter;
let sweep: Vec<(usize, u64)> = SIZES
.iter()
.map(|&n| {
let ks = keys(n);
let mut f = CompressedCuckooFilter::with_capacity(n);
for k in &ks {
f.insert(k);
}
let (p50, _) = keyed(&ks, |k| {
let _ = f.contains(k);
});
(n, p50)
})
.collect();
let (cat, reason) = classify_feature(&sweep, Some(base_p50), None);
let mut f = CompressedCuckooFilter::with_capacity(canon);
let (_, insert99) = keyed(&canon_keys, |k| {
f.insert(k);
});
let (_, lookup99) = keyed(&canon_keys, |k| {
let _ = f.contains(k);
});
let (_, delete99) = keyed(&canon_keys, |k| {
f.delete(k);
});
let mut p99 = BTreeMap::new();
p99.insert("insert".to_string(), insert99);
p99.insert("lookup".to_string(), lookup99);
p99.insert("delete".to_string(), delete99);
manifest.set_feature("compressed-buckets", cat, &p99, &reason);
}
std::fs::create_dir_all(path.parent().unwrap())?;
std::fs::write(&path, manifest.to_json())?;
io::stdout().write_all(manifest.to_json().as_bytes())?;
Ok(())
}