PhiAccrual.Core
(phi_accrual v1.0.0)
View Source
Pure EWMA φ-accrual math. No processes, no side effects.
State evolves via observe/2 (fed the arrival timestamp of a new
heartbeat) and is queried via phi/2 (computes φ for the elapsed
time since the last arrival).
The estimator follows Hayashibara 2004, with West 1979 incremental variance and separate α for mean and variance (dual-α EWMA):
delta = sample - mean
mean' = mean + α_mean * delta
variance' = (1 - α_var) * (variance + α_var * delta²)Variance typically needs more smoothing than mean — otherwise a single
anomalous sample doubles variance and craters φ. Defaults keep both α
equal; tune alpha_var smaller than alpha_mean under bursty input.
Summary
Functions
Build fresh estimator state. Accepts any struct field as a keyword override,
plus :initial_interval_ms / :initial_std_dev_ms which seed mean and
variance before the first sample lands.
Record a heartbeat arrival at local monotonic-ms timestamp ts.
Compute φ given state and current monotonic-ms timestamp.
Types
@type phi_result() :: {:ok, float(), :steady} | {:ok, float(), :recovering} | {:insufficient_data, pos_integer()} | {:stale, non_neg_integer()}
@type t() :: %PhiAccrual.Core{ alpha_mean: float(), alpha_var: float(), last_arrival_ts: integer() | nil, last_interval_ms: float() | nil, mean: float(), min_samples: pos_integer(), min_std_dev_ms: float(), recovering_grace_samples: non_neg_integer(), recovering_remaining: non_neg_integer(), recovering_threshold_ms: pos_integer(), samples_seen: non_neg_integer(), stale_after_ms: pos_integer(), variance: float() }
Functions
Build fresh estimator state. Accepts any struct field as a keyword override,
plus :initial_interval_ms / :initial_std_dev_ms which seed mean and
variance before the first sample lands.
Record a heartbeat arrival at local monotonic-ms timestamp ts.
First call seeds last_arrival_ts without updating mean/variance —
an EWMA update needs two timestamps to derive an interval.
@spec phi(t(), integer()) :: phi_result()
Compute φ given state and current monotonic-ms timestamp.
Returns one of four states per the v1 contract:
{:ok, phi, :steady}— warm estimator, normal operation{:ok, phi, :recovering}— warm estimator, still absorbing a recent gap{:insufficient_data, n}— bootstrap phase,nsamples remaining{:stale, elapsed_ms}— no arrival for longer thanstale_after_ms