-module(pharos@threshold_eval).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/pharos/threshold_eval.gleam").
-export([evaluate_probe/2, evaluate/2, metric_name/1, sample_value/2, threshold_limit/1]).
-export_type([verdict/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(
" Evaluate a `Threshold` against a decoded `Measurement`.\n"
"\n"
" Returns a three-way verdict so callers can branch without reaching\n"
" for nested `case` over the cartesian product of measurements and\n"
" thresholds. Helper functions match every measurement variant\n"
" exhaustively, so adding a new measurement kind surfaces as a\n"
" compiler warning here.\n"
).
-type verdict() :: not_applicable | healthy | breached.
-file("src/pharos/threshold_eval.gleam", 344).
-spec verdict(boolean()) -> verdict().
verdict(Is_breached) ->
case Is_breached of
true ->
breached;
false ->
healthy
end.
-file("src/pharos/threshold_eval.gleam", 33).
?DOC(
" Evaluate a probe `threshold` against a decoded probe `sample`, returning\n"
" the same `Verdict` as `evaluate/2`. A `NotApplicable` verdict means the\n"
" threshold's field is absent from this sample, so the caller should neither\n"
" breach nor recover.\n"
).
-spec evaluate_probe(
gleam@dict:dict(binary(), float()),
pharos@probe:probe_threshold()
) -> verdict().
evaluate_probe(Sample, Threshold) ->
case gleam_stdlib:map_get(Sample, erlang:element(3, Threshold)) of
{error, nil} ->
not_applicable;
{ok, Value} ->
case erlang:element(4, Threshold) of
{above, Limit} ->
verdict(Value > Limit);
{below, Limit@1} ->
verdict(Value < Limit@1)
end
end.
-file("src/pharos/threshold_eval.gleam", 310).
-spec check_scheduler(
pharos@measurement:measurement(),
fun((pharos@measurement:scheduler_stats()) -> boolean())
) -> verdict().
check_scheduler(Measurement, Predicate) ->
case Measurement of
{beam_scheduler, Sched} ->
verdict(Predicate(Sched));
{beam_memory, _} ->
not_applicable;
{beam_run_queues, _} ->
not_applicable;
{beam_system_counts, _} ->
not_applicable;
{beam_persistent_term, _} ->
not_applicable;
{process_info, _} ->
not_applicable;
{cluster_nodes, _} ->
not_applicable;
{host_memory, _} ->
not_applicable;
{host_disk, _} ->
not_applicable;
{host_cpu, _} ->
not_applicable;
{host_network, _} ->
not_applicable;
{beam_reductions, _} ->
not_applicable
end.
-file("src/pharos/threshold_eval.gleam", 290).
-spec check_host_cpu(
pharos@measurement:measurement(),
fun((pharos@measurement:host_cpu_stats()) -> boolean())
) -> verdict().
check_host_cpu(Measurement, Predicate) ->
case Measurement of
{host_cpu, Cpu} ->
verdict(Predicate(Cpu));
{beam_memory, _} ->
not_applicable;
{beam_run_queues, _} ->
not_applicable;
{beam_system_counts, _} ->
not_applicable;
{beam_persistent_term, _} ->
not_applicable;
{process_info, _} ->
not_applicable;
{cluster_nodes, _} ->
not_applicable;
{host_memory, _} ->
not_applicable;
{host_disk, _} ->
not_applicable;
{host_network, _} ->
not_applicable;
{beam_scheduler, _} ->
not_applicable;
{beam_reductions, _} ->
not_applicable
end.
-file("src/pharos/threshold_eval.gleam", 333).
?DOC(
" Apply `predicate` to a host probe's stats unless it is still reporting\n"
" `Unimplemented` (its source unavailable), in which case it is\n"
" `NotApplicable` so placeholder values never breach/recover.\n"
).
-spec check_host_probe(
pharos@measurement:host_probe_status(),
JVA,
fun((JVA) -> boolean())
) -> verdict().
check_host_probe(Status, Stats, Predicate) ->
case Status of
unimplemented ->
not_applicable;
implemented ->
verdict(Predicate(Stats))
end.
-file("src/pharos/threshold_eval.gleam", 270).
?DOC(
" Evaluate a host-disk threshold. Like host memory, a probe still reporting\n"
" `Unimplemented` (os_mon unavailable) is `NotApplicable`.\n"
).
-spec check_host_disk(
pharos@measurement:measurement(),
fun((pharos@measurement:host_disk_stats()) -> boolean())
) -> verdict().
check_host_disk(Measurement, Predicate) ->
case Measurement of
{host_disk, Disk} ->
check_host_probe(erlang:element(2, Disk), Disk, Predicate);
{beam_memory, _} ->
not_applicable;
{beam_run_queues, _} ->
not_applicable;
{beam_system_counts, _} ->
not_applicable;
{beam_persistent_term, _} ->
not_applicable;
{process_info, _} ->
not_applicable;
{cluster_nodes, _} ->
not_applicable;
{host_memory, _} ->
not_applicable;
{host_cpu, _} ->
not_applicable;
{host_network, _} ->
not_applicable;
{beam_scheduler, _} ->
not_applicable;
{beam_reductions, _} ->
not_applicable
end.
-file("src/pharos/threshold_eval.gleam", 247).
?DOC(
" Evaluate a host-memory threshold. A host probe still reporting\n"
" `Unimplemented` (e.g. the non-Linux `/proc/meminfo` fallback) is treated as\n"
" `NotApplicable` so its placeholder zeros never produce breach/recover noise.\n"
).
-spec check_host_memory(
pharos@measurement:measurement(),
fun((pharos@measurement:host_memory_stats()) -> boolean())
) -> verdict().
check_host_memory(Measurement, Predicate) ->
case Measurement of
{host_memory, Host} ->
check_host_probe(erlang:element(2, Host), Host, Predicate);
{beam_memory, _} ->
not_applicable;
{beam_run_queues, _} ->
not_applicable;
{beam_system_counts, _} ->
not_applicable;
{beam_persistent_term, _} ->
not_applicable;
{process_info, _} ->
not_applicable;
{cluster_nodes, _} ->
not_applicable;
{host_disk, _} ->
not_applicable;
{host_cpu, _} ->
not_applicable;
{host_network, _} ->
not_applicable;
{beam_scheduler, _} ->
not_applicable;
{beam_reductions, _} ->
not_applicable
end.
-file("src/pharos/threshold_eval.gleam", 224).
-spec check_persistent_term(
pharos@measurement:measurement(),
fun((pharos@measurement:persistent_term_stats()) -> boolean())
) -> verdict().
check_persistent_term(Measurement, Predicate) ->
case Measurement of
{beam_persistent_term, Term} ->
verdict(Predicate(Term));
{beam_memory, _} ->
not_applicable;
{beam_run_queues, _} ->
not_applicable;
{beam_system_counts, _} ->
not_applicable;
{process_info, _} ->
not_applicable;
{cluster_nodes, _} ->
not_applicable;
{host_memory, _} ->
not_applicable;
{host_disk, _} ->
not_applicable;
{host_cpu, _} ->
not_applicable;
{host_network, _} ->
not_applicable;
{beam_scheduler, _} ->
not_applicable;
{beam_reductions, _} ->
not_applicable
end.
-file("src/pharos/threshold_eval.gleam", 204).
-spec check_system_counts(
pharos@measurement:measurement(),
fun((pharos@measurement:system_count_stats()) -> boolean())
) -> verdict().
check_system_counts(Measurement, Predicate) ->
case Measurement of
{beam_system_counts, Counts} ->
verdict(Predicate(Counts));
{beam_memory, _} ->
not_applicable;
{beam_run_queues, _} ->
not_applicable;
{beam_persistent_term, _} ->
not_applicable;
{process_info, _} ->
not_applicable;
{cluster_nodes, _} ->
not_applicable;
{host_memory, _} ->
not_applicable;
{host_disk, _} ->
not_applicable;
{host_cpu, _} ->
not_applicable;
{host_network, _} ->
not_applicable;
{beam_scheduler, _} ->
not_applicable;
{beam_reductions, _} ->
not_applicable
end.
-file("src/pharos/threshold_eval.gleam", 184).
-spec check_run_queue(
pharos@measurement:measurement(),
fun((pharos@measurement:run_queue_stats()) -> boolean())
) -> verdict().
check_run_queue(Measurement, Predicate) ->
case Measurement of
{beam_run_queues, Queue} ->
verdict(Predicate(Queue));
{beam_memory, _} ->
not_applicable;
{beam_system_counts, _} ->
not_applicable;
{beam_persistent_term, _} ->
not_applicable;
{process_info, _} ->
not_applicable;
{cluster_nodes, _} ->
not_applicable;
{host_memory, _} ->
not_applicable;
{host_disk, _} ->
not_applicable;
{host_cpu, _} ->
not_applicable;
{host_network, _} ->
not_applicable;
{beam_scheduler, _} ->
not_applicable;
{beam_reductions, _} ->
not_applicable
end.
-file("src/pharos/threshold_eval.gleam", 164).
-spec check_memory(
pharos@measurement:measurement(),
fun((pharos@measurement:beam_memory_stats()) -> boolean())
) -> verdict().
check_memory(Measurement, Predicate) ->
case Measurement of
{beam_memory, Memory} ->
verdict(Predicate(Memory));
{beam_run_queues, _} ->
not_applicable;
{beam_system_counts, _} ->
not_applicable;
{beam_persistent_term, _} ->
not_applicable;
{process_info, _} ->
not_applicable;
{cluster_nodes, _} ->
not_applicable;
{host_memory, _} ->
not_applicable;
{host_disk, _} ->
not_applicable;
{host_cpu, _} ->
not_applicable;
{host_network, _} ->
not_applicable;
{beam_scheduler, _} ->
not_applicable;
{beam_reductions, _} ->
not_applicable
end.
-file("src/pharos/threshold_eval.gleam", 48).
?DOC(" Evaluate `threshold` against `measurement` and return a `Verdict`.\n").
-spec evaluate(pharos@measurement:measurement(), pharos@config:threshold()) -> verdict().
evaluate(Measurement, Threshold) ->
case Threshold of
{total_memory, Limit} ->
check_memory(
Measurement,
fun(Memory) -> erlang:element(3, Memory) > Limit end
);
{process_memory, Limit@1} ->
check_memory(
Measurement,
fun(Memory@1) -> erlang:element(4, Memory@1) > Limit@1 end
);
{system_memory, Limit@2} ->
check_memory(
Measurement,
fun(Memory@2) -> erlang:element(6, Memory@2) > Limit@2 end
);
{binary_memory, Limit@3} ->
check_memory(
Measurement,
fun(Memory@3) -> erlang:element(9, Memory@3) > Limit@3 end
);
{ets_memory, Limit@4} ->
check_memory(
Measurement,
fun(Memory@4) -> erlang:element(11, Memory@4) > Limit@4 end
);
{total_run_queue, Limit@5} ->
check_run_queue(
Measurement,
fun(Queue) -> erlang:element(2, Queue) > Limit@5 end
);
{cpu_run_queue, Limit@6} ->
check_run_queue(
Measurement,
fun(Queue@1) -> erlang:element(3, Queue@1) > Limit@6 end
);
{process_count, Limit@7} ->
check_system_counts(
Measurement,
fun(Counts) -> erlang:element(2, Counts) > Limit@7 end
);
{atom_count, Limit@8} ->
check_system_counts(
Measurement,
fun(Counts@1) -> erlang:element(3, Counts@1) > Limit@8 end
);
{port_count, Limit@9} ->
check_system_counts(
Measurement,
fun(Counts@2) -> erlang:element(4, Counts@2) > Limit@9 end
);
{persistent_term_count, Limit@10} ->
check_persistent_term(
Measurement,
fun(Term) -> erlang:element(2, Term) > Limit@10 end
);
{persistent_term_memory, Limit@11} ->
check_persistent_term(
Measurement,
fun(Term@1) -> erlang:element(4, Term@1) > Limit@11 end
);
{host_memory_used, Limit@12} ->
check_host_memory(
Measurement,
fun(Host) -> erlang:element(5, Host) > Limit@12 end
);
{host_disk_used, Limit@13} ->
check_host_disk(
Measurement,
fun(Disk) -> erlang:element(5, Disk) > Limit@13 end
);
{host_cpu_util, Limit@14} ->
check_host_cpu(
Measurement,
fun(Cpu) -> erlang:element(2, Cpu) > Limit@14 end
);
{scheduler_utilization, Limit@15} ->
check_scheduler(
Measurement,
fun(Sched) -> erlang:element(2, Sched) > Limit@15 end
);
{windowed, Over, _, _} ->
evaluate(Measurement, Over)
end.
-file("src/pharos/threshold_eval.gleam", 119).
?DOC(
" The flattened metric name a threshold compares against (see\n"
" `metric.from_measurement`).\n"
).
-spec metric_name(pharos@config:threshold()) -> binary().
metric_name(Threshold) ->
case Threshold of
{total_memory, _} ->
<<"vm.memory.total"/utf8>>;
{process_memory, _} ->
<<"vm.memory.processes"/utf8>>;
{system_memory, _} ->
<<"vm.memory.system"/utf8>>;
{binary_memory, _} ->
<<"vm.memory.binary"/utf8>>;
{ets_memory, _} ->
<<"vm.memory.ets"/utf8>>;
{total_run_queue, _} ->
<<"vm.run_queue.total"/utf8>>;
{cpu_run_queue, _} ->
<<"vm.run_queue.cpu"/utf8>>;
{process_count, _} ->
<<"vm.system.process_count"/utf8>>;
{atom_count, _} ->
<<"vm.system.atom_count"/utf8>>;
{port_count, _} ->
<<"vm.system.port_count"/utf8>>;
{persistent_term_count, _} ->
<<"vm.persistent_term.count"/utf8>>;
{persistent_term_memory, _} ->
<<"vm.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, _} ->
<<"vm.scheduler.utilization"/utf8>>;
{windowed, Over, _, _} ->
metric_name(Over)
end.
-file("src/pharos/threshold_eval.gleam", 107).
?DOC(
" Extract the current value of the metric `threshold` compares, by name, from\n"
" the measurement's flattened metrics. Returns `Error(Nil)` when the\n"
" measurement doesn't carry that metric (wrong kind, or an `Unimplemented`\n"
" host probe, which flattens to no metrics), so such ticks contribute no\n"
" sample to a window.\n"
).
-spec sample_value(pharos@measurement:measurement(), pharos@config:threshold()) -> {ok,
float()} |
{error, nil}.
sample_value(Measurement, Threshold) ->
Name = metric_name(Threshold),
_pipe = pharos@metric:from_measurement(Measurement),
_pipe@1 = gleam@list:find(
_pipe,
fun(M) -> erlang:element(2, M) =:= Name end
),
gleam@result:map(_pipe@1, fun(M@1) -> erlang:element(3, M@1) end).
-file("src/pharos/threshold_eval.gleam", 142).
?DOC(" The threshold's comparison limit as a `Float` (integer limits are widened).\n").
-spec threshold_limit(pharos@config:threshold()) -> float().
threshold_limit(Threshold) ->
case Threshold of
{total_memory, Above} ->
Above;
{process_memory, Above@1} ->
Above@1;
{system_memory, Above@2} ->
Above@2;
{binary_memory, Above@3} ->
Above@3;
{ets_memory, Above@4} ->
Above@4;
{persistent_term_memory, Above@5} ->
Above@5;
{host_memory_used, Above@6} ->
Above@6;
{host_disk_used, Above@7} ->
Above@7;
{host_cpu_util, Above@8} ->
Above@8;
{scheduler_utilization, Above@9} ->
Above@9;
{total_run_queue, Above@10} ->
erlang:float(Above@10);
{cpu_run_queue, Above@11} ->
erlang:float(Above@11);
{process_count, Above@12} ->
erlang:float(Above@12);
{atom_count, Above@13} ->
erlang:float(Above@13);
{port_count, Above@14} ->
erlang:float(Above@14);
{persistent_term_count, Above@15} ->
erlang:float(Above@15);
{windowed, Over, _, _} ->
threshold_limit(Over)
end.