-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.