./ct_report/coverage/tr_util.COVER.html

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:stanza_metadata()) ->
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)]).
Line Hits Source