Packages
ejabberd
23.10.0
26.4.0
26.3.0
26.2.0
26.1.0
25.10.0
25.8.0
25.7.0
25.4.0
25.3.0
24.12.0
24.10.0
24.7.0
24.6.0
24.2.6
23.10.0
23.4.0
23.1.0
22.10.0
22.5.0
21.12.0
21.7.0
21.4.0
21.1.0
20.12.0
20.7.0
20.4.0
20.3.0
20.2.0
20.1.0
19.9.1
19.9.0
19.8.0
19.5.0
19.2.0
18.12.1
18.12.0
18.6.0
18.4.0
18.3.0
18.1.0
17.11.0
17.9.0
17.6.0
17.3.0
17.1.0
16.12.0-beta1
16.9.0
16.8.0
16.6.2
16.6.1
16.6.0
16.4.1
16.4.0
16.3.0
16.2.0
16.1.0-beta1
Robust, Ubiquitous and Massively Scalable Messaging Platform (XMPP, MQTT, SIP Server)
Current section
Files
Jump to
Current section
Files
src/ejabberd_hooks.erl
%%%----------------------------------------------------------------------
%%% File : ejabberd_hooks.erl
%%% Author : Alexey Shchepin <alexey@process-one.net>
%%% Purpose : Manage hooks
%%% Created : 8 Aug 2004 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2023 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License along
%%% with this program; if not, write to the Free Software Foundation, Inc.,
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%%
%%%----------------------------------------------------------------------
-module(ejabberd_hooks).
-author('alexey@process-one.net').
-behaviour(gen_server).
%% External exports
-export([start_link/0,
add/3,
add/4,
add/5,
delete/3,
delete/4,
delete/5,
run/2,
run/3,
run_fold/3,
run_fold/4]).
%% gen_server callbacks
-export([init/1,
handle_call/3,
handle_cast/2,
code_change/3,
handle_info/2,
terminate/2]).
-export(
[
get_tracing_options/3,
trace_off/3,
trace_on/5,human_readable_time_string/1
]
).
-include("logger.hrl").
-include("ejabberd_stacktrace.hrl").
-record(state, {}).
-type hook() :: {Seq :: integer(), Module :: atom(), Function :: atom() | fun()}.
-define(TRACE_HOOK_KEY, '$trace_hook').
-define(TIMING_KEY, '$trace_hook_timer').
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
-spec add(atom(), fun(), integer()) -> ok.
%% @doc See add/4.
add(Hook, Function, Seq) when is_function(Function) ->
add(Hook, global, undefined, Function, Seq).
-spec add(atom(), HostOrModule :: binary() | atom(), fun() | atom() , integer()) -> ok.
add(Hook, Host, Function, Seq) when is_function(Function) ->
add(Hook, Host, undefined, Function, Seq);
%% @doc Add a module and function to this hook.
%% The integer sequence is used to sort the calls: low number is called before high number.
add(Hook, Module, Function, Seq) ->
add(Hook, global, Module, Function, Seq).
-spec add(atom(), binary() | global, atom(), atom() | fun(), integer()) -> ok.
add(Hook, Host, Module, Function, Seq) ->
gen_server:call(?MODULE, {add, Hook, Host, Module, Function, Seq}).
-spec delete(atom(), fun(), integer()) -> ok.
%% @doc See del/4.
delete(Hook, Function, Seq) when is_function(Function) ->
delete(Hook, global, undefined, Function, Seq).
-spec delete(atom(), binary() | atom(), atom() | fun(), integer()) -> ok.
delete(Hook, Host, Function, Seq) when is_function(Function) ->
delete(Hook, Host, undefined, Function, Seq);
%% @doc Delete a module and function from this hook.
%% It is important to indicate exactly the same information than when the call was added.
delete(Hook, Module, Function, Seq) ->
delete(Hook, global, Module, Function, Seq).
-spec delete(atom(), binary() | global, atom(), atom() | fun(), integer()) -> ok.
delete(Hook, Host, Module, Function, Seq) ->
gen_server:call(?MODULE, {delete, Hook, Host, Module, Function, Seq}).
-spec run(atom(), list()) -> ok.
%% @doc Run the calls of this hook in order, don't care about function results.
%% If a call returns stop, no more calls are performed.
run(Hook, Args) ->
run(Hook, global, Args).
-spec run(atom(), binary() | global, list()) -> ok.
run(Hook, Host, Args) ->
try ets:lookup(hooks, {Hook, Host}) of
[{_, Ls}] ->
case erlang:get(?TRACE_HOOK_KEY) of
undefined ->
run1(Ls, Hook, Args);
TracingHooksOpts ->
case do_get_tracing_options(Hook, Host, TracingHooksOpts) of
undefined ->
run1(Ls, Hook, Args);
TracingOpts ->
foreach_start_hook_tracing(TracingOpts, Hook, Host, Args),
run2(Ls, Hook, Args, Host, TracingOpts)
end
end;
[] ->
ok
catch _:badarg ->
ok
end.
-spec run_fold(atom(), T, list()) -> T.
%% @doc Run the calls of this hook in order.
%% The arguments passed to the function are: [Val | Args].
%% The result of a call is used as Val for the next call.
%% If a call returns 'stop', no more calls are performed.
%% If a call returns {stop, NewVal}, no more calls are performed and NewVal is returned.
run_fold(Hook, Val, Args) ->
run_fold(Hook, global, Val, Args).
-spec run_fold(atom(), binary() | global, T, list()) -> T.
run_fold(Hook, Host, Val, Args) ->
try ets:lookup(hooks, {Hook, Host}) of
[{_, Ls}] ->
case erlang:get(?TRACE_HOOK_KEY) of
undefined ->
run_fold1(Ls, Hook, Val, Args);
TracingHooksOpts ->
case do_get_tracing_options(Hook, Host, TracingHooksOpts) of
undefined ->
run_fold1(Ls, Hook, Val, Args);
TracingOpts ->
fold_start_hook_tracing(TracingOpts, Hook, Host, [Val | Args]),
run_fold2(Ls, Hook, Val, Args, Host, TracingOpts)
end
end;
[] ->
Val
catch _:badarg ->
Val
end.
get_tracing_options(Hook, Host, Pid) when Pid == erlang:self() ->
do_get_tracing_options(Hook, Host, erlang:get(?TRACE_HOOK_KEY));
get_tracing_options(Hook, Host, Pid) when erlang:is_pid(Pid) ->
case erlang:process_info(Pid, dictionary) of
{_, DictPropList} ->
case lists:keyfind(?TRACE_HOOK_KEY, 1, DictPropList) of
{_, TracingHooksOpts} ->
do_get_tracing_options(Hook, Host, TracingHooksOpts);
_ ->
undefined
end;
_ ->
undefined
end.
trace_on(Hook, Host, Pid, #{}=Opts, Timeout) when Pid == erlang:self() ->
do_trace_on(Hook, Host, Opts, Timeout);
trace_on(Hook, Host, Proc, #{}=Opts, Timeout) ->
try sys:replace_state(
Proc,
fun(State) ->
do_trace_on(Hook, Host, Opts, Timeout),
State
end,
15000
) of
_ -> % process state
ok
catch
_:Reason ->
{error, Reason}
end.
trace_off(Hook, Host, Pid) when Pid == erlang:self() ->
do_trace_off(Hook, Host);
trace_off(Hook, Host, Proc) ->
try sys:replace_state(
Proc,
fun(State) ->
do_trace_off(Hook, Host),
State
end,
15000
) of
_ -> % process state
ok
catch
_:Reason ->
{error, Reason}
end.
%%%----------------------------------------------------------------------
%%% Callback functions from gen_server
%%%----------------------------------------------------------------------
init([]) ->
_ = ets:new(hooks, [named_table, {read_concurrency, true}]),
{ok, #state{}}.
handle_call({add, Hook, Host, Module, Function, Seq}, _From, State) ->
HookFormat = {Seq, Module, Function},
Reply = handle_add(Hook, Host, HookFormat),
{reply, Reply, State};
handle_call({delete, Hook, Host, Module, Function, Seq}, _From, State) ->
HookFormat = {Seq, Module, Function},
Reply = handle_delete(Hook, Host, HookFormat),
{reply, Reply, State};
handle_call(Request, From, State) ->
?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]),
{noreply, State}.
-spec handle_add(atom(), atom(), hook()) -> ok.
handle_add(Hook, Host, El) ->
case ets:lookup(hooks, {Hook, Host}) of
[{_, Ls}] ->
case lists:member(El, Ls) of
true ->
ok;
false ->
NewLs = lists:merge(Ls, [El]),
ets:insert(hooks, {{Hook, Host}, NewLs}),
ok
end;
[] ->
NewLs = [El],
ets:insert(hooks, {{Hook, Host}, NewLs}),
ok
end.
-spec handle_delete(atom(), atom(), hook()) -> ok.
handle_delete(Hook, Host, El) ->
case ets:lookup(hooks, {Hook, Host}) of
[{_, Ls}] ->
NewLs = lists:delete(El, Ls),
ets:insert(hooks, {{Hook, Host}, NewLs}),
ok;
[] ->
ok
end.
handle_cast(Msg, State) ->
?WARNING_MSG("Unexpected cast: ~p", [Msg]),
{noreply, State}.
handle_info(Info, State) ->
?WARNING_MSG("Unexpected info: ~p", [Info]),
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%%----------------------------------------------------------------------
%%% Internal functions
%%%----------------------------------------------------------------------
-spec run1([hook()], atom(), list()) -> ok.
run1([], _Hook, _Args) ->
ok;
run1([{_Seq, Module, Function} | Ls], Hook, Args) ->
Res = safe_apply(Hook, Module, Function, Args),
case Res of
'EXIT' ->
run1(Ls, Hook, Args);
stop ->
ok;
_ ->
run1(Ls, Hook, Args)
end.
-spec run_fold1([hook()], atom(), T, list()) -> T.
run_fold1([], _Hook, Val, _Args) ->
Val;
run_fold1([{_Seq, Module, Function} | Ls], Hook, Val, Args) ->
Res = safe_apply(Hook, Module, Function, [Val | Args]),
case Res of
'EXIT' ->
run_fold1(Ls, Hook, Val, Args);
stop ->
Val;
{stop, NewVal} ->
NewVal;
NewVal ->
run_fold1(Ls, Hook, NewVal, Args)
end.
-spec safe_apply(atom(), atom(), atom() | fun(), list()) -> any().
safe_apply(Hook, Module, Function, Args) ->
?DEBUG("Running hook ~p: ~p:~p/~B",
[Hook, Module, Function, length(Args)]),
try if is_function(Function) ->
apply(Function, Args);
true ->
apply(Module, Function, Args)
end
catch ?EX_RULE(E, R, St) when E /= exit; R /= normal ->
Stack = ?EX_STACK(St),
?ERROR_MSG("Hook ~p crashed when running ~p:~p/~p:~n" ++
string:join(
["** ~ts"|
["** Arg " ++ integer_to_list(I) ++ " = ~p"
|| I <- lists:seq(1, length(Args))]],
"~n"),
[Hook, Module, Function, length(Args),
misc:format_exception(2, E, R, Stack)|Args]),
'EXIT'
end.
%%%----------------------------------------------------------------------
%%% Internal tracing functions
%%%----------------------------------------------------------------------
do_trace_on(Hook, Host, Opts, Timeout) when erlang:is_list(Host) ->
do_trace_on(Hook, erlang:list_to_binary(Host), Opts, Timeout);
do_trace_on(Hook, Host, Opts, undefined) ->
case erlang:get(?TRACE_HOOK_KEY) of
_ when Hook == all andalso Host == <<"*">> ->
% Trace everything:
erlang:put(?TRACE_HOOK_KEY, #{all => #{<<"*">> => Opts}});
#{all := #{<<"*">> := _}} -> % Already tracing everything
% Update Opts:
erlang:put(?TRACE_HOOK_KEY, #{all => #{<<"*">> => Opts}});
#{all := HostOpts} when Hook == all -> % Already Tracing everything for some hosts
% Add/Update Host and Opts:
erlang:put(?TRACE_HOOK_KEY, #{all => HostOpts#{Host => Opts}});
#{all := _} -> % Already tracing everything and Hook is not all
ok;
#{} when Hook == all ->
% Remove other hooks by just adding all:
erlang:put(?TRACE_HOOK_KEY, #{all => #{Host => Opts}});
#{}=TraceHooksOpts when Host == <<"*">> -> % Want to trace a hook for all hosts
erlang:put(?TRACE_HOOK_KEY, TraceHooksOpts#{Hook => #{Host => Opts}});
#{}=TraceHooksOpts ->
case maps:get(Hook, TraceHooksOpts, #{}) of
#{<<"*">> := _} -> % Already tracing this hook for all hosts
ok;
HostOpts ->
erlang:put(?TRACE_HOOK_KEY, TraceHooksOpts#{Hook => HostOpts#{Host => Opts}})
end;
undefined ->
erlang:put(?TRACE_HOOK_KEY, #{Hook => #{Host => Opts}})
end,
ok;
do_trace_on(Hook, Host, Opts, TimeoutSeconds) -> % Trace myself `Timeout` time
Timeout = timer:seconds(TimeoutSeconds),
ParentPid = erlang:self(),
try erlang:spawn(
fun() ->
MonitorRef = erlang:monitor(process, ParentPid),
receive
{_, MonitorRef, _, _, _} ->
ok
after Timeout ->
trace_off(Hook, Host, ParentPid)
end,
erlang:exit(normal)
end
) of
_ ->
do_trace_on(Hook, Host, Opts, undefined) % ok
catch
_:Reason -> % system_limit
{error, Reason}
end.
do_trace_off(Hook, Host) when erlang:is_list(Host) ->
do_trace_off(Hook, erlang:list_to_binary(Host));
do_trace_off(Hook, Host) ->
case erlang:get(?TRACE_HOOK_KEY) of
_ when Hook == all andalso Host == <<"*">> ->
% Remove all tracing:
erlang:erase(?TRACE_HOOK_KEY);
#{all := HostOpts} when Hook == all -> % Already tracing all hooks
% Remove Host:
HostOpts2 = maps:remove(Host, HostOpts),
if
HostOpts2 == #{} ->
% Remove all tracing:
erlang:erase(?TRACE_HOOK_KEY);
true ->
erlang:put(?TRACE_HOOK_KEY, #{all => HostOpts2})
end;
#{}=TraceHooksOpts when Host == <<"*">> ->
% Remove tracing of this hook for all hosts:
TraceHooksOpts2 = maps:remove(Hook, TraceHooksOpts),
if
TraceHooksOpts2 == #{} ->
% Remove all tracing:
erlang:erase(?TRACE_HOOK_KEY);
true ->
erlang:put(?TRACE_HOOK_KEY, TraceHooksOpts2)
end;
#{}=TraceHooksOpts ->
case maps:get(Hook, TraceHooksOpts, undefined) of
#{}=HostOpts ->
NewHostOpts = maps:remove(Host, HostOpts),
if
NewHostOpts == #{} ->
% Remove hook:
erlang:put(?TRACE_HOOK_KEY, maps:remove(Hook, TraceHooksOpts));
true ->
erlang:put(?TRACE_HOOK_KEY, TraceHooksOpts#{Hook => NewHostOpts})
end;
_ ->
ok
end;
undefined ->
ok
end,
ok.
do_get_tracing_options(Hook, Host, MaybeMap) ->
case MaybeMap of
undefined ->
undefined;
#{all := #{<<"*">> := Opts}} -> % Tracing everything
Opts;
#{all := HostOpts} -> % Tracing all hooks for some hosts
maps:get(Host, HostOpts, undefined);
#{}=TraceHooksOpts ->
HostOpts = maps:get(Hook, TraceHooksOpts, #{}),
case maps:get(Host, HostOpts, undefined) of
undefined ->
maps:get(<<"*">>, HostOpts, undefined);
Opts ->
Opts
end
end.
run2([], Hook, Args, Host, Opts) ->
foreach_stop_hook_tracing(Opts, Hook, Host, Args, undefined),
ok;
run2([{Seq, Module, Function} | Ls], Hook, Args, Host, TracingOpts) ->
foreach_start_callback_tracing(TracingOpts, Hook, Host, Module, Function, Args, Seq),
Res = safe_apply(Hook, Module, Function, Args),
foreach_stop_callback_tracing(TracingOpts, Hook, Host, Module, Function, Args, Seq, Res),
case Res of
'EXIT' ->
run2(Ls, Hook, Args, Host, TracingOpts);
stop ->
foreach_stop_hook_tracing(TracingOpts, Hook, Host, Args, {Module, Function, Seq, Ls}),
ok;
_ ->
run2(Ls, Hook, Args, Host, TracingOpts)
end.
run_fold2([], Hook, Val, Args, Host, Opts) ->
fold_stop_hook_tracing(Opts, Hook, Host, [Val | Args], undefined),
Val;
run_fold2([{Seq, Module, Function} | Ls], Hook, Val, Args, Host, TracingOpts) ->
fold_start_callback_tracing(TracingOpts, Hook, Host, Module, Function, [Val | Args], Seq),
Res = safe_apply(Hook, Module, Function, [Val | Args]),
fold_stop_callback_tracing(TracingOpts, Hook, Host, Module, Function, [Val | Args], Seq, Res),
case Res of
'EXIT' ->
run_fold2(Ls, Hook, Val, Args, Host, TracingOpts);
stop ->
fold_stop_hook_tracing(TracingOpts, Hook, Host, [Val | Args], {Module, Function, Seq, {old, Val}, Ls}),
Val;
{stop, NewVal} ->
fold_stop_hook_tracing(TracingOpts, Hook, Host, [Val | Args], {Module, Function, Seq, {new, NewVal}, Ls}),
NewVal;
NewVal ->
run_fold2(Ls, Hook, NewVal, Args, Host, TracingOpts)
end.
foreach_start_hook_tracing(TracingOpts, Hook, Host, Args) ->
run_event_handlers(TracingOpts, Hook, Host, start_hook, [Args], foreach).
foreach_stop_hook_tracing(TracingOpts, Hook, Host, Args, BreakCallback) ->
run_event_handlers(TracingOpts, Hook, Host, stop_hook, [Args, BreakCallback], foreach).
foreach_start_callback_tracing(TracingOpts, Hook, Host, Mod, Func, Args, Seq) ->
run_event_handlers(TracingOpts, Hook, Host, start_callback, [Mod, Func, Args, Seq], foreach).
foreach_stop_callback_tracing(TracingOpts, Hook, Host, Mod, Func, Args, Seq, Res) ->
run_event_handlers(TracingOpts, Hook, Host, stop_callback, [Mod, Func, Args, Seq, Res], foreach).
fold_start_hook_tracing(TracingOpts, Hook, Host, Args) ->
run_event_handlers(TracingOpts, Hook, Host, start_hook, [Args], fold).
fold_stop_hook_tracing(TracingOpts, Hook, Host, Args, BreakCallback) ->
run_event_handlers(TracingOpts, Hook, Host, stop_hook, [Args, BreakCallback], fold).
fold_start_callback_tracing(TracingOpts, Hook, Host, Mod, Func, Args, Seq) ->
run_event_handlers(TracingOpts, Hook, Host, start_callback, [Mod, Func, Args, Seq], fold).
fold_stop_callback_tracing(TracingOpts, Hook, Host, Mod, Func, Args, Seq, Res) ->
run_event_handlers(TracingOpts, Hook, Host, stop_callback, [Mod, Func, Args, Seq, Res], fold).
run_event_handlers(TracingOpts, Hook, Host, Event, EventArgs, RunType) ->
EventHandlerList = maps:get(event_handler_list, TracingOpts, default_tracing_event_handler_list()),
EventHandlerOpts = maps:get(event_handler_options, TracingOpts, #{}),
if
erlang:is_list(EventHandlerList) ->
lists:foreach(
fun(EventHandler) ->
try
if
erlang:is_function(EventHandler) ->
erlang:apply(
EventHandler,
[Event, EventArgs, RunType, Hook, Host, EventHandlerOpts, TracingOpts]
);
true ->
EventHandler:handle_hook_tracing_event(
Event,
EventArgs,
RunType,
Hook,
Host,
EventHandlerOpts,
TracingOpts
)
end
of
_ ->
ok
catch
?EX_RULE(E, R, St) ->
Stack = ?EX_STACK(St),
?ERROR_MSG(
"(~0p|~ts|~0p) Tracing event '~0p' handler exception(~0p): ~0p: ~0p",
[Hook, Host, erlang:self(), EventHandler, E, R, Stack]
),
ok
end
end,
EventHandlerList
); % ok
true ->
?ERROR_MSG("(~0p|~ts|~0p) Bad event handler list: ~0p", [Hook, Host, erlang:self(), EventHandlerList]),
ok
end.
default_tracing_event_handler_list() ->
[fun tracing_timing_event_handler/7].
tracing_timing_event_handler(start_hook, EventArgs, RunType, Hook, Host, _, TracingOpts) ->
HookStart = erlang:system_time(nanosecond),
% Generate new event:
run_event_handlers(TracingOpts, Hook, Host, start_hook_timing, EventArgs ++ [HookStart], RunType);
tracing_timing_event_handler(stop_hook, EventArgs, RunType, Hook, Host, _, TracingOpts) ->
HookStop = erlang:system_time(nanosecond),
TimingMap = #{} = erlang:get(?TIMING_KEY),
{HookStart, CallbackList} = maps:get({Hook, Host}, TimingMap),
{CallbackListTiming, CallbackListTotal} = lists:foldl(
fun({_, _, _, CallbackStart, CallbackStop}=CallbackTimingInfo, {CallbackListTimingX, Total}) ->
{CallbackListTimingX ++ [CallbackTimingInfo], Total + (CallbackStop - CallbackStart)}
end,
{[], 0},
CallbackList
),
% Generate new event:
run_event_handlers(
TracingOpts,
Hook,
Host,
stop_hook_timing,
EventArgs ++ [HookStart, HookStop, CallbackListTiming, CallbackListTotal],
RunType
);
tracing_timing_event_handler(start_callback, EventArgs, RunType, Hook, Host, _, TracingOpts) ->
CallbackStart = erlang:system_time(nanosecond),
% Generate new event:
run_event_handlers(TracingOpts, Hook, Host, start_callback_timing, EventArgs ++ [CallbackStart], RunType);
tracing_timing_event_handler(stop_callback, EventArgs, RunType, Hook, Host, _, TracingOpts) ->
CallbackStop = erlang:system_time(nanosecond),
TimingMap = #{} = erlang:get(?TIMING_KEY),
{_, [{_, _, _, CallbackStart} | _]} = maps:get({Hook, Host}, TimingMap),
run_event_handlers(
TracingOpts,
Hook,
Host,
stop_callback_timing,
EventArgs ++ [CallbackStart, CallbackStop],
RunType
),
ok;
tracing_timing_event_handler(start_hook_timing, [_, HookStart], RunType, Hook, Host, EventHandlerOpts, _) ->
tracing_output(EventHandlerOpts, "(~0p|~ts|~0p|~0p) Timing started\n", [Hook, Host, erlang:self(), RunType]),
case erlang:get(?TIMING_KEY) of
#{}=TimingMap ->
erlang:put(?TIMING_KEY, TimingMap#{{Hook, Host} => {HookStart, []}});
_ ->
erlang:put(?TIMING_KEY, #{{Hook, Host} => {HookStart, []}})
end,
ok;
tracing_timing_event_handler(
stop_hook_timing,
[_, _, HookStart, HookStop, CallbackListTiming, CallbackListTotal],
RunType,
Hook,
Host,
EventHandlerOpts,
_
) ->
if
erlang:length(CallbackListTiming) < 2 -> % We don't need sorted timing result
ok;
true ->
CallbackListTimingText =
lists:foldl(
fun({Mod, Func, Arity, Diff}, CallbackListTimingText) ->
CallbackListTimingText
++ "\n\t"
++ mfa_string({Mod, Func, Arity})
++ " -> "
++ human_readable_time_string(Diff)
end,
"",
lists:keysort(
4,
[
{Mod, Func, Arity, CallbackStop - CallbackStart} ||
{Mod, Func, Arity, CallbackStart, CallbackStop} <- CallbackListTiming
]
)
),
tracing_output(
EventHandlerOpts,
"(~0p|~ts|~0p|~0p) All callbacks took ~ts to run. Sorted running time:"
++ CallbackListTimingText
++ "\n",
[Hook, Host, erlang:self(), RunType, human_readable_time_string(CallbackListTotal)]
),
tracing_output(
EventHandlerOpts,
"(~0p|~ts|~0p|~0p) Time calculations for all callbacks took ~ts\n",
[
Hook,
Host,
erlang:self(),
RunType,
human_readable_time_string((HookStop - HookStart) - CallbackListTotal)
]
)
end,
tracing_output(EventHandlerOpts, "(~0p|~ts|~0p|~0p) Timing stopped\n", [Hook, Host, erlang:self(), RunType]),
TimingMap = #{} = erlang:get(?TIMING_KEY),
NewTimingMap = maps:remove({Hook, Host}, TimingMap),
if
NewTimingMap == #{} ->
erlang:erase(?TIMING_KEY);
true ->
erlang:put(?TIMING_KEY, NewTimingMap)
end,
ok;
tracing_timing_event_handler(start_callback_timing, [Mod, Func, Args, _, CallbackStart], _, Hook, Host, _, _) ->
TimingMap = #{} = erlang:get(?TIMING_KEY),
{HookStart, Callbacks} = maps:get({Hook, Host}, TimingMap),
erlang:put(
?TIMING_KEY,
TimingMap#{
{Hook, Host} => {HookStart, [{Mod, Func, erlang:length(Args), CallbackStart} | Callbacks]}
}
),
ok;
tracing_timing_event_handler(
stop_callback_timing,
[Mod, Func, _, _, _, CallbackStart, CallbackStop],
RunType,
Hook,
Host,
EventHandlerOpts,
_
) ->
TimingMap = #{} = erlang:get(?TIMING_KEY),
{HookStart, [{Mod, Func, Arity, CallbackStart} | Callbacks]} = maps:get({Hook, Host}, TimingMap),
maps:get(output_for_each_callback, maps:get(timing, EventHandlerOpts, #{}), false) andalso tracing_output(
EventHandlerOpts,
"(~0p|~ts|~0p|~0p) "
++ mfa_string({Mod, Func, Arity})
++ " took "
++ human_readable_time_string(CallbackStop - CallbackStart)
++ "\n",
[Hook, Host, erlang:self(), RunType]
),
erlang:put(
?TIMING_KEY,
TimingMap#{
{Hook, Host} => {HookStart, [{Mod, Func, Arity, CallbackStart, CallbackStop} | Callbacks]}
}
),
ok;
tracing_timing_event_handler(_, _, _, _, _, _, _) ->
ok.
tracing_output(#{output_function := OutputF}, Text, Args) ->
try
OutputF(Text, Args)
of
_ ->
ok
catch
?EX_RULE(E, R, St) ->
Stack = ?EX_STACK(St),
?ERROR_MSG("Tracing output function exception(~0p): ~0p: ~0p", [E, R, Stack]),
ok
end;
tracing_output(#{output_log_level := Output}, Text, Args) ->
if
Output == debug ->
?DEBUG(Text, Args);
true -> % info
?INFO_MSG(Text, Args)
end,
ok;
tracing_output(Opts, Text, Args) ->
tracing_output(Opts#{output_log_level => info}, Text, Args).
mfa_string({_, Fun, _}) when erlang:is_function(Fun) ->
io_lib:format("~0p", [Fun]);
mfa_string({Mod, Func, Arity}) ->
erlang:atom_to_list(Mod) ++ ":" ++ erlang:atom_to_list(Func) ++ "/" ++ erlang:integer_to_list(Arity).
human_readable_time_string(TimeNS) ->
{Time, Unit, Decimals} =
if
TimeNS >= 1000000000 ->
{TimeNS / 1000000000, "", 10};
TimeNS >= 1000000 ->
{TimeNS / 1000000, "m", 7};
TimeNS >= 1000 ->
{TimeNS / 1000, "μ", 4};
true ->
{TimeNS / 1, "n", 0}
end,
erlang:float_to_list(Time, [{decimals, Decimals}, compact]) ++ Unit ++ "s".