Skip to main content

src/pharos@probe.erl

-module(pharos@probe).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/pharos/probe.gleam").
-export([new/5, id/1, event_name/1, interval_ms/1, source/1, decode/2, threshold_id/1]).
-export_type([source/0, probe/0, comparison/0, probe_threshold/0]).

-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.

?MODULEDOC(
    " Custom monitoring probes - the pluggable lane of pharos.\n"
    "\n"
    " The built-in `Statistic` / `Measurement` / `Threshold` enums cover BEAM\n"
    " and host signals. A `Probe` opens the same \n"
    "    `poll` -> `decode` -> `threshold` -> `alert` \n"
    " pipeline to *any* signal a caller can express, without modifying\n"
    " pharos: it bundles how to poll (a `Source`), the telemetry event it\n"
    " emits, and how to decode that event into named numeric readings\n"
    " (`MetricSample`). `ProbeThreshold`s compare a single sample field against\n"
    " a limit and feed the same soak/cool alert machinery the BEAM lane uses.\n"
    "\n"
    " This is what lets a consumer such as `logothetes` monitor MicroVMs:\n"
    " register a probe whose `Source` points at an emitter that reads\n"
    " `/proc/<pid>` and `telemetry:execute`s the readings, plus thresholds over\n"
    " the decoded fields.\n"
).

-type source() :: {custom_mfa,
        gleam@erlang@atom:atom_(),
        gleam@erlang@atom:atom_(),
        list(gleam@dynamic:dynamic_())}.

-opaque probe() :: {probe,
        binary(),
        list(gleam@erlang@atom:atom_()),
        integer(),
        source(),
        fun((pharos@measurement:telemetry_event()) -> {ok,
                gleam@dict:dict(binary(), float())} |
            {error, binary()})}.

-type comparison() :: {above, float()} | {below, float()}.

-type probe_threshold() :: {probe_threshold,
        binary(),
        binary(),
        comparison(),
        pharos@alert:alert_level()}.

-file("src/pharos/probe.gleam", 56).
?DOC(
    " Define a probe.\n"
    "\n"
    " - `id` - stable identifier, referenced by `ProbeThreshold.probe_id`.\n"
    " - `event_name` - the telemetry event the `source` emits (e.g.\n"
    "   `[atom.create(\"logothetes\"), atom.create(\"vm\"), atom.create(\"metrics\")]`).\n"
    " - `interval_ms` - how often to poll.\n"
    " - `source` - how the reading is produced.\n"
    " - `decode` - lift the raw telemetry event into a `MetricSample`.\n"
).
-spec new(
    binary(),
    list(gleam@erlang@atom:atom_()),
    integer(),
    source(),
    fun((pharos@measurement:telemetry_event()) -> {ok,
            gleam@dict:dict(binary(), float())} |
        {error, binary()})
) -> probe().
new(Id, Event_name, Interval_ms, Source, Decode) ->
    {probe, Id, Event_name, Interval_ms, Source, Decode}.

-file("src/pharos/probe.gleam", 73).
?DOC(" The probe's stable identifier.\n").
-spec id(probe()) -> binary().
id(Probe) ->
    erlang:element(2, Probe).

-file("src/pharos/probe.gleam", 78).
?DOC(" The telemetry event name the probe emits.\n").
-spec event_name(probe()) -> list(gleam@erlang@atom:atom_()).
event_name(Probe) ->
    erlang:element(3, Probe).

-file("src/pharos/probe.gleam", 83).
?DOC(" The probe's polling interval in milliseconds.\n").
-spec interval_ms(probe()) -> integer().
interval_ms(Probe) ->
    erlang:element(4, Probe).

-file("src/pharos/probe.gleam", 88).
?DOC(" How the probe is polled.\n").
-spec source(probe()) -> source().
source(Probe) ->
    erlang:element(5, Probe).

-file("src/pharos/probe.gleam", 94).
?DOC(
    " Decode a raw telemetry event into a `MetricSample` using the probe's\n"
    " decoder.\n"
).
-spec decode(probe(), pharos@measurement:telemetry_event()) -> {ok,
        gleam@dict:dict(binary(), float())} |
    {error, binary()}.
decode(Probe, Event) ->
    (erlang:element(6, Probe))(Event).

-file("src/pharos/probe.gleam", 124).
?DOC(
    " Stable string id derived from a probe threshold. Used to register the\n"
    " per-threshold alert manager under a deterministic name (mirrors\n"
    " `config.threshold_id` for the BEAM lane).\n"
).
-spec threshold_id(probe_threshold()) -> binary().
threshold_id(Threshold) ->
    Comparison = case erlang:element(4, Threshold) of
        {above, _} ->
            <<"above"/utf8>>;

        {below, _} ->
            <<"below"/utf8>>
    end,
    <<<<<<<<(erlang:element(2, Threshold))/binary, "_"/utf8>>/binary,
                (erlang:element(3, Threshold))/binary>>/binary,
            "_"/utf8>>/binary,
        Comparison/binary>>.