Class: ActiveMutator::TimeoutCalibrator
- Inherits:
-
Object
- Object
- ActiveMutator::TimeoutCalibrator
- Defined in:
- lib/active_mutator/timeout_calibrator.rb
Overview
Adaptive timeout budgets (#9). Static budgets derive from baseline times measured warm and unloaded; under parallel load they misclassify slow-but-honest kills as timeouts. The Scheduler feeds this with the observed wall time of every KILLED fork (errors exit artificially fast, survivors run their whole covering set — both would bias the median; timed-out forks have no known wall time at all). One instance per lane: parallel and serial run under different load regimes.
Utilization is elapsed / the EFFECTIVE budget the fork ran under (the value budget_for returned at its spawn), not the static item.timeout — otherwise a pinned-high scale could never observe recovery (ratchet). Once WARMUP observations exist, remaining budgets' variable part is scaled by the clamped median utilization. The fixed part (timeout_floor
- browser boot) is never scaled: fork boot cost does not shrink because examples run fast.
The scale is grow-only (MIN_SCALE = 1.0): budgets may only extend beyond the static value, never fall below it. Downscaling has no recovery signal because timeouts are CENSORED samples — the scheduler skips them (they have no known wall time), so only killed forks feed the window. A low median utilization would shrink the budget, causing a legitimately slow kill to be reaped as a timeout; that censored kill never enters the window, so nothing ever pushes utilization back up. The shrink self-sustains — an asymmetric ratchet. Growing is safe (a too-large budget still records a real kill and relaxes); shrinking is not, so we forbid it.
Constant Summary collapse
- WARMUP =
5- WINDOW =
A real sliding window, not full-run history: a 500-mutant run's early load regime must age out completely instead of anchoring the median forever. 30 samples is enough for a stable median and small enough to track load changes within a few dozen finishes.
30- TARGET_UTILIZATION =
0.25- MIN_SCALE =
1.0- MAX_SCALE =
4.0
Instance Method Summary collapse
- #budget_for(item) ⇒ Object
-
#initialize ⇒ TimeoutCalibrator
constructor
A new instance of TimeoutCalibrator.
- #record(elapsed_seconds, budget) ⇒ Object
- #scale ⇒ Object
- #warmed? ⇒ Boolean
Constructor Details
#initialize ⇒ TimeoutCalibrator
Returns a new instance of TimeoutCalibrator.
38 39 40 |
# File 'lib/active_mutator/timeout_calibrator.rb', line 38 def initialize @utilizations = [] end |
Instance Method Details
#budget_for(item) ⇒ Object
51 52 53 54 55 56 |
# File 'lib/active_mutator/timeout_calibrator.rb', line 51 def budget_for(item) return item.timeout unless warmed? fixed = item.timeout - item.variable item.variable * scale + fixed end |
#record(elapsed_seconds, budget) ⇒ Object
42 43 44 45 46 47 |
# File 'lib/active_mutator/timeout_calibrator.rb', line 42 def record(elapsed_seconds, budget) return unless budget.positive? @utilizations << elapsed_seconds / budget @utilizations.shift while @utilizations.size > WINDOW end |
#scale ⇒ Object
58 59 60 61 62 63 64 65 |
# File 'lib/active_mutator/timeout_calibrator.rb', line 58 def scale # An empty window has no median; a caller invoking scale without warmed? # (median of [] is nil) would otherwise raise. Neutral scale = no change. return 1.0 if @utilizations.empty? s = median(@utilizations) / TARGET_UTILIZATION s.clamp(MIN_SCALE, MAX_SCALE) end |
#warmed? ⇒ Boolean
49 |
# File 'lib/active_mutator/timeout_calibrator.rb', line 49 def warmed? = @utilizations.size >= WARMUP |