%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2000-2011. 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% %% %% %%---------------------------------------------------------------------- %% Purpose : Megaco/H.248 customization of the Event Tracer tool %%---------------------------------------------------------------------- %% -module(megaco_filter). -export([start/0, start/1, filter/1, raw_filter/1, pretty_error/1, string_to_term/1]). -include_lib("megaco/include/megaco.hrl"). -include_lib("megaco/include/megaco_message_v1.hrl"). -include_lib("megaco/src/app/megaco_internal.hrl"). -include_lib("et/include/et.hrl"). %%---------------------------------------------------------------------- %% BUGBUG: There are some opportunities for improvements: %% %% * This version of the module does only handle version 1 of the messages. %% %% * The record definition of megaco_transaction_reply is copied from %% megaco_message_internal.hrl as that header file contains some %% records that already are defined in megaco_message_{v1,v2,v3}.hrl. %% * The records megaco_udp and megaco_tcp are copied from the files %% megaco_udp.hrl and megaco_tcp.hrl respectively, as we cannot include %% both header files. %% They both defines the macros HEAP_SIZE and GC_MSG_LIMIT. %%-include("megaco_message_internal.hrl"). -record('megaco_transaction_reply', { transactionId, immAckRequired = asn1_NOVALUE, transactionResult, segmentNumber = asn1_NOVALUE, segmentationComplete = asn1_NOVALUE }). %% -include_lib("megaco/src/udp/megaco_udp.hrl"). -record(megaco_udp, {port, options = [], socket, receive_handle, module = megaco, serialize = false % false: Spawn a new process for each message }). %% -include_lib("megaco/src/tcp/megaco_tcp.hrl"). -record(megaco_tcp, {host, port, options = [], socket, proxy_pid, receive_handle, module = megaco, serialize = false % false: Spawn a new process for each message }). %%---------------------------------------------------------------------- start() -> start([]). start(ExtraOptions) -> Options = [{event_order, event_ts}, {scale, 3}, {max_actors, infinity}, {trace_pattern, {megaco, max}}, {trace_global, true}, {dict_insert, {filter, ?MODULE}, fun filter/1}, {active_filter, ?MODULE}, {title, "Megaco tracer - Erlang/OTP"} | ExtraOptions], et_viewer:start(Options). filter(E) -> case catch raw_filter(E) of {'EXIT', Reason} = Error-> io:format("~p: ~p\n", [?MODULE, Error]), exit(Reason); E2 -> E2 end. raw_filter(E) when is_record(E, event) -> From = filter_actor(E#event.from), To = filter_actor(E#event.to), E2 = E#event{from = From, to = To}, E3 = filter_contents(E#event.contents, E2), {true, E3}. filter_actors(From, To, E) when (E#event.from =:= ?APPLICATION) andalso (E#event.to =:= ?APPLICATION) -> Label = E#event.label, case lists:prefix("callback:", Label) of true -> E#event{from = filter_actor(From), to = filter_user_actor(From)}; false -> case lists:prefix("return:", Label) of true -> E#event{from = filter_user_actor(From), to = filter_actor(From)}; false -> case lists:prefix("receive bytes", Label) of true -> E#event{from = filter_actor(To), to = filter_actor(From)}; false -> E#event{from = filter_actor(From), to = filter_actor(To)} end end end; filter_actors(_From, _To, E) -> E. filter_actor(Actor) -> String = do_filter_actor(Actor), if length(String) > 21 -> string:substr(String, 1, 21) ++ [$*]; true -> String end. filter_user_actor(Actor) -> String = do_filter_actor(Actor) ++ "@user", if length(String) > 21 -> string:substr(String, 1, 21) ++ [$*]; true -> String end. do_filter_actor(CH) when is_record(CH, megaco_conn_handle) -> Mid = CH#megaco_conn_handle.local_mid, do_filter_actor(Mid); do_filter_actor(RH) when is_record(RH, megaco_receive_handle) -> Mid = RH#megaco_receive_handle.local_mid, do_filter_actor(Mid); do_filter_actor(Actor) -> case Actor of {ip4Address, {'IP4Address', [A1,A2,A3,A4], asn1_NOVALUE}} -> integer_to_list(A1) ++ [$.] ++ integer_to_list(A2) ++ [$.] ++ integer_to_list(A3) ++ [$.] ++ integer_to_list(A4); {ip4Address, {'IP4Address', [A1,A2,A3,A4], Port}} -> integer_to_list(A1) ++ [$.] ++ integer_to_list(A2) ++ [$.] ++ integer_to_list(A3) ++ [$.] ++ integer_to_list(A4) ++ [$:] ++ integer_to_list(Port); {domainName, {'DomainName', Name, asn1_NOVALUE}} -> Name; {domainName, {'DomainName', Name, Port}} -> Name ++ [$:] ++ integer_to_list(Port); {deviceName, Name} -> Name; unknown_remote_mid -> "preliminary_mid"; preliminary_mid -> "preliminary_mid"; megaco -> megaco; _Other -> "UNKNOWN" end. filter_contents(Contents, E) -> do_filter_contents(Contents, E, missing_conn_data, []). do_filter_contents([H | T], E, ConnData, Contents) -> case H of Udp when is_record(Udp, megaco_udp) -> RH = Udp#megaco_udp.receive_handle, Actor = filter_actor(RH), E2 = E#event{from = Actor, to = Actor}, Pretty = ["Port: ", integer_to_list(Udp#megaco_udp.port), "\n", "Encoder: ", atom_to_list(RH#megaco_receive_handle.encoding_mod)], do_filter_contents(T, E2, ConnData, [[Pretty, "\n"], Contents]); Tcp when is_record(Tcp, megaco_tcp) -> RH = Tcp#megaco_tcp.receive_handle, Actor = filter_actor(RH), E2 = E#event{from = Actor, to = Actor}, Pretty = ["Port: ", integer_to_list(Tcp#megaco_tcp.port), "\n", "Encoder: ", atom_to_list(RH#megaco_receive_handle.encoding_mod)], do_filter_contents(T, E2, ConnData, [[Pretty, "\n"], Contents]); CD when is_record(CD, conn_data) -> CH = CD#conn_data.conn_handle, From = CH#megaco_conn_handle.local_mid, To = CH#megaco_conn_handle.remote_mid, E2 = filter_actors(From, To, E), Serial = CD#conn_data.serial, E3 = append_serial(Serial, E2), do_filter_contents(T, E3, CD, Contents); CH when is_record(CH, megaco_conn_handle) -> From = CH#megaco_conn_handle.local_mid, To = CH#megaco_conn_handle.remote_mid, E2 = filter_actors(From, To, E), do_filter_contents(T, E2, ConnData, Contents); RH when is_record(RH, megaco_receive_handle) -> Actor = RH#megaco_receive_handle.local_mid, E2 = filter_actors(Actor, Actor, E), do_filter_contents(T, E2, ConnData, Contents); {error, Reason} -> Pretty = pretty_error({error, Reason}), E2 = prepend_error(E), do_filter_contents(T, E2, ConnData, [[Pretty, "\n"], Contents]); {'EXIT', Reason} -> Pretty = pretty_error({'EXIT', Reason}), E2 = prepend_error(E), do_filter_contents(T, E2, ConnData, [[Pretty, "\n"], Contents]); ED when is_record(ED, 'ErrorDescriptor') -> Pretty = pretty_error(ED), E2 = prepend_error(E), do_filter_contents(T, E2, ConnData, [[Pretty, "\n"], Contents]); Trans when is_record(Trans, 'TransactionRequest') -> Pretty = pretty(ConnData, {trans, {transactionRequest, Trans}}), do_filter_contents([], E, ConnData, [[Pretty, "\n"], Contents]); {transactionRequest, Trans} when is_record(Trans, 'TransactionRequest') -> Pretty = pretty(ConnData, {trans, {transactionRequest, Trans}}), do_filter_contents([], E, ConnData, [[Pretty, "\n"], Contents]); Trans when is_record(Trans, 'TransactionReply') -> Pretty = pretty(ConnData, {trans, {transactionReply, Trans}}), do_filter_contents([], E, ConnData, [[Pretty, "\n"], Contents]); Trans when is_record(Trans, megaco_transaction_reply) -> %% BUGBUG: Version 1 special TransV1 = #'TransactionReply'{transactionId = Trans#megaco_transaction_reply.transactionId, immAckRequired = Trans#megaco_transaction_reply.immAckRequired, transactionResult = Trans#megaco_transaction_reply.transactionResult}, Pretty = pretty(ConnData, {trans, {transactionReply, TransV1}}), do_filter_contents([], E, ConnData, [[Pretty, "\n"], Contents]); Trans when is_record(Trans, 'TransactionPending') -> Pretty = pretty(ConnData, {trans, {transactionPending, Trans}}), do_filter_contents([], E, ConnData, [[Pretty, "\n"], Contents]); Trans when is_record(Trans, 'TransactionAck') -> Pretty = pretty(ConnData, {trans, {transactionResponseAck, [Trans]}}), case Trans#'TransactionAck'.lastAck of asn1_NOVALUE -> do_filter_contents([], E, ConnData, [[Pretty, "\n"], Contents]); Last -> Label = term_to_string(E#event.label), E2 = E#event{label = Label ++ ".." ++ integer_to_list(Last)}, do_filter_contents([], E2, ConnData, [[Pretty, "\n"], Contents]) end; {context_id, _ContextId} -> Pretty = pretty(ConnData, H), do_filter_contents(T, E, ConnData, [[Pretty, "\n"], Contents]); {command_request, CmdReq} -> Pretty = pretty(ConnData, CmdReq), do_filter_contents(T, E, ConnData, [[Pretty, "\n"], Contents]); {user_reply, {ok, ARS}} -> Pretty = [[pretty(ConnData, AR), "\n"] || AR <- ARS], do_filter_contents(T, E, ConnData, [["USER REPLY OK: \n", Pretty, "\n"], Contents]); {user_reply, Error} -> Pretty = pretty_error(Error), do_filter_contents(T, E, ConnData, [["USER REPLY ERROR: \n", Pretty, "\n"], Contents]); {actionReplies, ARS} -> Pretty = [[pretty(ConnData, AR), "\n"] || AR <- ARS], do_filter_contents(T, E, ConnData, [["ACTION REPLIES: \n", Pretty, "\n"], Contents]); MegaMsg when is_record(MegaMsg, 'MegacoMessage') -> Pretty = pretty(ConnData, MegaMsg), do_filter_contents(T, E, ConnData, [Pretty, "\n", Contents]); {message, MegaMsg} when is_record(MegaMsg, 'MegacoMessage') -> Pretty = pretty(ConnData, MegaMsg), do_filter_contents(T, E, ConnData, [Pretty, "\n", Contents]); {bytes, Bin} when is_binary(Bin) -> E2 = case E#event.label of [$s, $e, $n, $d, $ , $b, $y, $t, $e, $s | Tail] -> L = lists:concat(["send ", size(Bin), " bytes", Tail]), E#event{label = L}; [$r, $e, $c, $e, $i, $v, $e, $ , $b, $y, $t, $e, $s | Tail] -> L = lists:concat(["receive ", size(Bin), " bytes", Tail]), E#event{label = L}; _ -> E end, CharList = erlang:binary_to_list(Bin), do_filter_contents(T, E2, ConnData, [[CharList , "\n"], Contents]); List when is_list(List) -> %% BUGBUG: Workaround as megaco_messenger puts nested lists in its traces do_filter_contents(List ++ T, E, ConnData, Contents); Int when is_integer(Int) -> %% BUGBUG: Workaround as megaco_messenger puts nested lists in its traces do_filter_contents(T, E, ConnData, Contents); {line, _Mod, _Line} -> do_filter_contents(T, E, ConnData, Contents); {orig_conn_handle, _CH} -> do_filter_contents(T, E, ConnData, Contents); {pid, Pid} when is_pid(Pid) -> do_filter_contents(T, E, ConnData, Contents); pending -> do_filter_contents(T, E, ConnData, Contents); reply -> do_filter_contents(T, E, ConnData, Contents); {test_lib, _Mod, _Fun} -> do_filter_contents(T, E, ConnData, Contents); {trans_id, _TransId} -> do_filter_contents(T, E, ConnData, Contents); {send_func, _FunName} -> do_filter_contents(T, E, ConnData, Contents); Pid when is_pid(Pid) -> do_filter_contents(T, E, ConnData, Contents); Other -> Pretty = pretty(ConnData, Other), do_filter_contents(T, E, ConnData, [[Pretty, "\n"], Contents]) end; do_filter_contents([], E, _ConnData, Contents) -> E#event{contents = lists:flatten(lists:reverse(Contents))}. append_serial(Serial, E) when is_integer(Serial) -> Label = term_to_string(E#event.label), E#event{label = Label ++ " #" ++ integer_to_list(Serial)}; append_serial(_Serial, E) -> E. prepend_error(E) -> Label = term_to_string(E#event.label), E#event{label = " " ++ Label}. pretty(_ConnData, {context_id, ContextId}) -> if ContextId =:= ?megaco_null_context_id -> ["CONTEXT ID: -\n"]; ContextId =:= ?megaco_choose_context_id -> ["CONTEXT ID: $\n"]; ContextId =:= ?megaco_all_context_id -> ["CONTEXT ID: *\n"]; is_integer(ContextId) -> ["CONTEXT ID: ",integer_to_list(ContextId), "\n"] end; pretty(_ConnData, MegaMsg) when is_record(MegaMsg, 'MegacoMessage') -> {ok, Bin} = megaco_pretty_text_encoder:encode_message([], MegaMsg), term_to_string(Bin); pretty(_ConnData, CmdReq) when is_record(CmdReq, 'CommandRequest') -> {ok, Bin} = megaco_pretty_text_encoder:encode_command_request(CmdReq), term_to_string(Bin); pretty(_ConnData, {complete_success, ContextId, RepList}) -> ActRep = #'ActionReply'{contextId = ContextId, commandReply = RepList}, {ok, Bin} = megaco_pretty_text_encoder:encode_action_reply(ActRep), term_to_string(Bin); pretty(_ConnData, AR) when is_record(AR, 'ActionReply') -> {ok, Bin} = megaco_pretty_text_encoder:encode_action_reply(AR), term_to_string(Bin); pretty(_ConnData, {partial_failure, ContextId, RepList}) -> ActRep = #'ActionReply'{contextId = ContextId, commandReply = RepList}, {ok, Bin} = megaco_pretty_text_encoder:encode_action_reply(ActRep), term_to_string(Bin); pretty(_ConnData, {trans, Trans}) -> {ok, Bin} = megaco_pretty_text_encoder:encode_transaction(Trans), term_to_string(Bin); pretty(__ConnData, Other) -> term_to_string(Other). pretty_error({error, Reason}) -> ["ERROR: ", pretty_error(Reason)]; pretty_error({'EXIT', Reason}) -> ["EXIT: ", pretty_error(Reason)]; pretty_error({'ErrorDescriptor', Code, Reason}) -> ["CODE: ", integer_to_list(Code), " TEXT: ", pretty_error(Reason)]; pretty_error(Ugly) -> case string_to_term(Ugly) of {ok, Pretty} -> ["\n", Pretty]; _ -> ["\n", term_to_string(Ugly)] end. string_to_term(Chars) -> do_string_to_term([], Chars, 1). do_string_to_term(Cont, Chars, Line) -> case catch erl_scan:tokens(Cont, Chars, Line) of {done, {ok, Tokens, _EndLine}, _Rest} -> case erl_parse:parse_term(Tokens) of {ok, Term} -> {ok, Term}; {error, Reason} -> {error, Reason} end; {more, Cont2} -> do_string_to_term(Cont2, ". ", Line); Other -> {error, Other} end. term_to_string(Bin) when is_binary(Bin) -> binary_to_list(Bin); term_to_string(Term) -> case catch io_lib:format("~s", [Term]) of {'EXIT', _} -> lists:flatten(io_lib:format("~p", [Term])); GoodString -> lists:flatten(GoodString) end.