algorithm.md
August 8, 2026 ยท View on GitHub
The recommendation engine is a chain of six composable estimators. Each estimator wraps the previous one, processing the result before passing it along.
Chain overview
flowchart LR
A["1. Percentile"] --> B["2. Margin"]
B --> C["3. Burst"]
C --> D["4. Confidence"]
D --> E["5. Bounds"]
E --> F["6. Change Filter"]
The chain is constructed in recommendation.NewEngine() and invoked via
Recommend(profile, current).
Metrics input (before the chain)
Prometheus queries feed the profile builder. By default,
metricsSource.podAggregation is Max (max by (container)), so each
container name contributes one series (the hottest pod) before percentiles
run. Set podAggregation: None or Avg only when you need the legacy
multi-pod sample pool. Caps such as maxPodsInMetricsQuery and
maxProfileSamples further bound query and memory cost; see the
scaling guide.
1. Percentile Estimator
Selects the configured percentile from the usage profile. Usage data is bucketed into 24 hourly slots, and the estimator takes the maximum across all hours to ensure peak-hour coverage.
result = max(selectPercentile(overallPercentiles),
max(selectPercentile(hourlyPercentiles[0..23])))
Supported percentiles: 50, 90, 95, 99 (default: 95 for CPU, 99
for memory).
Time-of-day awareness
The hourly bucketing provides built-in time-of-day awareness. A workload
that peaks at 2 PM will have a high p95 in bucket 14, and that peak
propagates through the max() to the final recommendation. This prevents
under-provisioning for workloads with strong diurnal patterns.
2. Margin Estimator
Multiplies the inner result by a safety factor to provide headroom:
result = inner * factor
Typical values: 20 (20% headroom) for CPU, 30 (30%) for memory.
Internally, overhead percentage is converted to a multiplier (1 + overhead/100)
and applied in millicore precision, rounded up.
3. Burst Estimator
BuildProfile() flags bursts when max > 3x p95. The burst estimator
uses BurstMagnitude to apply a logarithmic overhead boost after the
base overhead and before the confidence adjustment. See the
Burst detection section below for the full formula and
sensitivity tuning.
4. Confidence Estimator
Widens the recommendation when data confidence is low. High-confidence recommendations (near 1.0) pass through with minimal adjustment. Low confidence inflates the result to be conservative.
Formula:
result = inner * (1 + multiplier / max(confidence, 0.1)) ^ exponent
| Parameter | Default | Effect |
|---|---|---|
multiplier | 1.0 | Controls inflation magnitude |
exponent | 2.0 | Controls curve steepness |
Confidence is floored at 0.1 to prevent division by zero.
Example: with confidence = 0.5, multiplier = 1.0, exponent = 2.0:
factor = (1 + 1.0/0.5)^2 = 3.$0^{2}$ = 9.0
This means a low-confidence recommendation is inflated 9x, resulting in a very conservative (high) value that avoids under-provisioning.
How confidence is computed
Confidence is derived from two components in metrics.BuildProfile():
timeComponent = timeSpanDays
dataComponent = sqrt(dataPoints / 24)
confidence = clamp(min(timeComponent, dataComponent) / 7, 0, 1)
A full 7-day history window at the default queryStep: 5m yields confidence near 1.0.
5. Bounds Estimator
Clamps the result to user-defined minimum and maximum values:
result = clamp(inner, min, max)
This ensures recommendations never drop below a safe floor (e.g. 50m CPU)
or exceed a known capacity ceiling (e.g. 4000m CPU).
6. Change Filter
Prevents thrashing from tiny adjustments and dangerous large swings:
changePct = abs(recommended - current) / current * 100
if changePct < MinChangePercent:
return current # suppress noise
if changePct > MaxChangePercent:
return current +/- (current * MaxChangePercent / 100) # cap
| Parameter | Default | Purpose |
|---|---|---|
MinChangePercent | 10% | Ignore changes below this threshold |
MaxChangePercent | 50% (CPU) / 30% (memory) | Cap changes above this threshold |
Burst detection
BuildProfile() flags bursts when max > 3x p95. The recommendation engine
uses BurstMagnitude to apply a logarithmic overhead boost:
burstFactor = 1 + sensitivity * log2(BurstMagnitude)
The sensitivity defaults to 0.1 and can be configured per resource via
spec.cpu.burstSensitivity / spec.memory.burstSensitivity. Set to "0"
to disable burst boost entirely (useful for batch jobs).
| Burst magnitude | Boost (sensitivity=0.1) | Boost (sensitivity=0.2) |
|---|---|---|
| 4x | +20% | +40% |
| 8x | +30% | +60% |
| 16x | +40% | +80% |
| 100x | +66% | +133% |
This step runs after the base overhead and before the confidence
adjustment. When no burst is detected (or magnitude <= 1), the factor
is 1.0 (no change). The burst factor is visible in kubectl attune explain
output via the burstFactor and afterBurst fields, and as the
attune_burst_factor Prometheus metric.
Full pipeline example
Given: p95 CPU = 200m, overhead = 20%, confidence = 0.8, bounds = [1m, 4000m], current = 500m, max change = 50%.
| Stage | Calculation | Result |
|---|---|---|
| Percentile | max across hourly p95 | 200m |
| Overhead | 200m * (1 + 20/100) = 200m * 1.2 | 240m |
| Confidence | 240m * (1 + 1/0.8)^2 = 240m * 5.0625 | 1215m |
| Bounds | clamp(1215m, 1m, 4000m) | 1215m |
| Change Filter | change = abs(1215-500)/500 = 143% > 50%, cap | 750m |
Final recommendation: 750m (capped at 50% increase from 500m).