Packages

Watches a named metric from another monitor and emits a health signal based

Current section

Files

Jump to
raven_integration_boundary_probe lib integrations boundary_probe.ex
Raw

lib/integrations/boundary_probe.ex

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"""
<div class="space-y-2">
<div class="flex items-center gap-2">
<CodeNameRaven.MonitorComponents.status_badge status={@status} />
<CodeNameRaven.MonitorComponents.check_time checked_at={@last_checked_at} />
</div>
<div :if={@result} class="mt-2 text-sm text-base-content/80">
<div><span class="font-semibold">Watching:</span> <code class="font-mono bg-base-200 px-1 rounded"><%= @result.monitor_id %>/<%= @result.metric_name %></code></div>
<div class="mt-1"><span class="font-semibold">Last Value:</span> <span class="font-mono"><%= @result.last_value || "none" %></span></div>
<div class="mt-1 text-xs text-base-content/50">
Thresholds:
<%= if @result.soft, do: "Soft > #{@result.soft}", else: "" %>
<%= if @result.hard, do: " | Hard > #{@result.hard}", else: "" %>
<%= if not @result.soft and not @result.hard, do: "none" %>
</div>
</div>
<CodeNameRaven.MonitorComponents.error_message message={@last_error} />
</div>
"""
end
end