use std::time::Duration;
pub const HELLO_PERF_SAMPLE_COUNT: usize = 10_000;
pub const HELLO_PERF_GUARD_ENV: &str = "RUNNING_PROCESS_BROKER_HELLO_PERF_GUARD";
pub const HELLO_P50_BUDGET: Duration = Duration::from_micros(200);
pub const HELLO_P99_BUDGET: Duration = Duration::from_millis(1);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct HelloLatencySummary {
pub sample_count: usize,
pub p50: Duration,
pub p99: Duration,
}
#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
pub enum PerfGuardError {
#[error("hello perf guard received no samples")]
EmptySamples,
#[error("hello perf guard needs at least {required} samples, got {actual}")]
TooFewSamples {
required: usize,
actual: usize,
},
#[error("hello P50 budget exceeded: actual {actual:?}, budget {budget:?}")]
P50Exceeded {
actual: Duration,
budget: Duration,
},
#[error("hello P99 budget exceeded: actual {actual:?}, budget {budget:?}")]
P99Exceeded {
actual: Duration,
budget: Duration,
},
}
pub fn summarize_hello_latencies(
samples: &[Duration],
) -> Result<HelloLatencySummary, PerfGuardError> {
if samples.is_empty() {
return Err(PerfGuardError::EmptySamples);
}
let mut sorted = samples.to_vec();
sorted.sort_unstable();
Ok(HelloLatencySummary {
sample_count: sorted.len(),
p50: percentile_nearest_rank(&sorted, 50),
p99: percentile_nearest_rank(&sorted, 99),
})
}
pub fn enforce_hello_latency_budget(
samples: &[Duration],
) -> Result<HelloLatencySummary, PerfGuardError> {
let summary = summarize_hello_latencies(samples)?;
if summary.sample_count < HELLO_PERF_SAMPLE_COUNT {
return Err(PerfGuardError::TooFewSamples {
required: HELLO_PERF_SAMPLE_COUNT,
actual: summary.sample_count,
});
}
if summary.p50 > HELLO_P50_BUDGET {
return Err(PerfGuardError::P50Exceeded {
actual: summary.p50,
budget: HELLO_P50_BUDGET,
});
}
if summary.p99 > HELLO_P99_BUDGET {
return Err(PerfGuardError::P99Exceeded {
actual: summary.p99,
budget: HELLO_P99_BUDGET,
});
}
Ok(summary)
}
fn percentile_nearest_rank(sorted: &[Duration], percentile: usize) -> Duration {
debug_assert!(!sorted.is_empty());
debug_assert!((1..=100).contains(&percentile));
let rank = sorted.len() * percentile;
let index = rank.div_ceil(100).saturating_sub(1);
sorted[index.min(sorted.len() - 1)]
}