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