Class: ActiveMutator::TimeoutCalibrator

Inherits:
Object
  • Object
show all
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

Constructor Details

#initializeTimeoutCalibrator

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

#scaleObject



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

Returns:

  • (Boolean)


49
# File 'lib/active_mutator/timeout_calibrator.rb', line 49

def warmed? = @utilizations.size >= WARMUP