%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2000-2010. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%
%%
%%----------------------------------------------------------------------
%% Purpose: Monitor connections and timers
%%----------------------------------------------------------------------
-module(megaco_monitor).
-behaviour(gen_server).
%%-----------------------------------------------------------------
%% Include files
%%-----------------------------------------------------------------
-include_lib("megaco/src/app/megaco_internal.hrl").
%% Application internal exports
-export([
start_link/0,
stop/0,
apply_after/4,
apply_after/5,
cancel_apply_after/1,
lookup_request/1,
lookup_request_field/2,
match_requests/1,
which_requests/1,
insert_request/1,
update_request_field/3, update_request_fields/2,
delete_request/1,
request_lockcnt_cre/1,
request_lockcnt_del/1,
request_lockcnt_inc/1,
request_lockcnt_dec/1,
lookup_reply/1,
lookup_reply_field/2,
match_replies/1,
which_replies/1,
insert_reply/1, insert_reply_new/1,
update_reply_field/3, update_reply_fields/2,
delete_reply/1,
apply_at_exit/4,
cancel_apply_at_exit/1
]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-define(SERVER, ?MODULE).
-record(state, {parent_pid}).
-record(apply_at_exit, {ref, pid, module, function, arguments}).
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
start_link() ->
?d("start -> entry", []),
gen_server:start_link({local, ?SERVER}, ?MODULE, [self()], []).
stop() ->
call(stop).
lookup_request(Key) ->
ets:lookup(megaco_requests, Key).
lookup_request_field(Key, Field) ->
try
begin
{ok, ets:lookup_element(megaco_requests, Key, Field)}
end
catch
error:badarg ->
{error, not_found}
end.
match_requests(Pat) ->
ets:match_object(megaco_requests, Pat).
which_requests(Pat) ->
Spec = [{Pat, [], ['$$']}],
ets:select(megaco_requests, Spec).
insert_request(Rec) ->
ets:insert(megaco_requests, Rec).
update_request_field(Key, Field, NewValue) ->
ets:update_element(megaco_requests, Key, {Field, NewValue}).
update_request_fields(Key, NewFields) when is_list(NewFields) ->
ets:update_element(megaco_requests, Key, NewFields).
delete_request(Key) ->
ets:delete(megaco_requests, Key).
request_lockcnt_cre(TransId) ->
Key = {TransId, lockcnt},
ets:insert_new(megaco_requests, {Key, 1}).
request_lockcnt_del(TransId) ->
Key = {TransId, lockcnt},
ets:delete(megaco_requests, Key).
request_lockcnt_inc(TransId) ->
Key = {TransId, lockcnt},
(catch ets:update_counter(megaco_requests, Key, 1)).
request_lockcnt_dec(TransId) ->
Key = {TransId, lockcnt},
(catch ets:update_counter(megaco_requests, Key, -1)).
lookup_reply(Key) ->
ets:lookup(megaco_replies, Key).
lookup_reply_field(Key, Field) ->
try
begin
{ok, ets:lookup_element(megaco_replies, Key, Field)}
end
catch
error:badarg ->
{error, not_found}
end.
match_replies(Pat) ->
ets:match_object(megaco_replies, Pat).
which_replies(Pat) ->
Spec = [{Pat, [], ['$$']}],
ets:select(megaco_replies, Spec).
insert_reply(Rec) ->
ets:insert(megaco_replies, Rec).
insert_reply_new(Rec) ->
ets:insert_new(megaco_replies, Rec).
update_reply_field(Key, Field, NewValue) ->
ets:update_element(megaco_replies, Key, {Field, NewValue}).
update_reply_fields(Key, NewFields) when is_list(NewFields) ->
ets:update_element(megaco_replies, Key, NewFields).
delete_reply(Key) ->
ets:delete(megaco_replies, Key).
apply_after(M, F, A, Time) ->
apply_after(spawn_method, M, F, A, Time).
apply_after(Method, M, F, A, Time)
when is_atom(M) andalso is_atom(F) andalso is_list(A) ->
if
Time =:= infinity ->
apply_after_infinity;
is_integer(Time) ->
Msg = {apply_after, Method, M, F, A},
Ref = erlang:send_after(Time, whereis(?SERVER), Msg),
{apply_after, Ref}
end.
cancel_apply_after({apply_after, Ref}) ->
case erlang:cancel_timer(Ref) of
TimeLeft when is_integer(TimeLeft) ->
{ok, TimeLeft};
_ ->
{ok, 0}
end;
cancel_apply_after(apply_after_infinity) ->
ok;
cancel_apply_after(BadRef) ->
{error, {bad_ref, BadRef}}.
%% Performs apply(M, F, [Reason | A]) when process Pid dies
apply_at_exit(M, F, A, Pid)
when is_atom(M) andalso is_atom(F) andalso is_list(A) andalso is_pid(Pid) ->
Ref = call({apply_at_exit, M, F, A, Pid}),
{apply_at_exit, Ref}.
cancel_apply_at_exit({apply_at_exit, Ref}) ->
cast({cancel_apply_at_exit, Ref});
cancel_apply_at_exit(BadRef) ->
{error, {bad_ref, BadRef}}.
call(Request) ->
gen_server:call(?SERVER, Request, infinity).
cast(Msg) ->
?SERVER ! Msg, ok.
%%%----------------------------------------------------------------------
%%% Callback functions from gen_server
%%%----------------------------------------------------------------------
%%----------------------------------------------------------------------
%% Func: init/1
%% Returns: {ok, State} |
%% {ok, State, Timeout} |
%% ignore |
%% {stop, Reason}
%%----------------------------------------------------------------------
init([Parent]) ->
?d("init -> entry", []),
process_flag(trap_exit, true),
ets:new(megaco_requests, [public, named_table, {keypos, 2}]),
ets:new(megaco_replies, [public, named_table, {keypos, 2}]),
?d("init -> done", []),
{ok, #state{parent_pid = Parent}}.
%%----------------------------------------------------------------------
%% Func: handle_call/3
%% Returns: {reply, Reply, State} |
%% {reply, Reply, State, Timeout} |
%% {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, Reply, State} | (terminate/2 is called)
%% {stop, Reason, State} (terminate/2 is called)
%%----------------------------------------------------------------------
handle_call({apply_at_exit, M, F, A, Pid}, _From, S) ->
Ref = erlang:monitor(process, Pid),
AAE = #apply_at_exit{ref = Ref,
pid = Pid,
module = M,
function = F,
arguments = A},
put({?MODULE, Ref}, AAE),
Reply = Ref,
{reply, Reply, S};
handle_call(stop, {Parent, _} = _From, #state{parent_pid = Parent} = S) ->
{stop, normal, ok, S};
handle_call(Req, From, S) ->
warning_msg("received unexpected request from ~p: "
"~n~w",[From, Req]),
{reply, {error, {bad_request, Req}}, S}.
%%----------------------------------------------------------------------
%% Func: handle_cast/2
%% Returns: {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State} (terminate/2 is called)
%%----------------------------------------------------------------------
handle_cast(Msg, S) ->
warning_msg("received unexpected message: "
"~n~w", [Msg]),
{noreply, S}.
%%----------------------------------------------------------------------
%% Func: handle_info/2
%% Returns: {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State} (terminate/2 is called)
%%----------------------------------------------------------------------
handle_info({cancel_apply_at_exit, Ref}, S) ->
case erase({?MODULE, Ref}) of
undefined ->
%% Reply = {error, {already_cancelled, {apply_at_exit, Ref}}},
{noreply, S};
_AAE ->
erlang:demonitor(Ref),
{noreply, S}
end;
handle_info({apply_after, Method, M, F, A}, S) ->
handle_apply(Method, M, F, A, apply_after),
{noreply, S};
%% Handle the old format also...
handle_info({apply_after, M, F, A}, S) ->
handle_apply(M, F, A, apply_after),
{noreply, S};
handle_info({'DOWN', Ref, process, _Pid, Reason}, S) ->
case erase({?MODULE, Ref}) of
undefined ->
{noreply, S};
AAE ->
M = AAE#apply_at_exit.module,
F = AAE#apply_at_exit.function,
A = AAE#apply_at_exit.arguments,
handle_apply(M, F, [Reason | A], apply_at_exit),
{noreply, S}
end;
handle_info({'EXIT', Pid, Reason}, S) when Pid == S#state.parent_pid ->
%% [megaco_messenger:disconnect(CH, {stopped, Reason})
%% || CH <- megaco:lookup_system_info(connections)],
{stop, Reason, S};
handle_info(Info, S) ->
warning_msg("received unknown info: "
"~n~w", [Info]),
{noreply, S}.
%%----------------------------------------------------------------------
%% Func: terminate/2
%% Purpose: Shutdown the server
%% Returns: any (ignored by gen_server)
%%----------------------------------------------------------------------
terminate(_Reason, _State) ->
ok.
%%----------------------------------------------------------------------
%% Func: code_change/3
%% Purpose: Convert process state when code is changed
%% Returns: {ok, NewState}
%%----------------------------------------------------------------------
code_change(_Vsn, S, _Extra) ->
{ok, S}.
%%%----------------------------------------------------------------------
%%% Internal functions
%%%----------------------------------------------------------------------
handle_apply(M, F, A, _ErrorTag) ->
spawn(M, F, A).
handle_apply(spawn_method, M, F, A, _ErrorTag) ->
spawn(M, F, A);
handle_apply(_Method, M, F, A, _ErrorTag) ->
(catch apply(M, F, A)).
warning_msg(F, A) ->
?megaco_warning("Monitor server: " ++ F, A).
% d(F) ->
% d(F,[]).
% d(F,A) ->
% %% d(true,F,A).
% d(get(dbg),F,A).
% d(true,F,A) ->
% io:format("*** [~s] ~p:~p ***"
% "~n " ++ F ++ "~n",
% [format_timestamp(now()), self(),?MODULE|A]);
% d(_, _, _) ->
% ok.
% format_timestamp(Now) ->
% {N1, N2, N3} = Now,
% {Date, Time} = calendar:now_to_datetime(Now),
% {YYYY,MM,DD} = Date,
% {Hour,Min,Sec} = Time,
% FormatDate =
% io_lib:format("~.4w:~.2.0w:~.2.0w ~.2.0w:~.2.0w:~.2.0w 4~w",
% [YYYY,MM,DD,Hour,Min,Sec,round(N3/1000)]),
% lists:flatten(FormatDate).