running_process/broker/server/
perf_guard.rs1use std::time::Duration;
4
5pub const HELLO_PERF_SAMPLE_COUNT: usize = 10_000;
7
8pub const HELLO_PERF_GUARD_ENV: &str = "RUNNING_PROCESS_BROKER_HELLO_PERF_GUARD";
10
11pub const HELLO_P50_BUDGET: Duration = Duration::from_micros(200);
13
14pub const HELLO_P99_BUDGET: Duration = Duration::from_millis(1);
16
17#[derive(Clone, Copy, Debug, PartialEq, Eq)]
19pub struct HelloLatencySummary {
20 pub sample_count: usize,
22 pub p50: Duration,
24 pub p99: Duration,
26}
27
28#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
30pub enum PerfGuardError {
31 #[error("hello perf guard received no samples")]
33 EmptySamples,
34 #[error("hello perf guard needs at least {required} samples, got {actual}")]
36 TooFewSamples {
37 required: usize,
39 actual: usize,
41 },
42 #[error("hello P50 budget exceeded: actual {actual:?}, budget {budget:?}")]
44 P50Exceeded {
45 actual: Duration,
47 budget: Duration,
49 },
50 #[error("hello P99 budget exceeded: actual {actual:?}, budget {budget:?}")]
52 P99Exceeded {
53 actual: Duration,
55 budget: Duration,
57 },
58}
59
60pub fn summarize_hello_latencies(
62 samples: &[Duration],
63) -> Result<HelloLatencySummary, PerfGuardError> {
64 if samples.is_empty() {
65 return Err(PerfGuardError::EmptySamples);
66 }
67
68 let mut sorted = samples.to_vec();
69 sorted.sort_unstable();
70 Ok(HelloLatencySummary {
71 sample_count: sorted.len(),
72 p50: percentile_nearest_rank(&sorted, 50),
73 p99: percentile_nearest_rank(&sorted, 99),
74 })
75}
76
77pub fn enforce_hello_latency_budget(
79 samples: &[Duration],
80) -> Result<HelloLatencySummary, PerfGuardError> {
81 let summary = summarize_hello_latencies(samples)?;
82 if summary.sample_count < HELLO_PERF_SAMPLE_COUNT {
83 return Err(PerfGuardError::TooFewSamples {
84 required: HELLO_PERF_SAMPLE_COUNT,
85 actual: summary.sample_count,
86 });
87 }
88 if summary.p50 > HELLO_P50_BUDGET {
89 return Err(PerfGuardError::P50Exceeded {
90 actual: summary.p50,
91 budget: HELLO_P50_BUDGET,
92 });
93 }
94 if summary.p99 > HELLO_P99_BUDGET {
95 return Err(PerfGuardError::P99Exceeded {
96 actual: summary.p99,
97 budget: HELLO_P99_BUDGET,
98 });
99 }
100 Ok(summary)
101}
102
103fn percentile_nearest_rank(sorted: &[Duration], percentile: usize) -> Duration {
104 debug_assert!(!sorted.is_empty());
105 debug_assert!((1..=100).contains(&percentile));
106
107 let rank = sorted.len() * percentile;
108 let index = rank.div_ceil(100).saturating_sub(1);
109 sorted[index.min(sorted.len() - 1)]
110}