-module(pharos). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/pharos.gleam"). -export([start_link/1, subscribe/2, unsubscribe/2, stop/1]). -export_type([pharos/0, start_error/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( " Public API.\n" "\n" " `start_link/1` boots the entire pharos supervision tree from a\n" " `Config`. The returned `Pharos` handle lets callers `subscribe` to\n" " alert events, `unsubscribe` later, and `stop` everything when done.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " import pharos\n" " import pharos/config\n" " import pharos/statistic\n" "\n" " let assert Ok(started) =\n" " pharos.start_link(\n" " config.new()\n" " |> config.with_statistics([\n" " statistic.poll(statistic.BeamMemory),\n" " statistic.poll_every(statistic.BeamRunQueues, 500),\n" " ])\n" " |> config.with_thresholds([config.TotalMemory(above: 500.0)]),\n" " )\n" "\n" " let assert Ok(_handler) =\n" " pharos.subscribe(started.data, fn(event) { handle(event) })\n" " ```\n" ). -opaque pharos() :: {pharos, gleam@erlang@process:pid_(), pharos@event_bus:event_bus(), gleam@erlang@atom:atom_()}. -type start_error() :: {supervisor_start_failed, binary()} | {telemetry_attach_failed, binary()}. -file("src/pharos.gleam", 193). -spec format_attach_error(pharos@internal@telemetry:attach_error()) -> binary(). format_attach_error(Error) -> case Error of handler_already_attached -> <<"telemetry handler already attached"/utf8>>; {attach_failed, Reason} -> Reason end. -file("src/pharos.gleam", 185). -spec format_actor_error(gleam@otp@actor:start_error()) -> binary(). format_actor_error(Error) -> case Error of init_timeout -> <<"supervisor initialisation timed out"/utf8>>; {init_failed, Reason} -> Reason; {init_exited, _} -> <<"supervisor exited during initialisation"/utf8>> end. -file("src/pharos.gleam", 63). ?DOC( " Boot pharos from `config`.\n" "\n" " Generates stable registered names for the event bus and one per\n" " threshold, builds the supervision tree, then attaches a single\n" " telemetry handler that decodes events and routes\n" " `breach`/`recover` casts to the matching alert managers.\n" ). -spec start_link(pharos@config:config()) -> {ok, gleam@otp@actor:started(pharos())} | {error, start_error()}. start_link(Config) -> Bus_name = gleam_erlang_ffi:new_name(<<"pharos_event_bus"/utf8>>), Beam_lane = gleam@list:map( erlang:element(3, Config), fun(Threshold) -> Id = pharos@config:threshold_id(Threshold), {Threshold, gleam_erlang_ffi:new_name(<<"pharos_alert_"/utf8, Id/binary>>)} end ), Probe_lane = gleam@list:map( erlang:element(5, Config), fun(Threshold@1) -> Id@1 = pharos@probe:threshold_id(Threshold@1), {Threshold@1, gleam_erlang_ffi:new_name(<<"pharos_probe_"/utf8, Id@1/binary>>)} end ), Manager_specs = lists:append( gleam@list:map( Beam_lane, fun(Pair) -> {Threshold@2, Name} = Pair, {alert_manager_spec, {alert_data, pharos@config:threshold_id(Threshold@2), erlang:element(10, Config), erlang:element(8, Config), erlang:element(9, Config)}, Name} end ), gleam@list:map( Probe_lane, fun(Pair@1) -> {Threshold@3, Name@1} = Pair@1, {alert_manager_spec, {alert_data, pharos@probe:threshold_id(Threshold@3), erlang:element(5, Threshold@3), erlang:element(8, Config), erlang:element(9, Config)}, Name@1} end ) ), case pharos@internal@supervisor:start_link(Config, Bus_name, Manager_specs) of {error, Error} -> {error, {supervisor_start_failed, format_actor_error(Error)}}; {ok, Started} -> Bus = pharos@event_bus:from_name(Bus_name), Handler_id = erlang:binary_to_atom(<<"pharos_handler"/utf8>>), Manager_handles = gleam@list:map( Beam_lane, fun(Pair@2) -> {Threshold@4, Name@2} = Pair@2, {Threshold@4, pharos@alert_manager:from_name(Name@2)} end ), Probe_handles = gleam@list:map( Probe_lane, fun(Pair@3) -> {Threshold@5, Name@3} = Pair@3, {Threshold@5, pharos@alert_manager:from_name(Name@3)} end ), case pharos@internal@telemetry:attach( Config, Manager_handles, Probe_handles, erlang:element(4, Config), Handler_id ) of {ok, nil} -> {ok, {started, erlang:element(2, Started), {pharos, erlang:element(2, Started), Bus, Handler_id}}}; {error, Attach_error} -> pharos_ffi:shutdown_supervisor(erlang:element(2, Started)), {error, {telemetry_attach_failed, format_attach_error(Attach_error)}} end end. -file("src/pharos.gleam", 155). ?DOC( " Subscribe `on_event` to the alert event bus. Returns an opaque handle\n" " that can be passed to `unsubscribe`.\n" ). -spec subscribe(pharos(), fun((pharos@alert:alert_event()) -> nil)) -> {ok, pharos@event_bus:handler_id()} | {error, eparch@event_manager:add_error(nil, nil)}. subscribe(Pharos, On_event) -> pharos@event_bus:add_handler(erlang:element(3, Pharos), On_event). -file("src/pharos.gleam", 163). ?DOC(" Unsubscribe a previously registered handler.\n"). -spec unsubscribe(pharos(), pharos@event_bus:handler_id()) -> {ok, nil} | {error, eparch@event_manager:remove_error(nil, nil)}. unsubscribe(Pharos, Handler) -> pharos@event_bus:remove_handler(erlang:element(3, Pharos), Handler). -file("src/pharos.gleam", 173). ?DOC( " Stop pharos: detach the telemetry handler, then shut down the\n" " supervision tree. Safe to call multiple times - extra calls become\n" " no-ops once the supervisor is already gone.\n" ). -spec stop(pharos()) -> nil. stop(Pharos) -> _ = pharos@internal@telemetry:detach(erlang:element(4, Pharos)), pharos_ffi:shutdown_supervisor(erlang:element(2, Pharos)).