%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2010-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. %% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. %% %% %CopyrightEnd% %% %% %% Information and debug functions. %% -module(diameter_dbg). -export([table/1, tables/0, fields/1, modules/0, versions/0, version_info/0, compiled/0, procs/0, latest/0, nl/0]). -export([diameter_config/0, diameter_peer/0, diameter_reg/0, diameter_request/0, diameter_sequence/0, diameter_service/0, diameter_stats/0]). -export([pp/1, subscriptions/0, children/0]). %% Trace help. -export([tracer/0, tracer/1, p/0, p/1, stop/0, tpl/1, tp/1]). -include_lib("diameter/include/diameter.hrl"). -define(APP, diameter). -define(I, diameter_info). -define(LOCAL, [diameter_config, diameter_peer, diameter_reg, diameter_request, diameter_sequence, diameter_service, diameter_stats]). -define(VALUES(Rec), tl(tuple_to_list(Rec))). %% ---------------------------------------------------------- %% # table/1 %% %% Pretty-print a diameter table. Returns the number of records %% printed, or undefined. %% ---------------------------------------------------------- table(T) when (T == diameter_peer) orelse (T == diameter_reg) -> ?I:format(collect(T), fields(T), fun ?I:split/2); table(Table) when is_atom(Table) -> case fields(Table) of undefined = No -> No; Fields -> ?I:format(Table, Fields, fun split/2) end. split([started, name | Fs], [S, N | Vs]) -> {name, [started | Fs], N, [S | Vs]}; split([[F|FT]|Fs], [Rec|Vs]) -> [_, V | VT] = tuple_to_list(Rec), {F, FT ++ Fs, V, VT ++ Vs}; split([F|Fs], [V|Vs]) -> {F, Fs, V, Vs}. %% ---------------------------------------------------------- %% # TableName/0 %% ---------------------------------------------------------- -define(TABLE(Name), Name() -> table(Name)). ?TABLE(diameter_config). ?TABLE(diameter_peer). ?TABLE(diameter_reg). ?TABLE(diameter_request). ?TABLE(diameter_sequence). ?TABLE(diameter_service). ?TABLE(diameter_stats). %% ---------------------------------------------------------- %% # tables/0 %% %% Pretty-print diameter tables from all nodes. Returns the number of %% records printed. %% ---------------------------------------------------------- tables() -> ?I:format(field(?LOCAL), fun split/3, fun collect/1). field(Tables) -> lists:map(fun(T) -> {T, fields(T)} end, lists:sort(Tables)). split(_, Fs, Vs) -> split(Fs, Vs). %% ---------------------------------------------------------- %% # modules/0 %% ---------------------------------------------------------- modules() -> Path = filename:join([appdir(), atom_to_list(?APP) ++ ".app"]), {ok, [{application, ?APP, Attrs}]} = file:consult(Path), {modules, Mods} = lists:keyfind(modules, 1, Attrs), Mods. appdir() -> [_|_] = code:lib_dir(?APP, ebin). %% ---------------------------------------------------------- %% # versions/0 %% ---------------------------------------------------------- versions() -> ?I:versions(modules()). %% ---------------------------------------------------------- %% # version_info/0 %% ---------------------------------------------------------- version_info() -> ?I:version_info(modules()). %% ---------------------------------------------------------- %% # compiled/0 %% ---------------------------------------------------------- compiled() -> ?I:compiled(modules()). %% ---------------------------------------------------------- %% # procs/0 %% ---------------------------------------------------------- procs() -> ?I:procs(?APP). %% ---------------------------------------------------------- %% # latest/0 %% ---------------------------------------------------------- latest() -> ?I:latest(modules()). %% ---------------------------------------------------------- %% # nl/0 %% ---------------------------------------------------------- nl() -> lists:foreach(fun(M) -> abcast = c:nl(M) end, modules()). %% ---------------------------------------------------------- %% # pp/1 %% %% Description: Pretty-print a message binary. %% ---------------------------------------------------------- %% Network byte order = big endian. pp(<>) -> ?I:sep(), ppp(["Version", "Message length", "[Actual length]", "R(equest)", "P(roxiable)", "E(rror)", "T(Potential retrans)", "Reserved bits", "Command code", "Application id", "Hop by hop id", "End to end id"], [Version, MsgLength, size(AVPs) + 20, Rbit, Pbit, Ebit, Tbit, Reserved, CmdCode, ApplId, HbHid, E2Eid]), N = avp_loop({AVPs, MsgLength - 20}, 0), ?I:sep(), N; pp(<<_Version:8, MsgLength:24, _/binary>> = Bin) -> {bad_message_length, MsgLength, size(Bin)}; pp(Bin) when is_binary(Bin) -> {truncated_binary, size(Bin)}; pp(_) -> not_binary. %% avp_loop/2 avp_loop({Bin, Size}, N) -> avp_loop(avp(Bin, Size), N+1); avp_loop(ok, N) -> N; avp_loop([_E, _Rest] = L, N) -> io:format("! ~s: ~p~n", L), N; avp_loop([E, Rest, Fmt | Values], N) when is_binary(Rest) -> io:format("! ~s (" ++ Fmt ++ "): ~p~n", [E|Values] ++ [Rest]), N. %% avp/2 avp(<<>>, 0) -> ok; avp(<>, Size) -> avp(Code, Flags, Length, Rest, Size); avp(Bin, _) -> ["truncated AVP header", Bin]. %% avp/5 avp(Code, Flags, Length, Rest, Size) -> <> = Flags, b(), ppp(["AVP Code", "V(endor)", "M(andatory)", "P(Security)", "R(eserved)", "Length"], [Code, V, M, P, Res, Length]), avp(V, Rest, Length - 8, Size - 8). %% avp/4 avp(1, <>, Length, Size) -> ppp({"Vendor-ID", V}), data(Data, Length - 4, Size - 4); avp(1, Bin, _, _) -> ["truncated Vendor-ID", Bin]; avp(0, Data, Length, Size) -> data(Data, Length, Size). data(Bin, Length, Size) when size(Bin) >= Length -> <> = Bin, ppp({"Data", AVP}), unpad(Rest, Size - Length, Length rem 4); data(Bin, _, _) -> ["truncated AVP data", Bin]. %% Remove padding bytes up to the next word boundary. unpad(Bin, Size, 0) -> {Bin, Size}; unpad(Bin, Size, N) -> un(Bin, Size, 4 - N). un(Bin, Size, N) when size(Bin) >= N -> ppp({"Padding bytes", N}), <> = Bin, Bits = N*8, case Pad of <<0:Bits>> -> {Rest, Size - N}; _ -> ["non-zero padding", Bin, "~p", N] end; un(Bin, _, _) -> ["truncated padding", Bin]. b() -> io:format("#~n"). ppp(Fields, Values) -> lists:foreach(fun ppp/1, lists:zip(Fields, Values)). ppp({Field, Value}) -> io:format(": ~-22s : ~p~n", [Field, Value]). %% ---------------------------------------------------------- %% # subscriptions/0 %% %% Returns a list of {SvcName, Pid}. %% ---------------------------------------------------------- subscriptions() -> diameter_service:subscriptions(). %% ---------------------------------------------------------- %% # children/0 %% ---------------------------------------------------------- children() -> diameter_sup:tree(). %% ---------------------------------------------------------- %% tracer/[12] tracer(Port) when is_integer(Port) -> dbg:tracer(port, dbg:trace_port(ip, Port)); tracer(Path) when is_list(Path) -> dbg:tracer(port, dbg:trace_port(file, Path)). tracer() -> dbg:tracer(process, {fun p/2, ok}). p(T,_) -> io:format("+ ~p~n", [T]). %% p/[01] p() -> p([c,timestamp]). p(T) -> dbg:p(all,T). %% stop/0 stop() -> dbg:ctp(), dbg:stop_clear(). %% tpl/1 %% tp/1 tpl(T) -> dbg(tpl, T). tp(T) -> dbg(tp, T). %% dbg/2 dbg(F, L) when is_list(L) -> [dbg(F, X) || X <- L]; dbg(F, M) when is_atom(M) -> apply(dbg, F, [M, x]); dbg(F, T) when is_tuple(T) -> apply(dbg, F, tuple_to_list(T)). %% =========================================================================== %% =========================================================================== %% collect/1 collect(diameter_peer) -> lists:flatmap(fun peers/1, diameter:services()); collect(diameter_reg) -> diameter_reg:terms(); collect(Name) -> c(ets:info(Name), Name). c(undefined, _) -> []; c(_, Name) -> ets:tab2list(Name). %% peers/1 peers(Name) -> peers(Name, diameter:service_info(Name, transport)). peers(_, undefined) -> []; peers(Name, Ts) -> lists:flatmap(fun(T) -> mk_peers(Name, T) end, Ts). mk_peers(Name, [_, {type, connect} | _] = Ts) -> [[Name | mk_peer(Ts)]]; mk_peers(Name, [R, {type, listen}, O, {accept = A, As} | _]) -> [[Name | mk_peer([R, {type, A}, O | Ts])] || Ts <- As]. %% This is a bit lame: service_info works to build this list and out %% of something like what we want here and then we take it apart. mk_peer(Vs) -> [Type, Ref, State, Opts, WPid, TPid, SApps, Caps] = get_values(Vs, [type,ref,state,options,watchdog,peer,apps,caps]), [Ref, State, [{type, Type} | Opts], s(WPid), s(TPid), SApps, Caps]. get_values(Vs, Ks) -> [proplists:get_value(K, Vs) || K <- Ks]. s(undefined = T) -> T; s({Pid, _Started, _State}) -> state(Pid); s({Pid, _Started}) -> state(Pid). %% Collect states from watchdog/transport pids. state(Pid) -> MRef = erlang:monitor(process, Pid), Pid ! {state, self()}, receive {'DOWN', MRef, process, _, _} -> Pid; {Pid, _} = T -> erlang:demonitor(MRef, [flush]), T end. %% fields/1 -define(FIELDS(Table), fields(Table) -> record_info(fields, Table)). fields(diameter_config) -> []; fields(T) when T == diameter_request; T == diameter_sequence -> fun kv/1; fields(diameter_stats) -> fun({Ctr, N}) when not is_pid(Ctr) -> {[counter, value], [Ctr, N]}; (_) -> [] end; fields(diameter_service) -> [started, name, record_info(fields, diameter_service), watchdogT, peerT, shared_peers, local_peers, monitor, options]; ?FIELDS(diameter_event); ?FIELDS(diameter_uri); ?FIELDS(diameter_avp); ?FIELDS(diameter_header); ?FIELDS(diameter_packet); ?FIELDS(diameter_app); ?FIELDS(diameter_caps); fields(diameter_peer) -> [service, ref, state, options, watchdog, peer, applications, capabilities]; fields(diameter_reg) -> [property, pids]; fields(_) -> undefined. kv({_,_}) -> [key, value]; kv(_) -> [].