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
ParameterDefaultEffect
multiplier1.0Controls inflation magnitude
exponent2.0Controls 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
ParameterDefaultPurpose
MinChangePercent10%Ignore changes below this threshold
MaxChangePercent50% (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 magnitudeBoost (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%.

StageCalculationResult
Percentilemax across hourly p95200m
Overhead200m * (1 + 20/100) = 200m * 1.2240m
Confidence240m * (1 + 1/0.8)^2 = 240m * 5.06251215m
Boundsclamp(1215m, 1m, 4000m)1215m
Change Filterchange = abs(1215-500)/500 = 143% > 50%, cap750m

Final recommendation: 750m (capped at 50% increase from 500m).