Skip to main content

src/pharos@config.erl

-module(pharos@config).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/pharos/config.gleam").
-export([new/0, with_statistics/2, with_thresholds/2, with_custom_statistics/2, with_custom_thresholds/2, with_alert_sinks/2, with_memory_unit/2, with_soak_period/2, with_cool_period/2, with_default_alert_level/2, with_metric_buffer_capacity/2, with_brain_stream/3, with_metric_spillover/2, with_poll_jitter/2, threshold_id/1]).
-export_type([threshold/0, alert_sink/0, brain_stream/0, config/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(
    " User-facing configuration for `pharos.start_link`.\n"
    "\n"
    " Build a `Config` with `new/0` (sensible defaults), pipe through the\n"
    " `with_*` setters to add statistics, thresholds, and tune the alert\n"
    " timing knobs, then pass it to `pharos.start_link`.\n"
).

-type threshold() :: {total_memory, float()} |
    {process_memory, float()} |
    {system_memory, float()} |
    {binary_memory, float()} |
    {ets_memory, float()} |
    {total_run_queue, integer()} |
    {cpu_run_queue, integer()} |
    {process_count, integer()} |
    {atom_count, integer()} |
    {port_count, integer()} |
    {persistent_term_count, integer()} |
    {persistent_term_memory, float()} |
    {host_memory_used, float()} |
    {host_disk_used, float()} |
    {host_cpu_util, float()} |
    {scheduler_utilization, float()} |
    {windowed, threshold(), integer(), pharos@alert:window_mode()}.

-type alert_sink() :: {console, logging:log_level()} |
    {webhook, binary()} |
    {brain, binary(), binary()} |
    {otlp, binary()}.

-type brain_stream() :: {brain_stream, binary(), binary()}.

-type config() :: {config,
        list(pharos@statistic:statistic()),
        list(threshold()),
        list(pharos@probe:probe()),
        list(pharos@probe:probe_threshold()),
        list(alert_sink()),
        pharos@measurement:memory_unit(),
        integer(),
        integer(),
        pharos@alert:alert_level(),
        integer(),
        gleam@option:option(brain_stream()),
        gleam@option:option(binary()),
        gleam@option:option(float())}.

-file("src/pharos/config.gleam", 144).
?DOC(
    " Sensible defaults: no statistics, no thresholds, log alerts at\n"
    " `Warning`, scale memory in `Mb`, 30 s soak, 60 s cool, default alert\n"
    " level `Warning`. Pipe through `with_*` setters to customise.\n"
).
-spec new() -> config().
new() ->
    {config,
        [],
        [],
        [],
        [],
        [{console, warning}],
        mb,
        30000,
        60000,
        warning,
        10000,
        none,
        none,
        none}.

-file("src/pharos/config.gleam", 163).
?DOC(" Set the statistics to poll. Replaces any previously configured list.\n").
-spec with_statistics(config(), list(pharos@statistic:statistic())) -> config().
with_statistics(Config, Statistics) ->
    {config,
        Statistics,
        erlang:element(3, Config),
        erlang:element(4, Config),
        erlang:element(5, Config),
        erlang:element(6, Config),
        erlang:element(7, Config),
        erlang:element(8, Config),
        erlang:element(9, Config),
        erlang:element(10, Config),
        erlang:element(11, Config),
        erlang:element(12, Config),
        erlang:element(13, Config),
        erlang:element(14, Config)}.

-file("src/pharos/config.gleam", 168).
?DOC(" Set the thresholds to evaluate. Replaces any previously configured list.\n").
-spec with_thresholds(config(), list(threshold())) -> config().
with_thresholds(Config, Thresholds) ->
    {config,
        erlang:element(2, Config),
        Thresholds,
        erlang:element(4, Config),
        erlang:element(5, Config),
        erlang:element(6, Config),
        erlang:element(7, Config),
        erlang:element(8, Config),
        erlang:element(9, Config),
        erlang:element(10, Config),
        erlang:element(11, Config),
        erlang:element(12, Config),
        erlang:element(13, Config),
        erlang:element(14, Config)}.

-file("src/pharos/config.gleam", 173).
?DOC(" Set the custom probes to poll. Replaces any previously configured list.\n").
-spec with_custom_statistics(config(), list(pharos@probe:probe())) -> config().
with_custom_statistics(Config, Probes) ->
    {config,
        erlang:element(2, Config),
        erlang:element(3, Config),
        Probes,
        erlang:element(5, Config),
        erlang:element(6, Config),
        erlang:element(7, Config),
        erlang:element(8, Config),
        erlang:element(9, Config),
        erlang:element(10, Config),
        erlang:element(11, Config),
        erlang:element(12, Config),
        erlang:element(13, Config),
        erlang:element(14, Config)}.

-file("src/pharos/config.gleam", 178).
?DOC(" Set the custom probe thresholds. Replaces any previously configured list.\n").
-spec with_custom_thresholds(config(), list(pharos@probe:probe_threshold())) -> config().
with_custom_thresholds(Config, Thresholds) ->
    {config,
        erlang:element(2, Config),
        erlang:element(3, Config),
        erlang:element(4, Config),
        Thresholds,
        erlang:element(6, Config),
        erlang:element(7, Config),
        erlang:element(8, Config),
        erlang:element(9, Config),
        erlang:element(10, Config),
        erlang:element(11, Config),
        erlang:element(12, Config),
        erlang:element(13, Config),
        erlang:element(14, Config)}.

-file("src/pharos/config.gleam", 186).
?DOC(" Set the alert sinks. Replaces any previously configured list.\n").
-spec with_alert_sinks(config(), list(alert_sink())) -> config().
with_alert_sinks(Config, Sinks) ->
    {config,
        erlang:element(2, Config),
        erlang:element(3, Config),
        erlang:element(4, Config),
        erlang:element(5, Config),
        Sinks,
        erlang:element(7, Config),
        erlang:element(8, Config),
        erlang:element(9, Config),
        erlang:element(10, Config),
        erlang:element(11, Config),
        erlang:element(12, Config),
        erlang:element(13, Config),
        erlang:element(14, Config)}.

-file("src/pharos/config.gleam", 192).
?DOC(
    " Set the memory unit used for threshold comparisons and decoded\n"
    " measurements.\n"
).
-spec with_memory_unit(config(), pharos@measurement:memory_unit()) -> config().
with_memory_unit(Config, Unit) ->
    {config,
        erlang:element(2, Config),
        erlang:element(3, Config),
        erlang:element(4, Config),
        erlang:element(5, Config),
        erlang:element(6, Config),
        Unit,
        erlang:element(8, Config),
        erlang:element(9, Config),
        erlang:element(10, Config),
        erlang:element(11, Config),
        erlang:element(12, Config),
        erlang:element(13, Config),
        erlang:element(14, Config)}.

-file("src/pharos/config.gleam", 198).
?DOC(
    " Set the soak period (milliseconds a threshold must stay breached before\n"
    " firing). Set to 0 to fire immediately.\n"
).
-spec with_soak_period(config(), integer()) -> config().
with_soak_period(Config, Milliseconds) ->
    {config,
        erlang:element(2, Config),
        erlang:element(3, Config),
        erlang:element(4, Config),
        erlang:element(5, Config),
        erlang:element(6, Config),
        erlang:element(7, Config),
        Milliseconds,
        erlang:element(9, Config),
        erlang:element(10, Config),
        erlang:element(11, Config),
        erlang:element(12, Config),
        erlang:element(13, Config),
        erlang:element(14, Config)}.

-file("src/pharos/config.gleam", 204).
?DOC(
    " Set the cool-down period (milliseconds recovery must hold before\n"
    " resolving).\n"
).
-spec with_cool_period(config(), integer()) -> config().
with_cool_period(Config, Milliseconds) ->
    {config,
        erlang:element(2, Config),
        erlang:element(3, Config),
        erlang:element(4, Config),
        erlang:element(5, Config),
        erlang:element(6, Config),
        erlang:element(7, Config),
        erlang:element(8, Config),
        Milliseconds,
        erlang:element(10, Config),
        erlang:element(11, Config),
        erlang:element(12, Config),
        erlang:element(13, Config),
        erlang:element(14, Config)}.

-file("src/pharos/config.gleam", 209).
?DOC(" Set the default alert level attached to firing alerts.\n").
-spec with_default_alert_level(config(), pharos@alert:alert_level()) -> config().
with_default_alert_level(Config, Level) ->
    {config,
        erlang:element(2, Config),
        erlang:element(3, Config),
        erlang:element(4, Config),
        erlang:element(5, Config),
        erlang:element(6, Config),
        erlang:element(7, Config),
        erlang:element(8, Config),
        erlang:element(9, Config),
        Level,
        erlang:element(11, Config),
        erlang:element(12, Config),
        erlang:element(13, Config),
        erlang:element(14, Config)}.

-file("src/pharos/config.gleam", 214).
?DOC(" Set the hot-buffer capacity (number of recent metrics retained).\n").
-spec with_metric_buffer_capacity(config(), integer()) -> config().
with_metric_buffer_capacity(Config, Capacity) ->
    {config,
        erlang:element(2, Config),
        erlang:element(3, Config),
        erlang:element(4, Config),
        erlang:element(5, Config),
        erlang:element(6, Config),
        erlang:element(7, Config),
        erlang:element(8, Config),
        erlang:element(9, Config),
        erlang:element(10, Config),
        Capacity,
        erlang:element(12, Config),
        erlang:element(13, Config),
        erlang:element(14, Config)}.

-file("src/pharos/config.gleam", 220).
?DOC(
    " Stream buffered metrics to the registered process `name` on `node` (empty\n"
    " `node` = local node). Enables the connection manager.\n"
).
-spec with_brain_stream(config(), binary(), binary()) -> config().
with_brain_stream(Config, Node, Name) ->
    {config,
        erlang:element(2, Config),
        erlang:element(3, Config),
        erlang:element(4, Config),
        erlang:element(5, Config),
        erlang:element(6, Config),
        erlang:element(7, Config),
        erlang:element(8, Config),
        erlang:element(9, Config),
        erlang:element(10, Config),
        erlang:element(11, Config),
        {some, {brain_stream, Node, Name}},
        erlang:element(13, Config),
        erlang:element(14, Config)}.

-file("src/pharos/config.gleam", 227).
?DOC(
    " Enable the Dets cold-tier spillover at `path`. Metrics spill to this file\n"
    " when the Brain connection is lost and the hot buffer fills, and are replayed\n"
    " (disk first) on reconnect. Only effective alongside `with_brain_stream`.\n"
).
-spec with_metric_spillover(config(), binary()) -> config().
with_metric_spillover(Config, Path) ->
    {config,
        erlang:element(2, Config),
        erlang:element(3, Config),
        erlang:element(4, Config),
        erlang:element(5, Config),
        erlang:element(6, Config),
        erlang:element(7, Config),
        erlang:element(8, Config),
        erlang:element(9, Config),
        erlang:element(10, Config),
        erlang:element(11, Config),
        erlang:element(12, Config),
        {some, Path},
        erlang:element(14, Config)}.

-file("src/pharos/config.gleam", 235).
?DOC(
    " Jitter polling by up to `ratio` of each statistic's interval (clamped to\n"
    " `[0.0, 1.0]`). Each poller starts after a random delay in\n"
    " `[0, interval * ratio)`, staggering traffic across a fleet so agents never\n"
    " poll in lockstep.\n"
).
-spec with_poll_jitter(config(), float()) -> config().
with_poll_jitter(Config, Ratio) ->
    {config,
        erlang:element(2, Config),
        erlang:element(3, Config),
        erlang:element(4, Config),
        erlang:element(5, Config),
        erlang:element(6, Config),
        erlang:element(7, Config),
        erlang:element(8, Config),
        erlang:element(9, Config),
        erlang:element(10, Config),
        erlang:element(11, Config),
        erlang:element(12, Config),
        erlang:element(13, Config),
        {some, gleam@float:clamp(Ratio, +0.0, 1.0)}}.

-file("src/pharos/config.gleam", 246).
?DOC(
    " Stable string id derived from a threshold variant. Used to register\n"
    " per-threshold alert managers under a deterministic name so they can be\n"
    " looked up after a restart.\n"
).
-spec threshold_id(threshold()) -> binary().
threshold_id(Threshold) ->
    case Threshold of
        {total_memory, _} ->
            <<"total_memory"/utf8>>;

        {process_memory, _} ->
            <<"process_memory"/utf8>>;

        {system_memory, _} ->
            <<"system_memory"/utf8>>;

        {binary_memory, _} ->
            <<"binary_memory"/utf8>>;

        {ets_memory, _} ->
            <<"ets_memory"/utf8>>;

        {total_run_queue, _} ->
            <<"total_run_queue"/utf8>>;

        {cpu_run_queue, _} ->
            <<"cpu_run_queue"/utf8>>;

        {process_count, _} ->
            <<"process_count"/utf8>>;

        {atom_count, _} ->
            <<"atom_count"/utf8>>;

        {port_count, _} ->
            <<"port_count"/utf8>>;

        {persistent_term_count, _} ->
            <<"persistent_term_count"/utf8>>;

        {persistent_term_memory, _} ->
            <<"persistent_term_memory"/utf8>>;

        {host_memory_used, _} ->
            <<"host_memory_used"/utf8>>;

        {host_disk_used, _} ->
            <<"host_disk_used"/utf8>>;

        {host_cpu_util, _} ->
            <<"host_cpu_util"/utf8>>;

        {scheduler_utilization, _} ->
            <<"scheduler_utilization"/utf8>>;

        {windowed, Over, _, _} ->
            <<"windowed_"/utf8, (threshold_id(Over))/binary>>
    end.