1 |
|
%% @doc This module contains debug utilities intended for use with Erlang Doctor. |
2 |
|
%% Beware that this tool has the potential of seriously impacting |
3 |
|
%% your system, leading to various issues including system crash or data loss. |
4 |
|
%% Therefore, it is intended for use in development or QA environments, |
5 |
|
%% and using it in a production environment is risky. |
6 |
|
%% |
7 |
|
%% Example: let's capture and list all stanzas exchanged between Alice and Bob. |
8 |
|
%% |
9 |
|
%% ``` |
10 |
|
%% tr:start(). |
11 |
|
%% tr:trace([mongoose_c2s_hooks]). |
12 |
|
%% |
13 |
|
%% %% Exchange stanzas between users |
14 |
|
%% |
15 |
|
%% tr:stop_tracing(). |
16 |
|
%% tr_util:c2s_elements_between_jids([<<"alice@localhost">>, <<"bob@localhost">>]). |
17 |
|
%% |
18 |
|
%% %% You will get a list of `c2s_element_info' maps with the exchanged stanzas. |
19 |
|
%% ''' |
20 |
|
%% |
21 |
|
%% @reference See <a href="https://hexdocs.pm/erlang_doctor/readme.html">Hex Docs</a> |
22 |
|
%% for more information about Erlang Doctor. |
23 |
|
|
24 |
|
-module(tr_util). |
25 |
|
|
26 |
|
%% Debugging API for mongoose_c2s events and XMPP traffic |
27 |
|
-export([c2s_elements_between_jids/1, |
28 |
|
c2s_hooks/0, |
29 |
|
c2s_elements/0]). |
30 |
|
|
31 |
|
%% Selectors for use with `tr:call_stat' etc. |
32 |
|
-export([tr_to_element_info/1, |
33 |
|
tr_to_hook_name_and_tag/1]). |
34 |
|
|
35 |
|
%% Predicates for use with `tr:filter' etc. |
36 |
|
-export([filter_c2s_hook/1]). |
37 |
|
|
38 |
|
-include_lib("erlang_doctor/include/tr.hrl"). |
39 |
|
-include_lib("exml/include/exml.hrl"). |
40 |
|
|
41 |
|
-ignore_xref(?MODULE). |
42 |
|
|
43 |
|
-type c2s_element_info() :: #{name := binary(), |
44 |
|
contents := binary(), |
45 |
|
ref := reference(), |
46 |
|
hooks := [atom()], |
47 |
|
jid := jid:literal_jid(), |
48 |
|
from_jid := jid:literal_jid(), |
49 |
|
to_jid := jid:literal_jid(), |
50 |
|
id := binary(), |
51 |
|
type := binary()}. |
52 |
|
|
53 |
|
%% Complete utilities |
54 |
|
|
55 |
|
%% @doc Get a list of XML elements (usually stanzas) exchanged between the listed JIDs. |
56 |
|
%% The `to' and `from' attributes must match different JIDs from the list. |
57 |
|
%% Matching starts from the beginning of the list. A bare JID matches any resource. |
58 |
|
%% |
59 |
|
%% Requires traces from modules: `[mongoose_c2s_hooks]'. |
60 |
|
-spec c2s_elements_between_jids([jid:literal_jid()]) -> [c2s_element_info()]. |
61 |
|
c2s_elements_between_jids(TargetBinJids) -> |
62 |
2 |
Targets = lists:map(fun jid:from_binary_noprep/1, TargetBinJids), |
63 |
2 |
lists:filter(fun(#{from_jid := From, to_jid := To}) -> |
64 |
24 |
case match_target_jids(From, Targets) of |
65 |
4 |
[] -> false; |
66 |
20 |
[H|_] -> match_target_jids(To, Targets -- [H]) =/= [] |
67 |
|
end |
68 |
|
end, c2s_elements()). |
69 |
|
|
70 |
|
%% @doc Get a list of all C2S hooks in the execution order, annotated by user JIDs, |
71 |
|
%% for which they were executed. |
72 |
|
%% |
73 |
|
%% Requires traces from modules: `[mongoose_c2s_hooks]'. |
74 |
|
-spec c2s_hooks() -> [{jid:literal_jid(), atom(), mongoose_acc:t()}]. |
75 |
|
c2s_hooks() -> |
76 |
2 |
[{jid:to_binary(mongoose_c2s:get_jid(Data)), Hook, Acc} || |
77 |
|
#tr{mfa = {_, Hook, _}, data = [_HT, Acc, #{c2s_data := Data}]} <- |
78 |
2 |
tr:filter(fun filter_c2s_hook/1) |
79 |
|
]. |
80 |
|
|
81 |
|
%% @doc Get information about XML elements, for which C2S hooks were executed. |
82 |
|
%% |
83 |
|
%% Requires traces from modules: `[mongoose_c2s_hooks]'. |
84 |
|
-spec c2s_elements() -> [c2s_element_info()]. |
85 |
|
c2s_elements() -> |
86 |
4 |
join_hooks(c2s_element_hooks()). |
87 |
|
|
88 |
|
%% Selectors for use with `tr:call_stat' etc. |
89 |
|
|
90 |
|
-spec tr_to_hook_name_and_tag(tr:tr()) -> {gen_hook:hook_name(), gen_hook:hook_tag()}. |
91 |
|
tr_to_hook_name_and_tag(#tr{mfa = {gen_hook, run_fold, _}, data = [HookName, Tag | _]}) -> |
92 |
35 |
{HookName, Tag}. |
93 |
|
|
94 |
|
-spec tr_to_element_info(tr:tr()) -> c2s_element_info(). |
95 |
|
tr_to_element_info(#tr{mfa = {mongoose_c2s_hooks, Hook, _}, |
96 |
|
data = [_HT, #{stanza := ElementAcc}, #{c2s_data := Data}]}) -> |
97 |
93 |
element_info(Data, Hook, ElementAcc). |
98 |
|
|
99 |
|
%% Predicates for use with `tr:filter' etc. |
100 |
|
|
101 |
|
-spec filter_c2s_hook(tr:tr()) -> boolean(). |
102 |
26 |
filter_c2s_hook(#tr{mfa = {mongoose_c2s_hooks, _, _}}) -> true. |
103 |
|
|
104 |
|
-spec filter_c2s_hook_with_element(tr:tr()) -> boolean(). |
105 |
|
filter_c2s_hook_with_element(#tr{mfa = {mongoose_c2s_hooks, _, _}, |
106 |
|
data = [_HT, #{stanza := #{}}, _Data]}) -> |
107 |
93 |
true. |
108 |
|
|
109 |
|
%% Internal helpers |
110 |
|
|
111 |
|
-spec c2s_element_hooks() -> [c2s_element_info()]. |
112 |
|
c2s_element_hooks() -> |
113 |
4 |
lists:map(fun tr_to_element_info/1, tr:filter(fun filter_c2s_hook_with_element/1)). |
114 |
|
|
115 |
|
-spec match_target_jids(jid:literal_jid(), [jid:jid()]) -> [jid:jid()]. |
116 |
|
match_target_jids(ActualBJid, Targets) -> |
117 |
44 |
Actual = jid:from_binary_noprep(ActualBJid), |
118 |
44 |
lists:filter(fun(Target) -> match_jid(Target, Actual) end, Targets). |
119 |
|
|
120 |
|
-spec match_jid(jid:jid(), jid:jid()) -> boolean(). |
121 |
|
match_jid(Target, Actual) -> |
122 |
68 |
case jid:lresource(Target) of |
123 |
34 |
<<>> -> jid:are_bare_equal(Target, Actual); |
124 |
34 |
_ -> jid:are_equal(Target, Actual) |
125 |
|
end. |
126 |
|
|
127 |
|
-spec join_hooks([c2s_element_info()]) -> [c2s_element_info()]. |
128 |
|
join_hooks([First | Rest]) -> |
129 |
3 |
lists:reverse(lists:foldl(fun join_hooks_step/2, [First], Rest)); |
130 |
|
join_hooks([]) -> |
131 |
1 |
[]. |
132 |
|
|
133 |
|
-spec join_hooks_step(c2s_element_info(), [c2s_element_info()]) -> [c2s_element_info()]. |
134 |
|
join_hooks_step(Cur, [Prev | Acc]) -> |
135 |
90 |
case {maps:take(hooks, Cur), maps:take(hooks, Prev)} of |
136 |
|
{{CurHooks, D}, {PrevHooks, D}} -> |
137 |
57 |
[D#{hooks => PrevHooks ++ CurHooks} | Acc]; |
138 |
|
_ -> |
139 |
33 |
[Cur, Prev | Acc] |
140 |
|
end. |
141 |
|
|
142 |
|
-spec element_info(mongoose_c2s:data(), gen_hook:hook_name(), mongoose_acc:stanza_metadata()) -> |
143 |
|
c2s_element_info(). |
144 |
|
element_info(Data, Hook, #{element := Element, ref := Ref, from_jid := From, to_jid := To}) -> |
145 |
93 |
Info = #{name => Element#xmlel.name, |
146 |
|
contents => exml:to_binary(Element#xmlel.children), |
147 |
|
ref => Ref, |
148 |
|
hooks => [Hook], |
149 |
|
jid => jid:to_binary(mongoose_c2s:get_jid(Data)), |
150 |
|
from_jid => jid:to_binary(From), |
151 |
|
to_jid => jid:to_binary(To)}, |
152 |
93 |
maps:merge(Info, element_attr_info(Element#xmlel.attrs)). |
153 |
|
|
154 |
|
-spec element_attr_info([exml:attr()]) -> #{atom() => binary()}. |
155 |
|
element_attr_info(Attrs) -> |
156 |
93 |
AllowedAttrs = [<<"id">>, <<"type">>], |
157 |
93 |
maps:from_list([{binary_to_existing_atom(Key), Value} || {Key, Value} <- Attrs, |
158 |
219 |
lists:member(Key, AllowedAttrs)]). |