%% @copyright 2017 Takeru Ohta %% %% @doc Tracer Registry. %% %% === Examples === %% %% ``` %% Context = passage_span_context_null, %% Sampler = passage_sampler_null:new(), %% Reporter = passage_reporter_null:new(), %% %% %% Registers %% ok = passage_tracer_registry:register(foo, Context, Sampler, Reporter), %% [foo] = passage_tracer_registry:which_tracers(), %% %% %% Deregisters %% ok = passage_tracer_registry:deregister(foo), %% [] = passage_tracer_registry:which_tracers() %% ''' -module(passage_tracer_registry). -behavior(gen_server). %%------------------------------------------------------------------------------ %% Exported API %%------------------------------------------------------------------------------ -export([register/4]). -export([deregister/1]). -export([get_span_context_module/1]). -export([get_sampler/1]). -export([set_sampler/2]). -export([get_reporter/1]). -export([set_reporter/2]). -export([which_tracers/0]). %%------------------------------------------------------------------------------ %% Application Internal API %%------------------------------------------------------------------------------ -export([start_link/0]). %%------------------------------------------------------------------------------ %% 'gen_server' Callback API %%------------------------------------------------------------------------------ -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). %%------------------------------------------------------------------------------ %% Macros and Records %%------------------------------------------------------------------------------ -define(TABLE, ?MODULE). -define(STATE, ?MODULE). -record(?STATE, { tracers = [] :: [passage:tracer_id()] }). %%------------------------------------------------------------------------------ %% Application Internal Functions %%------------------------------------------------------------------------------ %% @private -spec start_link() -> {ok, pid()} | {error, Reason :: term()}. start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). %%------------------------------------------------------------------------------ %% Exported Functions %%------------------------------------------------------------------------------ %% @doc Registers the tracer. -spec register(Tracer, Module, Sampler, Reporter) -> ok | {error, Reason} when Tracer :: passage:tracer_id(), Module :: passage_span_context:implementation_module(), Sampler :: passage_sampler:sampler(), Reporter :: passage_reporter:reporter(), Reason :: already_registered | term(). register(Tracer, Module, Sampler, Reporter) -> Args = [Tracer, Module, Sampler, Reporter], is_atom(Tracer) orelse error(badarg, Args), is_atom(Module) orelse error(badarg, Args), passage_sampler:is_sampler(Sampler) orelse error(badarg, Args), passage_reporter:is_reporter(Reporter) orelse error(badarg, Args), gen_server:call(?MODULE, {register, {Tracer, Module, Sampler, Reporter}}). %% @doc Deregisters the tracer. %% %% If `Tracer' has not been registered, it will be simply ignored. -spec deregister(passage:tracer_id()) -> ok. deregister(Tracer) -> gen_server:cast(?MODULE, {deregister, Tracer}). %% @doc Returns the list of the registered tracers. -spec which_tracers() -> [passage:tracer_id()]. which_tracers() -> gen_server:call(?MODULE, which_tracers). %% @doc Returns the `passage_span_context' implementation module associated with `Tracer'. %% %% If `Tracer' has not been registered, it will return `error'. -spec get_span_context_module(passage:tracer_id()) -> {ok, Module} | error when Module :: passage_span_context:implementation_module(). get_span_context_module(Tracer) -> case ets:lookup(?TABLE, {module, Tracer}) of [{_, Module}] -> {ok, Module}; _ -> error end. %% @doc Returns the sampler associated with `Tracer'. %% %% If `Tracer' has not been registered, it will return `error'. -spec get_sampler(passage:tracer_id()) -> {ok, passage_sampler:sampler()} | error. get_sampler(Tracer) -> case ets:lookup(?TABLE, {sampler, Tracer}) of [{_, Sampler}] -> {ok, Sampler}; _ -> error end. %% @doc Updates the sampler associated with `Tracer' to `Sampler'. %% %% If `Tracer' has not been registered, it will be simply ignored. -spec set_sampler(passage:tracer_id(), passage_sampler:sampler()) -> ok. set_sampler(Tracer, Sampler) -> gen_server:cast(?MODULE, {set_sampler, {Tracer, Sampler}}). %% @doc Returns the reporter associated with `Tracer'. %% %% If `Tracer' has not been registered, it will return `error'. -spec get_reporter(passage:tracer_id()) -> {ok, passage_reporter:reporter()} | error. get_reporter(TracerId) -> case ets:lookup(?TABLE, {reporter, TracerId}) of [{_, Reporter}] -> {ok, Reporter}; _ -> error end. %% @doc Updates the reporter associated with `Tracer' to `Reporter'. %% %% If `Tracer' has not been registered, it will be simply ignored. -spec set_reporter(passage:tracer_id(), passage_reporter:reporter()) -> ok. set_reporter(Tracer, Reporter) -> gen_server:cast(?MODULE, {set_reporter, {Tracer, Reporter}}). %%------------------------------------------------------------------------------ %% 'gen_server' Callback Functions %%------------------------------------------------------------------------------ %% @private init([]) -> _ = ets:new(?MODULE, [named_table, protected, {read_concurrency, true}]), State = #?STATE{}, {ok, State}. %% @private handle_call({register, Args}, _From, State) -> handle_register(Args, State); handle_call(which_tracers, _From, State) -> {reply, State#?STATE.tracers, State}; handle_call(_Request, _From, State) -> {noreply, State}. %% @private handle_cast({deregister, Args}, State) -> handle_deregister(Args, State); handle_cast({set_sampler, Args}, State) -> handle_set_sampler(Args, State); handle_cast({set_reporter, Args}, State) -> handle_set_reporter(Args, State); handle_cast(_Request, State) -> {noreply, State}. %% @private handle_info(_Info, State) -> {noreply, State}. %% @private terminate(_Reason, _State) -> ok. %% @private code_change(_OldVsn, State, _Extra) -> {ok, State}. %%------------------------------------------------------------------------------ %% Internal Functions %%------------------------------------------------------------------------------ -spec handle_register(Args, #?STATE{}) -> {reply, Result, #?STATE{}} when Args :: {passage:tracer_id(), passage_span_context:implementation_module(), passage_sampler:sampler(), passage_reporter:reporter()}, Result :: ok | {error, already_registered}. handle_register({Tracer, Module, Sampler, Reporter}, State0) -> case lists:member(Tracer, State0#?STATE.tracers) of true -> {reply, {error, already_registered}, State0}; false -> ets:insert(?TABLE, {{module, Tracer}, Module}), ets:insert(?TABLE, {{sampler, Tracer}, Sampler}), ets:insert(?TABLE, {{reporter, Tracer}, Reporter}), State1 = State0#?STATE{tracers = [Tracer | State0#?STATE.tracers]}, {reply, ok, State1} end. -spec handle_deregister(passage:tracer_id(), #?STATE{}) -> {noreply, #?STATE{}}. handle_deregister(Tracer, State0) -> Tracers = lists:delete(Tracer, State0#?STATE.tracers), ets:delete(?TABLE, {module, Tracer}), ets:delete(?TABLE, {sampler, Tracer}), ets:delete(?TABLE, {reporter, Tracer}), State1 = State0#?STATE{tracers = Tracers}, {noreply, State1}. -spec handle_set_sampler(Args, #?STATE{}) -> {noreply, #?STATE{}} when Args :: {passage:tracer_id(), passage_sampler:sampler()}. handle_set_sampler({Tracer, Sampler}, State) -> case lists:member(Tracer, State#?STATE.tracers) of false -> {noreply, State}; true -> ets:insert(?TABLE, {{sampler, Tracer}, Sampler}), {noreply, State} end. -spec handle_set_reporter(Args, #?STATE{}) -> {noreply, #?STATE{}} when Args :: {passage:tracer_id(), passage_reporter:reporter()}. handle_set_reporter({Tracer, Reporter}, State) -> case lists:member(Tracer, State#?STATE.tracers) of false -> {noreply, State}; true -> ets:insert(?TABLE, {{reporter, Tracer}, Reporter}), {noreply, State} end.