25
1
mirror of https://github.com/processone/ejabberd.git synced 2024-12-22 17:28:25 +01:00
xmpp.chapril.org-ejabberd/tools/hook_deps.sh
Evgeniy Khramtsov 66fc1bf3b6 Remove 'iqdisc' option
Since we got rid of all bottle-neck processes and we have
a connection pool for every database, the option is no longer
needed and in fact is detrimental: in practice what you get
is just a bunch of overloaded processes in the IQ handlers pool
no matter how much you increase the `iqdisc` value.

Given that there are close to zero operators understanding
the meaning of the option and, hence, not using it all,
it's not simply deprecated but completely removed.

The commit also deprecates the following functions:
- gen_iq_handler:add_iq_handler/6
- gen_iq_handler:handle/5
- gen_iq_handler:iqdisc/1
2018-02-11 12:54:15 +03:00

393 lines
12 KiB
Bash
Executable File

#!/usr/bin/env escript
%% -*- erlang -*-
%%! -pa ebin
-record(state, {run_hooks = dict:new(),
run_fold_hooks = dict:new(),
hooked_funs = dict:new(),
mfas = dict:new(),
specs = dict:new(),
module :: module(),
file :: filename:filename()}).
main([Dir]) ->
State =
filelib:fold_files(
Dir, ".+\.[eh]rl\$", false,
fun(FileIn, Res) ->
case get_forms(FileIn) of
{ok, Forms} ->
Tree = erl_syntax:form_list(Forms),
Mod = list_to_atom(filename:rootname(filename:basename(FileIn))),
Acc0 = analyze_form(Tree, Res#state{module = Mod, file = FileIn}),
erl_syntax_lib:fold(
fun(Form, Acc) ->
case erl_syntax:type(Form) of
application ->
case erl_syntax_lib:analyze_application(Form) of
{ejabberd_hooks, {run, N}}
when N == 2; N == 3 ->
analyze_run_hook(Form, Acc);
{ejabberd_hooks, {run_fold, N}}
when N == 3; N == 4 ->
analyze_run_fold_hook(Form, Acc);
{ejabberd_hooks, {add, N}}
when N == 4; N == 5 ->
analyze_run_fun(Form, Acc);
{gen_iq_handler, {add_iq_handler, N}}
when N == 5; N == 6 ->
analyze_iq_handler(Form, Acc);
_ ->
Acc
end;
attribute ->
case catch erl_syntax_lib:analyze_attribute(Form) of
{spec, _} ->
analyze_type_spec(Form, Acc);
_ ->
Acc
end;
_ ->
Acc
end
end, Acc0, Tree);
_Err ->
Res
end
end, #state{}),
report_orphaned_funs(State),
RunDeps = build_deps(State#state.run_hooks, State#state.hooked_funs),
RunFoldDeps = build_deps(State#state.run_fold_hooks, State#state.hooked_funs),
emit_module(RunDeps, RunFoldDeps, State#state.specs, Dir, hooks_type_test).
analyze_form(_Form, State) ->
%% case catch erl_syntax_lib:analyze_forms(Form) of
%% Props when is_list(Props) ->
%% M = State#state.module,
%% MFAs = lists:foldl(
%% fun({F, A}, Acc) ->
%% dict:append({M, F}, A, Acc)
%% end, State#state.mfas,
%% proplists:get_value(functions, Props, [])),
%% State#state{mfas = MFAs};
%% _ ->
%% State
%% end.
State.
analyze_run_hook(Form, State) ->
[Hook|Tail] = erl_syntax:application_arguments(Form),
case atom_value(Hook, State) of
undefined ->
State;
HookName ->
Args = case Tail of
[_Host, Args0] -> Args0;
[Args0] ->
Args0
end,
Arity = erl_syntax:list_length(Args),
Hooks = dict:store({HookName, Arity},
{State#state.file, erl_syntax:get_pos(Hook)},
State#state.run_hooks),
State#state{run_hooks = Hooks}
end.
analyze_run_fold_hook(Form, State) ->
[Hook|Tail] = erl_syntax:application_arguments(Form),
case atom_value(Hook, State) of
undefined ->
State;
HookName ->
Args = case Tail of
[_Host, _Val, Args0] -> Args0;
[_Val, Args0] -> Args0
end,
Arity = erl_syntax:list_length(Args) + 1,
Hooks = dict:store({HookName, Arity},
{State#state.file, erl_syntax:get_pos(Form)},
State#state.run_fold_hooks),
State#state{run_fold_hooks = Hooks}
end.
analyze_run_fun(Form, State) ->
[Hook|Tail] = erl_syntax:application_arguments(Form),
case atom_value(Hook, State) of
undefined ->
State;
HookName ->
{Module, Fun, Seq} = case Tail of
[_Host, M, F, S] ->
{M, F, S};
[M, F, S] ->
{M, F, S}
end,
ModName = module_name(Module, State),
FunName = atom_value(Fun, State),
if ModName /= undefined, FunName /= undefined ->
Funs = dict:append(
HookName,
{ModName, FunName, integer_value(Seq, State),
{State#state.file, erl_syntax:get_pos(Form)}},
State#state.hooked_funs),
State#state{hooked_funs = Funs};
true ->
State
end
end.
analyze_iq_handler(Form, State) ->
[_Component, _Host, _NS, Module, Function|_] =
erl_syntax:application_arguments(Form),
Mod = module_name(Module, State),
Fun = atom_value(Function, State),
if Mod /= undefined, Fun /= undefined ->
code:ensure_loaded(Mod),
case erlang:function_exported(Mod, Fun, 1) of
false ->
log("~s:~p: Error: function ~s:~s/1 is registered "
"as iq handler, but is not exported~n",
[State#state.file, erl_syntax:get_pos(Form),
Mod, Fun]);
true ->
ok
end;
true ->
ok
end,
State.
analyze_type_spec(Form, State) ->
case catch erl_syntax:revert(Form) of
{attribute, _, spec, {{F, A}, _}} ->
Specs = dict:store({State#state.module, F, A},
{Form, State#state.file},
State#state.specs),
State#state{specs = Specs};
_ ->
State
end.
build_deps(Hooks, Hooked) ->
dict:fold(
fun({Hook, Arity}, {_File, _LineNo} = Meta, Deps) ->
case dict:find(Hook, Hooked) of
{ok, Funs} ->
ExportedFuns =
lists:flatmap(
fun({M, F, Seq, {FunFile, FunLineNo} = FunMeta}) ->
code:ensure_loaded(M),
case erlang:function_exported(M, F, Arity) of
false ->
log("~s:~p: Error: function ~s:~s/~p "
"is hooked on ~s/~p, but is not "
"exported~n",
[FunFile, FunLineNo, M, F,
Arity, Hook, Arity]),
[];
true ->
[{{M, F, Arity}, Seq, FunMeta}]
end
end, Funs),
dict:append_list({Hook, Arity, Meta}, ExportedFuns, Deps);
error ->
%% log("~s:~p: Warning: hook ~p/~p is unused~n",
%% [_File, _LineNo, Hook, Arity]),
dict:append_list({Hook, Arity, Meta}, [], Deps)
end
end, dict:new(), Hooks).
report_orphaned_funs(State) ->
dict:map(
fun(Hook, Funs) ->
lists:foreach(
fun({M, F, _, {File, Line}}) ->
case get_fun_arities(M, F, State) of
[] ->
log("~s:~p: Error: function ~s:~s is "
"hooked on hook ~s, but is not exported~n",
[File, Line, M, F, Hook]);
Arities ->
case lists:any(
fun(Arity) ->
dict:is_key({Hook, Arity},
State#state.run_hooks) orelse
dict:is_key({Hook, Arity},
State#state.run_fold_hooks);
(_) ->
false
end, Arities) of
false ->
Arity = hd(Arities),
log("~s:~p: Error: function ~s:~s/~p is hooked"
" on non-existent hook ~s/~p~n",
[File, Line, M, F, Arity, Hook, Arity]);
true ->
ok
end
end
end, Funs)
end, State#state.hooked_funs).
get_fun_arities(Mod, Fun, _State) ->
proplists:get_all_values(Fun, Mod:module_info(exports)).
module_name(Form, State) ->
try
Name = erl_syntax:macro_name(Form),
'MODULE' = erl_syntax:variable_name(Name),
State#state.module
catch _:_ ->
atom_value(Form, State)
end.
atom_value(Form, State) ->
case erl_syntax:type(Form) of
atom ->
erl_syntax:atom_value(Form);
_ ->
log("~s:~p: Warning: not an atom: ~s~n",
[State#state.file,
erl_syntax:get_pos(Form),
erl_prettypr:format(Form)]),
undefined
end.
integer_value(Form, State) ->
case erl_syntax:type(Form) of
integer ->
erl_syntax:integer_value(Form);
_ ->
log("~s:~p: Warning: not an integer: ~s~n",
[State#state.file,
erl_syntax:get_pos(Form),
erl_prettypr:format(Form)]),
0
end.
emit_module(RunDeps, RunFoldDeps, Specs, Dir, Module) ->
File = filename:join([Dir, Module]) ++ ".erl",
try
{ok, Fd} = file:open(File, [write]),
write(Fd, "-module(~s).~n~n", [Module]),
emit_export(Fd, RunDeps, "run hooks"),
emit_export(Fd, RunFoldDeps, "run_fold hooks"),
emit_run_hooks(Fd, RunDeps, Specs),
emit_run_fold_hooks(Fd, RunFoldDeps, Specs),
file:close(Fd),
log("Module written to file ~s~n", [File])
catch _:{badmatch, {error, Reason}} ->
log("writing to ~s failed: ~s", [File, file:format_error(Reason)])
end.
emit_run_hooks(Fd, Deps, Specs) ->
DepsList = lists:sort(dict:to_list(Deps)),
lists:foreach(
fun({{Hook, Arity, {File, LineNo}}, []}) ->
Args = lists:duplicate(Arity, "_"),
write(Fd, "%% called at ~s:~p~n", [File, LineNo]),
write(Fd, "~s(~s) -> ok.~n~n", [Hook, string:join(Args, ", ")]);
({{Hook, Arity, {File, LineNo}}, Funs}) ->
emit_specs(Fd, Funs, Specs),
write(Fd, "%% called at ~s:~p~n", [File, LineNo]),
Args = string:join(
[[N] || N <- lists:sublist(lists:seq($A, $Z), Arity)],
", "),
write(Fd, "~s(~s) ->~n ", [Hook, Args]),
Calls = [io_lib:format("~s:~s(~s)", [Mod, Fun, Args])
|| {{Mod, Fun, _}, _Seq, _} <- lists:keysort(2, Funs)],
write(Fd, "~s.~n~n", [string:join(Calls, ",\n ")])
end, DepsList).
emit_run_fold_hooks(Fd, Deps, Specs) ->
DepsList = lists:sort(dict:to_list(Deps)),
lists:foreach(
fun({{Hook, Arity, {File, LineNo}}, []}) ->
write(Fd, "%% called at ~s:~p~n", [File, LineNo]),
Args = ["Acc"|lists:duplicate(Arity - 1, "_")],
write(Fd, "~s(~s) -> Acc.~n~n", [Hook, string:join(Args, ", ")]);
({{Hook, Arity, {File, LineNo}}, Funs}) ->
emit_specs(Fd, Funs, Specs),
write(Fd, "%% called at ~s:~p~n", [File, LineNo]),
Args = [[N] || N <- lists:sublist(lists:seq($A, $Z), Arity - 1)],
write(Fd, "~s(~s) ->~n ", [Hook, string:join(["Acc0"|Args], ", ")]),
{Calls, _} = lists:mapfoldl(
fun({{Mod, Fun, _}, _Seq, _}, N) ->
Args1 = ["Acc" ++ integer_to_list(N)|Args],
{io_lib:format("Acc~p = ~s:~s(~s)",
[N+1, Mod, Fun,
string:join(Args1, ", ")]),
N + 1}
end, 0, lists:keysort(2, Funs)),
write(Fd, "~s,~n", [string:join(Calls, ",\n ")]),
write(Fd, " Acc~p.~n~n", [length(Funs)])
end, DepsList).
emit_export(Fd, Deps, Comment) ->
DepsList = lists:sort(dict:to_list(Deps)),
Exports = lists:map(
fun({{Hook, Arity, _}, _}) ->
io_lib:format("~s/~p", [Hook, Arity])
end, DepsList),
write(Fd, "%% ~s~n-export([~s]).~n~n",
[Comment, string:join(Exports, ",\n ")]).
emit_specs(Fd, Funs, Specs) ->
lists:foreach(
fun({{M, _, _} = MFA, _, _}) ->
case dict:find(MFA, Specs) of
{ok, {Form, _File}} ->
Lines = string:tokens(erl_syntax:get_ann(Form), "\n"),
lists:foreach(
fun("%" ++ _) ->
ok;
("-spec" ++ Spec) ->
write(Fd, "%% -spec ~p:~s~n",
[M, string:strip(Spec, left)]);
(Line) ->
write(Fd, "%% ~s~n", [Line])
end, Lines);
error ->
ok
end
end, lists:keysort(2, Funs)).
get_forms(Path) ->
case file:open(Path, [read]) of
{ok, Fd} ->
parse(Path, Fd, 1, []);
Err ->
Err
end.
parse(Path, Fd, Line, Acc) ->
{ok, Pos} = file:position(Fd, cur),
case epp_dodger:parse_form(Fd, Line) of
{ok, Form, NewLine} ->
{ok, NewPos} = file:position(Fd, cur),
{ok, RawForm} = file:pread(Fd, Pos, NewPos - Pos),
file:position(Fd, {bof, NewPos}),
AnnForm = erl_syntax:set_ann(Form, RawForm),
parse(Path, Fd, NewLine, [AnnForm|Acc]);
{eof, _} ->
{ok, NewPos} = file:position(Fd, cur),
if NewPos > Pos ->
{ok, RawForm} = file:pread(Fd, Pos, NewPos - Pos),
Form = erl_syntax:text(""),
AnnForm = erl_syntax:set_ann(Form, RawForm),
{ok, lists:reverse([AnnForm|Acc])};
true ->
{ok, lists:reverse(Acc)}
end;
{error, {_, _, ErrDesc}, LineNo} = Err ->
log("~s:~p: Error: ~s~n",
[Path, LineNo, erl_parse:format_error(ErrDesc)]),
Err
end.
log(Format, Args) ->
io:format(standard_io, Format, Args).
write(Fd, Format, Args) ->
file:write(Fd, io_lib:format(Format, Args)).