use std::collections::BTreeMap;
use std::io::{self, Write};
use std::path::PathBuf;
use subms::{SubMsFeatureManifest, SubMsP99Source, SubMsPerfHarness, classify_feature, summarize};
use subms_hdr_histogram::HdrHistogram;
const DIGITS: [u32; 3] = [3, 4, 5];
const CANON_D: u32 = DIGITS[DIGITS.len() - 1];
const OPS: usize = 20_000;
const BULK_REPS: usize = 256;
const BULK_WARM_NANOS: u64 = 300_000_000;
const BULK_WARM_MAX_REPS: usize = 5_000;
const MAX_VALUE: u64 = 10_000_000;
fn value_at(i: usize) -> u64 {
1 + ((i as u64).wrapping_mul(2_654_435_761) % MAX_VALUE)
}
fn sub_count(d: u32) -> usize {
HdrHistogram::new(d).sub_count() as usize
}
fn stat(h: &SubMsPerfHarness, median: bool) -> u64 {
summarize(h)
.stages
.iter()
.find(|s| s.name == "op")
.map_or(0, |s| if median { s.p50_ns } else { s.p99_ns })
}
fn keyed(mut op: impl FnMut(usize), median: bool) -> u64 {
let mut h = SubMsPerfHarness::new("hdr-feature", "rust");
let st = h.stage("op", OPS);
for i in 0..OPS {
st.time(|| op(i));
}
stat(&h, median)
}
fn bulk<T>(mut setup: impl FnMut() -> T, mut op: impl FnMut(&mut T), median: bool) -> u64 {
let mut input = setup();
let start = std::time::Instant::now();
for _ in 0..BULK_WARM_MAX_REPS {
op(&mut input);
if start.elapsed().as_nanos() as u64 >= BULK_WARM_NANOS {
break;
}
}
let mut h = SubMsPerfHarness::new("hdr-feature", "rust");
let st = h.stage("op", BULK_REPS);
for _ in 0..BULK_REPS {
st.time(|| op(&mut input));
}
stat(&h, median)
}
fn sweep(label: &str, mut at: impl FnMut(u32) -> u64) -> Vec<(usize, u64)> {
let rows: Vec<(usize, u64)> = DIGITS.iter().map(|&d| (sub_count(d), at(d))).collect();
eprintln!("sweep {label}: {rows:?}");
rows
}
fn filled(d: u32) -> HdrHistogram {
let mut h = HdrHistogram::new(d);
for i in 0..sub_count(d) {
h.record(value_at(i));
}
h
}
fn main() -> io::Result<()> {
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 mut base = HdrHistogram::new(CANON_D);
let base_p50 = keyed(|i| base.record(value_at(i)), true);
eprintln!("base record p50: {base_p50}ns");
#[cfg(feature = "concurrent-writes")]
{
use subms_hdr_histogram::ConcurrentHdrHistogram;
let sw = sweep("concurrent-writes/record", |d| {
let c = ConcurrentHdrHistogram::new(d);
keyed(|i| c.record(value_at(i)), true)
});
let (cat, reason) = classify_feature(&sw, Some(base_p50), None);
let c = ConcurrentHdrHistogram::new(CANON_D);
let mut p99 = BTreeMap::new();
p99.insert(
"record".to_string(),
keyed(|i| c.record(value_at(i)), false),
);
p99.insert(
"percentile".to_string(),
keyed(|_| _ = c.value_at_percentile(99.0), false),
);
manifest.set_feature("concurrent-writes", cat, &p99, &reason);
}
#[cfg(feature = "dual-recorder")]
{
use subms_hdr_histogram::DualRecorder;
let sw = sweep("dual-recorder/interval", |d| {
let r = DualRecorder::new(d);
for i in 0..sub_count(d) {
r.record(value_at(i));
}
bulk(|| (), |()| _ = r.get_interval_histogram(), true)
});
let (cat, reason) = classify_feature(
&sw,
Some(base_p50),
Some(subms::SubMsFeatureCategory::Structural),
);
let r = DualRecorder::new(CANON_D);
for i in 0..OPS {
r.record(value_at(i));
}
let mut p99 = BTreeMap::new();
p99.insert(
"record".to_string(),
keyed(|i| r.record(value_at(i)), false),
);
p99.insert(
"interval_read".to_string(),
bulk(|| (), |()| _ = r.get_interval_histogram(), false),
);
manifest.set_feature("dual-recorder", cat, &p99, &reason);
}
#[cfg(feature = "merge")]
{
use subms_hdr_histogram::merge;
let sw = sweep("merge/merge", |d| {
bulk(
|| (filled(d), filled(d)),
|(dst, src)| merge(dst, src).expect("same precision"),
true,
)
});
let (cat, reason) = classify_feature(&sw, Some(base_p50), None);
let mut p99 = BTreeMap::new();
p99.insert(
"merge".to_string(),
bulk(
|| (filled(CANON_D), filled(CANON_D)),
|(dst, src)| merge(dst, src).expect("same precision"),
false,
),
);
manifest.set_feature("merge", cat, &p99, &reason);
}
#[cfg(feature = "decay")]
{
use subms_hdr_histogram::{Clock, DecayingHdrHistogram};
struct TickingClock(std::cell::Cell<u64>);
impl Clock for TickingClock {
fn now_ns(&self) -> u64 {
self.0.set(self.0.get() + 1_000_000);
self.0.get()
}
}
let halflife = 1_000_000_000;
let sw = sweep("decay/record", |d| {
let mut x =
DecayingHdrHistogram::new(d, halflife, TickingClock(std::cell::Cell::new(0)));
keyed(|i| x.record(value_at(i)), true)
});
let (cat, reason) = classify_feature(&sw, Some(base_p50), None);
let mut x =
DecayingHdrHistogram::new(CANON_D, halflife, TickingClock(std::cell::Cell::new(0)));
let mut p99 = BTreeMap::new();
p99.insert(
"record".to_string(),
keyed(|i| x.record(value_at(i)), false),
);
p99.insert(
"percentile".to_string(),
keyed(|_| _ = x.value_at_percentile(99.0), false),
);
manifest.set_feature("decay", cat, &p99, &reason);
}
#[cfg(feature = "value-tagging")]
{
use subms_hdr_histogram::TaggedHdrHistogram;
let sw = sweep("value-tagging/record", |d| {
let mut t = TaggedHdrHistogram::new(d);
keyed(|i| t.record(value_at(i), (i % 4) as u8), true)
});
let (cat, reason) = classify_feature(&sw, Some(base_p50), None);
let mut t = TaggedHdrHistogram::new(CANON_D);
let mut p99 = BTreeMap::new();
p99.insert(
"record".to_string(),
keyed(|i| t.record(value_at(i), (i % 4) as u8), false),
);
p99.insert(
"percentile_for_tag".to_string(),
keyed(|_| _ = t.value_at_percentile_for_tag(99.0, 0), false),
);
manifest.set_feature("value-tagging", cat, &p99, &reason);
}
#[cfg(feature = "iterators")]
{
let sw = sweep("iterators/percentiles", |d| {
let h = filled(d);
bulk(|| (), |()| _ = h.iter_percentiles(1.0).count(), true)
});
let (cat, reason) = classify_feature(
&sw,
Some(base_p50),
Some(subms::SubMsFeatureCategory::Structural),
);
let h = filled(CANON_D);
let mut p99 = BTreeMap::new();
p99.insert(
"iter_percentiles".to_string(),
bulk(|| (), |()| _ = h.iter_percentiles(1.0).count(), false),
);
p99.insert(
"iter_logarithmic".to_string(),
bulk(|| (), |()| _ = h.iter_logarithmic().count(), false),
);
manifest.set_feature("iterators", 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(())
}