Kepler.Budget (Kepler v0.1.0)

Copy Markdown View Source

Kepler measuring its own cost, and shedding work when it exceeds it.

A library that asks to be left on in production permanently owes you a number. This is that number: the fraction of one node's compute capacity Kepler's evaluation pass consumed, measured directly rather than estimated.

Kepler.status().budget
#=> %{share: 0.0008, level: 0, reductions_per_second: 4_120, ...}

What is measured

:share is tick duration / (window x schedulers) — the poller's wall time as a fraction of the compute the node had available over the same window. It is measured with a monotonic clock around the evaluation pass, so it is a real observation, not a model.

:reductions_per_second covers Kepler's processes as a whole, including delivery. Reductions are exact and per-process, so reading them costs nothing and disturbs nothing.

Neither number includes the telemetry handlers, because those run on your processes and are one atomic increment each. That is the point of the design: the cost that scales with your traffic is the cost that is too small to measure.

Shedding

Going over budget for :shed_after consecutive ticks raises the shed level; staying under for :restore_after lowers it again. The hysteresis matters — shedding on a single slow tick would make Kepler flap alongside whatever made the node slow.

LevelEffect
0Everything runs.
1Tier 2 sampling (process:, vm:) runs on one tick in four.
2Level 1, and the tick interval is multiplied by four, up to :max_tick.

Tiers 0 and 1 are never shed. A system monitor costs nothing until it trips, and a telemetry counter costs nothing to leave incrementing — turning either off would save nothing and lose data.

Summary

Functions

The tick interval to use, given the current shed level.

Builds a budget from the :budget config group.

Records what the last tick cost and decides whether to shed.

Whether tier 2 sources should be sampled on tick number tick.

The budget as it appears in Kepler.status/0 and in event payloads.

Types

t()

@type t() :: %Kepler.Budget{
  level: 0..2,
  limit: float() | :infinity,
  max_tick: pos_integer(),
  over: non_neg_integer(),
  reductions: non_neg_integer(),
  reductions_per_second: float(),
  restore_after: pos_integer(),
  schedulers: pos_integer(),
  share: float(),
  shed_after: pos_integer(),
  ticks: non_neg_integer(),
  under: non_neg_integer()
}

Functions

interval(budget, configured)

@spec interval(t(), pos_integer()) :: pos_integer()

The tick interval to use, given the current shed level.

new(opts)

@spec new(map()) :: t()

Builds a budget from the :budget config group.

observe(budget, duration_us, window_us, reductions)

@spec observe(t(), non_neg_integer(), pos_integer(), non_neg_integer()) :: t()

Records what the last tick cost and decides whether to shed.

duration_us is how long the evaluation pass took, window_us how long the window it covered was, and reductions Kepler's total reductions to date across its own processes.

sample_tier_two?(budget, tick)

@spec sample_tier_two?(t(), non_neg_integer()) :: boolean()

Whether tier 2 sources should be sampled on tick number tick.

Tier 2 is the only sampling with a per-tick cost worth skipping, and skipping it entirely would leave gauges frozen, so a shed level samples it less often rather than not at all.

to_map(budget)

@spec to_map(t()) :: map()

The budget as it appears in Kepler.status/0 and in event payloads.