//! Hot-path micro-benchmarks for the load generator's OWN per-request cost. //! //! These run with NO server and NO network, so they isolate exactly the work //! the generator does per dispatched request — request construction //! ([`Workload::next`]) and latency aggregation ([`LatencyHistogram::record`]). //! That is the only honest way to answer "is this allocation material?": the //! number here is the generator's CPU floor, the thing that must stay far below //! the network round-trip it measures, and the before/after the perf sweep is //! graded against. use std::hint::black_box; use std::time::Duration; use criterion::{Criterion, criterion_group, criterion_main}; use tidal_stress::metrics::LatencyHistogram; use tidal_stress::workload::{Workload, parse_mix}; fn make_workload(mix: &str) -> Workload { Workload::new( // Three read gateways (round-robin), writes pinned to one gateway => // write_bases len == 1, the headline `--leader-url` path. vec![ "http://10.0.0.1:9500".into(), "http://10.0.0.2:9500".into(), "http://10.0.0.3:9500".into(), ], vec!["http://10.0.0.1:9500".into()], parse_mix(mix).expect("mix preset parses"), 10_000, // corpus 50_000, // users 1.3, // hot_skew 24, // feed_limit 128, // embedding_dim None, // forced_profile (use the weighted feed-profile mix) ) } /// `Workload::next` — the per-dispatched-request construction cost (URL String + /// body). `peach` is the headline signal-dominated mix; `writes` forces the body /// path on every call; `reads` isolates the URL-only feed/search path. fn bench_next(c: &mut Criterion) { let wl_peach = make_workload("peach"); let wl_writes = make_workload("writes"); let wl_reads = make_workload("reads"); let mut rng = rand::rng(); let mut g = c.benchmark_group("workload_next"); g.bench_function("peach", |b| b.iter(|| black_box(wl_peach.next(&mut rng)))); g.bench_function("writes", |b| b.iter(|| black_box(wl_writes.next(&mut rng)))); g.bench_function("reads", |b| b.iter(|| black_box(wl_reads.next(&mut rng)))); g.finish(); } /// `LatencyHistogram::record` — the per-OK-request collector cost (single-writer, /// allocation-free), plus the per-stage `percentile` report cost. fn bench_histogram(c: &mut Criterion) { c.bench_function("histogram_record", |b| { let mut h = LatencyHistogram::default(); // Cheap LCG so successive records land in different buckets rather than // hammering one — closer to a real latency distribution. let mut state = 0x2545_F491_4F6C_DD1Du64; b.iter(|| { state = state .wrapping_mul(6_364_136_223_846_793_005) .wrapping_add(1_442_695_040_888_963_407); let ns = (state >> 33) % 200_000_000 + 1; // ~1ns..200ms h.record(black_box(Duration::from_nanos(ns))); }); }); let mut h = LatencyHistogram::default(); for i in 1..=100_000u64 { h.record(Duration::from_nanos(i * 1_500)); } c.bench_function("histogram_p99", |b| { b.iter(|| black_box(h.percentile(black_box(0.99)))); }); } criterion_group!(benches, bench_next, bench_histogram); criterion_main!(benches);