use clap::Parser;
use ktstr::workload::{
SCHBENCH_PERCENTILES, SchbenchConfig, StandaloneReport, pipe_transfer_report, run_standalone,
};
#[derive(Parser)]
#[command(
name = "ktstr-schbench-validate",
about = "Run the native schbench_rs engine host-side and print a schbench-comparable latency report"
)]
struct Args {
#[arg(short = 'm', long, default_value_t = 1)]
message_threads: usize,
#[arg(short = 't', long, default_value_t = 0)]
worker_threads: usize,
#[arg(short = 'F', long, default_value_t = 256)]
cache_footprint_kib: usize,
#[arg(short = 'n', long, default_value_t = 5)]
operations: usize,
#[arg(short = 's', long, default_value_t = 100)]
sleep_usec: u64,
#[arg(short = 'L', long, default_value_t = false)]
skip_locking: bool,
#[arg(short = 'r', long, default_value_t = 30)]
runtime_secs: u64,
#[arg(short = 'R', long, default_value_t = 0)]
rps: usize,
#[arg(short = 'A', long, default_value_t = 0)]
auto_rps: usize,
#[arg(long)]
split: Option<usize>,
#[arg(short = 'p', long, default_value_t = 0)]
pipe: usize,
}
fn main() {
let args = Args::parse();
let config = SchbenchConfig::default()
.message_threads(args.message_threads)
.worker_threads(args.worker_threads)
.cache_footprint_kib(args.cache_footprint_kib)
.operations(args.operations)
.sleep_usec(args.sleep_usec)
.skip_locking(args.skip_locking)
.requests_per_sec(args.rps)
.auto_rps(args.auto_rps)
.split_percent(args.split)
.pipe_transfer_bytes(args.pipe);
let report = run_standalone(&config, args.runtime_secs);
print_report(&report, args.runtime_secs, args.auto_rps, args.pipe);
}
const LAT_ROW_INDICES: [usize; 4] = [1, 2, 3, 4];
const LAT_ROW_INDICES_PIPE: [usize; 5] = [0, 1, 2, 3, 4];
const LAT_STAR_INDEX: usize = 3;
const RPS_ROW_INDICES: [usize; 3] = [0, 1, 2];
const RPS_STAR_INDEX: usize = 1;
fn print_report(r: &StandaloneReport, runtime_secs: u64, auto_rps: usize, pipe_bytes: usize) {
let wakeup_rows: &[usize] = if pipe_bytes > 0 {
&LAT_ROW_INDICES_PIPE
} else {
&LAT_ROW_INDICES
};
print_distribution(
"Wakeup Latencies",
"usec",
runtime_secs,
r.nr_wakeup_samples,
&r.wakeup_pcts_us,
&r.wakeup_counts,
r.wakeup_min_us,
r.wakeup_max_us,
wakeup_rows,
LAT_STAR_INDEX,
);
if pipe_bytes > 0 {
let t = pipe_transfer_report(r.achieved_rps, pipe_bytes, r.nr_workers);
println!(
"avg worker transfer: {:.2} ops/sec {:.2}{}/s",
t.ops_per_sec, t.scaled, t.unit
);
return;
}
print_distribution(
"Request Latencies",
"usec",
runtime_secs,
r.nr_request_samples,
&r.request_pcts_us,
&r.request_counts,
r.request_min_us,
r.request_max_us,
&LAT_ROW_INDICES,
LAT_STAR_INDEX,
);
print_distribution(
"RPS",
"requests",
runtime_secs,
r.nr_rps_samples,
&r.rps_pcts,
&r.rps_counts,
r.rps_min,
r.rps_max,
&RPS_ROW_INDICES,
RPS_STAR_INDEX,
);
if auto_rps != 0 {
println!("final rps goal was {}", r.final_rps_goal);
} else {
println!(
"average rps: {:.2}",
r.loop_count as f64 / runtime_secs as f64
);
}
println!(
"sched delay: message {} (usec) worker {} (usec)",
r.sched_delay_msg_ns / 1000,
r.sched_delay_worker_ns / 1000
);
}
#[allow(clippy::too_many_arguments)]
fn print_distribution(
label: &str,
units: &str,
runtime_secs: u64,
nr_samples: u64,
pcts: &[u32; 5],
counts: &[u64; 5],
min: u32,
max: u32,
rows: &[usize],
star: usize,
) {
println!(
"{label} percentiles ({units}) runtime {runtime_secs} (s) ({nr_samples} total samples)"
);
for &i in rows {
let marker = if i == star { "* " } else { " " };
println!(
"\t{marker}{:.1}th: {:<10} ({} samples)",
SCHBENCH_PERCENTILES[i], pcts[i], counts[i]
);
}
println!("\t min={min}, max={max}");
}