use std::hint::black_box;
use crate::relation::RelationKind;
use crate::test_support::{relation_fixture, RelationFixture, TID};
use gnitz_foundation::perf::{Counter, Resident};
use gnitz_wire::{ReadBound, ReadSpec, TypeCode, ViewProps, WireStatus};
use gnitz_zset::repr::{Batch, BatchBuilder};
use gnitz_zset::schema::{SchemaColumn, SchemaDescriptor};
const STRING: &str = "a-payload-string-of-forty-characters-xxx";
fn schema(strings: bool) -> SchemaDescriptor {
let c = SchemaColumn::new;
let i = c(TypeCode::I64, false);
match strings {
false => SchemaDescriptor::new(&[c(TypeCode::U64, false), i, i, i], &[0]),
true => SchemaDescriptor::new(&[c(TypeCode::U64, false), i, c(TypeCode::String, false)], &[0]),
}
}
fn round(s: &SchemaDescriptor, strings: bool, first: u64, rows: u64) -> Batch {
let mut bb = BatchBuilder::new(s);
for id in first..first + rows {
bb.begin_row(id as u128, 1);
bb.put_u64(id % 512);
match strings {
false => {
bb.put_u64(id);
bb.put_u64(!id);
}
true => bb.put_string(STRING),
}
bb.end_row();
}
bb.finish()
}
fn io_written() -> (u64, u64) {
let io = std::fs::read_to_string("/proc/self/io").expect("read /proc/self/io");
let field = |name: &str| {
io.lines()
.find_map(|l| l.strip_prefix(name)?.strip_prefix(": ")?.parse::<u64>().ok())
.unwrap_or_else(|| panic!("no {name} in /proc/self/io"))
};
(field("wchar"), field("write_bytes"))
}
struct Run {
instr: u64,
wchar: u64,
write_bytes: u64,
peak: Option<u64>,
held: Option<u64>,
fixture: RelationFixture,
}
fn run(counter: &Counter, kind: RelationKind, strings: bool, rounds: u64, per_round: u64) -> Run {
let s = schema(strings);
let resident = Resident::baseline();
let (w0, b0) = io_written();
let mut r = relation_fixture(kind, s, &[], []);
let mut instr = 0;
for n in 0..rounds {
let batch = round(&s, strings, n * per_round, per_round);
instr += counter
.measure(|| r.ingest_at(TID, batch, Some(2 + n), false).unwrap())
.1;
}
let (w1, b1) = io_written();
Run {
instr,
wchar: w1 - w0,
write_bytes: b1 - b0,
peak: resident.as_ref().map(Resident::peak_added),
held: resident.as_ref().map(Resident::added),
fixture: r,
}
}
fn readable(r: &RelationFixture, strings: bool, last_round: u64) -> (u64, usize, usize) {
let layout = schema(strings).layout_digest();
let read = |after: u64| r.delta_read(TID, after, ReadSpec::all_rows(ReadBound::None), layout);
let (mut lo, mut hi) = (1, last_round);
while lo < hi {
let mid = lo + (hi - lo) / 2;
match read(mid) {
Ok(_) => hi = mid,
Err(f) => {
assert!(matches!(f.status, WireStatus::DeltaExpired), "{}", f.text);
lo = mid + 1;
}
}
}
let rows = read(lo).unwrap();
(lo, rows.len(), rows.total_bytes())
}
fn env_list(name: &str, default: &[u64]) -> Vec<u64> {
match std::env::var(name) {
Ok(v) => v.split(',').map(|x| x.trim().parse().unwrap()).collect(),
Err(_) => default.to_vec(),
}
}
#[test]
#[ignore = "benchmark; run with --release --ignored --nocapture --test-threads=1"]
fn delta_capture_bench() {
let counter = Counter::instructions();
let ram_tier = crate::relation::StoreConfig::default().ram_tier_bytes as u64;
let only = |name: &str, value: &str| std::env::var(name).map_or(true, |v| v == value);
for budget_mib in env_list("GNITZ_CAPTURE_BUDGETS_MIB", &[4, 64, 256]) {
let budget = budget_mib << 20;
for (strings, schema_name) in [(false, "ints"), (true, "strings")] {
for per_round in env_list("GNITZ_CAPTURE_ROUND_ROWS", &[1, 100, 8192]) {
if (budget_mib > 64 && per_round == 1) || !only("GNITZ_CAPTURE_SCHEMA", schema_name) {
continue;
}
let row_bytes = round(&schema(strings), strings, 0, 64).total_bytes() as u64 / 64;
let rounds = (2 * (ram_tier + budget) / row_bytes).div_ceil(per_round);
let rows = rounds * per_round;
let fed = RelationKind::View(ViewProps::Fed {
delta_bytes: std::num::NonZeroU64::new(budget).unwrap(),
});
for (kind, kind_name) in [(RelationKind::View(ViewProps::Plain), "plain"), (fed, "fed")] {
if !only("GNITZ_CAPTURE_KIND", kind_name) {
continue;
}
let ran = run(&counter, kind, strings, rounds, per_round);
let (floor, kept_rows, kept_bytes) = match kind_name {
"fed" => readable(&ran.fixture, strings, 1 + rounds),
_ => (0, 0, 0),
};
black_box(&ran.fixture);
println!(
"delta_capture_bench kind={kind_name} schema={schema_name} rows_per_round={per_round} \
budget_mib={budget_mib} rounds={rounds} rows={rows} ingested_bytes={} instr={} wchar={} \
write_bytes={} peak_rss={} held_rss={} floor={floor} readable_rows={kept_rows} \
readable_bytes={kept_bytes}",
rows * row_bytes,
ran.instr,
ran.wchar,
ran.write_bytes,
ran.peak.map_or(-1, |b| b as i64),
ran.held.map_or(-1, |b| b as i64),
);
}
}
}
}
}
#[test]
#[ignore = "benchmark; run with --release --ignored --nocapture --test-threads=1"]
fn delta_idle_bench() {
for budget_mib in env_list("GNITZ_CAPTURE_BUDGETS_MIB", &[4, 64, 256]) {
let resident = Resident::baseline().expect("run under GLIBC_TUNABLES=glibc.malloc.mmap_threshold=131072");
let fed = RelationKind::View(ViewProps::Fed {
delta_bytes: std::num::NonZeroU64::new(budget_mib << 20).unwrap(),
});
let mut r = relation_fixture(fed, schema(false), &[], []);
let idle = resident.added();
r.ingest_at(TID, round(&schema(false), false, 0, 1), Some(2), false)
.unwrap();
let one_row = resident.added();
black_box(&r);
println!("delta_idle_bench budget_mib={budget_mib} idle_rss={idle} one_row_rss={one_row}");
}
}