blob: f26adfd14e61aceb3e3b5793b3b1e2145079c452 [file] [log] [blame]
%% @copyright 2017 Takeru Ohta <phjgt308@gmail.com>
%%
%% @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.