Skip to main content

src/pharos@alert.erl

-module(pharos@alert).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/pharos/alert.gleam").
-export([prune_samples/3, window_breached/3]).
-export_type([alert_level/0, alert_event/0, alert_state/0, alert_data/0, window_mode/0, window_spec/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.

-type alert_level() :: warning | critical.

-type alert_event() :: {alert_firing, binary(), alert_level(), binary()} |
    {alert_resolved, binary()}.

-type alert_state() :: clear | pending | firing | cooling.

-type alert_data() :: {alert_data,
        binary(),
        alert_level(),
        integer(),
        integer(),
        gleam@option:option(window_spec())}.

-type window_mode() :: average | {percentage, float()}.

-type window_spec() :: {window_spec, integer(), float(), window_mode()}.

-file("src/pharos/alert.gleam", 74).
?DOC(" Drop samples (a `#(timestamp_ms, value)` list) older than `now - window_ms`.\n").
-spec prune_samples(list({integer(), float()}), integer(), integer()) -> list({integer(),
    float()}).
prune_samples(Samples, Now, Window_ms) ->
    Cutoff = Now - Window_ms,
    gleam@list:filter(
        Samples,
        fun(Sample) -> erlang:element(1, Sample) >= Cutoff end
    ).

-file("src/pharos/alert.gleam", 106).
-spec mean(list(float())) -> float().
mean(Values) ->
    Sum = gleam@list:fold(Values, +0.0, fun(Acc, Value) -> Acc + Value end),
    case erlang:float(erlang:length(Values)) of
        +0.0 -> +0.0;
        -0.0 -> -0.0;
        Gleam@denominator -> Sum / Gleam@denominator
    end.

-file("src/pharos/alert.gleam", 85).
?DOC(
    " Whether the window currently breaches per `spec`, considering only samples\n"
    " within `[now - window_ms, now]`. An empty window is never breaching.\n"
).
-spec window_breached(list({integer(), float()}), integer(), window_spec()) -> boolean().
window_breached(Samples, Now, Spec) ->
    Values = begin
        _pipe = prune_samples(Samples, Now, erlang:element(2, Spec)),
        gleam@list:map(_pipe, fun(Sample) -> erlang:element(2, Sample) end)
    end,
    case Values of
        [] ->
            false;

        _ ->
            case erlang:element(4, Spec) of
                average ->
                    mean(Values) > erlang:element(3, Spec);

                {percentage, Ratio} ->
                    Breaching = gleam@list:count(
                        Values,
                        fun(Value) -> Value > erlang:element(3, Spec) end
                    ),
                    (case erlang:float(erlang:length(Values)) of
                        +0.0 -> +0.0;
                        -0.0 -> -0.0;
                        Gleam@denominator -> erlang:float(Breaching) / Gleam@denominator
                    end) >= Ratio
            end
    end.