defmodule Integrations.BoundaryProbe do @moduledoc """ Watches a named metric from another monitor and emits a health signal based on configurable soft and hard boundaries. Headless by design — no dashboard card. The alert *is* the product here; see `#267` on the backlog for the reasoning. Configure alerting/handlers against this monitor's health signal directly. ## How it works Raven subscribes this monitor to metric events automatically (it's `CodeNameRaven.Probe`-shaped — see `probe_init/1`/`on_metric/3` below). When a metric event arrives matching the configured `monitor_id` and `metric_name`, it updates its cached value and immediately triggers a check cycle. `healthy?/1` compares the cached value against the configured thresholds. ## Params * `monitor_id` — the id of the monitor whose metric to watch (required) * `metric_name` — the name of the metric to watch (required) * `soft` — soft threshold (optional); crossing emits `:degraded` * `hard` — hard threshold (optional); crossing emits `:down` Thresholds are upper bounds: a value *greater than* the threshold is a violation. Both are optional; if neither is set, the probe always returns `:up` when a value is present. ## Example An HTTP monitor publishes `latency_ms` on every check. A BoundaryProbe configured as: monitor_id = "my-http-check" metric_name = "latency_ms" soft = 500 hard = 2000 returns `:up` when latency < 500ms, `:degraded` when 500–2000ms, and `:down` when > 2000ms. """ use CodeNameRaven.Probe use CodeNameRaven.Display, size_hint: :small, category: :system @impl true def params_template do %{monitor_id: "", metric_name: "", soft: nil, hard: nil} end @impl true def probe_init(%{monitor_id: monitor_id, metric_name: metric_name} = params) when is_binary(monitor_id) and is_binary(metric_name) do {:ok, %{ monitor_id: monitor_id, metric_name: metric_name, soft: params[:soft], hard: params[:hard], last_value: nil }} end def probe_init(_params), do: {:error, "monitor_id and metric_name are required"} @impl true def on_metric( %{monitor_id: mid, metric_name: name, value: value, node: msg_node}, _params, %{monitor_id: mid, metric_name: name} = state ) do if msg_node in [nil, :nonode@nohost, node()] do {:ok, %{state | last_value: value}, :check_now} else {:ok, state} end end def on_metric(_metric, _params, state), do: {:ok, state} @impl true def healthy?(%{last_value: nil}), do: :unknown def healthy?(%{last_value: value, soft: soft, hard: hard}) when is_number(value) do cond do is_number(hard) and value > hard -> :down is_number(soft) and value > soft -> :degraded true -> :up end end @impl true def target_uri(%{monitor_id: mid, metric_name: name}), do: {:ok, "boundary://#{mid}/#{name}"} def target_uri(_), do: :none @impl CodeNameRaven.Display def compatible_monitors, do: [__MODULE__] @impl CodeNameRaven.Display def render(assigns) do ~H"""
<%= @result.monitor_id %>/<%= @result.metric_name %>