Skip to main content

thread_broker/
controller.rs

1//! The control loop: measure each stage's true rate, solve for the split, jump.
2//!
3//! The shape of this loop is taken from DS2 (Kalavri et al., OSDI'18): estimate
4//! a rate per stage that is independent of the resources currently assigned to
5//! it, then compute the assignment directly rather than searching for it. The
6//! guards around the jump — ratify against throughput, remember what failed,
7//! blank out measurements taken while the system is still moving — come from
8//! Flink's autoscaler (FLIP-271) and from FDP (Suleman et al., PACT'10), which
9//! demote hill-climbing to a check on a model-based decision rather than the
10//! decision itself.
11//!
12//! See `notes/thread-broker/design.md` for the derivation and the citations.
13
14use std::collections::HashMap;
15use std::sync::Arc;
16use std::thread::JoinHandle;
17use std::time::{Duration, Instant};
18
19use crate::{
20    BrokerError, BrokerErrorKind, Consumer, OpeningPolicy, Producer, ProducerMeasurementMode,
21    ProducerMeasurementStats, ProducerPressure, ResizeSide, SteadyStatePolicy, Stop, ThreadBroker,
22    Work, idle_fraction, now,
23};
24
25/// What the broker did, for logging and for tests.
26#[derive(Debug, Clone, Default, serde::Serialize)]
27pub struct BrokerReport {
28    /// Configured behavior after the first stable split.
29    pub steady_state_policy: SteadyStatePolicy,
30    /// True when the controller terminated itself at convergence instead of
31    /// continuing to sample until the application called `finish`.
32    pub monitoring_stopped_after_convergence: bool,
33    /// Number of controller wakeups, including startup warm-up windows.
34    pub controller_samples: u64,
35    /// Wall time for which the controller remained alive.
36    pub controller_elapsed: Duration,
37    /// Controller thread CPU time across its complete lifetime. This is
38    /// measured with only one thread-clock read at start and one at exit.
39    pub controller_cpu_nanos: Option<u64>,
40    /// Controller thread-clock reads that failed.
41    pub controller_cpu_accounting_failures: usize,
42    /// Effective cadence between responsive steady-state probes.
43    pub steady_probe_interval: Duration,
44    /// Cost and result of the optional startup-only opening bracket.
45    pub opening_bracket: OpeningBracketReport,
46    /// Consumer threads at each decision, in order.
47    pub consumer_trajectory: Vec<usize>,
48    /// Producer limit at each decision, in order.
49    pub producer_trajectory: Vec<usize>,
50    /// Actual live/active occupancy observed at every recorded sample.
51    pub actual_consumer_trajectory: Vec<usize>,
52    pub actual_producer_trajectory: Vec<usize>,
53    /// Controller elapsed time for each retained trajectory point.
54    pub trajectory_elapsed: Vec<Duration>,
55    /// Times at which the inferred producer ceiling or its evidence changed.
56    pub cap_trajectory: Vec<CapObservation>,
57    /// Real pipeline-throughput windows retained while a decision was open.
58    /// Steady-state probes are excluded, so this remains bounded startup or
59    /// resurvey evidence rather than recurring telemetry overhead.
60    pub throughput_trace: Vec<f64>,
61    pub throughput_trace_dropped: usize,
62    /// Oldest samples discarded after reaching `trace_capacity`.
63    pub trace_samples_dropped: usize,
64    /// Splits applied, including reverts.
65    pub moves: usize,
66    /// Moves undone because throughput got worse.
67    pub reverts: usize,
68    /// Guarded local candidates attempted after the opening rejected or could
69    /// not distinguish the model's differing answer.
70    pub nonlinear_probes: usize,
71    /// Probe targets retained because their throughput confidence interval was
72    /// strictly better than the previous split.
73    pub nonlinear_probe_improvements: usize,
74    /// Whether the final split is held by measured opening-bracket evidence
75    /// rather than the allocation-independent cost model.
76    pub nonlinear_override: bool,
77    /// Ratifications whose uncertainty interval could not establish a
78    /// regression or a clear non-regression.
79    pub inconclusive_ratifications: usize,
80    /// Times a settled split was re-opened by a detected regime change.
81    pub resurveys: usize,
82    /// Windows discarded as carrying too little work to estimate a rate from.
83    pub rejected_windows: usize,
84    /// Otherwise-usable windows rejected because producer buffers filled or
85    /// drained materially, so the two stages did not process equivalent work.
86    pub flow_transient_windows: usize,
87    /// Samples where the run was source-bound rather than decode-bound.
88    pub source_bound_samples: usize,
89    /// Samples where the producer could not use more concurrency at all.
90    pub inelastic_samples: usize,
91    /// Model-requested producer shrinks vetoed because runnable producer work
92    /// was still queued behind the current limit.
93    pub pressure_vetoed_shrinks: usize,
94    /// Time from the end of warm-up until the split stopped changing.
95    ///
96    /// Measured to the last actual **change of allocation**, not to the moment
97    /// the controller last stopped deliberating. Those differ: a settled split
98    /// can be re-opened by drift, re-solved, and left exactly where it was. That
99    /// is a decision being reconsidered, not an allocation moving, and reporting
100    /// it as a fresh convergence is misleading in the direction that matters —
101    /// it makes a stable run look unstable.
102    ///
103    /// Measured on a 2.3 billion read Flex run at `-t 64`: the split reached
104    /// 25 decode slots about two seconds in and never moved again for the
105    /// remaining 111 seconds, while a single harmless resurvey made this field
106    /// read 87 seconds.
107    pub time_to_converge: Option<Duration>,
108    /// Final split.
109    pub final_consumer_threads: usize,
110    pub final_producer_limit: usize,
111    /// Actual controlled occupancy observed when the controller stopped. Under
112    /// freeze-after-convergence this is earlier than application shutdown.
113    pub final_consumer_live: usize,
114    pub final_producer_active: usize,
115    /// Largest observed sum of live consumer workers and active producer slots.
116    pub peak_controlled_slots: usize,
117    /// Peak live threads explicitly outside the controlled slot budget: the
118    /// controller itself plus producer-reported coordinators/optional samplers.
119    pub peak_auxiliary_threads: usize,
120    /// Producer measurement diagnostics. Native cumulative counters report
121    /// zero samples and observation time; sampled adapters also report their
122    /// cadence and sampler CPU.
123    pub producer_measurement: Option<ProducerMeasurementStats>,
124    /// Rejected targets and the workload epoch in which they failed.
125    pub rejections: Vec<Rejection>,
126    pub final_epoch: u64,
127    pub final_phase: BrokerPhase,
128    pub current_drift: Duration,
129    pub terminal_error: Option<String>,
130    /// Last solved model, for diagnosing a split that looks wrong.
131    pub final_model: Option<Model>,
132}
133
134/// Auditable cost and result of [`OpeningPolicy::Bracket`].
135#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize)]
136pub struct OpeningBracketReport {
137    /// Candidate allocations whose evidence horizon completed. The already
138    /// measured opening is not counted.
139    pub points_measured: usize,
140    /// Clean throughput samples consumed by candidate evidence horizons.
141    pub samples: usize,
142    /// Wall time from first stable disagreement until the bracket completed.
143    pub wall_nanos: u64,
144    pub outcome: OpeningBracketOutcome,
145}
146
147#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize)]
148#[serde(rename_all = "snake_case")]
149pub enum OpeningBracketOutcome {
150    #[default]
151    NotConfigured,
152    /// The first stable model answer matched the opening, so no bracket work
153    /// was performed.
154    ModelAgreed,
155    /// The model's differing answer was measured and retained.
156    ModelSelected,
157    /// The model answer and local alternatives failed to beat the opening.
158    OpeningRetained,
159    /// A measured local alternative beat both the model answer and opening.
160    AlternativeSelected,
161    /// The configured wall budget ended the experiment.
162    BudgetExhausted,
163    /// Mapping ended before the experiment reached a decision.
164    Incomplete,
165    /// The model-only freeze policy deliberately skipped calibration.
166    SkippedBySteadyPolicy,
167}
168
169#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize)]
170pub struct Rejection {
171    pub epoch: u64,
172    pub producer_target: usize,
173    pub baseline_rate: f64,
174    pub achieved_rate: f64,
175    pub baseline_uncertainty: f64,
176    pub achieved_uncertainty: f64,
177    pub producer_cost_share: f64,
178}
179
180impl BrokerReport {
181    /// Whether the broker settled and stayed settled.
182    pub fn converged(&self) -> bool {
183        self.time_to_converge.is_some()
184            && self.final_phase == BrokerPhase::Steady
185            && self.current_drift.is_zero()
186            && self.terminal_error.is_none()
187    }
188
189    fn record_sample(
190        &mut self,
191        requested: Split,
192        actual: Split,
193        elapsed: Duration,
194        capacity: usize,
195    ) {
196        let unchanged = self.consumer_trajectory.last() == Some(&requested.0)
197            && self.producer_trajectory.last() == Some(&requested.1)
198            && self.actual_consumer_trajectory.last() == Some(&actual.0)
199            && self.actual_producer_trajectory.last() == Some(&actual.1);
200        if unchanged {
201            return;
202        }
203        if self.consumer_trajectory.len() == capacity {
204            self.consumer_trajectory.remove(0);
205            self.producer_trajectory.remove(0);
206            self.actual_consumer_trajectory.remove(0);
207            self.actual_producer_trajectory.remove(0);
208            self.trajectory_elapsed.remove(0);
209            self.trace_samples_dropped += 1;
210        }
211        self.consumer_trajectory.push(requested.0);
212        self.producer_trajectory.push(requested.1);
213        self.actual_consumer_trajectory.push(actual.0);
214        self.actual_producer_trajectory.push(actual.1);
215        self.trajectory_elapsed.push(elapsed);
216    }
217
218    fn record_cap(
219        &mut self,
220        elapsed: Duration,
221        useful_cap: usize,
222        reason: ProducerCapReason,
223        capacity: usize,
224    ) {
225        let unchanged = self.cap_trajectory.last().is_some_and(|observation| {
226            observation.useful_cap == useful_cap && observation.reason == reason
227        });
228        if unchanged {
229            return;
230        }
231        if self.cap_trajectory.len() == capacity {
232            self.cap_trajectory.remove(0);
233        }
234        self.cap_trajectory.push(CapObservation {
235            elapsed,
236            useful_cap,
237            reason,
238        });
239    }
240
241    fn record_throughput(&mut self, throughput: f64, capacity: usize) {
242        if self.throughput_trace.len() == capacity {
243            self.throughput_trace.remove(0);
244            self.throughput_trace_dropped += 1;
245        }
246        self.throughput_trace.push(throughput);
247    }
248}
249
250#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
251pub struct CapObservation {
252    pub elapsed: Duration,
253    pub useful_cap: usize,
254    pub reason: ProducerCapReason,
255}
256
257/// The solved model at one instant.
258#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize)]
259pub struct Model {
260    /// Fraction of the pipeline's total per-item cost that falls on the
261    /// producer: `s_p / (s_p + s_c)`. The split is this times the budget.
262    pub producer_cost_share: f64,
263    /// Approximate 95% half-width of the cost-share estimate across retained
264    /// clean windows.
265    pub producer_cost_share_uncertainty: f64,
266    /// Producer slots the model asks for, before clamping.
267    pub ideal_producer_slots: usize,
268    /// Producer slots above which more concurrency has been observed not to
269    /// help. `usize::MAX` when nothing has been observed to bound it.
270    pub useful_cap: usize,
271    /// Evidence that established `useful_cap`.
272    pub useful_cap_reason: ProducerCapReason,
273    /// Movement thresholds after scaling the configured absolute floors by the
274    /// observed uncertainty at this budget.
275    pub effective_deadband_threads: usize,
276    pub effective_resurvey_distance: usize,
277}
278
279/// Evidence behind an empirical producer-concurrency ceiling.
280#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize)]
281#[serde(rename_all = "snake_case")]
282pub enum ProducerCapReason {
283    #[default]
284    None,
285    /// The producer persistently left granted execution slots idle.
286    Slack,
287    /// The producer persistently reported source-bound or inelastic pressure.
288    Source,
289    /// Both signals independently support a ceiling.
290    SlackAndSource,
291}
292
293#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize)]
294#[serde(rename_all = "snake_case")]
295pub enum BrokerPhase {
296    #[default]
297    Starting,
298    Survey,
299    Draining,
300    Blackout,
301    Ratifying,
302    Steady,
303}
304
305/// A broker sampling in the background. Dropping it stops the loop.
306pub struct RunningBroker {
307    stop: Arc<Stop>,
308    join: Option<JoinHandle<Result<BrokerReport, BrokerError>>>,
309}
310
311impl RunningBroker {
312    /// Stop sampling and collect the report.
313    pub fn finish(mut self) -> Result<BrokerReport, BrokerError> {
314        self.stop.set();
315        let Some(join) = self.join.take() else {
316            return Err(BrokerError::new(BrokerErrorKind::ThreadPanicked));
317        };
318        join.join()
319            .map_err(|_| BrokerError::new(BrokerErrorKind::ThreadPanicked))?
320    }
321}
322
323impl Drop for RunningBroker {
324    fn drop(&mut self) {
325        self.stop.set();
326        if let Some(h) = self.join.take() {
327            match h.join() {
328                Ok(Ok(_)) => {}
329                Ok(Err(error)) => tracing::error!(%error, "thread broker failed while dropping"),
330                Err(_) => tracing::error!("thread-broker sampler panicked while dropping"),
331            }
332        }
333    }
334}
335
336/// A split: consumer threads, producer slots.
337type Split = (usize, usize);
338
339/// Where the loop is.
340#[derive(Debug, Clone)]
341enum Phase {
342    /// Gathering cost evidence at the current split.
343    Survey,
344    /// The shrinking side has accepted its new target; wait until its actual
345    /// occupancy releases the slots before growing the other side.
346    Draining {
347        side: ResizeSide,
348        to: Split,
349        started: Instant,
350        next: Box<Phase>,
351    },
352    /// A move was just applied; measurements are contaminated until it settles.
353    Blackout { left: usize, next: Box<Phase> },
354    /// The opening bracket exhausted its wall budget. Restore the last proven
355    /// split before returning to ordinary steady-state control.
356    OpeningBracketRestore { restore: Split },
357    /// The move has settled. Is throughput actually better?
358    Ratify {
359        left: usize,
360        from: Split,
361        target: usize,
362        baseline: RateEstimate,
363        rates: Vec<f64>,
364        kind: RatificationKind,
365    },
366    /// Settled. Sleeping until the model's answer moves a long way, and stays
367    /// moved.
368    Steady { drifted_for: Duration },
369}
370
371#[derive(Debug, Clone, Copy, PartialEq, Eq)]
372enum RatificationKind {
373    Model,
374    OpeningModel,
375    NonlinearProbe,
376}
377
378impl Phase {
379    fn public(&self) -> BrokerPhase {
380        match self {
381            Self::Survey => BrokerPhase::Survey,
382            Self::Draining { .. } => BrokerPhase::Draining,
383            Self::Blackout { .. } | Self::OpeningBracketRestore { .. } => BrokerPhase::Blackout,
384            Self::Ratify { .. } => BrokerPhase::Ratifying,
385            Self::Steady { .. } => BrokerPhase::Steady,
386        }
387    }
388
389    fn drift_duration(&self) -> Duration {
390        match self {
391            Self::Steady { drifted_for } => *drifted_for,
392            _ => Duration::ZERO,
393        }
394    }
395
396    fn measurement_mode(&self) -> ProducerMeasurementMode {
397        match self {
398            Self::Survey | Self::Ratify { .. } | Self::OpeningBracketRestore { .. } => {
399                ProducerMeasurementMode::Calibration
400            }
401            Self::Draining { .. } | Self::Blackout { .. } | Self::Steady { .. } => {
402                ProducerMeasurementMode::Monitoring
403            }
404        }
405    }
406}
407
408impl<C: Consumer + 'static, P: Producer + 'static> ThreadBroker<C, P> {
409    /// Apply the starting split and begin sampling.
410    ///
411    /// The start is deliberately coarse. Under a solving controller it is only
412    /// the point at which the first rates are measured, not a position that has
413    /// to be walked away from one step at a time — which is what made the
414    /// starting point dominate the outcome under the previous control law. It
415    /// stays biased toward the consumer because a run that never needs decode
416    /// concurrency then never pays for any.
417    pub fn start(self) -> Result<RunningBroker, BrokerError> {
418        let floor = self.config.min_producer_slots.max(1);
419        let ceiling = self.budget - self.config.min_consumer_threads;
420        let coarse = (self.budget / 8).clamp(floor, ceiling);
421        let initial_producer = self
422            .initial_producer_slots
423            .unwrap_or(coarse)
424            .clamp(floor, ceiling);
425        let initial_consumer = self.budget - initial_producer;
426
427        self.consumer
428            .set_threads(initial_consumer)
429            .map_err(|source| {
430                BrokerError::new(BrokerErrorKind::ResizeRefused {
431                    side: ResizeSide::Consumer,
432                    requested: initial_consumer,
433                    source,
434                })
435            })?;
436        self.producer
437            .set_limit(initial_producer)
438            .map_err(|source| {
439                BrokerError::new(BrokerErrorKind::ResizeRefused {
440                    side: ResizeSide::Producer,
441                    requested: initial_producer,
442                    source,
443                })
444            })?;
445        let initial = (initial_consumer, initial_producer);
446
447        let stop = Stop::new();
448        let stop_thread = Arc::clone(&stop);
449        let join = std::thread::Builder::new()
450            .name("thread-broker".into())
451            .spawn(move || self.run(stop_thread, initial))
452            .map_err(|source| BrokerError::new(BrokerErrorKind::ThreadSpawn(source)))?;
453
454        Ok(RunningBroker {
455            stop,
456            join: Some(join),
457        })
458    }
459
460    fn run(self, stop: Arc<Stop>, initial: Split) -> Result<BrokerReport, BrokerError> {
461        let controller_cpu = crate::ThreadCpuTimer::start();
462        let cfg = self.config;
463        let steady_probe_interval = cfg.steady_probe_interval.unwrap_or(cfg.sample_interval);
464        let opening_bracket = match cfg.opening_policy {
465            OpeningPolicy::Fixed => None,
466            OpeningPolicy::Bracket(bracket) => Some(bracket),
467        };
468        let opening_bracket_enabled = opening_bracket.is_some()
469            && matches!(
470                self.steady_state_policy,
471                SteadyStatePolicy::Responsive | SteadyStatePolicy::FreezeAfterFullCalibration
472            );
473        let mut report = BrokerReport {
474            steady_state_policy: self.steady_state_policy,
475            steady_probe_interval,
476            opening_bracket: OpeningBracketReport {
477                outcome: match (opening_bracket, opening_bracket_enabled) {
478                    (None, _) => OpeningBracketOutcome::NotConfigured,
479                    (Some(_), false) => OpeningBracketOutcome::SkippedBySteadyPolicy,
480                    (Some(_), true) => OpeningBracketOutcome::Incomplete,
481                },
482                ..OpeningBracketReport::default()
483            },
484            ..BrokerReport::default()
485        };
486
487        let mut measurement_mode = ProducerMeasurementMode::Calibration;
488        self.producer
489            .set_monitoring_interval((steady_probe_interval / 4).max(Duration::from_nanos(1)));
490        self.producer.set_measurement_mode(measurement_mode);
491        self.consumer.set_measurement_mode(measurement_mode);
492
493        let started = now();
494        let mut prev_consumer = self.consumer.work();
495        let mut prev_producer = self.producer.work();
496        let mut prev_buffer = self.producer.buffered_items();
497        let mut prev_at = started;
498
499        // The broker's own view of the split, rather than reading it back. Both
500        // pools converge toward what they are told over some interval, so
501        // reading the current value back conflates "what I asked for" with
502        // "what has taken effect yet". The budget must be enforced against the
503        // former or two consecutive decisions can double-spend the same threads.
504        let mut split = initial;
505        // When the split last *changed*, which is what convergence means. A
506        // decision that is re-opened and reaches the same answer has not
507        // un-converged: nothing moved, no work was redone, and the allocation
508        // the run has been using is still the one it started with.
509        let mut last_split_change = started;
510
511        let mut costs = Costs::new(cfg.smoothing_windows);
512        // Throughput over the same span the ratify stage will measure, so the
513        // before/after comparison is like for like. A single pre-move window as
514        // the baseline made the test a coin flip: one unlucky window sets a bar
515        // the move cannot clear, or an unusually good one hides a genuine
516        // regression.
517        let opening_bracket_samples = opening_bracket
518            .map(|bracket| duration_samples(bracket.horizon, cfg.sample_interval))
519            .unwrap_or(0);
520        let mut recent = Recent::new(cfg.ratify_samples.max(opening_bracket_samples));
521        let mut caps = Caps::new(cfg.cap_history, cfg.cap_persistence);
522        // Targets that were tried and made throughput worse. FDP calls this the
523        // tabu set; without it a model that keeps recomputing the same answer
524        // re-applies a move that has already been measured as a regression.
525        let mut rejected: HashMap<usize, f64> = HashMap::new();
526        let mut epoch = 0u64;
527        let mut phase = Phase::Survey;
528        let mut warm_ended: Option<Instant> = None;
529        let mut surveyed = 0usize;
530        let mut settled_rate: Option<f64> = None;
531        let mut pressure_vetoed_for = Duration::ZERO;
532        // The full responsive policy pays a bounded startup probe when the
533        // isolated cost model collapses to the producer floor. This catches
534        // negative consumer scaling and allocation-dependent stage costs that
535        // no one-point service-cost estimate can identify. The cheaper
536        // convergence freeze is explicitly model-only; full-calibration freeze
537        // runs the same bounded probe as responsive mode before stopping.
538        let mut nonlinear_targets = std::collections::VecDeque::new();
539        let mut nonlinear_probe_complete = !opening_bracket_enabled;
540        let mut opening_bracket_started: Option<Instant> = None;
541        let mut opening_bracket_points_started = 0usize;
542        let mut nonlinear_override = false;
543        // The achieved estimate at the last retained nonlinear point. Reusing
544        // it makes the next probe compare adjacent points on the response
545        // curve; `recent` still contains pre-probe windows and is only the
546        // baseline for the first point.
547        let mut nonlinear_rate: Option<RateEstimate> = None;
548        // Fallback point in the current bounded exploration. The model answer
549        // is primary while its first apparent regression is being confirmed;
550        // a proven regression replaces it with the opening. Optional local
551        // candidates must prove improvement before replacing that fallback.
552        let mut nonlinear_best_split: Option<Split> = None;
553        let mut nonlinear_tried = std::collections::HashSet::new();
554        // Model drift is meaningful only relative to the model that established
555        // the settled split. Comparing only the newly rounded target makes a
556        // stable share near a slot boundary alternate forever between adjacent
557        // integers. Keep the settled evidence so reopening requires either a
558        // material share change or a changed empirical cap.
559        let mut settled_model: Option<Model> = None;
560
561        while !stop.is_set() {
562            // Establish convergence with one ordinary-cadence clean steady
563            // window. Only an already-converged responsive broker adopts the
564            // sparse cadence; otherwise a five-minute probe interval would also
565            // delay convergence (and freeze) by five minutes.
566            let mut interval =
567                if matches!(phase, Phase::Steady { .. }) && report.time_to_converge.is_some() {
568                    steady_probe_interval
569                } else {
570                    cfg.sample_interval
571                };
572            // A wall-bounded startup bracket must not inherit a long steady
573            // sleep just as its deadline arrives. Wake on the deadline itself;
574            // an in-flight drain remains governed by resize_timeout because it
575            // is unsafe to grow the opposite pool before shrink acknowledgement.
576            if !matches!(phase, Phase::Draining { .. })
577                && !nonlinear_probe_complete
578                && let (Some(bracket), Some(bracket_started)) =
579                    (opening_bracket, opening_bracket_started)
580            {
581                let remaining = bracket
582                    .total_budget
583                    .saturating_sub(now().saturating_duration_since(bracket_started));
584                interval = interval.min(remaining);
585            }
586            if stop.wait_timeout(interval) {
587                break;
588            }
589
590            let sampled_at = now();
591            report.controller_samples += 1;
592            let consumer = self.consumer.work();
593            let producer = self.producer.work();
594            for (side, previous, observed) in [
595                (ResizeSide::Consumer, prev_consumer, consumer),
596                (ResizeSide::Producer, prev_producer, producer),
597            ] {
598                if observed.busy_nanos < previous.busy_nanos || observed.items < previous.items {
599                    let actual = (self.consumer.live_threads(), self.producer.active_slots());
600                    return Err(runtime_failure(
601                        BrokerErrorKind::WorkCountersRegressed {
602                            side,
603                            previous,
604                            observed,
605                        },
606                        report,
607                        split,
608                        actual,
609                        phase.public(),
610                        epoch,
611                    ));
612                }
613            }
614            let window = sampled_at.saturating_duration_since(prev_at);
615            let dc = consumer.delta(prev_consumer);
616            let dp = producer.delta(prev_producer);
617            let buffer = self.producer.buffered_items();
618            let flow_stable = match (prev_buffer, buffer) {
619                (Some(previous), Some(current)) => {
620                    let drift = previous.abs_diff(current);
621                    let relative = (dp.items as f64 * cfg.max_buffer_drift_fraction).round() as u64;
622                    drift <= cfg.max_buffer_drift_items.max(relative)
623                }
624                _ => true,
625            };
626            prev_consumer = consumer;
627            prev_producer = producer;
628            prev_buffer = buffer;
629            prev_at = sampled_at;
630
631            // Discard the startup ramp entirely, re-basing as we go so the first
632            // recorded window contains none of it. Without this every workload
633            // reports ~99% consumer starvation in its first 75 ms, because the
634            // consumer's threads are still starting and nothing has been
635            // processed yet.
636            if sampled_at.saturating_duration_since(started) < cfg.warmup {
637                continue;
638            }
639            let warm_ended = *warm_ended.get_or_insert(sampled_at);
640
641            let pressure = self.producer.pressure();
642            match pressure {
643                ProducerPressure::SourceBound => report.source_bound_samples += 1,
644                ProducerPressure::Inelastic => report.inelastic_samples += 1,
645                _ => {}
646            }
647
648            // Throughput of the pipeline as a whole, which is what the
649            // accept/revert decision is actually about. Measured at the
650            // consumer, because the producer's output can run ahead into buffers
651            // and briefly show progress the pipeline did not make.
652            let throughput = rate_per_second(dc.items, window);
653
654            let usable = usable_window(dc, dp, window);
655            let resizing = matches!(phase, Phase::Draining { .. });
656            let clean = usable && flow_stable && !resizing;
657            // A zero-progress window is unusable for per-item service cost,
658            // but it is essential throughput evidence. Dropping it conditions
659            // the rate on batch completion and can make a bursty, slow split
660            // look as fast as a smooth one. Keep rate and cost eligibility
661            // deliberately separate.
662            let rate_clean = flow_stable && !resizing && !window.is_zero();
663            if rate_clean && !matches!(phase, Phase::Steady { .. }) {
664                report.record_throughput(throughput, cfg.trace_capacity);
665            }
666            if clean {
667                costs.push(dc.busy_nanos, dp.busy_nanos);
668                caps.observe_slack(dp, window, split.1);
669            } else if !resizing && !usable {
670                report.rejected_windows += 1;
671            } else if !resizing && !flow_stable {
672                report.flow_transient_windows += 1;
673            }
674            // Source/elasticity pressure describes producer admission directly;
675            // unlike busy-derived slack, it does not require equivalent work to
676            // have crossed the downstream buffer. Requiring stable flow here
677            // delayed a persistent source ceiling by 5--12 seconds on Gate G.
678            if !resizing {
679                caps.observe_pressure(window, split.1, pressure);
680            }
681            if rate_clean && matches!(phase, Phase::Survey | Phase::Steady { .. }) {
682                recent.push(dc.items, window);
683            }
684
685            let live = self.consumer.live_threads();
686            let active = self.producer.active_slots();
687            report.peak_controlled_slots = report
688                .peak_controlled_slots
689                .max(live.saturating_add(active));
690            report.peak_auxiliary_threads = report
691                .peak_auxiliary_threads
692                .max(self.producer.auxiliary_threads().saturating_add(1));
693            report.producer_measurement = self.producer.measurement_stats();
694            let idle = idle_fraction(dc.busy_nanos, live, window);
695
696            if !matches!(phase, Phase::Survey) {
697                pressure_vetoed_for = Duration::ZERO;
698            }
699            let opening_bracket_expired = !nonlinear_probe_complete
700                && opening_bracket.is_some_and(|bracket| {
701                    opening_bracket_started.is_some_and(|started| {
702                        sampled_at.saturating_duration_since(started) >= bracket.total_budget
703                    })
704                });
705
706            phase = match phase {
707                Phase::OpeningBracketRestore { restore } => {
708                    nonlinear_probe_complete = true;
709                    nonlinear_targets.clear();
710                    finish_opening_bracket(
711                        &mut report,
712                        &mut opening_bracket_started,
713                        OpeningBracketOutcome::BudgetExhausted,
714                        sampled_at,
715                    );
716                    settled_rate = nonlinear_rate.map(|rate| rate.mean);
717                    recent.clear();
718                    if split == restore {
719                        Phase::Steady {
720                            drifted_for: Duration::ZERO,
721                        }
722                    } else {
723                        report.reverts += 1;
724                        match self.begin_move(
725                            split,
726                            restore,
727                            Phase::Blackout {
728                                left: cfg.blackout_samples,
729                                next: Box::new(Phase::Steady {
730                                    drifted_for: Duration::ZERO,
731                                }),
732                            },
733                        ) {
734                            Ok((interim, phase)) => {
735                                split = interim;
736                                last_split_change = now();
737                                phase
738                            }
739                            Err(kind) => {
740                                report.final_consumer_threads = split.0;
741                                report.final_producer_limit = split.1;
742                                report.final_consumer_live = live;
743                                report.final_producer_active = active;
744                                return Err(runtime_failure(
745                                    kind,
746                                    report,
747                                    split,
748                                    (live, active),
749                                    BrokerPhase::Blackout,
750                                    epoch,
751                                ));
752                            }
753                        }
754                    }
755                }
756                Phase::Draining {
757                    side,
758                    to,
759                    started,
760                    next,
761                } => {
762                    let (observed, target) = match side {
763                        ResizeSide::Consumer => (live, to.0),
764                        ResizeSide::Producer => (active, to.1),
765                    };
766                    if observed <= target {
767                        let growth = match side {
768                            ResizeSide::Consumer => {
769                                self.producer.set_limit(to.1).map_err(|source| {
770                                    BrokerErrorKind::ResizeRefused {
771                                        side: ResizeSide::Producer,
772                                        requested: to.1,
773                                        source,
774                                    }
775                                })
776                            }
777                            ResizeSide::Producer => {
778                                self.consumer.set_threads(to.0).map_err(|source| {
779                                    BrokerErrorKind::ResizeRefused {
780                                        side: ResizeSide::Consumer,
781                                        requested: to.0,
782                                        source,
783                                    }
784                                })
785                            }
786                        };
787                        if let Err(kind) = growth {
788                            report.final_consumer_threads = split.0;
789                            report.final_producer_limit = split.1;
790                            report.final_consumer_live = live;
791                            report.final_producer_active = active;
792                            return Err(runtime_failure(
793                                kind,
794                                report,
795                                split,
796                                (live, active),
797                                BrokerPhase::Draining,
798                                epoch,
799                            ));
800                        }
801                        split = to;
802                        last_split_change = now();
803                        report.moves += 1;
804                        costs.clear();
805                        *next
806                    } else if sampled_at.saturating_duration_since(started) >= cfg.resize_timeout {
807                        report.final_consumer_threads = split.0;
808                        report.final_producer_limit = split.1;
809                        report.final_consumer_live = live;
810                        report.final_producer_active = active;
811                        let kind = BrokerErrorKind::ResizeTimedOut {
812                            side,
813                            target,
814                            observed,
815                            timeout: cfg.resize_timeout,
816                        };
817                        return Err(runtime_failure(
818                            kind,
819                            report,
820                            split,
821                            (live, active),
822                            BrokerPhase::Draining,
823                            epoch,
824                        ));
825                    } else {
826                        Phase::Draining {
827                            side,
828                            to,
829                            started,
830                            next,
831                        }
832                    }
833                }
834
835                Phase::Blackout { left, next } => {
836                    if opening_bracket_expired {
837                        Phase::OpeningBracketRestore {
838                            restore: nonlinear_best_split.unwrap_or(initial),
839                        }
840                    } else if !rate_clean {
841                        Phase::Blackout { left, next }
842                    } else if left > 1 {
843                        Phase::Blackout {
844                            left: left - 1,
845                            next,
846                        }
847                    } else {
848                        // Everything measured while the pools were converging is
849                        // about a split that no longer exists.
850                        costs.clear();
851                        *next
852                    }
853                }
854
855                Phase::Ratify {
856                    left,
857                    from,
858                    target,
859                    baseline,
860                    mut rates,
861                    kind,
862                } => {
863                    if opening_bracket_expired {
864                        Phase::OpeningBracketRestore {
865                            restore: nonlinear_best_split.unwrap_or(initial),
866                        }
867                    } else if !rate_clean {
868                        Phase::Ratify {
869                            left,
870                            from,
871                            target,
872                            baseline,
873                            rates,
874                            kind,
875                        }
876                    } else {
877                        rates.push(rate_per_second(dc.items, window));
878                        if matches!(
879                            kind,
880                            RatificationKind::OpeningModel | RatificationKind::NonlinearProbe
881                        ) {
882                            report.opening_bracket.samples += 1;
883                        }
884                        if left > 1 {
885                            Phase::Ratify {
886                                left: left - 1,
887                                from,
888                                target,
889                                baseline,
890                                rates,
891                                kind,
892                            }
893                        } else {
894                            let achieved = RateEstimate::from_rates(rates.iter().copied());
895                            let opening_model_max_samples =
896                                cfg.ratify_samples.max(opening_bracket_samples);
897                            let confirm_opening_model_regression = kind
898                                == RatificationKind::OpeningModel
899                                && rates.len() < opening_model_max_samples
900                                && compare_opening_model_rates(
901                                    baseline,
902                                    achieved,
903                                    cfg.regression_tolerance,
904                                ) == RatificationOutcome::Regressed;
905                            if confirm_opening_model_regression {
906                                tracing::debug!(
907                                    "thread-broker: extending apparent opening-model regression \
908                                     from {} to {} samples",
909                                    rates.len(),
910                                    opening_model_max_samples,
911                                );
912                                Phase::Ratify {
913                                    left: opening_model_max_samples - rates.len(),
914                                    from,
915                                    target,
916                                    baseline,
917                                    rates,
918                                    kind,
919                                }
920                            } else {
921                                if matches!(
922                                    kind,
923                                    RatificationKind::OpeningModel
924                                        | RatificationKind::NonlinearProbe
925                                ) {
926                                    report.opening_bracket.points_measured += 1;
927                                }
928                                if kind == RatificationKind::NonlinearProbe {
929                                    let improved = nonlinear_probe_improved(
930                                        baseline,
931                                        achieved,
932                                        cfg.regression_tolerance,
933                                    );
934                                    if improved {
935                                        tracing::debug!(
936                                            "thread-broker: nonlinear probe kept producer {} -> {} \
937                                         ({:.0} [{:.0}, {:.0}] vs {:.0} [{:.0}, {:.0}] items/s)",
938                                            from.1,
939                                            target,
940                                            achieved.mean,
941                                            achieved.lower(),
942                                            achieved.upper(),
943                                            baseline.mean,
944                                            baseline.lower(),
945                                            baseline.upper(),
946                                        );
947                                        report.nonlinear_probe_improvements += 1;
948                                        nonlinear_override = true;
949                                        nonlinear_rate = Some(achieved);
950                                        nonlinear_best_split = Some(split);
951                                        settled_rate = Some(achieved.mean);
952                                        costs.clear();
953                                        // The model has already lost to the
954                                        // opening. The first local point is on the
955                                        // opposite side of that rejected answer;
956                                        // once it proves an improvement, continuing
957                                        // back toward the rejected model cannot
958                                        // justify another startup move.
959                                        nonlinear_probe_complete = true;
960                                        nonlinear_targets.clear();
961                                        finish_opening_bracket(
962                                            &mut report,
963                                            &mut opening_bracket_started,
964                                            OpeningBracketOutcome::AlternativeSelected,
965                                            sampled_at,
966                                        );
967                                        Phase::Steady {
968                                            drifted_for: Duration::ZERO,
969                                        }
970                                    } else {
971                                        let comparison = compare_rates(
972                                            baseline,
973                                            achieved,
974                                            cfg.regression_tolerance,
975                                        );
976                                        tracing::debug!(
977                                            "thread-broker: nonlinear probe did not retain producer {} \
978                                         ({:.0} [{:.0}, {:.0}] vs {:.0} [{:.0}, {:.0}] items/s)",
979                                            target,
980                                            achieved.mean,
981                                            achieved.lower(),
982                                            achieved.upper(),
983                                            baseline.mean,
984                                            baseline.lower(),
985                                            baseline.upper(),
986                                        );
987                                        if comparison == RatificationOutcome::Inconclusive {
988                                            report.inconclusive_ratifications += 1;
989                                        }
990                                        // Every optional point has the higher
991                                        // burden of proving improvement. A failure
992                                        // does not discard a queued candidate on
993                                        // the other side of the opening: that point
994                                        // is what turns a one-sided guess into a
995                                        // bracket.
996                                        costs.clear();
997                                        if nonlinear_targets.is_empty() {
998                                            nonlinear_probe_complete = true;
999                                            let restore = nonlinear_best_split.unwrap_or(from);
1000                                            let outcome = retained_bracket_outcome(
1001                                                nonlinear_override,
1002                                                nonlinear_best_split,
1003                                                initial,
1004                                            );
1005                                            finish_opening_bracket(
1006                                                &mut report,
1007                                                &mut opening_bracket_started,
1008                                                outcome,
1009                                                sampled_at,
1010                                            );
1011                                            settled_rate = nonlinear_rate.map(|rate| rate.mean);
1012                                            nonlinear_best_split = None;
1013                                            recent.clear();
1014                                            if split == restore {
1015                                                Phase::Steady {
1016                                                    drifted_for: Duration::ZERO,
1017                                                }
1018                                            } else {
1019                                                report.reverts += 1;
1020                                                match self.begin_move(
1021                                                    split,
1022                                                    restore,
1023                                                    Phase::Blackout {
1024                                                        left: cfg.blackout_samples,
1025                                                        next: Box::new(Phase::Steady {
1026                                                            drifted_for: Duration::ZERO,
1027                                                        }),
1028                                                    },
1029                                                ) {
1030                                                    Ok((interim, phase)) => {
1031                                                        split = interim;
1032                                                        last_split_change = now();
1033                                                        phase
1034                                                    }
1035                                                    Err(error_kind) => {
1036                                                        report.final_consumer_threads = split.0;
1037                                                        report.final_producer_limit = split.1;
1038                                                        report.final_consumer_live = live;
1039                                                        report.final_producer_active = active;
1040                                                        return Err(runtime_failure(
1041                                                            error_kind,
1042                                                            report,
1043                                                            split,
1044                                                            (live, active),
1045                                                            BrokerPhase::Ratifying,
1046                                                            epoch,
1047                                                        ));
1048                                                    }
1049                                                }
1050                                            }
1051                                        } else {
1052                                            // Restore the last proven point before
1053                                            // measuring the queued candidate on the
1054                                            // other side of the opening. Otherwise
1055                                            // it inherits cold-start cost from a
1056                                            // failed endpoint while being compared
1057                                            // with a warm baseline.
1058                                            let restore = nonlinear_best_split.unwrap_or(from);
1059                                            if split != restore {
1060                                                match self.begin_move(
1061                                                    split,
1062                                                    restore,
1063                                                    Phase::Blackout {
1064                                                        left: cfg.blackout_samples,
1065                                                        next: Box::new(Phase::Steady {
1066                                                            drifted_for: Duration::ZERO,
1067                                                        }),
1068                                                    },
1069                                                ) {
1070                                                    Ok((interim, phase)) => {
1071                                                        split = interim;
1072                                                        last_split_change = now();
1073                                                        phase
1074                                                    }
1075                                                    Err(error_kind) => {
1076                                                        report.final_consumer_threads = split.0;
1077                                                        report.final_producer_limit = split.1;
1078                                                        report.final_consumer_live = live;
1079                                                        report.final_producer_active = active;
1080                                                        return Err(runtime_failure(
1081                                                            error_kind,
1082                                                            report,
1083                                                            split,
1084                                                            (live, active),
1085                                                            BrokerPhase::Ratifying,
1086                                                            epoch,
1087                                                        ));
1088                                                    }
1089                                                }
1090                                            } else {
1091                                                Phase::Steady {
1092                                                    drifted_for: Duration::ZERO,
1093                                                }
1094                                            }
1095                                        }
1096                                    }
1097                                } else {
1098                                    // Only a regression reverts; absence of improvement does
1099                                    // not. Near the optimum the surface is locally flat --
1100                                    // measured, 22 and 23 producer slots of 64 differ by 3%,
1101                                    // which is inside this comparison's own noise -- so "no
1102                                    // better" is a perfectly ordinary reading for a *correct*
1103                                    // move. Treating it as failure would revert the broker to
1104                                    // wherever it happened to start, which is the one place
1105                                    // with no evidence for it at all.
1106                                    //
1107                                    // Flink reaches the same conclusion from the other end:
1108                                    // its `detectIneffectiveScaleUp` compares against a
1109                                    // model-*predicted* increase rather than any increase,
1110                                    // and still ships disabled by default.
1111                                    let opening_model_validation =
1112                                        kind == RatificationKind::OpeningModel;
1113                                    let comparison = if opening_model_validation {
1114                                        compare_opening_model_rates(
1115                                            baseline,
1116                                            achieved,
1117                                            cfg.regression_tolerance,
1118                                        )
1119                                    } else {
1120                                        compare_rates(baseline, achieved, cfg.regression_tolerance)
1121                                    };
1122                                    let cap_confirms_opening_model = opening_model_validation
1123                                        && empirical_cap_confirms_target(
1124                                            report.final_model,
1125                                            target,
1126                                        );
1127                                    if opening_model_validation
1128                                        && comparison != RatificationOutcome::Kept
1129                                        && !cap_confirms_opening_model
1130                                    {
1131                                        if comparison == RatificationOutcome::Inconclusive {
1132                                            report.inconclusive_ratifications += 1;
1133                                        }
1134                                        tracing::debug!(
1135                                            "thread-broker: exploring around opening {} after {} \
1136                                             model point {} ({:.0} vs {:.0} items/s)",
1137                                            from.1,
1138                                            if comparison == RatificationOutcome::Regressed {
1139                                                "regressed"
1140                                            } else {
1141                                                "inconclusive"
1142                                            },
1143                                            target,
1144                                            achieved.mean,
1145                                            baseline.mean,
1146                                        );
1147                                        // The opening is a pivot and a measured
1148                                        // high-water mark, not a fallback. An
1149                                        // optional point must beat the higher of
1150                                        // the opening/model rates, while failure
1151                                        // still returns to the primary model.
1152                                        // This lets an ambiguous t8 comparison
1153                                        // discover five without letting a
1154                                        // temporarily depressed t32 model make
1155                                        // another mediocre point look better.
1156                                        nonlinear_rate = Some(if achieved.mean >= baseline.mean {
1157                                            achieved
1158                                        } else {
1159                                            baseline
1160                                        });
1161                                        nonlinear_best_split = Some(split);
1162                                        nonlinear_tried.insert(from.1);
1163                                        nonlinear_tried.insert(target);
1164                                        let bracket = opening_bracket
1165                                            .expect("opening model validation requires config");
1166                                        nonlinear_targets = opening_bracket_targets(
1167                                            self.budget,
1168                                            cfg.min_consumer_threads,
1169                                            cfg.min_producer_slots,
1170                                            from.1,
1171                                            target,
1172                                            bracket
1173                                                .max_points
1174                                                .saturating_sub(opening_bracket_points_started),
1175                                        );
1176                                        settled_rate = Some(achieved.mean);
1177                                        costs.clear();
1178                                        Phase::Steady {
1179                                            drifted_for: Duration::ZERO,
1180                                        }
1181                                    } else if comparison == RatificationOutcome::Regressed
1182                                        && !opening_model_validation
1183                                    {
1184                                        tracing::debug!(
1185                                            "thread-broker: reverting producer {} -> {} after \
1186                                             measured regression ({:.0} vs {:.0} items/s)",
1187                                            target,
1188                                            from.1,
1189                                            achieved.mean,
1190                                            baseline.mean,
1191                                        );
1192                                        let rejected_share = report
1193                                            .final_model
1194                                            .map(|model| model.producer_cost_share)
1195                                            .unwrap_or_default();
1196                                        rejected.insert(target, rejected_share);
1197                                        report.rejections.push(Rejection {
1198                                            epoch,
1199                                            producer_target: target,
1200                                            baseline_rate: baseline.mean,
1201                                            achieved_rate: achieved.mean,
1202                                            baseline_uncertainty: baseline.half_width,
1203                                            achieved_uncertainty: achieved.half_width,
1204                                            producer_cost_share: rejected_share,
1205                                        });
1206                                        settled_rate = Some(baseline.mean);
1207                                        report.reverts += 1;
1208                                        costs.clear();
1209                                        match self.begin_move(
1210                                            split,
1211                                            from,
1212                                            Phase::Blackout {
1213                                                left: cfg.blackout_samples,
1214                                                next: Box::new(Phase::Steady {
1215                                                    drifted_for: Duration::ZERO,
1216                                                }),
1217                                            },
1218                                        ) {
1219                                            Ok((interim, phase)) => {
1220                                                split = interim;
1221                                                last_split_change = now();
1222                                                phase
1223                                            }
1224                                            Err(kind) => {
1225                                                report.final_consumer_threads = split.0;
1226                                                report.final_producer_limit = split.1;
1227                                                report.final_consumer_live = live;
1228                                                report.final_producer_active = active;
1229                                                return Err(runtime_failure(
1230                                                    kind,
1231                                                    report,
1232                                                    split,
1233                                                    (live, active),
1234                                                    BrokerPhase::Ratifying,
1235                                                    epoch,
1236                                                ));
1237                                            }
1238                                        }
1239                                    } else {
1240                                        if comparison == RatificationOutcome::Inconclusive {
1241                                            report.inconclusive_ratifications += 1;
1242                                        }
1243                                        if cap_confirms_opening_model
1244                                            && comparison != RatificationOutcome::Kept
1245                                        {
1246                                            tracing::debug!(
1247                                                "thread-broker: retaining opening-model producer \
1248                                                 {} despite {} throughput evidence; empirical {} \
1249                                                 cap independently confirms the target",
1250                                                target,
1251                                                if comparison == RatificationOutcome::Regressed {
1252                                                    "regressed"
1253                                                } else {
1254                                                    "inconclusive"
1255                                                },
1256                                                match report
1257                                                    .final_model
1258                                                    .expect("confirmed model has a snapshot")
1259                                                    .useful_cap_reason
1260                                                {
1261                                                    ProducerCapReason::Slack => "slack",
1262                                                    ProducerCapReason::Source => "source",
1263                                                    ProducerCapReason::SlackAndSource => {
1264                                                        "slack-and-source"
1265                                                    }
1266                                                    ProducerCapReason::None => "missing",
1267                                                },
1268                                            );
1269                                        }
1270                                        // The differing model answer was measured
1271                                        // and retained. The bracket has answered
1272                                        // its only question, so it must not add an
1273                                        // exploratory tax after agreement.
1274                                        if opening_model_validation {
1275                                            nonlinear_probe_complete = true;
1276                                            nonlinear_targets.clear();
1277                                            nonlinear_rate = None;
1278                                            nonlinear_best_split = None;
1279                                            finish_opening_bracket(
1280                                                &mut report,
1281                                                &mut opening_bracket_started,
1282                                                OpeningBracketOutcome::ModelSelected,
1283                                                sampled_at,
1284                                            );
1285                                        }
1286                                        // Kept. Re-survey once: the rates may have been
1287                                        // measured under a split that distorted them, and
1288                                        // DS2 reports convergence in at most a few such
1289                                        // rounds. The deadband and the tabu set are what
1290                                        // stop this from cycling.
1291                                        surveyed = 0;
1292                                        settled_rate = None;
1293                                        Phase::Survey
1294                                    }
1295                                }
1296                            }
1297                        }
1298                    }
1299                }
1300
1301                Phase::Steady { .. } if opening_bracket_expired => Phase::OpeningBracketRestore {
1302                    restore: nonlinear_best_split.unwrap_or(initial),
1303                },
1304
1305                Phase::Steady { drifted_for } => {
1306                    // Keep solving, but act only on a large drift. The model is
1307                    // the change detector: it is computed from the cost ratio
1308                    // rather than from the outcome, so it moves as soon as the
1309                    // workload does -- including when the change is an
1310                    // *opportunity* rather than a loss, which a throughput
1311                    // monitor cannot see at all.
1312                    let solved = costs.solve(self.budget, &cfg, &caps);
1313                    // Refresh the diagnostic even when nothing is acted on: a
1314                    // report read at the end of a long settled run should
1315                    // describe the workload as it finished, not as it looked at
1316                    // the one sample that happened to settle it.
1317                    if let Some(s) = &solved {
1318                        report.final_model = Some(s.snapshot);
1319                    }
1320                    let bracket = opening_bracket;
1321                    let bracket_budget_expired = bracket.is_some_and(|bracket| {
1322                        opening_bracket_started.is_some_and(|started| {
1323                            sampled_at.saturating_duration_since(started)
1324                                + bracket.horizon.saturating_mul(2)
1325                                > bracket.total_budget
1326                        })
1327                    });
1328                    let bracket_has_point_budget = bracket
1329                        .is_some_and(|bracket| opening_bracket_points_started < bracket.max_points);
1330                    let probe_target = if !nonlinear_probe_complete
1331                        && !bracket_budget_expired
1332                        && bracket_has_point_budget
1333                    {
1334                        let target = loop {
1335                            match nonlinear_targets.pop_front() {
1336                                Some(target)
1337                                    if target != split.1 && !nonlinear_tried.contains(&target) =>
1338                                {
1339                                    break Some(target);
1340                                }
1341                                Some(_) => {}
1342                                None => break None,
1343                            }
1344                        };
1345                        if target.is_none() {
1346                            nonlinear_probe_complete = true;
1347                            let outcome = retained_bracket_outcome(
1348                                nonlinear_override,
1349                                nonlinear_best_split,
1350                                initial,
1351                            );
1352                            finish_opening_bracket(
1353                                &mut report,
1354                                &mut opening_bracket_started,
1355                                outcome,
1356                                sampled_at,
1357                            );
1358                        }
1359                        target
1360                    } else {
1361                        if !nonlinear_probe_complete {
1362                            nonlinear_probe_complete = true;
1363                            nonlinear_targets.clear();
1364                            let outcome = if bracket_budget_expired {
1365                                OpeningBracketOutcome::BudgetExhausted
1366                            } else {
1367                                retained_bracket_outcome(
1368                                    nonlinear_override,
1369                                    nonlinear_best_split,
1370                                    initial,
1371                                )
1372                            };
1373                            finish_opening_bracket(
1374                                &mut report,
1375                                &mut opening_bracket_started,
1376                                outcome,
1377                                sampled_at,
1378                            );
1379                        }
1380                        None
1381                    };
1382
1383                    if let Some(target) = probe_target {
1384                        opening_bracket_points_started += 1;
1385                        report.nonlinear_probes += 1;
1386                        let from = split;
1387                        let to = (self.budget - target, target);
1388                        // A local bracket point is optional and must prove a real
1389                        // improvement, unlike a model-directed move which only
1390                        // has to avoid regression. Progress publishers must be
1391                        // fine grained enough that the ordinary ratification
1392                        // horizon is representative; stretching the horizon to
1393                        // compensate for bursty publication only delays the
1394                        // decision and hides the real measurement problem.
1395                        let probe_samples = opening_bracket_samples;
1396                        let probe_blackout_samples = cfg.blackout_samples;
1397                        let baseline = nonlinear_rate.unwrap_or_else(|| recent.estimate());
1398                        nonlinear_rate.get_or_insert(baseline);
1399                        nonlinear_best_split.get_or_insert(from);
1400                        nonlinear_tried.insert(from.1);
1401                        nonlinear_tried.insert(target);
1402                        tracing::debug!(
1403                            "thread-broker: probing opening bracket producer {} -> {}",
1404                            from.1,
1405                            target,
1406                        );
1407                        match self.begin_move(
1408                            from,
1409                            to,
1410                            Phase::Blackout {
1411                                left: probe_blackout_samples,
1412                                next: Box::new(Phase::Ratify {
1413                                    left: probe_samples,
1414                                    from,
1415                                    target,
1416                                    baseline,
1417                                    rates: Vec::with_capacity(probe_samples),
1418                                    kind: RatificationKind::NonlinearProbe,
1419                                }),
1420                            },
1421                        ) {
1422                            Ok((interim, phase)) => {
1423                                split = interim;
1424                                last_split_change = now();
1425                                phase
1426                            }
1427                            Err(kind) => {
1428                                report.final_consumer_threads = split.0;
1429                                report.final_producer_limit = split.1;
1430                                report.final_consumer_live = live;
1431                                report.final_producer_active = active;
1432                                return Err(runtime_failure(
1433                                    kind,
1434                                    report,
1435                                    split,
1436                                    (live, active),
1437                                    BrokerPhase::Steady,
1438                                    epoch,
1439                                ));
1440                            }
1441                        }
1442                    } else {
1443                        let model_drifted = !nonlinear_override
1444                            && solved.as_ref().is_some_and(|s| {
1445                                let far_enough = s.target.abs_diff(split.1)
1446                                    >= s.snapshot.effective_resurvey_distance;
1447                                let changed_since_settling = settled_model.is_none_or(|settled| {
1448                                    let combined_uncertainty = settled
1449                                        .producer_cost_share_uncertainty
1450                                        + s.snapshot.producer_cost_share_uncertainty;
1451                                    let share_changed = (settled.producer_cost_share
1452                                        - s.snapshot.producer_cost_share)
1453                                        .abs()
1454                                        > combined_uncertainty.max(0.05);
1455                                    let cap_changed = settled.useful_cap != s.snapshot.useful_cap
1456                                        && (settled.useful_cap_reason != ProducerCapReason::None
1457                                            || s.snapshot.useful_cap_reason
1458                                                != ProducerCapReason::None);
1459                                    share_changed || cap_changed
1460                                });
1461                                let pressure_allows =
1462                                    !(s.target < split.1 && pressure == ProducerPressure::Starved);
1463                                let changed_since_rejection =
1464                                    rejected.get(&s.target).is_none_or(|rejected_share| {
1465                                        (s.snapshot.producer_cost_share - rejected_share).abs()
1466                                            > (2.0 * s.snapshot.producer_cost_share_uncertainty)
1467                                                .max(0.05)
1468                                    });
1469                                far_enough
1470                                    && changed_since_settling
1471                                    && pressure_allows
1472                                    && changed_since_rejection
1473                            });
1474                        let current_rate = recent.estimate();
1475                        // A common-mode rate change with an unchanged cost share
1476                        // does not imply a different split. Reopening on throughput
1477                        // alone made ordinary machine-load variation (and the tail
1478                        // of a finite input) look like a workload epoch. The rate
1479                        // detector remains useful only when it can expire rejection
1480                        // evidence: a previously bad target may become valid after
1481                        // the response surface changes even if the local cost share
1482                        // rounds to the same split.
1483                        let throughput_drifted = settled_rate.is_some_and(|baseline| {
1484                            if baseline <= 0.0 {
1485                                return false;
1486                            }
1487                            let regressed = current_rate.upper() < baseline * 0.9;
1488                            let improved = current_rate.lower() > baseline * 1.1;
1489                            // After measuring a nonlinear response surface, an
1490                            // upside-only rate change at the retained split is
1491                            // not evidence that another split is better. The
1492                            // first scATAC probe otherwise repeatedly reopened
1493                            // while its reduced consumer set warmed up. A real
1494                            // loss still reopens it. Model rejections retain the
1495                            // two-sided test so changed work can expire evidence
1496                            // against a previously rejected target.
1497                            (nonlinear_override && regressed)
1498                                || (!nonlinear_override
1499                                    && !rejected.is_empty()
1500                                    && (regressed || improved))
1501                        });
1502                        let next_drift =
1503                            consecutive_drift_duration(drifted_for, window, cfg.sample_interval);
1504                        match model_drifted || throughput_drifted {
1505                            // Distance alone is not enough: the noise in the solved
1506                            // target grows with the budget while a fixed distance
1507                            // does not, so at 64 threads ordinary jitter clears a
1508                            // band that 32 threads never approaches. Persistence is
1509                            // what tells the two apart -- noise does not survive a
1510                            // second of consecutive windows, a regime change does.
1511                            true if next_drift >= cfg.resurvey_persistence => {
1512                                tracing::debug!(
1513                                    "thread-broker: persistent workload change at producer split {} \
1514                                 (model target {}, throughput {:.0}/s); re-opening",
1515                                    split.1,
1516                                    solved.as_ref().map_or(split.1, |model| model.target),
1517                                    current_rate.mean,
1518                                );
1519                                report.resurveys += 1;
1520                                epoch += 1;
1521                                rejected.clear();
1522                                nonlinear_override = false;
1523                                nonlinear_rate = None;
1524                                nonlinear_best_split = None;
1525                                nonlinear_tried.clear();
1526                                // Opening calibration is deliberately
1527                                // startup-only. A later regime change reuses
1528                                // the ordinary model/ratification path and
1529                                // cannot re-arm the bracket or its fine
1530                                // measurement cadence.
1531                                nonlinear_targets.clear();
1532                                nonlinear_probe_complete = true;
1533                                settled_model = None;
1534                                // Persistent drift was itself measured from a
1535                                // full clean cost window. Reuse that evidence
1536                                // and require one fresh confirmation instead of
1537                                // paying another complete smoothing horizon;
1538                                // this keeps recovery under one second without
1539                                // weakening the 800 ms false-resurvey guard.
1540                                surveyed = cfg.smoothing_windows.saturating_sub(1);
1541                                settled_rate = None;
1542                                Phase::Survey
1543                            }
1544                            true => Phase::Steady {
1545                                drifted_for: next_drift,
1546                            },
1547                            false => {
1548                                if settled_model.is_none() {
1549                                    settled_model = solved.map(|model| model.snapshot);
1550                                }
1551                                // Measured to the last split change, not to
1552                                // this sample: a run that opened at the right
1553                                // allocation and never moved converged
1554                                // immediately, however long it then ran.
1555                                report.time_to_converge =
1556                                    Some(last_split_change.saturating_duration_since(warm_ended));
1557                                settled_rate.get_or_insert(current_rate.mean);
1558                                Phase::Steady {
1559                                    drifted_for: Duration::ZERO,
1560                                }
1561                            }
1562                        }
1563                    }
1564                }
1565
1566                Phase::Survey => {
1567                    surveyed += clean as usize;
1568                    match costs.solve(self.budget, &cfg, &caps) {
1569                        // Not enough evidence yet, or one side has produced no
1570                        // work at all. Keep looking rather than guessing.
1571                        _ if surveyed < cfg.smoothing_windows => Phase::Survey,
1572                        None => Phase::Survey,
1573                        Some(model) => {
1574                            // A target that regressed is tabu only in this
1575                            // workload epoch. Keep the last ratified split
1576                            // until a proven regime change opens a new epoch,
1577                            // rather than walking through unsupported neighbors.
1578                            let mut target = if rejected.contains_key(&model.target) {
1579                                split.1
1580                            } else {
1581                                model.target
1582                            };
1583                            let opening_model_disagrees = opening_bracket_enabled
1584                                && !nonlinear_probe_complete
1585                                && split == initial
1586                                && model.target != initial.1;
1587                            // The cost model is intentionally primary, but it
1588                            // assumes allocation-independent service cost. At a
1589                            // low allocation, a decoder can under-report the
1590                            // thread-time a second slot would unlock. A live
1591                            // `Starved` signal may therefore veto *removing* a
1592                            // slot that still has runnable work behind it. It
1593                            // never chooses a larger target, preserving the
1594                            // isolated cost-share solution instead of reviving
1595                            // the old starvation hill climber.
1596                            let current_capacity_used =
1597                                producer_capacity_used(active, split.1, dp.busy_nanos, window);
1598                            let source_growth_wait = target > split.1
1599                                && matches!(
1600                                    pressure,
1601                                    ProducerPressure::SourceBound | ProducerPressure::Inelastic
1602                                )
1603                                && model.snapshot.useful_cap_reason == ProducerCapReason::None;
1604                            let pressure_veto = target < split.1
1605                                && pressure == ProducerPressure::Starved
1606                                && current_capacity_used
1607                                && !opening_model_disagrees;
1608                            if pressure_veto {
1609                                target = split.1;
1610                                report.pressure_vetoed_shrinks += 1;
1611                                pressure_vetoed_for += window;
1612                            } else {
1613                                pressure_vetoed_for = Duration::ZERO;
1614                            }
1615                            let distance = target.abs_diff(split.1);
1616                            let opening_disagrees = opening_model_disagrees;
1617                            if opening_bracket_enabled
1618                                && !nonlinear_probe_complete
1619                                && split == initial
1620                                && target == initial.1
1621                            {
1622                                finish_opening_bracket(
1623                                    &mut report,
1624                                    &mut opening_bracket_started,
1625                                    OpeningBracketOutcome::ModelAgreed,
1626                                    sampled_at,
1627                                );
1628                                nonlinear_probe_complete = true;
1629                            } else if opening_disagrees && opening_bracket_started.is_none() {
1630                                opening_bracket_started = Some(sampled_at);
1631                            }
1632                            // A single queued snapshot can be a short decode
1633                            // burst, especially for mapping-heavy modalities.
1634                            // Keep surveying until starvation has persisted as
1635                            // long as other cap evidence before accepting the
1636                            // current split as the bounded bracket correction.
1637                            if source_growth_wait {
1638                                // Direct source pressure can arrive before its
1639                                // duration-based cap is mature. Do not race a
1640                                // large growth move against that evidence; keep
1641                                // surveying until the signal either clears or
1642                                // establishes the current grant as the cap.
1643                                Phase::Survey
1644                            } else if pressure_veto && pressure_vetoed_for < cfg.cap_persistence {
1645                                Phase::Survey
1646                            } else if opening_disagrees && recent.len() < opening_bracket_samples {
1647                                // Compare like-sized blocks. Starting the model
1648                                // candidate with only the smoothing horizon in
1649                                // its baseline made short, bursty workloads
1650                                // needlessly inconclusive.
1651                                Phase::Survey
1652                            } else if opening_disagrees
1653                                && opening_bracket.is_some_and(|bracket| {
1654                                    opening_bracket_started.is_some_and(|started| {
1655                                        sampled_at.saturating_duration_since(started)
1656                                            + bracket.horizon.saturating_mul(2)
1657                                            > bracket.total_budget
1658                                    })
1659                                })
1660                            {
1661                                nonlinear_probe_complete = true;
1662                                finish_opening_bracket(
1663                                    &mut report,
1664                                    &mut opening_bracket_started,
1665                                    OpeningBracketOutcome::BudgetExhausted,
1666                                    sampled_at,
1667                                );
1668                                report.final_model = Some(model.snapshot);
1669                                settled_rate = Some(recent.estimate().mean);
1670                                Phase::Steady {
1671                                    drifted_for: Duration::ZERO,
1672                                }
1673                            } else if distance < model.snapshot.effective_deadband_threads
1674                                && !opening_disagrees
1675                            {
1676                                report.final_model = Some(model.snapshot);
1677                                settled_rate = Some(recent.estimate().mean);
1678                                Phase::Steady {
1679                                    drifted_for: Duration::ZERO,
1680                                }
1681                            } else {
1682                                let from = split;
1683                                let ratification_kind = if opening_disagrees {
1684                                    opening_bracket_points_started += 1;
1685                                    RatificationKind::OpeningModel
1686                                } else {
1687                                    RatificationKind::Model
1688                                };
1689                                let ratify_samples = if opening_disagrees {
1690                                    opening_bracket_samples
1691                                } else {
1692                                    cfg.ratify_samples
1693                                };
1694                                let ratify_capacity = if opening_disagrees {
1695                                    ratify_samples.max(cfg.ratify_samples)
1696                                } else {
1697                                    ratify_samples
1698                                };
1699                                let blackout_samples = cfg.blackout_samples;
1700                                tracing::debug!(
1701                                    "thread-broker: solved producer {} -> {} \
1702                                     (cost share {:.2}, cap {})",
1703                                    from.1,
1704                                    target,
1705                                    model.snapshot.producer_cost_share,
1706                                    fmt_cap(model.snapshot.useful_cap),
1707                                );
1708                                report.final_model = Some(model.snapshot);
1709                                let to = (self.budget - target, target);
1710                                if opening_disagrees {
1711                                    // Until a material, separated regression is
1712                                    // confirmed, absence of evidence favors the
1713                                    // primary model over the opening hint. This
1714                                    // also makes a bracket deadline during the
1715                                    // confirmation extension retain the model.
1716                                    nonlinear_best_split = Some(to);
1717                                }
1718                                match self.begin_move(
1719                                    from,
1720                                    to,
1721                                    Phase::Blackout {
1722                                        left: blackout_samples,
1723                                        next: Box::new(Phase::Ratify {
1724                                            left: ratify_samples,
1725                                            from,
1726                                            target,
1727                                            baseline: recent.estimate(),
1728                                            rates: Vec::with_capacity(ratify_capacity),
1729                                            kind: ratification_kind,
1730                                        }),
1731                                    },
1732                                ) {
1733                                    Ok((interim, phase)) => {
1734                                        split = interim;
1735                                        last_split_change = now();
1736                                        phase
1737                                    }
1738                                    Err(kind) => {
1739                                        report.final_consumer_threads = split.0;
1740                                        report.final_producer_limit = split.1;
1741                                        report.final_consumer_live = live;
1742                                        report.final_producer_active = active;
1743                                        return Err(runtime_failure(
1744                                            kind,
1745                                            report,
1746                                            split,
1747                                            (live, active),
1748                                            BrokerPhase::Survey,
1749                                            epoch,
1750                                        ));
1751                                    }
1752                                }
1753                            }
1754                        }
1755                    }
1756                }
1757            };
1758
1759            // Keep high-resolution progress publication through the bounded
1760            // opening bracket, including its resize/blackout stages. A
1761            // slow split is precisely where coarse item batches quantize the
1762            // baseline. Once probing is complete, both stages can use their
1763            // settled cadence.
1764            let next_measurement_mode = if nonlinear_probe_complete {
1765                phase.measurement_mode()
1766            } else {
1767                ProducerMeasurementMode::Calibration
1768            };
1769            if next_measurement_mode != measurement_mode {
1770                self.producer.set_measurement_mode(next_measurement_mode);
1771                self.consumer.set_measurement_mode(next_measurement_mode);
1772                measurement_mode = next_measurement_mode;
1773            }
1774
1775            report.record_sample(
1776                split,
1777                (live, active),
1778                sampled_at.saturating_duration_since(started),
1779                cfg.trace_capacity,
1780            );
1781            let (useful_cap, useful_cap_reason) = caps.useful();
1782            report.record_cap(
1783                sampled_at.saturating_duration_since(started),
1784                useful_cap,
1785                useful_cap_reason,
1786                cfg.trace_capacity,
1787            );
1788
1789            tracing::trace!(
1790                "thread-broker: {:?} idle {:.1}% pressure {:?} {:.0} items/s \
1791                 consumer {} (live {live}) producer {} (active {active})",
1792                PhaseName(&phase),
1793                idle * 100.0,
1794                pressure,
1795                throughput,
1796                split.0,
1797                split.1,
1798            );
1799
1800            // The low-overhead policy pays the full, guarded convergence cost
1801            // and one clean steady-state validation window, then ends both the
1802            // controller thread and (when it is owned only by this broker) the
1803            // producer's sampling adapter. There is no long timeout or dormant
1804            // polling loop: recurring cost after this point is exactly zero.
1805            if matches!(
1806                self.steady_state_policy,
1807                SteadyStatePolicy::FreezeAfterConvergence
1808                    | SteadyStatePolicy::FreezeAfterFullCalibration
1809            ) && report.time_to_converge.is_some()
1810                && matches!(
1811                    phase,
1812                    Phase::Steady {
1813                        drifted_for: Duration::ZERO
1814                    }
1815                )
1816            {
1817                report.monitoring_stopped_after_convergence = true;
1818                break;
1819            }
1820        }
1821
1822        if report.opening_bracket.outcome == OpeningBracketOutcome::Incomplete {
1823            finish_opening_bracket(
1824                &mut report,
1825                &mut opening_bracket_started,
1826                OpeningBracketOutcome::Incomplete,
1827                now(),
1828            );
1829        }
1830        report.final_consumer_threads = split.0;
1831        report.final_producer_limit = split.1;
1832        report.final_consumer_live = self.consumer.live_threads();
1833        report.final_producer_active = self.producer.active_slots();
1834        report.peak_controlled_slots = report.peak_controlled_slots.max(
1835            report
1836                .final_consumer_live
1837                .saturating_add(report.final_producer_active),
1838        );
1839        report.final_epoch = epoch;
1840        report.final_phase = phase.public();
1841        report.nonlinear_override = nonlinear_override;
1842        report.current_drift = phase.drift_duration();
1843        report.controller_elapsed = now().saturating_duration_since(started);
1844        report.producer_measurement = self.producer.finish_measurement();
1845        report.controller_cpu_nanos = controller_cpu
1846            .elapsed()
1847            .and_then(|elapsed| u64::try_from(elapsed.as_nanos()).ok());
1848        report.controller_cpu_accounting_failures =
1849            usize::from(report.controller_cpu_nanos.is_none());
1850        Ok(report)
1851    }
1852
1853    /// Begin a two-phase move by changing only the shrinking side.
1854    ///
1855    /// The order matters: growing first lets the two sides briefly sum above the
1856    /// budget, which on a full machine is exactly the oversubscription the budget
1857    /// exists to prevent.
1858    ///
1859    /// The direction comes from the caller rather than from
1860    /// [`Producer::limit`]. Reading it back would reintroduce the very confusion
1861    /// the broker avoids by tracking its own view of the split: a pool that has
1862    /// not yet converged to the last request reports the old value, and the
1863    /// ordering decision would then be made against a split that no longer
1864    /// exists.
1865    fn begin_move(
1866        &self,
1867        from: Split,
1868        to: Split,
1869        next: Phase,
1870    ) -> Result<(Split, Phase), BrokerErrorKind> {
1871        if to.1 > from.1 {
1872            self.consumer
1873                .set_threads(to.0)
1874                .map_err(|source| BrokerErrorKind::ResizeRefused {
1875                    side: ResizeSide::Consumer,
1876                    requested: to.0,
1877                    source,
1878                })?;
1879            Ok((
1880                (to.0, from.1),
1881                Phase::Draining {
1882                    side: ResizeSide::Consumer,
1883                    to,
1884                    started: now(),
1885                    next: Box::new(next),
1886                },
1887            ))
1888        } else {
1889            self.producer
1890                .set_limit(to.1)
1891                .map_err(|source| BrokerErrorKind::ResizeRefused {
1892                    side: ResizeSide::Producer,
1893                    requested: to.1,
1894                    source,
1895                })?;
1896            Ok((
1897                (from.0, to.1),
1898                Phase::Draining {
1899                    side: ResizeSide::Producer,
1900                    to,
1901                    started: now(),
1902                    next: Box::new(next),
1903                },
1904            ))
1905        }
1906    }
1907}
1908
1909/// Recent pipeline throughput, over a fixed number of windows.
1910///
1911/// Exists only so the ratify comparison averages the same span on both sides of
1912/// a move. Both halves are `items / elapsed` over `ratify_samples` windows, so
1913/// neither is more exposed to a single noisy window than the other.
1914struct Recent {
1915    ring: std::collections::VecDeque<(u64, Duration)>,
1916    capacity: usize,
1917}
1918
1919impl Recent {
1920    fn new(windows: usize) -> Self {
1921        let capacity = windows.max(1);
1922        Self {
1923            ring: std::collections::VecDeque::with_capacity(capacity),
1924            capacity,
1925        }
1926    }
1927
1928    fn push(&mut self, items: u64, window: Duration) {
1929        if self.ring.len() == self.capacity {
1930            self.ring.pop_front();
1931        }
1932        self.ring.push_back((items, window));
1933    }
1934
1935    fn estimate(&self) -> RateEstimate {
1936        RateEstimate::from_rates(
1937            self.ring
1938                .iter()
1939                .map(|&(items, elapsed)| rate_per_second(items, elapsed)),
1940        )
1941    }
1942
1943    fn len(&self) -> usize {
1944        self.ring.len()
1945    }
1946
1947    fn clear(&mut self) {
1948        self.ring.clear();
1949    }
1950}
1951
1952fn duration_samples(horizon: Duration, interval: Duration) -> usize {
1953    let samples = horizon.as_nanos().div_ceil(interval.as_nanos().max(1));
1954    usize::try_from(samples).unwrap_or(usize::MAX).max(1)
1955}
1956
1957fn finish_opening_bracket(
1958    report: &mut BrokerReport,
1959    started: &mut Option<Instant>,
1960    outcome: OpeningBracketOutcome,
1961    at: Instant,
1962) {
1963    if let Some(started) = started.take() {
1964        report.opening_bracket.wall_nanos =
1965            u64::try_from(at.saturating_duration_since(started).as_nanos()).unwrap_or(u64::MAX);
1966    }
1967    report.opening_bracket.outcome = outcome;
1968}
1969
1970fn retained_bracket_outcome(
1971    alternative_selected: bool,
1972    retained: Option<Split>,
1973    opening: Split,
1974) -> OpeningBracketOutcome {
1975    if alternative_selected {
1976        OpeningBracketOutcome::AlternativeSelected
1977    } else if retained.is_some_and(|split| split != opening) {
1978        OpeningBracketOutcome::ModelSelected
1979    } else {
1980        OpeningBracketOutcome::OpeningRetained
1981    }
1982}
1983
1984/// Candidate allocations adjacent to the opening, first away from the rejected
1985/// model and then toward it. The model point itself has already been measured.
1986/// Alternating around the opening distinguishes opposite response shapes with
1987/// the same model answer: at scATAC t32 the lower model point wins, whereas at
1988/// t8 the first point above the opening is the real peak.
1989fn opening_bracket_targets(
1990    budget: usize,
1991    min_consumer_threads: usize,
1992    min_producer_slots: usize,
1993    opening: usize,
1994    model: usize,
1995    maximum_points: usize,
1996) -> std::collections::VecDeque<usize> {
1997    let mut targets = std::collections::VecDeque::new();
1998    if maximum_points == 0 {
1999        return targets;
2000    }
2001    let floor = min_producer_slots;
2002    let ceiling = budget.saturating_sub(min_consumer_threads).max(floor);
2003    let toward_is_lower = model < opening;
2004    for distance in 1..budget {
2005        let lower = opening
2006            .checked_sub(distance)
2007            .filter(|target| *target >= floor);
2008        let upper = opening
2009            .checked_add(distance)
2010            .filter(|target| *target <= ceiling);
2011        let ordered = if toward_is_lower {
2012            [upper, lower]
2013        } else {
2014            [lower, upper]
2015        };
2016        for target in ordered.into_iter().flatten() {
2017            if target != model && !targets.contains(&target) {
2018                targets.push_back(target);
2019                if targets.len() == maximum_points {
2020                    return targets;
2021                }
2022            }
2023        }
2024    }
2025    targets
2026}
2027
2028#[derive(Debug, Clone, Copy, Default)]
2029struct RateEstimate {
2030    mean: f64,
2031    half_width: f64,
2032    samples: usize,
2033    zero_samples: usize,
2034}
2035
2036#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2037enum RatificationOutcome {
2038    Kept,
2039    Inconclusive,
2040    Regressed,
2041}
2042
2043fn compare_rates(
2044    baseline: RateEstimate,
2045    achieved: RateEstimate,
2046    tolerance: f64,
2047) -> RatificationOutcome {
2048    if baseline.mean <= 0.0 {
2049        return RatificationOutcome::Kept;
2050    }
2051    // One output flush or I/O pause is evidence that this block is noisy, not
2052    // evidence that the target is bad. Sustained zero progress remains visible
2053    // (and can still regress); only the isolated case is forced inconclusive.
2054    if achieved.samples > 1 && achieved.zero_samples == 1 {
2055        return RatificationOutcome::Inconclusive;
2056    }
2057
2058    let boundary = baseline.mean * (1.0 - tolerance);
2059    // The blocks are independent, so uncertainty in their difference combines
2060    // in quadrature rather than by adding both 95% widths. Ratification asks a
2061    // directional question (is the new rate below the tolerated boundary?),
2062    // hence the one-sided 95% factor 1.645 rather than 1.96.
2063    let difference_half_width =
2064        (1.645 / 1.96) * (baseline.half_width * (1.0 - tolerance)).hypot(achieved.half_width);
2065    if achieved.mean + difference_half_width < boundary {
2066        RatificationOutcome::Regressed
2067    } else if achieved.mean - difference_half_width < boundary {
2068        RatificationOutcome::Inconclusive
2069    } else {
2070        RatificationOutcome::Kept
2071    }
2072}
2073
2074/// A differing model answer has higher evidential status than the opening.
2075///
2076/// Ordinary ratification's directional test is intentionally sensitive to a
2077/// tolerated loss. Rejecting the model point is more consequential: it restores
2078/// an unevidenced opening and starts the optional response search. Require the
2079/// same two independent facts used to retain an optional point, in reverse:
2080/// non-overlapping intervals and a material loss. A directional rejection that
2081/// does not clear both gates is inconclusive and therefore keeps the model.
2082fn compare_opening_model_rates(
2083    baseline: RateEstimate,
2084    achieved: RateEstimate,
2085    tolerance: f64,
2086) -> RatificationOutcome {
2087    match compare_rates(baseline, achieved, tolerance) {
2088        RatificationOutcome::Regressed
2089            if baseline.lower() > achieved.upper()
2090                && achieved.mean < baseline.mean * (1.0 - tolerance) =>
2091        {
2092            RatificationOutcome::Regressed
2093        }
2094        RatificationOutcome::Regressed => RatificationOutcome::Inconclusive,
2095        outcome => outcome,
2096    }
2097}
2098
2099fn empirical_cap_confirms_target(model: Option<Model>, target: usize) -> bool {
2100    model.is_some_and(|model| {
2101        model.useful_cap <= target && model.useful_cap_reason != ProducerCapReason::None
2102    })
2103}
2104
2105/// A nonlinear probe has to be both statistically and materially better.
2106///
2107/// Applying the materiality tolerance to the baseline confidence endpoint
2108/// double-counts uncertainty: two already-disjoint 95% intervals can fail only
2109/// because the noisier endpoint is inflated a second time. Keep the questions
2110/// separate instead: intervals must not overlap, and means must differ by the
2111/// configured practical threshold.
2112fn nonlinear_probe_improved(
2113    baseline: RateEstimate,
2114    achieved: RateEstimate,
2115    tolerance: f64,
2116) -> bool {
2117    achieved.lower() > baseline.upper() && achieved.mean > baseline.mean * (1.0 + tolerance)
2118}
2119
2120impl RateEstimate {
2121    fn from_rates(rates: impl Iterator<Item = f64>) -> Self {
2122        let rates: Vec<_> = rates.filter(|rate| rate.is_finite()).collect();
2123        if rates.is_empty() {
2124            return Self::default();
2125        }
2126        let mean = rates.iter().sum::<f64>() / rates.len() as f64;
2127        let half_width = if rates.len() > 1 {
2128            let variance = rates.iter().map(|rate| (rate - mean).powi(2)).sum::<f64>()
2129                / (rates.len() - 1) as f64;
2130            1.96 * variance.sqrt() / (rates.len() as f64).sqrt()
2131        } else {
2132            f64::INFINITY
2133        };
2134        Self {
2135            mean,
2136            half_width,
2137            samples: rates.len(),
2138            zero_samples: rates.iter().filter(|rate| **rate == 0.0).count(),
2139        }
2140    }
2141
2142    fn lower(self) -> f64 {
2143        (self.mean - self.half_width).max(0.0)
2144    }
2145
2146    fn upper(self) -> f64 {
2147        self.mean + self.half_width
2148    }
2149}
2150
2151/// Smoothed busy time for the two stages over the recent windows.
2152///
2153/// Sums rather than an average of per-window ratios, so a window in which the
2154/// pipeline barely moved contributes proportionally rather than equally.
2155struct Costs {
2156    ring: std::collections::VecDeque<(u64, u64)>,
2157    capacity: usize,
2158}
2159
2160struct Solved {
2161    target: usize,
2162    snapshot: Model,
2163}
2164
2165impl Costs {
2166    fn new(windows: usize) -> Self {
2167        let capacity = windows.max(1);
2168        Self {
2169            ring: std::collections::VecDeque::with_capacity(capacity),
2170            capacity,
2171        }
2172    }
2173
2174    fn push(&mut self, consumer_busy: u64, producer_busy: u64) {
2175        if self.ring.len() == self.capacity {
2176            self.ring.pop_front();
2177        }
2178        self.ring.push_back((consumer_busy, producer_busy));
2179    }
2180
2181    fn clear(&mut self) {
2182        self.ring.clear();
2183    }
2184
2185    /// The closed-form split.
2186    ///
2187    /// Writing `s_p` and `s_c` for the two stages' per-item costs, a budget `N`
2188    /// divided into `d` producer threads and `N - d` consumer threads finishes
2189    /// both sides together when `d / s_p = (N - d) / s_c`, whose solution is
2190    ///
2191    /// ```text
2192    ///     d* = N * s_p / (s_p + s_c)
2193    /// ```
2194    ///
2195    /// Sanity check the direction: as the producer gets more expensive per item,
2196    /// `d*` tends to `N`, which is right.
2197    ///
2198    /// # Why the busy times can be substituted directly
2199    ///
2200    /// Over a window in which the pipeline moved `X` items, the producer spent
2201    /// `X * s_p` thread-nanoseconds and the consumer `X * s_c`. So
2202    ///
2203    /// ```text
2204    ///     busy_p / (busy_p + busy_c) = s_p / (s_p + s_c)
2205    /// ```
2206    ///
2207    /// exactly — the `X` cancels, and with it any need for the two stages to
2208    /// count work in the same unit. In particular a run mixing compressed and
2209    /// uncompressed inputs is handled correctly for free: the producer simply
2210    /// does no work for the uncompressed ones, its busy time is proportionally
2211    /// smaller, and the split follows.
2212    ///
2213    /// # The assumption in that step
2214    ///
2215    /// "The same `X`" is exact only in steady state. Over a window in which the
2216    /// buffer between the stages is filling or draining, the two sides handled
2217    /// *different* numbers of items and the estimate is biased by the
2218    /// difference. Three things bound it — the estimate is summed over
2219    /// `smoothing_windows` rather than taken from one; a move is followed by a
2220    /// blackout long enough for the buffer to re-level; and a window in which
2221    /// the pipeline made no progress is discarded outright by
2222    /// [`usable_window`] — but none of them is a proof, and a workload whose
2223    /// compressibility varies in long runs is where to look first if the split
2224    /// comes out wrong.
2225    ///
2226    /// This is solvable at all only because busy time excludes blocking. Wall
2227    /// time would make both sides a function of `d`, and the equation would say
2228    /// nothing. DS2 (Kalavri et al., OSDI'18) calls this the *true* rate as
2229    /// against the *observed* rate, and section 2 of that paper is a catalogue
2230    /// of the controllers that go wrong by using the latter.
2231    fn solve(&self, budget: usize, cfg: &crate::BrokerConfig, caps: &Caps) -> Option<Solved> {
2232        let (consumer, producer) = self
2233            .ring
2234            .iter()
2235            .fold((0u64, 0u64), |(c, p), (dc, dp)| (c + dc, p + dp));
2236        let total = consumer.checked_add(producer)?;
2237        if total == 0 {
2238            return None;
2239        }
2240
2241        let share = producer as f64 / total as f64;
2242        let window_shares: Vec<_> = self
2243            .ring
2244            .iter()
2245            .filter_map(|(consumer, producer)| {
2246                let total = consumer.saturating_add(*producer);
2247                (total > 0).then_some(*producer as f64 / total as f64)
2248            })
2249            .collect();
2250        let uncertainty = if window_shares.len() > 1 {
2251            let variance = window_shares
2252                .iter()
2253                .map(|sample| (sample - share).powi(2))
2254                .sum::<f64>()
2255                / (window_shares.len() - 1) as f64;
2256            (1.96 * variance.sqrt() / (window_shares.len() as f64).sqrt()).clamp(0.0, 0.5)
2257        } else {
2258            0.5
2259        };
2260        let ideal = (budget as f64 * share).round().max(1.0) as usize;
2261
2262        let lo = cfg.min_producer_slots.max(1);
2263        let hi = budget.saturating_sub(cfg.min_consumer_threads).max(lo);
2264        let (useful, useful_cap_reason) = caps.useful();
2265        let target = ideal.clamp(lo, hi).min(useful.max(lo));
2266        let effective_deadband = cfg
2267            .deadband_threads
2268            .max((budget as f64 * uncertainty).ceil() as usize);
2269        let effective_resurvey = cfg
2270            .resurvey_distance
2271            .max((budget as f64 * uncertainty * 2.0).ceil() as usize)
2272            .max(effective_deadband);
2273
2274        Some(Solved {
2275            target,
2276            snapshot: Model {
2277                producer_cost_share: share,
2278                producer_cost_share_uncertainty: uncertainty,
2279                ideal_producer_slots: ideal,
2280                useful_cap: useful,
2281                useful_cap_reason,
2282                effective_deadband_threads: effective_deadband,
2283                effective_resurvey_distance: effective_resurvey,
2284            },
2285        })
2286    }
2287}
2288
2289/// The empirical ceiling on producer concurrency.
2290///
2291/// The model assumes a stage can use every thread it is given. A producer
2292/// reading from a saturated disk cannot, and the model has no way to know that:
2293/// the cost share says how much *work* decoding is, not whether there is
2294/// anything to decode it from. Left uncapped, the solver buys slots that sit
2295/// idle — which is FLINK-31215, where a non-scalable bottleneck upstream leads
2296/// the autoscaler to keep scaling a stage that cannot go any faster.
2297///
2298/// Two independent observations bound it, and both are one-sided: a cap is only
2299/// ever recorded when the producer *demonstrably* failed to use what it had.
2300///
2301/// # Why it is a recent window and not a running minimum
2302///
2303/// A running minimum never recovers. One quiet stretch — a between-files lull,
2304/// a stall downstream — would pin the ceiling for the rest of the run, and the
2305/// broker could not buy decode concurrency again however badly the next file
2306/// needed it. Bounding it to a recent window makes the cap self-healing, at the
2307/// cost of forgetting a constraint that has not been observed for a few seconds,
2308/// which is the right trade: a constraint that still holds will be observed
2309/// again almost immediately.
2310struct Caps {
2311    history: Duration,
2312    persistence: Duration,
2313    slack_for: Duration,
2314    slack_age: Duration,
2315    slack_peak: f64,
2316    slack_limit: Option<usize>,
2317    slack_candidate: Option<(usize, usize)>,
2318    slack_cap: Option<usize>,
2319    saturated_for: Duration,
2320    saturated_age: Duration,
2321    saturated_at: Option<usize>,
2322}
2323
2324impl Caps {
2325    fn new(history: Duration, persistence: Duration) -> Self {
2326        Self {
2327            history,
2328            persistence,
2329            slack_for: Duration::ZERO,
2330            slack_age: Duration::ZERO,
2331            slack_peak: 0.0,
2332            slack_limit: None,
2333            slack_candidate: None,
2334            slack_cap: None,
2335            saturated_for: Duration::ZERO,
2336            saturated_age: Duration::ZERO,
2337            saturated_at: None,
2338        }
2339    }
2340
2341    /// Observe busy-derived slack only when producer and consumer progress is
2342    /// comparable. Buffer fill/drain can otherwise make ordinary pipeline lag
2343    /// look like unused producer concurrency.
2344    fn observe_slack(&mut self, dp: Work, window: Duration, producer_limit: usize) {
2345        // Concurrency the producer actually achieved this window, in
2346        // thread-equivalents. Busy time excludes blocking, so this is work done
2347        // rather than slots held.
2348        let achieved = dp.busy_nanos as f64 / window.as_nanos().max(1) as f64;
2349        // One quiet sample is a lull, not scalability evidence. Require the
2350        // same condition continuously for a duration independent of sampling
2351        // cadence before turning it into a cap.
2352        if self.slack_limit != Some(producer_limit) {
2353            self.slack_for = Duration::ZERO;
2354            self.slack_peak = 0.0;
2355            self.slack_limit = Some(producer_limit);
2356        }
2357        if producer_limit as f64 > achieved + 1.0 {
2358            self.slack_for += window;
2359            self.slack_age = Duration::ZERO;
2360            self.slack_peak = self.slack_peak.max(achieved);
2361            if self.slack_for >= self.persistence {
2362                let observed_cap = self.slack_peak.ceil() as usize + 1;
2363                if producer_limit >= observed_cap.saturating_mul(2) {
2364                    // A grant at least twice the sustained ceiling is already
2365                    // a decisive exploration. This lets an initially invisible
2366                    // source ceiling recover after one oversized model move.
2367                    self.slack_cap = Some(observed_cap);
2368                    self.slack_candidate = Some((producer_limit, observed_cap));
2369                } else {
2370                    match self.slack_candidate {
2371                        Some((prior_limit, prior_cap))
2372                            if prior_limit != producer_limit
2373                                && prior_limit > prior_cap
2374                                && producer_limit > observed_cap
2375                                && prior_cap.abs_diff(observed_cap) <= 1 =>
2376                        {
2377                            // One mildly under-filled grant can be a batching or
2378                            // barrier artifact: real scRNA used more concurrency
2379                            // and halved wall time when its grant grew. Comparable
2380                            // ceilings at two distinct grants, each with real
2381                            // headroom above its inferred ceiling, make it a
2382                            // hard cap. A grant equal to the inferred cap
2383                            // cannot confirm its own saturation.
2384                            self.slack_cap = Some(prior_cap.max(observed_cap));
2385                        }
2386                        Some((prior_limit, _)) if prior_limit != producer_limit => {
2387                            self.slack_cap = None;
2388                            self.slack_candidate = Some((producer_limit, observed_cap));
2389                        }
2390                        None => {
2391                            self.slack_candidate = Some((producer_limit, observed_cap));
2392                        }
2393                        _ => {}
2394                    }
2395                }
2396            }
2397        } else {
2398            self.slack_for = Duration::ZERO;
2399            self.slack_peak = 0.0;
2400            self.slack_age += window;
2401            if self.slack_age >= self.history {
2402                self.slack_candidate = None;
2403                self.slack_cap = None;
2404            }
2405        }
2406    }
2407
2408    /// Observe the producer's direct source/elasticity classification.
2409    ///
2410    /// This evidence is independent of downstream buffer equivalence. It still
2411    /// requires duration-based persistence and expires after contradictory
2412    /// observations, so a lone source lull cannot become a lasting cap.
2413    fn observe_pressure(&mut self, window: Duration, producer_limit: usize, p: ProducerPressure) {
2414        // Cap history uses only the source/elasticity half of
2415        // `ProducerPressure`. It is a saturating signal -- it cannot tell "just
2416        // enough" from "far too much" -- so it may cap growth but must never
2417        // size the split. `Starved`'s separate bounded shrink veto lives at the
2418        // decision point, where it can retain but never increase the target.
2419        match p {
2420            ProducerPressure::SourceBound | ProducerPressure::Inelastic => {
2421                self.saturated_for += window;
2422                self.saturated_age = Duration::ZERO;
2423                if self.saturated_for >= self.persistence {
2424                    self.saturated_at = Some(match self.saturated_at {
2425                        Some(prev) => prev.min(producer_limit.max(1)),
2426                        None => producer_limit.max(1),
2427                    });
2428                }
2429            }
2430            _ => {
2431                self.saturated_for = Duration::ZERO;
2432                self.saturated_age += window;
2433                if self.saturated_age >= self.history {
2434                    self.saturated_at = None;
2435                }
2436            }
2437        }
2438    }
2439
2440    #[cfg(test)]
2441    fn observe(&mut self, dp: Work, window: Duration, producer_limit: usize, p: ProducerPressure) {
2442        self.observe_slack(dp, window, producer_limit);
2443        self.observe_pressure(window, producer_limit, p);
2444    }
2445
2446    /// The largest producer limit worth asking for, or `usize::MAX` if nothing
2447    /// has been observed to bound it.
2448    fn useful(&self) -> (usize, ProducerCapReason) {
2449        let mut cap = usize::MAX;
2450
2451        // Slack: if the producer was handed more than a thread's worth of slack
2452        // and declined to use it, more slots are not what it is short of. The
2453        // ceiling is the best it managed while it had that slack, plus one so a
2454        // marginal case is not pinched.
2455        if let Some(slack) = self.slack_cap {
2456            cap = cap.min(slack);
2457        }
2458
2459        if let Some(limit) = self.saturated_at {
2460            cap = cap.min(limit);
2461        }
2462        let reason = match (self.slack_cap.is_some(), self.saturated_at.is_some()) {
2463            (false, false) => ProducerCapReason::None,
2464            (true, false) => ProducerCapReason::Slack,
2465            (false, true) => ProducerCapReason::Source,
2466            (true, true) => ProducerCapReason::SlackAndSource,
2467        };
2468        (cap, reason)
2469    }
2470}
2471
2472/// Whether a window carries enough work to draw a conclusion from.
2473///
2474/// The cost share is a ratio, and a window containing a handful of nanoseconds
2475/// of work on one side gives a ratio dominated by where the window boundaries
2476/// happened to fall relative to the counters' flush granularity. The CLR thread
2477/// pool's hill climber guards the same hazard from the other direction,
2478/// rejecting a sample window when in-flight work is large relative to completed
2479/// work.
2480///
2481/// The test is on the *total*, not on each side, so that a producer doing no
2482/// work at all is admitted rather than discarded. That case is real and needs
2483/// the right answer: a run whose inputs are already uncompressed has no decode
2484/// cost, and the honest reading of the window is a cost share of zero, which
2485/// solves to the producer floor and hands the whole budget to the consumer.
2486/// Requiring both sides to be busy would instead reject every window forever and
2487/// strand the initial guess.
2488///
2489/// Progress is required as well as busy time, because a window in which the
2490/// pipeline moved nothing says only that something is stalled, and which side
2491/// happened to be spinning during it is not evidence about their relative costs.
2492///
2493/// The floor is a twentieth of one thread-window, far below anything a working
2494/// stage produces and far above the residue of one that has stopped.
2495fn usable_window(dc: Work, dp: Work, window: Duration) -> bool {
2496    let floor = window.as_nanos() as u64 / 20;
2497    dc.items > 0 && dc.busy_nanos.saturating_add(dp.busy_nanos) > floor
2498}
2499
2500/// Whether a `Starved` producer demonstrably used the allocation a shrink would
2501/// remove. Queue state alone can remain stale while already-produced buffers
2502/// drain, so it is not enough to invoke the nonlinear shrink veto.
2503fn producer_capacity_used(
2504    active_slots: usize,
2505    requested_slots: usize,
2506    busy_nanos: u64,
2507    window: Duration,
2508) -> bool {
2509    let capacity_nanos = (window.as_nanos() as u64).saturating_mul(requested_slots as u64);
2510    active_slots >= requested_slots
2511        || busy_nanos.saturating_mul(4) >= capacity_nanos.saturating_mul(3)
2512}
2513
2514fn rate_per_second(items: u64, window: Duration) -> f64 {
2515    let secs = window.as_secs_f64();
2516    if secs <= 0.0 {
2517        return 0.0;
2518    }
2519    items as f64 / secs
2520}
2521
2522/// Duration represented by consecutive drift observations.
2523///
2524/// The first sparse steady-state probe says only that drift exists *now*; it
2525/// cannot prove that drift was present throughout the entire sleep since the
2526/// previous clean probe. Credit it with one active-cadence window. A second
2527/// consecutive observation spans real elapsed time and may then satisfy the
2528/// wall-clock persistence guard.
2529fn consecutive_drift_duration(
2530    previous: Duration,
2531    observation_window: Duration,
2532    active_interval: Duration,
2533) -> Duration {
2534    if previous.is_zero() {
2535        observation_window.min(active_interval)
2536    } else {
2537        previous.saturating_add(observation_window)
2538    }
2539}
2540
2541fn fmt_cap(cap: usize) -> String {
2542    if cap == usize::MAX {
2543        "none".into()
2544    } else {
2545        cap.to_string()
2546    }
2547}
2548
2549fn runtime_failure(
2550    kind: BrokerErrorKind,
2551    mut report: BrokerReport,
2552    requested: Split,
2553    actual: Split,
2554    phase: BrokerPhase,
2555    epoch: u64,
2556) -> BrokerError {
2557    let error = BrokerError::new(kind);
2558    report.final_consumer_threads = requested.0;
2559    report.final_producer_limit = requested.1;
2560    report.final_consumer_live = actual.0;
2561    report.final_producer_active = actual.1;
2562    report.final_phase = phase;
2563    report.final_epoch = epoch;
2564    report.terminal_error = Some(error.to_string());
2565    error.with_report(report)
2566}
2567
2568struct PhaseName<'a>(&'a Phase);
2569
2570impl std::fmt::Debug for PhaseName<'_> {
2571    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2572        f.write_str(match self.0 {
2573            Phase::Survey => "survey",
2574            Phase::Draining { .. } => "draining",
2575            Phase::Blackout { .. } => "blackout",
2576            Phase::OpeningBracketRestore { .. } => "opening-bracket-restore",
2577            Phase::Ratify { .. } => "ratify",
2578            Phase::Steady { .. } => "steady",
2579        })
2580    }
2581}
2582
2583#[cfg(test)]
2584mod tests {
2585    use super::{
2586        BrokerPhase, BrokerReport, Caps, Costs, Model, Phase, ProducerCapReason, RateEstimate,
2587        RatificationKind, RatificationOutcome, Recent, compare_opening_model_rates, compare_rates,
2588        consecutive_drift_duration, duration_samples, empirical_cap_confirms_target,
2589        nonlinear_probe_improved, opening_bracket_targets, producer_capacity_used,
2590        retained_bracket_outcome,
2591    };
2592    use crate::{
2593        BrokerConfig, OpeningBracketOutcome, ProducerMeasurementMode, ProducerPressure, ResizeSide,
2594        Work,
2595    };
2596    use std::time::{Duration, Instant};
2597
2598    struct Noise(u64);
2599
2600    impl Noise {
2601        fn new(seed: u64) -> Self {
2602            Self(seed.max(1))
2603        }
2604
2605        fn uniform(&mut self) -> f64 {
2606            self.0 = self
2607                .0
2608                .wrapping_mul(6_364_136_223_846_793_005)
2609                .wrapping_add(1);
2610            (self.0 >> 11) as f64 / (1u64 << 53) as f64
2611        }
2612
2613        /// Irwin-Hall approximation to a standard normal. It is deterministic,
2614        /// portable, and sufficient for controller acceptance simulations.
2615        fn normal(&mut self) -> f64 {
2616            (0..12).map(|_| self.uniform()).sum::<f64>() - 6.0
2617        }
2618    }
2619
2620    fn estimate(rate: f64, relative_sigma: f64, noise: &mut Noise) -> RateEstimate {
2621        RateEstimate::from_rates((0..4).map(|_| rate * (1.0 + relative_sigma * noise.normal())))
2622    }
2623
2624    fn push_noisy_share(costs: &mut Costs, share: f64, noise: &mut Noise) {
2625        let observed = (share + 0.005 * noise.normal()).clamp(0.001, 0.999);
2626        let total = 1_000_000.0;
2627        costs.push(((1.0 - observed) * total) as u64, (observed * total) as u64);
2628    }
2629
2630    fn work_at(concurrency: usize, window: Duration) -> Work {
2631        Work {
2632            busy_nanos: (window.as_nanos() as u64).saturating_mul(concurrency as u64),
2633            items: 1,
2634        }
2635    }
2636
2637    #[test]
2638    fn one_slack_lull_does_not_create_a_cap() {
2639        let mut caps = Caps::new(Duration::from_secs(2), Duration::from_millis(300));
2640        let window = Duration::from_millis(100);
2641        caps.observe(work_at(2, window), window, 8, ProducerPressure::Satisfied);
2642        assert_eq!(caps.useful(), (usize::MAX, ProducerCapReason::None));
2643    }
2644
2645    #[test]
2646    fn cap_confidence_uses_duration_and_expires_by_duration() {
2647        for window in [Duration::from_millis(25), Duration::from_millis(100)] {
2648            let mut caps = Caps::new(Duration::from_secs(1), Duration::from_millis(300));
2649            let samples = Duration::from_millis(300)
2650                .as_nanos()
2651                .div_ceil(window.as_nanos());
2652            for _ in 0..samples {
2653                caps.observe(work_at(6, window), window, 8, ProducerPressure::Satisfied);
2654            }
2655            assert_eq!(
2656                caps.useful(),
2657                (usize::MAX, ProducerCapReason::None),
2658                "one grant must remain provisional"
2659            );
2660
2661            for _ in 0..samples {
2662                caps.observe(work_at(6, window), window, 12, ProducerPressure::Satisfied);
2663            }
2664            assert_eq!(caps.useful(), (7, ProducerCapReason::Slack));
2665
2666            let expiry = Duration::from_secs(1)
2667                .as_nanos()
2668                .div_ceil(window.as_nanos());
2669            for _ in 0..expiry {
2670                caps.observe(work_at(12, window), window, 12, ProducerPressure::Starved);
2671            }
2672            assert_eq!(caps.useful(), (usize::MAX, ProducerCapReason::None));
2673        }
2674    }
2675
2676    #[test]
2677    fn adjacent_grant_cannot_confirm_its_own_inferred_cap() {
2678        let mut caps = Caps::new(Duration::from_secs(1), Duration::from_millis(300));
2679        let window = Duration::from_millis(100);
2680        for _ in 0..3 {
2681            caps.observe(work_at(6, window), window, 8, ProducerPressure::Satisfied);
2682        }
2683        assert_eq!(caps.useful(), (usize::MAX, ProducerCapReason::None));
2684        for _ in 0..3 {
2685            caps.observe(work_at(6, window), window, 7, ProducerPressure::Satisfied);
2686        }
2687        assert_eq!(
2688            caps.useful(),
2689            (usize::MAX, ProducerCapReason::None),
2690            "a grant equal to the inferred cap has no headroom to confirm saturation"
2691        );
2692    }
2693
2694    #[test]
2695    fn source_caps_arrive_and_lull_caps_recover_within_one_second() {
2696        for window in [Duration::from_millis(25), Duration::from_millis(100)] {
2697            let mut source = Caps::new(Duration::from_millis(800), Duration::from_millis(300));
2698            let evidence = Duration::from_millis(300)
2699                .as_nanos()
2700                .div_ceil(window.as_nanos());
2701            for _ in 0..evidence {
2702                source.observe(work_at(8, window), window, 8, ProducerPressure::SourceBound);
2703            }
2704            assert_eq!(source.useful(), (8, ProducerCapReason::Source));
2705
2706            let mut lull = Caps::new(Duration::from_millis(800), Duration::from_millis(300));
2707            let lull_samples = Duration::from_secs(1)
2708                .as_nanos()
2709                .div_ceil(window.as_nanos());
2710            for _ in 0..lull_samples {
2711                lull.observe(work_at(0, window), window, 8, ProducerPressure::Satisfied);
2712            }
2713            assert_eq!(lull.useful(), (1, ProducerCapReason::Slack));
2714
2715            let recovery_samples = Duration::from_millis(800)
2716                .as_nanos()
2717                .div_ceil(window.as_nanos());
2718            for _ in 0..recovery_samples {
2719                lull.observe(work_at(8, window), window, 8, ProducerPressure::Starved);
2720            }
2721            assert_eq!(
2722                lull.useful(),
2723                (usize::MAX, ProducerCapReason::None),
2724                "{window:?} sampling retained a lull cap after 800 ms of contrary evidence"
2725            );
2726        }
2727    }
2728
2729    #[test]
2730    fn direct_pressure_cap_does_not_wait_for_flow_stability() {
2731        let mut caps = Caps::new(Duration::from_millis(800), Duration::from_millis(300));
2732        for _ in 0..3 {
2733            caps.observe_pressure(Duration::from_millis(100), 2, ProducerPressure::Inelastic);
2734        }
2735        assert_eq!(caps.useful(), (2, ProducerCapReason::Source));
2736    }
2737
2738    #[test]
2739    fn interval_ratification_meets_seeded_error_gates() {
2740        let mut false_rejections = 0;
2741        let mut regressions_detected = 0;
2742        for seed in 1..=1_000 {
2743            let mut noise = Noise::new(seed);
2744            let baseline = estimate(100.0, 0.02, &mut noise);
2745            let equal = estimate(100.0, 0.02, &mut noise);
2746            false_rejections +=
2747                usize::from(compare_rates(baseline, equal, 0.05) == RatificationOutcome::Regressed);
2748
2749            let baseline = estimate(100.0, 0.02, &mut noise);
2750            let regressed = estimate(90.0, 0.02, &mut noise);
2751            regressions_detected += usize::from(
2752                compare_rates(baseline, regressed, 0.05) == RatificationOutcome::Regressed,
2753            );
2754        }
2755        assert!(
2756            false_rejections < 10,
2757            "{false_rejections}/1000 equal-rate traces were confidently rejected"
2758        );
2759        assert!(
2760            regressions_detected >= 950,
2761            "only {regressions_detected}/1000 ten-percent regressions were detected"
2762        );
2763    }
2764
2765    #[test]
2766    fn uncertainty_hysteresis_meets_seeded_budget_gates() {
2767        let cfg = BrokerConfig::default();
2768        let caps = Caps::new(cfg.cap_history, cfg.cap_persistence);
2769
2770        for budget in [2usize, 4, 8, 16, 32, 64, 96] {
2771            let base_target = (budget / 3).clamp(1, budget - 1);
2772            let base_share = base_target as f64 / budget as f64;
2773            let mut stable_runs_without_resurvey = 0;
2774            let mut shifts_detected = 0;
2775
2776            for seed in 1..=1_000 {
2777                let mut noise = Noise::new(seed ^ ((budget as u64) << 32));
2778                let mut costs = Costs::new(cfg.smoothing_windows);
2779                let mut drifted_for = Duration::ZERO;
2780                let mut false_resurvey = false;
2781                for _ in 0..300 {
2782                    push_noisy_share(&mut costs, base_share, &mut noise);
2783                    if let Some(solved) = costs.solve(budget, &cfg, &caps) {
2784                        if solved.target.abs_diff(base_target)
2785                            >= solved.snapshot.effective_resurvey_distance
2786                        {
2787                            drifted_for += Duration::from_millis(100);
2788                            false_resurvey |= drifted_for >= cfg.resurvey_persistence;
2789                        } else {
2790                            drifted_for = Duration::ZERO;
2791                        }
2792                    }
2793                }
2794                stable_runs_without_resurvey += usize::from(!false_resurvey);
2795
2796                // A two-slot budget has only one legal split, so there is no
2797                // allocation change for any controller to detect.
2798                if budget > 2 {
2799                    let shift = ((budget as f64 * 0.10).ceil() as usize).max(1);
2800                    let changed_target = (base_target + shift).min(budget - 1);
2801                    let changed_share = changed_target as f64 / budget as f64;
2802                    drifted_for = Duration::ZERO;
2803                    let mut detected = false;
2804                    for _ in 0..20 {
2805                        push_noisy_share(&mut costs, changed_share, &mut noise);
2806                        if let Some(solved) = costs.solve(budget, &cfg, &caps) {
2807                            if solved.target.abs_diff(base_target)
2808                                >= solved.snapshot.effective_resurvey_distance
2809                            {
2810                                drifted_for += Duration::from_millis(100);
2811                                detected |= drifted_for >= cfg.resurvey_persistence;
2812                            } else {
2813                                drifted_for = Duration::ZERO;
2814                            }
2815                        }
2816                    }
2817                    shifts_detected += usize::from(detected);
2818                }
2819            }
2820
2821            assert!(
2822                stable_runs_without_resurvey >= 990,
2823                "budget {budget}: only {stable_runs_without_resurvey}/1000 stable 30-second traces avoided resurvey"
2824            );
2825            if budget > 2 {
2826                assert!(
2827                    shifts_detected >= 990,
2828                    "budget {budget}: only {shifts_detected}/1000 ten-percent shifts reopened within two seconds"
2829                );
2830            }
2831        }
2832    }
2833
2834    #[test]
2835    fn report_trace_is_bounded_and_convergence_is_terminal_state_aware() {
2836        let mut report = BrokerReport::default();
2837        for producer in 1..=10 {
2838            report.record_sample(
2839                (16 - producer, producer),
2840                (15 - producer, producer),
2841                Duration::from_millis(producer as u64),
2842                3,
2843            );
2844        }
2845        assert_eq!(report.producer_trajectory, vec![8, 9, 10]);
2846        assert_eq!(report.actual_producer_trajectory, vec![8, 9, 10]);
2847        assert_eq!(
2848            report.trajectory_elapsed,
2849            vec![
2850                Duration::from_millis(8),
2851                Duration::from_millis(9),
2852                Duration::from_millis(10),
2853            ]
2854        );
2855        assert_eq!(report.trace_samples_dropped, 7);
2856
2857        report.time_to_converge = Some(Duration::from_secs(1));
2858        for phase in [
2859            BrokerPhase::Starting,
2860            BrokerPhase::Survey,
2861            BrokerPhase::Draining,
2862            BrokerPhase::Blackout,
2863            BrokerPhase::Ratifying,
2864        ] {
2865            report.final_phase = phase;
2866            assert!(!report.converged(), "{phase:?} was labeled converged");
2867        }
2868        report.final_phase = BrokerPhase::Steady;
2869        assert!(report.converged());
2870        report.current_drift = Duration::from_millis(100);
2871        assert!(!report.converged());
2872        report.current_drift = Duration::ZERO;
2873        report.terminal_error = Some("injected".into());
2874        assert!(!report.converged());
2875    }
2876
2877    #[test]
2878    fn producer_measurement_is_dense_only_while_a_decision_is_open() {
2879        assert_eq!(
2880            Phase::Survey.measurement_mode(),
2881            ProducerMeasurementMode::Calibration
2882        );
2883        assert_eq!(
2884            Phase::Ratify {
2885                left: 1,
2886                from: (7, 1),
2887                target: 2,
2888                baseline: RateEstimate::from_rates([1.0].into_iter()),
2889                rates: Vec::new(),
2890                kind: RatificationKind::Model,
2891            }
2892            .measurement_mode(),
2893            ProducerMeasurementMode::Calibration
2894        );
2895
2896        for phase in [
2897            Phase::Draining {
2898                side: ResizeSide::Producer,
2899                to: (6, 2),
2900                started: Instant::now(),
2901                next: Box::new(Phase::Survey),
2902            },
2903            Phase::Blackout {
2904                left: 1,
2905                next: Box::new(Phase::Survey),
2906            },
2907            Phase::Steady {
2908                drifted_for: Duration::ZERO,
2909            },
2910        ] {
2911            assert_eq!(
2912                phase.measurement_mode(),
2913                ProducerMeasurementMode::Monitoring
2914            );
2915        }
2916    }
2917
2918    #[test]
2919    fn recent_throughput_keeps_zero_progress_windows() {
2920        let window = Duration::from_millis(100);
2921        let mut recent = Recent::new(3);
2922        recent.push(100, window);
2923        recent.push(0, window);
2924        recent.push(100, window);
2925
2926        let estimate = recent.estimate();
2927        assert!((estimate.mean - (2_000.0 / 3.0)).abs() < 1e-9);
2928        assert!(estimate.lower() < estimate.mean);
2929        assert!(estimate.upper() > estimate.mean);
2930    }
2931
2932    #[test]
2933    fn nonlinear_probe_separates_significance_from_materiality() {
2934        let baseline = RateEstimate {
2935            mean: 6_386.0,
2936            half_width: 3_066.0,
2937            ..RateEstimate::default()
2938        };
2939        let achieved = RateEstimate {
2940            mean: 12_453.0,
2941            half_width: 2_688.0,
2942            ..RateEstimate::default()
2943        };
2944        assert!(nonlinear_probe_improved(baseline, achieved, 0.05));
2945
2946        // A large mean gain without separated intervals is not evidence.
2947        assert!(!nonlinear_probe_improved(
2948            baseline,
2949            RateEstimate {
2950                mean: 12_000.0,
2951                half_width: 4_000.0,
2952                ..RateEstimate::default()
2953            },
2954            0.05,
2955        ));
2956        // Separated intervals with a sub-threshold mean gain are not useful.
2957        assert!(!nonlinear_probe_improved(
2958            RateEstimate {
2959                mean: 100.0,
2960                half_width: 0.1,
2961                ..RateEstimate::default()
2962            },
2963            RateEstimate {
2964                mean: 104.0,
2965                half_width: 0.1,
2966                ..RateEstimate::default()
2967            },
2968            0.05,
2969        ));
2970    }
2971
2972    #[test]
2973    fn opening_model_requires_separation_and_materiality_to_revert() {
2974        let baseline = RateEstimate {
2975            mean: 100.0,
2976            half_width: 2.0,
2977            samples: 2,
2978            ..RateEstimate::default()
2979        };
2980        assert_eq!(
2981            compare_opening_model_rates(
2982                baseline,
2983                RateEstimate {
2984                    mean: 90.0,
2985                    half_width: 2.0,
2986                    samples: 2,
2987                    ..RateEstimate::default()
2988                },
2989                0.05,
2990            ),
2991            RatificationOutcome::Regressed,
2992            "a separated, material loss must reject the model point"
2993        );
2994
2995        assert_eq!(
2996            compare_opening_model_rates(
2997                RateEstimate {
2998                    mean: 100.0,
2999                    half_width: 10.0,
3000                    samples: 2,
3001                    ..RateEstimate::default()
3002                },
3003                RateEstimate {
3004                    mean: 80.0,
3005                    half_width: 10.0,
3006                    samples: 2,
3007                    ..RateEstimate::default()
3008                },
3009                0.05,
3010            ),
3011            RatificationOutcome::Inconclusive,
3012            "touching intervals must not restore an unevidenced opening"
3013        );
3014
3015        assert_ne!(
3016            compare_opening_model_rates(
3017                RateEstimate {
3018                    mean: 100.0,
3019                    half_width: 0.1,
3020                    samples: 2,
3021                    ..RateEstimate::default()
3022                },
3023                RateEstimate {
3024                    mean: 96.0,
3025                    half_width: 0.1,
3026                    samples: 2,
3027                    ..RateEstimate::default()
3028                },
3029                0.05,
3030            ),
3031            RatificationOutcome::Regressed,
3032            "a statistically separated but immaterial loss must keep the model point"
3033        );
3034    }
3035
3036    #[test]
3037    fn bracket_without_a_proven_alternative_retains_the_model_not_the_opening() {
3038        let opening = (28, 4);
3039        let model = (31, 1);
3040        assert_eq!(
3041            retained_bracket_outcome(false, Some(model), opening),
3042            OpeningBracketOutcome::ModelSelected
3043        );
3044        assert_eq!(
3045            retained_bracket_outcome(false, Some(opening), opening),
3046            OpeningBracketOutcome::OpeningRetained
3047        );
3048        assert_eq!(
3049            retained_bracket_outcome(true, Some(model), opening),
3050            OpeningBracketOutcome::AlternativeSelected
3051        );
3052    }
3053
3054    #[test]
3055    fn empirical_cap_can_independently_confirm_the_model_target() {
3056        let model = |useful_cap, useful_cap_reason| Model {
3057            producer_cost_share: 0.0,
3058            producer_cost_share_uncertainty: 0.0,
3059            ideal_producer_slots: 1,
3060            useful_cap,
3061            useful_cap_reason,
3062            effective_deadband_threads: 1,
3063            effective_resurvey_distance: 1,
3064        };
3065        assert!(empirical_cap_confirms_target(
3066            Some(model(1, ProducerCapReason::Slack)),
3067            1
3068        ));
3069        assert!(empirical_cap_confirms_target(
3070            Some(model(1, ProducerCapReason::Source)),
3071            2
3072        ));
3073        assert!(!empirical_cap_confirms_target(
3074            Some(model(2, ProducerCapReason::Slack)),
3075            1
3076        ));
3077        assert!(!empirical_cap_confirms_target(
3078            Some(model(1, ProducerCapReason::None)),
3079            1
3080        ));
3081        assert!(!empirical_cap_confirms_target(None, 1));
3082    }
3083
3084    #[test]
3085    fn opening_bracket_horizon_rounds_up_to_whole_samples() {
3086        assert_eq!(
3087            duration_samples(Duration::from_millis(300), Duration::from_millis(100)),
3088            3
3089        );
3090        assert_eq!(
3091            duration_samples(Duration::from_millis(301), Duration::from_millis(100)),
3092            4
3093        );
3094        assert_eq!(
3095            duration_samples(Duration::from_nanos(1), Duration::from_millis(100)),
3096            1
3097        );
3098    }
3099
3100    #[test]
3101    fn opening_bracket_alternates_around_the_opening_and_is_bounded() {
3102        assert_eq!(
3103            opening_bracket_targets(32, 1, 1, 4, 1, 2)
3104                .into_iter()
3105                .collect::<Vec<_>>(),
3106            vec![5, 3]
3107        );
3108        assert_eq!(
3109            opening_bracket_targets(8, 1, 1, 4, 7, 3)
3110                .into_iter()
3111                .collect::<Vec<_>>(),
3112            vec![3, 5, 2]
3113        );
3114        assert!(opening_bracket_targets(8, 1, 1, 4, 1, 0).is_empty());
3115        assert_eq!(
3116            opening_bracket_targets(8, 1, 3, 4, 6, 4)
3117                .into_iter()
3118                .collect::<Vec<_>>(),
3119            vec![3, 5, 7],
3120            "candidate generation must preserve the configured safety floor"
3121        );
3122    }
3123
3124    #[test]
3125    fn isolated_zero_progress_cannot_create_rejection_evidence() {
3126        let baseline = RateEstimate::from_rates([100.0; 10].into_iter());
3127        let mut achieved = [100.0; 10];
3128        achieved[4] = 0.0;
3129        let achieved = RateEstimate::from_rates(achieved.into_iter());
3130        assert_eq!(
3131            compare_rates(baseline, achieved, 0.05),
3132            RatificationOutcome::Inconclusive
3133        );
3134    }
3135
3136    #[test]
3137    fn one_sparse_probe_cannot_satisfy_resurvey_persistence() {
3138        let active = Duration::from_millis(100);
3139        let sparse = Duration::from_secs(5);
3140        let first = consecutive_drift_duration(Duration::ZERO, sparse, active);
3141        assert_eq!(first, active);
3142        assert!(first < Duration::from_millis(800));
3143
3144        // A second consecutive observation really does establish that the
3145        // drift persisted across the sparse interval.
3146        assert_eq!(
3147            consecutive_drift_duration(first, sparse, active),
3148            Duration::from_millis(5_100)
3149        );
3150    }
3151
3152    #[test]
3153    fn shrink_veto_requires_evidence_that_current_capacity_was_used() {
3154        let window = Duration::from_millis(100);
3155        assert!(!producer_capacity_used(0, 2, 20_000_000, window));
3156        assert!(producer_capacity_used(2, 2, 0, window));
3157        assert!(producer_capacity_used(0, 2, 150_000_000, window));
3158    }
3159}