Current section

Files

Jump to
eflambe src eflambe_server.erl
Raw

src/eflambe_server.erl

%%%-------------------------------------------------------------------
%%% @doc
%%% E flambe server stores state for all traces that have been started. When
%%% No traces remain the server will shut down automatically.
%%% @end
%%%-------------------------------------------------------------------
-module(eflambe_server).
-behaviour(gen_server).
%% API
-export([start_link/0, start_trace/3, stop_trace/1]).
%% gen_server callbacks
-export([init/1,
handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2]).
-include_lib("kernel/include/logger.hrl").
-define(SERVER, ?MODULE).
-record(state, {traces = [] :: [trace()]}).
-record(trace, {
id :: any(),
max_calls :: integer(),
calls :: integer(),
running :: boolean(),
tracer :: pid(),
options = [] :: list()
}).
-type state() :: #state{}.
-type trace() :: #trace{}.
-type from() :: {pid(), Tag :: term()}.
%%%===================================================================
%%% API
%%%===================================================================
%%--------------------------------------------------------------------
%% @doc
%% Starts the server
%%
%% @end
%%--------------------------------------------------------------------
-spec start_link() -> {ok, pid()} | ignore | {error, Error :: any()}.
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
start_trace(Id, MaxCalls, Options) ->
gen_server:call(?SERVER, {start_trace, Id, MaxCalls, Options}).
stop_trace(Id) ->
gen_server:call(?SERVER, {stop_trace, Id}).
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
-spec init(Args :: list()) -> {ok, state()}.
init([]) ->
{ok, #state{}}.
-spec handle_call(Request :: any(), from(), state()) ->
{reply, Reply :: any(), state()} |
{reply, Reply :: any(), state(), timeout()}.
handle_call({start_trace, Id, CallLimit, Options}, {FromPid, _}, State) ->
case get_trace_by_id(State, Id) of
undefined ->
% Create new trace, spawn a tracer for the trace
{ok, TracerPid} = eflambe_tracer:start_link([{pid, FromPid}|Options]),
UpdatedTrace = #trace{
id = Id,
max_calls = CallLimit,
calls = 1,
options = Options,
running = true,
tracer = TracerPid
},
{reply, {ok, Id, true, TracerPid}, put_trace(State, UpdatedTrace)};
#trace{max_calls = MaxCalls, calls = Calls, tracer = TracerPid, running = false}
when Calls =:= MaxCalls ->
{reply, {ok, Id, false, TracerPid}, State};
#trace{max_calls = MaxCalls, calls = Calls, tracer = TracerPid, running = true}
when Calls =:= MaxCalls ->
{reply, {ok, Id, false, TracerPid}, State};
#trace{calls = Calls, running = false, tracer = TracerPid} = Trace ->
% Increment existing trace
NewCalls = Calls + 1,
% Update number of calls
NewState = update_trace(State, Id, Trace#trace{calls = NewCalls, running = true}),
{reply, {ok, Id, true, TracerPid}, NewState};
#trace{options = Options, running = true, tracer = TracerPid} ->
{reply, {ok, Id, false, TracerPid}, State}
end;
handle_call({stop_trace, Id}, _From, State) ->
case get_trace_by_id(State, Id) of
undefined ->
% No trace found
{reply, {error, unknown_trace}, State};
#trace{calls = Calls, options = Options, running = true, tracer = TracerPid} = Trace ->
% Stop trace
case proplists:get_value(meck, Options) of
undefined -> ok;
ModuleName ->
% Unload if module has been mecked
ok = meck:unload(ModuleName)
end,
% Stop tracer
eflambe_tracer:finish(TracerPid),
NewState = update_trace(State, Id, Trace#trace{running = false}),
{reply, {ok, Id, Calls, true, Options}, NewState};
#trace{calls = Calls, options = Options, running = false} ->
% Trace already stopped
{reply, {ok, Id, Calls, false, Options}, State}
end;
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.
-spec handle_cast(any(), state()) -> {noreply, state()} |
{noreply, state(), timeout()} |
{stop, Reason :: any(), state()}.
handle_cast(_Msg, State) ->
{noreply, State}.
-spec handle_info(Info :: any(), state()) -> {noreply, state()} |
{noreply, state(), timeout()} |
{stop, Reason :: any(), state()}.
handle_info(Info, State) ->
logger:error("Received unexpected info message: ~w", [Info]),
{noreply, State}.
-spec terminate(Reason :: any(), state()) -> any().
terminate(_Reason, _State) ->
ok.
%%%===================================================================
%%% Internal functions
%%%===================================================================
get_trace_by_id(#state{traces = Traces}, Id) ->
case lists:filter(lookup_fun(Id), Traces) of
[] -> undefined;
[Trace] -> Trace
end.
put_trace(#state{traces = ExistingTraces} = State, NewTrace) ->
State#state{traces = [NewTrace|ExistingTraces]}.
update_trace(#state{traces = ExistingTraces} = State, Id, UpdatedTrace) ->
{[_Trace], Rest} = lists:partition(lookup_fun(Id), ExistingTraces),
State#state{traces = [UpdatedTrace|Rest]}.
lookup_fun(Id) ->
fun
(#trace{id = TraceId}) when TraceId =:= Id -> true;
(_) -> false
end.