Functional Weave
Code in Python

monitor.alert-state@1.0.0

impl/typescript.ts

3,541 bytes · the TypeScript implementation · view raw

import { type AlertPolicy, type AlertState, type AlertTransition } from "./monitor_alert_state_types.ts";

/**
 * One step of an alert's lifecycle. Pure: the caller stores the returned
 * state and passes it back at the next evaluation.
 *
 * Firing needs the condition to hold forSeconds (Prometheus `for`);
 * resolving needs it to stay false clearForSeconds, so a flapping check does
 * not send resolved-firing-resolved storms. lastNotifiedAt records the last
 * notification of any kind; it drives the repeat interval while firing.
 */
export function nextAlertState(previous: AlertState, condition: boolean, now: number, policy: AlertPolicy): AlertTransition {
  if (!Number.isSafeInteger(now)) throw new RangeError("now must be a whole number of seconds");
  const { forSeconds, clearForSeconds } = policy;
  const renotify = policy.renotifySeconds ?? null;
  if (forSeconds < 0) throw new RangeError(`forSeconds must not be negative, received ${forSeconds}`);
  if (clearForSeconds < 0) throw new RangeError(`clearForSeconds must not be negative, received ${clearForSeconds}`);
  if (renotify !== null && renotify < 1) throw new RangeError(`renotifySeconds must be null or at least 1, received ${renotify}`);
  const phase = previous.phase;
  if (phase !== "inactive" && phase !== "pending" && phase !== "firing" && phase !== "resolved") {
    throw new RangeError(`unknown alert phase: ${String(phase)}`);
  }
  const since = previous.since ?? null;
  const last = previous.lastNotifiedAt ?? null;
  const clearSince = previous.clearSince ?? null;
  if ((phase === "pending" || phase === "firing") && since === null) {
    throw new RangeError(`since must be set in phase ${phase}`);
  }
  const times: Array<[string, number | null]> = [["since", since], ["lastNotifiedAt", last], ["clearSince", clearSince]];
  for (const [name, t] of times) {
    if (t !== null && now < t) throw new RangeError(`now ${now} is earlier than ${name} ${t}`);
  }
  const same: AlertState = { phase, since, lastNotifiedAt: last, clearSince };

  if (phase === "inactive" || phase === "resolved") {
    if (!condition) return { state: same, notify: null, changed: false };
    if (forSeconds === 0) {
      return { state: { phase: "firing", since: now, lastNotifiedAt: now, clearSince: null }, notify: "firing", changed: true };
    }
    return { state: { phase: "pending", since: now, lastNotifiedAt: last, clearSince: null }, notify: null, changed: true };
  }
  if (phase === "pending") {
    if (!condition) return { state: { phase: "inactive", since: now, lastNotifiedAt: last, clearSince: null }, notify: null, changed: true };
    if (now - (since as number) >= forSeconds) {
      return { state: { phase: "firing", since: now, lastNotifiedAt: now, clearSince: null }, notify: "firing", changed: true };
    }
    return { state: same, notify: null, changed: false };
  }
  // firing
  if (condition) {
    if (renotify !== null && (last === null || now - last >= renotify)) {
      return { state: { phase, since, lastNotifiedAt: now, clearSince: null }, notify: "repeat", changed: false };
    }
    return { state: { phase, since, lastNotifiedAt: last, clearSince: null }, notify: null, changed: false };
  }
  const clearing = clearSince ?? now;
  if (now - clearing >= clearForSeconds) {
    return { state: { phase: "resolved", since: now, lastNotifiedAt: now, clearSince: null }, notify: "resolved", changed: true };
  }
  return { state: { phase, since, lastNotifiedAt: last, clearSince: clearing }, notify: null, changed: false };
}