1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2010-2016. All Rights Reserved.
5%%
6%% Licensed under the Apache License, Version 2.0 (the "License");
7%% you may not use this file except in compliance with the License.
8%% You may obtain a copy of the License at
9%%
10%%     http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing, software
13%% distributed under the License is distributed on an "AS IS" BASIS,
14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15%% See the License for the specific language governing permissions and
16%% limitations under the License.
17%%
18%% %CopyrightEnd%
19%%
20
21%%
22%% Information and debug functions.
23%%
24
25-module(diameter_dbg).
26
27-export([table/1,
28         tables/0,
29         fields/1,
30         modules/0,
31         versions/0,
32         version_info/0,
33         compiled/0,
34         procs/0,
35         latest/0,
36         nl/0,
37         sizes/0]).
38
39-export([diameter_config/0,
40         diameter_peer/0,
41         diameter_reg/0,
42         diameter_request/0,
43         diameter_sequence/0,
44         diameter_service/0,
45         diameter_stats/0]).
46
47-export([pp/1,
48         subscriptions/0,
49         children/0]).
50
51%% Trace help.
52-export([tracer/0, tracer/1,
53         p/0, p/1,
54         stop/0,
55         tpl/1,
56         tp/1]).
57
58-include_lib("diameter/include/diameter.hrl").
59
60-define(APP, diameter).
61-define(I, diameter_info).
62
63-define(LOCAL, [diameter_config,
64                diameter_peer,
65                diameter_reg,
66                diameter_request,
67                diameter_sequence,
68                diameter_service,
69                diameter_stats]).
70
71-define(VALUES(Rec), tl(tuple_to_list(Rec))).
72
73%% ----------------------------------------------------------
74%% # sizes/0
75%%
76%% Return sizes of named tables.
77%% ----------------------------------------------------------
78
79sizes() ->
80    [{T, ets:info(T, size)} || T <- ?LOCAL, T /= diameter_peer].
81
82%% ----------------------------------------------------------
83%% # table/1
84%%
85%% Pretty-print a diameter table. Returns the number of records
86%% printed, or undefined.
87%% ----------------------------------------------------------
88
89table(T)
90  when (T == diameter_peer) orelse (T == diameter_reg) ->
91    ?I:format(collect(T), fields(T), fun ?I:split/2);
92
93table(Table)
94  when is_atom(Table) ->
95    case fields(Table) of
96        undefined = No ->
97            No;
98        Fields ->
99            ?I:format(Table, Fields, fun split/2)
100    end.
101
102split([started, name | Fs], [S, N | Vs]) ->
103    {name, [started | Fs], N, [S | Vs]};
104split([[F|FT]|Fs], [Rec|Vs]) ->
105    [_, V | VT] = tuple_to_list(Rec),
106    {F, FT ++ Fs, V, VT ++ Vs};
107split([F|Fs], [V|Vs]) ->
108    {F, Fs, V, Vs}.
109
110%% ----------------------------------------------------------
111%% # TableName/0
112%% ----------------------------------------------------------
113
114-define(TABLE(Name), Name() -> table(Name)).
115
116?TABLE(diameter_config).
117?TABLE(diameter_peer).
118?TABLE(diameter_reg).
119?TABLE(diameter_request).
120?TABLE(diameter_sequence).
121?TABLE(diameter_service).
122?TABLE(diameter_stats).
123
124%% ----------------------------------------------------------
125%% # tables/0
126%%
127%% Pretty-print diameter tables from all nodes. Returns the number of
128%% records printed.
129%% ----------------------------------------------------------
130
131tables() ->
132    ?I:format(field(?LOCAL), fun split/3, fun collect/1).
133
134field(Tables) ->
135    lists:map(fun(T) -> {T, fields(T)} end, lists:sort(Tables)).
136
137split(_, Fs, Vs) ->
138    split(Fs, Vs).
139
140%% ----------------------------------------------------------
141%% # modules/0
142%% ----------------------------------------------------------
143
144modules() ->
145    Path = filename:join([appdir(), atom_to_list(?APP) ++ ".app"]),
146    {ok, [{application, ?APP, Attrs}]} = file:consult(Path),
147    {modules, Mods} = lists:keyfind(modules, 1, Attrs),
148    Mods.
149
150appdir() ->
151    [_|_] = code:lib_dir(?APP, ebin).
152
153%% ----------------------------------------------------------
154%% # versions/0
155%% ----------------------------------------------------------
156
157versions() ->
158    ?I:versions(modules()).
159
160%% ----------------------------------------------------------
161%% # version_info/0
162%% ----------------------------------------------------------
163
164version_info() ->
165    ?I:version_info(modules()).
166
167%% ----------------------------------------------------------
168%% # compiled/0
169%% ----------------------------------------------------------
170
171compiled() ->
172    ?I:compiled(modules()).
173
174%% ----------------------------------------------------------
175%% # procs/0
176%% ----------------------------------------------------------
177
178procs() ->
179    ?I:procs(?APP).
180
181%% ----------------------------------------------------------
182%% # latest/0
183%% ----------------------------------------------------------
184
185latest() ->
186    ?I:latest(modules()).
187
188%% ----------------------------------------------------------
189%% # nl/0
190%% ----------------------------------------------------------
191
192nl() ->
193    lists:foreach(fun(M) -> abcast = c:nl(M) end, modules()).
194
195%% ----------------------------------------------------------
196%% # pp/1
197%%
198%% Description: Pretty-print a message binary.
199%% ----------------------------------------------------------
200
201%% Network byte order = big endian.
202
203pp(<<Version:8, MsgLength:24,
204     Rbit:1, Pbit:1, Ebit:1, Tbit:1, Reserved:4, CmdCode:24,
205     ApplId:32,
206     HbHid:32,
207     E2Eid:32,
208     AVPs/binary>>) ->
209    ?I:sep(),
210    ppp(["Version",
211         "Message length",
212         "[Actual length]",
213         "R(equest)",
214         "P(roxiable)",
215         "E(rror)",
216         "T(Potential retrans)",
217         "Reserved bits",
218         "Command code",
219         "Application id",
220         "Hop by hop id",
221         "End to end id"],
222        [Version, MsgLength, size(AVPs) + 20,
223         Rbit, Pbit, Ebit, Tbit, Reserved,
224         CmdCode,
225         ApplId,
226         HbHid,
227         E2Eid]),
228    N = avp_loop({AVPs, MsgLength - 20}, 0),
229    ?I:sep(),
230    N;
231
232pp(<<_Version:8, MsgLength:24, _/binary>> = Bin) ->
233    {bad_message_length, MsgLength, size(Bin)};
234
235pp(Bin)
236  when is_binary(Bin) ->
237    {truncated_binary, size(Bin)};
238
239pp(_) ->
240    not_binary.
241
242%% avp_loop/2
243
244avp_loop({Bin, Size}, N) ->
245    avp_loop(avp(Bin, Size), N+1);
246avp_loop(ok, N) ->
247    N;
248avp_loop([_E, _Rest] = L, N) ->
249    io:format("! ~s: ~p~n", L),
250    N;
251avp_loop([E, Rest, Fmt | Values], N)
252  when is_binary(Rest) ->
253    io:format("! ~s (" ++ Fmt ++ "): ~p~n", [E|Values] ++ [Rest]),
254    N.
255
256%% avp/2
257
258avp(<<>>, 0) ->
259    ok;
260avp(<<Code:32, Flags:1/binary, Length:24, Rest/binary>>,
261    Size) ->
262    avp(Code, Flags, Length, Rest, Size);
263avp(Bin, _) ->
264    ["truncated AVP header", Bin].
265
266%% avp/5
267
268avp(Code, Flags, Length, Rest, Size) ->
269    <<V:1, M:1, P:1, Res:5>>
270        = Flags,
271    b(),
272    ppp(["AVP Code",
273         "V(endor)",
274         "M(andatory)",
275         "P(Security)",
276         "R(eserved)",
277         "Length"],
278        [Code, V, M, P, Res, Length]),
279    avp(V, Rest, Length - 8, Size - 8).
280
281%% avp/4
282
283avp(1, <<V:32, Data/binary>>, Length, Size) ->
284    ppp({"Vendor-ID", V}),
285    data(Data, Length - 4, Size - 4);
286avp(1, Bin, _, _) ->
287    ["truncated Vendor-ID", Bin];
288avp(0, Data, Length, Size) ->
289    data(Data, Length, Size).
290
291data(Bin, Length, Size)
292  when size(Bin) >= Length ->
293    <<AVP:Length/binary, Rest/binary>> = Bin,
294    ppp({"Data", AVP}),
295    unpad(Rest, Size - Length, Length rem 4);
296
297data(Bin, _, _) ->
298    ["truncated AVP data", Bin].
299
300%% Remove padding bytes up to the next word boundary.
301unpad(Bin, Size, 0) ->
302    {Bin, Size};
303unpad(Bin, Size, N) ->
304    un(Bin, Size, 4 - N).
305
306un(Bin, Size, N)
307  when size(Bin) >= N ->
308    ppp({"Padding bytes", N}),
309    <<Pad:N/binary, Rest/binary>> = Bin,
310    Bits = N*8,
311    case Pad of
312        <<0:Bits>> ->
313            {Rest, Size - N};
314        _ ->
315            ["non-zero padding", Bin, "~p", N]
316    end;
317
318un(Bin, _, _) ->
319    ["truncated padding", Bin].
320
321b() ->
322    io:format("#~n").
323
324ppp(Fields, Values) ->
325    lists:foreach(fun ppp/1, lists:zip(Fields, Values)).
326
327ppp({Field, Value}) ->
328    io:format(": ~-22s : ~p~n", [Field, Value]).
329
330%% ----------------------------------------------------------
331%% # subscriptions/0
332%%
333%% Returns a list of {SvcName, Pid}.
334%% ----------------------------------------------------------
335
336subscriptions() ->
337    diameter_service:subscriptions().
338
339%% ----------------------------------------------------------
340%% # children/0
341%% ----------------------------------------------------------
342
343children() ->
344    diameter_sup:tree().
345
346%% ----------------------------------------------------------
347
348%% tracer/[12]
349
350tracer(Port)
351  when is_integer(Port) ->
352    dbg:tracer(port, dbg:trace_port(ip, Port));
353
354tracer(Path)
355  when is_list(Path) ->
356    dbg:tracer(port, dbg:trace_port(file, Path)).
357
358tracer() ->
359    dbg:tracer(process, {fun p/2, ok}).
360
361p(T,_) ->
362    io:format("+ ~p~n", [T]).
363
364%% p/[01]
365
366p() ->
367    p([c,timestamp]).
368
369p(T) ->
370    dbg:p(all,T).
371
372%% stop/0
373
374stop() ->
375    dbg:ctp(),
376    dbg:stop_clear().
377
378%% tpl/1
379%% tp/1
380
381tpl(T) ->
382    dbg(tpl, T).
383
384tp(T) ->
385    dbg(tp, T).
386
387%% dbg/2
388
389dbg(F, L)
390  when is_list(L) ->
391    [dbg(F, X) || X <- L];
392
393dbg(F, M)
394  when is_atom(M) ->
395    apply(dbg, F, [M, x]);
396
397dbg(F, T)
398  when is_tuple(T) ->
399    apply(dbg, F, tuple_to_list(T)).
400
401%% ===========================================================================
402%% ===========================================================================
403
404%% collect/1
405
406collect(diameter_peer) ->
407    lists:flatmap(fun peers/1, diameter:services());
408
409collect(diameter_reg) ->
410    diameter_reg:terms();
411
412collect(Name) ->
413    c(ets:info(Name), Name).
414
415c(undefined, _) ->
416    [];
417c(_, Name) ->
418    ets:tab2list(Name).
419
420%% peers/1
421
422peers(Name) ->
423    peers(Name, diameter:service_info(Name, transport)).
424
425peers(_, undefined) ->
426    [];
427peers(Name, Ts) ->
428    lists:flatmap(fun(T) -> mk_peers(Name, T) end, Ts).
429
430mk_peers(Name, [_, {type, connect} | _] = Ts) ->
431    [[Name | mk_peer(Ts)]];
432mk_peers(Name, [R, {type, listen}, O, {accept = A, As} | _]) ->
433    [[Name | mk_peer([R, {type, A}, O | Ts])] || Ts <- As].
434%% This is a bit lame: service_info works to build this list and out
435%% of something like what we want here and then we take it apart.
436
437mk_peer(Vs) ->
438    [Type, Ref, State, Opts, WPid, TPid, SApps, Caps]
439        = get_values(Vs, [type,ref,state,options,watchdog,peer,apps,caps]),
440    [Ref, State, [{type, Type} | Opts], s(WPid), s(TPid), SApps, Caps].
441
442get_values(Vs, Ks) ->
443    [proplists:get_value(K, Vs) || K <- Ks].
444
445s(undefined = T) ->
446    T;
447s({Pid, _Started, _State}) ->
448    state(Pid);
449s({Pid, _Started}) ->
450    state(Pid).
451
452%% Collect states from watchdog/transport pids.
453state(Pid) ->
454    MRef = erlang:monitor(process, Pid),
455    Pid ! {state, self()},
456    receive
457        {'DOWN', MRef, process, _, _} ->
458            Pid;
459        {Pid, _} = T ->
460            erlang:demonitor(MRef, [flush]),
461            T
462    end.
463
464%% fields/1
465
466-define(FIELDS(Table), fields(Table) -> record_info(fields, Table)).
467
468fields(diameter_config) ->
469    [];
470
471fields(T)
472  when T == diameter_request;
473       T == diameter_sequence ->
474    fun kv/1;
475
476fields(diameter_stats) ->
477    fun({Ctr, N}) when not is_pid(Ctr) ->
478            {[counter, value], [Ctr, N]};
479       (_) ->
480            []
481    end;
482
483fields(diameter_service) ->
484    [started,
485     name,
486     record_info(fields, diameter_service),
487     watchdogT,
488     peerT,
489     shared_peers,
490     local_peers,
491     monitor,
492     options];
493
494?FIELDS(diameter_event);
495?FIELDS(diameter_uri);
496?FIELDS(diameter_avp);
497?FIELDS(diameter_header);
498?FIELDS(diameter_packet);
499?FIELDS(diameter_app);
500?FIELDS(diameter_caps);
501
502fields(diameter_peer) ->
503    [service, ref, state, options, watchdog, peer, applications, capabilities];
504
505fields(diameter_reg) ->
506    [property, pids];
507
508fields(_) ->
509    undefined.
510
511kv({_,_}) ->
512    [key, value];
513kv(_) ->
514    [].
515