use std::path::PathBuf;
use std::time::Duration;
use waterui_preview_protocol::bench::{
BENCH_MAX_CLIP_LAYERS_ENV, BENCH_MAX_GPU_SURFACE_LAYERS_ENV, BENCH_MAX_MEAN_US_ENV,
BENCH_MAX_P95_US_ENV, BENCH_MAX_REBUILD_RATIO_ENV, BENCH_MAX_SCENE_LAYERS_ENV,
BENCH_REPETITIONS_ENV, BENCH_REPORT_DIR_ENV, BENCH_SAMPLES_ENV, BENCH_WARMUPS_ENV,
bench_report_file_name,
};
pub use waterui_preview_protocol::bench::{BenchBudgets, BenchReport, BenchRunConfig};
use crate::perf::{PerfConfig, PerfReport, PerfStats};
use crate::protocol::perf_report_to_protocol;
const SMOKE_CONFIG: PerfConfig = PerfConfig {
warmups: 0,
samples: 2,
repetitions: 1,
};
pub fn run_bench(
crate_name: &str,
bench_name: &str,
budgets: BenchBudgets,
run: impl FnOnce(PerfConfig) -> PerfReport,
) {
let full_run = run_config_from_env();
let config = full_run.map_or(SMOKE_CONFIG, |config| PerfConfig {
warmups: config.warmups,
samples: config.samples,
repetitions: config.repetitions,
});
let report = run(config);
assert!(
!report.measurements().is_empty(),
"bench `{crate_name}::{bench_name}` recorded no measurements; call `measure` at least once"
);
let Some(run_config) = full_run else {
return;
};
let effective = merge_budgets(budgets, budget_caps_from_env());
write_report(crate_name, bench_name, run_config, effective, &report);
enforce_budgets(crate_name, bench_name, effective, &report);
}
fn run_config_from_env() -> Option<BenchRunConfig> {
let samples = env_parsed::<u32>(BENCH_SAMPLES_ENV);
let warmups = env_parsed::<u32>(BENCH_WARMUPS_ENV);
let repetitions = env_parsed::<u32>(BENCH_REPETITIONS_ENV);
if samples.is_none() && warmups.is_none() && repetitions.is_none() {
return None;
}
let defaults = PerfConfig::default();
Some(BenchRunConfig {
warmups: warmups.unwrap_or(defaults.warmups),
samples: samples.unwrap_or(defaults.samples),
repetitions: repetitions.unwrap_or(defaults.repetitions),
})
}
fn budget_caps_from_env() -> BenchBudgets {
BenchBudgets {
max_p95_us: env_parsed::<u64>(BENCH_MAX_P95_US_ENV),
max_mean_us: env_parsed::<u64>(BENCH_MAX_MEAN_US_ENV),
max_rebuild_ratio: env_parsed::<f64>(BENCH_MAX_REBUILD_RATIO_ENV),
max_scene_layers: env_parsed::<u64>(BENCH_MAX_SCENE_LAYERS_ENV),
max_gpu_surface_layers: env_parsed::<u64>(BENCH_MAX_GPU_SURFACE_LAYERS_ENV),
max_clip_layers: env_parsed::<u64>(BENCH_MAX_CLIP_LAYERS_ENV),
}
}
fn merge_budgets(attribute: BenchBudgets, caps: BenchBudgets) -> BenchBudgets {
fn tighter<T: PartialOrd>(a: Option<T>, b: Option<T>) -> Option<T> {
match (a, b) {
(Some(a), Some(b)) => Some(if b < a { b } else { a }),
(value, None) | (None, value) => value,
}
}
BenchBudgets {
max_p95_us: tighter(attribute.max_p95_us, caps.max_p95_us),
max_mean_us: tighter(attribute.max_mean_us, caps.max_mean_us),
max_rebuild_ratio: tighter(attribute.max_rebuild_ratio, caps.max_rebuild_ratio),
max_scene_layers: tighter(attribute.max_scene_layers, caps.max_scene_layers),
max_gpu_surface_layers: tighter(
attribute.max_gpu_surface_layers,
caps.max_gpu_surface_layers,
),
max_clip_layers: tighter(attribute.max_clip_layers, caps.max_clip_layers),
}
}
fn enforce_budgets(crate_name: &str, bench_name: &str, budgets: BenchBudgets, report: &PerfReport) {
for measurement in report.measurements() {
let stats = measurement.stats();
let violation = |metric: &str, actual: String, limit: String| -> ! {
panic!(
"bench `{crate_name}::{bench_name}` measurement `{name}`: {metric} {actual} exceeded budget {limit}",
name = measurement.name,
)
};
if let Some(limit) = budgets.max_p95_us {
let actual = micros(stats.p95);
if actual > limit {
violation("frame p95", format!("{actual}us"), format!("{limit}us"));
}
}
if let Some(limit) = budgets.max_mean_us {
let actual = micros(stats.mean);
if actual > limit {
violation("frame mean", format!("{actual}us"), format!("{limit}us"));
}
}
if let Some(limit) = budgets.max_rebuild_ratio {
let actual = rebuild_ratio(&stats);
if actual > limit {
violation("rebuild ratio", format!("{actual:.4}"), format!("{limit}"));
}
}
if let Some(limit) = budgets.max_scene_layers
&& stats.scene_layers > limit
{
violation(
"scene layers",
stats.scene_layers.to_string(),
limit.to_string(),
);
}
if let Some(limit) = budgets.max_gpu_surface_layers
&& stats.gpu_surface_layers > limit
{
violation(
"GPU surface layers",
stats.gpu_surface_layers.to_string(),
limit.to_string(),
);
}
if let Some(limit) = budgets.max_clip_layers
&& stats.clip_layers > limit
{
violation(
"clip layers",
stats.clip_layers.to_string(),
limit.to_string(),
);
}
}
}
fn rebuild_ratio(stats: &PerfStats) -> f64 {
if stats.samples == 0 {
return 0.0;
}
#[expect(
clippy::cast_precision_loss,
reason = "frame counts are far below f64's integer-exact range"
)]
{
stats.rebuilt_frames as f64 / stats.samples as f64
}
}
fn write_report(
crate_name: &str,
bench_name: &str,
config: BenchRunConfig,
budgets: BenchBudgets,
report: &PerfReport,
) {
let Some(dir) = std::env::var_os(BENCH_REPORT_DIR_ENV) else {
return;
};
let dir = PathBuf::from(dir);
std::fs::create_dir_all(&dir).unwrap_or_else(|error| {
panic!(
"bench `{crate_name}::{bench_name}`: failed to create report directory `{}`: {error}",
dir.display()
)
});
let wire = BenchReport {
crate_name: crate_name.to_owned(),
bench_name: bench_name.to_owned(),
config,
budgets,
measurements: perf_report_to_protocol(report),
};
let path = dir.join(bench_report_file_name(crate_name, bench_name));
let json = serde_json::to_vec(&wire).unwrap_or_else(|error| {
panic!("bench `{crate_name}::{bench_name}`: failed to serialize report: {error}")
});
std::fs::write(&path, json).unwrap_or_else(|error| {
panic!(
"bench `{crate_name}::{bench_name}`: failed to write report `{}`: {error}",
path.display()
)
});
}
fn micros(duration: Duration) -> u64 {
u64::try_from(duration.as_micros()).unwrap_or(u64::MAX)
}
fn env_parsed<T>(name: &str) -> Option<T>
where
T: std::str::FromStr,
T::Err: core::fmt::Display,
{
let value = std::env::var(name).ok()?;
match value.trim().parse::<T>() {
Ok(parsed) => Some(parsed),
Err(error) => panic!("failed to parse `{name}={value}`: {error}"),
}
}