Skip to main content

jugar_probar/perf_gate/
window.rs

1//! §4.4.2 termination and §4.4.7 boundary effects, as a pure state machine.
2//!
3//! The measurement window is a *shared admission decision*, not a per-worker
4//! deadline check. Written as a deadline test inside each worker's loop, the
5//! sample-count bound cannot be expressed at all (no worker knows the total),
6//! and there is no single instant `T` to measure `drain_ms` from. Both of those
7//! are why this is a controller rather than an `if` in the loop.
8//!
9//! It is deliberately clock-free: every method takes the current offset in
10//! seconds. That is what lets the whole termination rule be unit-tested in
11//! microseconds instead of the 60 s it governs.
12
13use serde::{Deserialize, Serialize};
14
15use super::protocol::{BandConfig, DRAIN_SUSPECT_FRACTION};
16
17/// Shared admission gate for one band's closed-loop workers.
18#[derive(Debug, Clone)]
19pub struct WindowController {
20    min_samples: usize,
21    min_wall_s: f64,
22    issued: usize,
23    closed_at_s: Option<f64>,
24    last_completion_s: f64,
25    in_flight: usize,
26    peak_in_flight: usize,
27}
28
29impl WindowController {
30    /// Build the controller for `config`. The window opens at offset `0.0`;
31    /// callers pass offsets measured from the first sampled request's origin.
32    #[must_use]
33    pub fn new(config: &BandConfig) -> Self {
34        Self {
35            min_samples: config.min_samples,
36            min_wall_s: config.min_wall_clock.as_secs_f64(),
37            issued: 0,
38            closed_at_s: None,
39            last_completion_s: 0.0,
40            in_flight: 0,
41            peak_in_flight: 0,
42        }
43    }
44
45    /// A controller with explicit bounds, for the warmup phase (§4.4.2: exactly
46    /// `2 × c` requests, no wall-clock floor) and for tests.
47    #[must_use]
48    pub fn with_bounds(min_samples: usize, min_wall_s: f64) -> Self {
49        Self {
50            min_samples,
51            min_wall_s,
52            issued: 0,
53            closed_at_s: None,
54            last_completion_s: 0.0,
55            in_flight: 0,
56            peak_in_flight: 0,
57        }
58    }
59
60    /// §4.4.2 — "termination is whichever bound is satisfied **last**".
61    ///
62    /// Returns the index reserved for a new request, or `None` once the window
63    /// has closed. §4.4.7: **no new request is issued at or after `T`**, and `T`
64    /// is stamped here, once, at the first refusal.
65    pub fn try_admit(&mut self, now_s: f64) -> Option<usize> {
66        self.try_admit_with_in_flight(now_s).map(|(index, _)| index)
67    }
68
69    /// [`Self::try_admit`], also returning the in-flight count **including this
70    /// request**, read under the same lock that made the admission decision.
71    ///
72    /// A caller that admits, unlocks, and then asks for `in_flight()` reads a
73    /// number from a different instant. The per-request in-flight figure is the
74    /// evidence that the client was concurrent, so it must not be a racy guess.
75    pub fn try_admit_with_in_flight(&mut self, now_s: f64) -> Option<(usize, usize)> {
76        if self.closed_at_s.is_some() {
77            return None;
78        }
79        if self.issued >= self.min_samples && now_s >= self.min_wall_s {
80            self.closed_at_s = Some(now_s);
81            return None;
82        }
83        let index = self.issued;
84        self.issued += 1;
85        self.in_flight += 1;
86        self.peak_in_flight = self.peak_in_flight.max(self.in_flight);
87        Some((index, self.in_flight))
88    }
89
90    /// Record a completion at `now_s`. Returns `true` when this request
91    /// completed during the drain, i.e. after the window closed.
92    pub fn complete(&mut self, now_s: f64) -> bool {
93        self.in_flight = self.in_flight.saturating_sub(1);
94        self.last_completion_s = self.last_completion_s.max(now_s);
95        self.closed_at_s.is_some_and(|t| now_s > t)
96    }
97
98    /// Peak concurrent requests the client had in flight. This is the *client's*
99    /// number and is named as such: `max_in_flight` in §4.4.9 is what the
100    /// **server** admitted, which only the server can report.
101    #[must_use]
102    pub fn peak_in_flight(&self) -> usize {
103        self.peak_in_flight
104    }
105
106    /// Requests admitted so far.
107    #[must_use]
108    pub fn issued(&self) -> usize {
109        self.issued
110    }
111
112    /// Requests currently outstanding.
113    #[must_use]
114    pub fn in_flight(&self) -> usize {
115        self.in_flight
116    }
117
118    /// Whether the window has closed.
119    #[must_use]
120    pub fn is_closed(&self) -> bool {
121        self.closed_at_s.is_some()
122    }
123
124    /// Finalise the band. `window_close_s` defaults to the last completion when
125    /// the window never closed (a run cut short), which yields `drain_ms = 0`
126    /// rather than a negative number.
127    #[must_use]
128    pub fn report(&self) -> WindowReport {
129        let close = self.closed_at_s.unwrap_or(self.last_completion_s);
130        let drain_ms = ((self.last_completion_s - close).max(0.0)) * 1000.0;
131        let window_ms = close * 1000.0;
132        let mut suspect = Vec::new();
133        if window_ms > 0.0 && drain_ms > DRAIN_SUSPECT_FRACTION * window_ms {
134            suspect.push(format!(
135                "§4.4.7 drain_ms={drain_ms:.1} > 0.5 x window_ms={window_ms:.1}: one request \
136                 dominated the window; re-run this band with a longer window"
137            ));
138        }
139        if self.closed_at_s.is_none() {
140            suspect.push(
141                "§4.4.2 the window never closed: neither termination bound was reached, so this \
142                 band did not run the protocol"
143                    .to_string(),
144            );
145        }
146        WindowReport {
147            requested: self.issued,
148            window_ms,
149            drain_ms,
150            client_peak_in_flight: self.peak_in_flight,
151            suspect,
152        }
153    }
154}
155
156/// What the controller observed, for the receipt.
157#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
158#[serde(deny_unknown_fields)]
159pub struct WindowReport {
160    /// Requests admitted before `T`.
161    pub requested: usize,
162    /// `T` minus window open, in milliseconds.
163    pub window_ms: f64,
164    /// §4.4.7 — last drained completion minus `T`, in milliseconds.
165    pub drain_ms: f64,
166    /// Peak concurrent requests **the client** had outstanding.
167    pub client_peak_in_flight: usize,
168    /// §4.4.7 `SUSPECT` annotations, empty when the band is clean.
169    pub suspect: Vec<String>,
170}
171
172#[cfg(test)]
173mod tests {
174    use super::*;
175    use std::time::Duration;
176
177    /// The sample bound alone must not close the window: §4.4.2 says the last
178    /// bound wins. A fast host that hit 30 samples in 2 s still owes 60 s.
179    #[test]
180    fn sample_bound_alone_does_not_close_the_window() {
181        let mut w = WindowController::with_bounds(3, 60.0);
182        for i in 0..3 {
183            assert_eq!(w.try_admit(0.1 * f64::from(i)), Some(i as usize));
184        }
185        assert_eq!(w.try_admit(0.4), Some(3), "still open: 0.4s < 60s");
186        assert!(!w.is_closed());
187    }
188
189    /// And the wall-clock bound alone must not close it either: a slow host that
190    /// burned 60 s on 4 requests still owes `max(30, 8c)` samples.
191    #[test]
192    fn wall_clock_bound_alone_does_not_close_the_window() {
193        let mut w = WindowController::with_bounds(30, 1.0);
194        for _ in 0..4 {
195            assert!(
196                w.try_admit(100.0).is_some(),
197                "still open: only 4 of 30 samples"
198            );
199        }
200        assert!(!w.is_closed());
201    }
202
203    #[test]
204    fn window_closes_only_when_both_bounds_are_satisfied() {
205        let mut w = WindowController::with_bounds(3, 10.0);
206        assert!(w.try_admit(0.0).is_some());
207        assert!(w.try_admit(1.0).is_some());
208        assert!(w.try_admit(2.0).is_some());
209        assert!(!w.is_closed());
210        assert_eq!(w.try_admit(10.0), None, "3 samples AND 10s -> closed");
211        assert!(w.is_closed());
212    }
213
214    /// §4.4.7 — once closed, the gate stays closed. A late worker must not
215    /// sneak a request in after `T`.
216    #[test]
217    fn no_new_request_is_admitted_at_or_after_t() {
218        let mut w = WindowController::with_bounds(1, 1.0);
219        assert!(w.try_admit(0.0).is_some());
220        assert_eq!(w.try_admit(1.0), None);
221        assert_eq!(w.try_admit(1.0001), None);
222        assert_eq!(w.try_admit(500.0), None);
223        assert_eq!(w.issued(), 1, "exactly one request was ever admitted");
224    }
225
226    /// The drain is measured from `T`, not from the last admission.
227    #[test]
228    fn drain_ms_is_measured_from_window_close() {
229        let mut w = WindowController::with_bounds(2, 4.0);
230        assert!(w.try_admit(0.0).is_some());
231        assert!(w.try_admit(1.0).is_some());
232        assert!(!w.complete(2.0), "completed inside the window");
233        assert_eq!(w.try_admit(4.0), None, "T = 4.0");
234        assert!(w.complete(5.5), "completed during the drain");
235        let r = w.report();
236        assert!((r.window_ms - 4000.0).abs() < 1e-9, "{}", r.window_ms);
237        assert!((r.drain_ms - 1500.0).abs() < 1e-9, "{}", r.drain_ms);
238        assert!(r.suspect.is_empty(), "{:?}", r.suspect);
239    }
240
241    /// §4.4.7 — `drain_ms > 0.5 x window` is annotated SUSPECT.
242    #[test]
243    fn a_dominating_request_is_annotated_suspect() {
244        let mut w = WindowController::with_bounds(1, 2.0);
245        assert!(w.try_admit(0.0).is_some());
246        assert_eq!(w.try_admit(2.0), None);
247        assert!(w.complete(20.0));
248        let r = w.report();
249        assert!((r.drain_ms - 18000.0).abs() < 1e-9);
250        assert_eq!(r.suspect.len(), 1, "{:?}", r.suspect);
251        assert!(r.suspect[0].contains("drain_ms"));
252    }
253
254    /// A band whose window never closed did not run the protocol, and must say
255    /// so rather than reporting a clean `drain_ms = 0`.
256    #[test]
257    fn an_unclosed_window_is_suspect_not_clean() {
258        let mut w = WindowController::with_bounds(100, 60.0);
259        assert!(w.try_admit(0.0).is_some());
260        assert!(!w.complete(1.0));
261        let r = w.report();
262        assert_eq!(r.drain_ms, 0.0);
263        assert!(
264            r.suspect.iter().any(|s| s.contains("never closed")),
265            "{:?}",
266            r.suspect
267        );
268    }
269
270    /// The controller is the only place that knows how many requests were in
271    /// flight at once, and it is the evidence that `c` workers really overlapped.
272    #[test]
273    fn peak_in_flight_tracks_concurrent_admissions() {
274        let mut w = WindowController::with_bounds(100, 100.0);
275        for _ in 0..8 {
276            assert!(w.try_admit(0.0).is_some());
277        }
278        assert_eq!(w.in_flight(), 8);
279        assert_eq!(w.peak_in_flight(), 8);
280        for _ in 0..8 {
281            w.complete(1.0);
282        }
283        assert_eq!(w.in_flight(), 0);
284        assert_eq!(w.peak_in_flight(), 8, "peak is a high-water mark");
285    }
286
287    #[test]
288    fn controller_reads_its_bounds_from_the_band_config() {
289        let cfg = BandConfig::conformant(8);
290        let w = WindowController::new(&cfg);
291        assert_eq!(w.min_samples, 64, "max(30, 8*8)");
292        assert!((w.min_wall_s - 60.0).abs() < 1e-9);
293        assert_eq!(cfg.quiesce, Duration::from_secs(5));
294    }
295}
296
297/// A closed-loop concurrency proof over real OS threads.
298///
299/// Not `#[cfg(test)]`: this is the falsifier for the defect class that produced
300/// this ticket — a client that *says* concurrency `c` and issues requests one at
301/// a time. It is exported so any harness can run it against its own admission
302/// path, and it is exercised by [`concurrency_proof_tests`] under default
303/// features, where CI can actually see it.
304///
305/// `c` threads each loop: admit, "work" for `work` , complete. Returns the
306/// observed peak in flight and the wall time. A serialising implementation
307/// yields `peak == 1` and `wall ≈ requests × work`; a concurrent one yields
308/// `peak == c` and `wall ≈ requests / c × work`.
309#[must_use]
310pub fn closed_loop_probe(
311    concurrency: usize,
312    requests: usize,
313    work: std::time::Duration,
314) -> (usize, std::time::Duration) {
315    use std::sync::Mutex;
316    use std::time::Instant;
317
318    let controller = Mutex::new(WindowController::with_bounds(requests, 0.0));
319    let origin = Instant::now();
320    std::thread::scope(|scope| {
321        for _ in 0..concurrency {
322            scope.spawn(|| loop {
323                let admitted = {
324                    let mut c = controller
325                        .lock()
326                        .unwrap_or_else(std::sync::PoisonError::into_inner);
327                    c.try_admit(origin.elapsed().as_secs_f64())
328                };
329                if admitted.is_none() {
330                    break;
331                }
332                std::thread::sleep(work);
333                let mut c = controller
334                    .lock()
335                    .unwrap_or_else(std::sync::PoisonError::into_inner);
336                c.complete(origin.elapsed().as_secs_f64());
337            });
338        }
339    });
340    let peak = controller
341        .lock()
342        .unwrap_or_else(std::sync::PoisonError::into_inner)
343        .peak_in_flight();
344    (peak, origin.elapsed())
345}
346
347#[cfg(test)]
348mod concurrency_proof_tests {
349    use super::*;
350    use std::time::Duration;
351
352    /// The direct claim: `c` workers are in flight at the same time.
353    #[test]
354    fn eight_workers_are_actually_concurrent() {
355        let (peak, _) = closed_loop_probe(8, 64, Duration::from_millis(20));
356        assert_eq!(
357            peak, 8,
358            "peak in-flight must reach c; a serialising client gives 1"
359        );
360    }
361
362    /// The indirect claim, which a fake concurrent client cannot also satisfy:
363    /// the same request count must finish far faster at c=8 than at c=1.
364    ///
365    /// The bound is deliberately loose (4x, not 8x) so scheduler noise on a
366    /// loaded CI runner cannot red it, while a secretly-sequential client — which
367    /// would score 1.0x — still fails by a wide margin.
368    #[test]
369    fn wall_time_at_c8_is_not_eight_times_the_c1_time() {
370        let requests = 32;
371        let work = Duration::from_millis(20);
372        let (peak1, wall1) = closed_loop_probe(1, requests, work);
373        let (peak8, wall8) = closed_loop_probe(8, requests, work);
374
375        assert_eq!(peak1, 1);
376        assert_eq!(peak8, 8);
377        let speedup = wall1.as_secs_f64() / wall8.as_secs_f64();
378        eprintln!(
379            "closed_loop_probe: {requests} requests x {work:?} -- \
380             c=1 peak={peak1} wall={wall1:?}; c=8 peak={peak8} wall={wall8:?}; speedup={speedup:.2}x"
381        );
382        assert!(
383            speedup > 4.0,
384            "c=1 took {wall1:?}, c=8 took {wall8:?} (speedup {speedup:.2}x); \
385             a concurrent client must be much faster, a sequential one scores ~1.0x"
386        );
387    }
388
389    /// And the probe can tell the difference — a c=1 run is the negative control.
390    #[test]
391    fn the_probe_reports_one_for_a_sequential_client() {
392        let (peak, wall) = closed_loop_probe(1, 8, Duration::from_millis(10));
393        assert_eq!(peak, 1);
394        assert!(
395            wall >= Duration::from_millis(70),
396            "8 x 10ms sequential must take ~80ms, got {wall:?}"
397        );
398    }
399}