1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 2000-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%%---------------------------------------------------------------------- 23%% Purpose : Megaco/H.248 customization of the Event Tracer tool 24%%---------------------------------------------------------------------- 25%% 26 27-module(megaco_filter). 28 29-export([start/0, start/1, filter/1, raw_filter/1, 30 pretty_error/1, string_to_term/1]). 31 32 33-include_lib("megaco/include/megaco.hrl"). 34-include_lib("megaco/include/megaco_message_v1.hrl"). 35-include_lib("megaco/src/app/megaco_internal.hrl"). 36-include_lib("et/include/et.hrl"). 37 38 39%%---------------------------------------------------------------------- 40%% BUGBUG: There are some opportunities for improvements: 41%% 42%% * This version of the module does only handle version 1 of the messages. 43%% 44%% * The record definition of megaco_transaction_reply is copied from 45%% megaco_message_internal.hrl as that header file contains some 46%% records that already are defined in megaco_message_{v1,v2,v3}.hrl. 47%% * The records megaco_udp and megaco_tcp are copied from the files 48%% megaco_udp.hrl and megaco_tcp.hrl respectively, as we cannot include 49%% both header files. 50%% They both defines the macros HEAP_SIZE and GC_MSG_LIMIT. 51 52%%-include("megaco_message_internal.hrl"). 53-record('megaco_transaction_reply', 54 { 55 transactionId, 56 immAckRequired = asn1_NOVALUE, 57 transactionResult, 58 segmentNumber = asn1_NOVALUE, 59 segmentationComplete = asn1_NOVALUE 60 }). 61 62%% -include_lib("megaco/src/udp/megaco_udp.hrl"). 63-record(megaco_udp, 64 {port, 65 options = [], 66 socket, 67 receive_handle, 68 module = megaco, 69 serialize = false % false: Spawn a new process for each message 70 }). 71 72%% -include_lib("megaco/src/tcp/megaco_tcp.hrl"). 73-record(megaco_tcp, 74 {host, 75 port, 76 options = [], 77 socket, 78 proxy_pid, 79 receive_handle, 80 module = megaco, 81 serialize = false % false: Spawn a new process for each message 82 }). 83 84 85%%---------------------------------------------------------------------- 86 87start() -> 88 start([]). 89 90start(ExtraOptions) -> 91 Options = 92 [{event_order, event_ts}, 93 {scale, 3}, 94 {max_actors, infinity}, 95 {trace_pattern, {megaco, max}}, 96 {trace_global, true}, 97 {dict_insert, {filter, ?MODULE}, fun filter/1}, 98 {active_filter, ?MODULE}, 99 {title, "Megaco tracer - Erlang/OTP"} | ExtraOptions], 100 et_viewer:start(Options). 101 102filter(E) -> 103 case catch raw_filter(E) of 104 {'EXIT', Reason} = Error-> 105 io:format("~p: ~p\n", [?MODULE, Error]), 106 exit(Reason); 107 E2 -> 108 E2 109 end. 110 111raw_filter(E) when is_record(E, event) -> 112 From = filter_actor(E#event.from), 113 To = filter_actor(E#event.to), 114 E2 = E#event{from = From, to = To}, 115 E3 = filter_contents(E#event.contents, E2), 116 {true, E3}. 117 118filter_actors(From, To, E) 119 when (E#event.from =:= ?APPLICATION) andalso (E#event.to =:= ?APPLICATION) -> 120 Label = E#event.label, 121 case lists:prefix("callback:", Label) of 122 true -> 123 E#event{from = filter_actor(From), 124 to = filter_user_actor(From)}; 125 false -> 126 case lists:prefix("return:", Label) of 127 true -> 128 E#event{from = filter_user_actor(From), 129 to = filter_actor(From)}; 130 false -> 131 case lists:prefix("receive bytes", Label) of 132 true -> 133 E#event{from = filter_actor(To), 134 to = filter_actor(From)}; 135 false -> 136 E#event{from = filter_actor(From), 137 to = filter_actor(To)} 138 end 139 end 140 end; 141filter_actors(_From, _To, E) -> 142 E. 143 144filter_actor(Actor) -> 145 String = do_filter_actor(Actor), 146 if 147 length(String) > 21 -> 148 string:substr(String, 1, 21) ++ [$*]; 149 true -> 150 String 151 end. 152 153filter_user_actor(Actor) -> 154 String = do_filter_actor(Actor) ++ "@user", 155 if 156 length(String) > 21 -> 157 string:substr(String, 1, 21) ++ [$*]; 158 true -> 159 String 160 end. 161 162do_filter_actor(CH) when is_record(CH, megaco_conn_handle) -> 163 Mid = CH#megaco_conn_handle.local_mid, 164 do_filter_actor(Mid); 165do_filter_actor(RH) when is_record(RH, megaco_receive_handle) -> 166 Mid = RH#megaco_receive_handle.local_mid, 167 do_filter_actor(Mid); 168do_filter_actor(Actor) -> 169 case Actor of 170 {ip4Address, {'IP4Address', [A1,A2,A3,A4], asn1_NOVALUE}} -> 171 integer_to_list(A1) ++ [$.] ++ 172 integer_to_list(A2) ++ [$.] ++ 173 integer_to_list(A3) ++ [$.] ++ 174 integer_to_list(A4); 175 {ip4Address, {'IP4Address', [A1,A2,A3,A4], Port}} -> 176 integer_to_list(A1) ++ [$.] ++ 177 integer_to_list(A2) ++ [$.] ++ 178 integer_to_list(A3) ++ [$.] ++ 179 integer_to_list(A4) ++ [$:] ++ 180 integer_to_list(Port); 181 {domainName, {'DomainName', Name, asn1_NOVALUE}} -> 182 Name; 183 {domainName, {'DomainName', Name, Port}} -> 184 Name ++ [$:] ++ integer_to_list(Port); 185 {deviceName, Name} -> 186 Name; 187 unknown_remote_mid -> 188 "preliminary_mid"; 189 preliminary_mid -> 190 "preliminary_mid"; 191 megaco -> 192 megaco; 193 _Other -> 194 "UNKNOWN" 195 end. 196 197 198filter_contents(Contents, E) -> 199 do_filter_contents(Contents, E, missing_conn_data, []). 200 201do_filter_contents([H | T], E, ConnData, Contents) -> 202 case H of 203 Udp when is_record(Udp, megaco_udp) -> 204 RH = Udp#megaco_udp.receive_handle, 205 Actor = filter_actor(RH), 206 E2 = E#event{from = Actor, to = Actor}, 207 Pretty = 208 ["Port: ", integer_to_list(Udp#megaco_udp.port), "\n", 209 "Encoder: ", atom_to_list(RH#megaco_receive_handle.encoding_mod)], 210 do_filter_contents(T, E2, ConnData, [[Pretty, "\n"], Contents]); 211 Tcp when is_record(Tcp, megaco_tcp) -> 212 RH = Tcp#megaco_tcp.receive_handle, 213 Actor = filter_actor(RH), 214 E2 = E#event{from = Actor, to = Actor}, 215 Pretty = 216 ["Port: ", integer_to_list(Tcp#megaco_tcp.port), "\n", 217 "Encoder: ", atom_to_list(RH#megaco_receive_handle.encoding_mod)], 218 do_filter_contents(T, E2, ConnData, [[Pretty, "\n"], Contents]); 219 CD when is_record(CD, conn_data) -> 220 CH = CD#conn_data.conn_handle, 221 From = CH#megaco_conn_handle.local_mid, 222 To = CH#megaco_conn_handle.remote_mid, 223 E2 = filter_actors(From, To, E), 224 Serial = CD#conn_data.serial, 225 E3 = append_serial(Serial, E2), 226 do_filter_contents(T, E3, CD, Contents); 227 CH when is_record(CH, megaco_conn_handle) -> 228 From = CH#megaco_conn_handle.local_mid, 229 To = CH#megaco_conn_handle.remote_mid, 230 E2 = filter_actors(From, To, E), 231 do_filter_contents(T, E2, ConnData, Contents); 232 RH when is_record(RH, megaco_receive_handle) -> 233 Actor = RH#megaco_receive_handle.local_mid, 234 E2 = filter_actors(Actor, Actor, E), 235 do_filter_contents(T, E2, ConnData, Contents); 236 {error, Reason} -> 237 Pretty = pretty_error({error, Reason}), 238 E2 = prepend_error(E), 239 do_filter_contents(T, E2, ConnData, [[Pretty, "\n"], Contents]); 240 {'EXIT', Reason} -> 241 Pretty = pretty_error({'EXIT', Reason}), 242 E2 = prepend_error(E), 243 do_filter_contents(T, E2, ConnData, [[Pretty, "\n"], Contents]); 244 ED when is_record(ED, 'ErrorDescriptor') -> 245 Pretty = pretty_error(ED), 246 E2 = prepend_error(E), 247 do_filter_contents(T, E2, ConnData, [[Pretty, "\n"], Contents]); 248 Trans when is_record(Trans, 'TransactionRequest') -> 249 Pretty = pretty(ConnData, {trans, {transactionRequest, Trans}}), 250 do_filter_contents([], E, ConnData, [[Pretty, "\n"], Contents]); 251 {transactionRequest, Trans} when is_record(Trans, 'TransactionRequest') -> 252 Pretty = pretty(ConnData, {trans, {transactionRequest, Trans}}), 253 do_filter_contents([], E, ConnData, [[Pretty, "\n"], Contents]); 254 Trans when is_record(Trans, 'TransactionReply') -> 255 Pretty = pretty(ConnData, {trans, {transactionReply, Trans}}), 256 do_filter_contents([], E, ConnData, [[Pretty, "\n"], Contents]); 257 Trans when is_record(Trans, megaco_transaction_reply) -> 258 %% BUGBUG: Version 1 special 259 TransV1 = 260 #'TransactionReply'{transactionId = Trans#megaco_transaction_reply.transactionId, 261 immAckRequired = Trans#megaco_transaction_reply.immAckRequired, 262 transactionResult = Trans#megaco_transaction_reply.transactionResult}, 263 Pretty = pretty(ConnData, {trans, {transactionReply, TransV1}}), 264 do_filter_contents([], E, ConnData, [[Pretty, "\n"], Contents]); 265 Trans when is_record(Trans, 'TransactionPending') -> 266 Pretty = pretty(ConnData, {trans, {transactionPending, Trans}}), 267 do_filter_contents([], E, ConnData, [[Pretty, "\n"], Contents]); 268 Trans when is_record(Trans, 'TransactionAck') -> 269 Pretty = pretty(ConnData, {trans, {transactionResponseAck, [Trans]}}), 270 case Trans#'TransactionAck'.lastAck of 271 asn1_NOVALUE -> 272 do_filter_contents([], E, ConnData, [[Pretty, "\n"], Contents]); 273 Last -> 274 Label = term_to_string(E#event.label), 275 E2 = E#event{label = Label ++ ".." ++ integer_to_list(Last)}, 276 do_filter_contents([], E2, ConnData, [[Pretty, "\n"], Contents]) 277 end; 278 {context_id, _ContextId} -> 279 Pretty = pretty(ConnData, H), 280 do_filter_contents(T, E, ConnData, [[Pretty, "\n"], Contents]); 281 {command_request, CmdReq} -> 282 Pretty = pretty(ConnData, CmdReq), 283 do_filter_contents(T, E, ConnData, [[Pretty, "\n"], Contents]); 284 {user_reply, {ok, ARS}} -> 285 Pretty = [[pretty(ConnData, AR), "\n"] || AR <- ARS], 286 do_filter_contents(T, E, ConnData, [["USER REPLY OK: \n", Pretty, "\n"], Contents]); 287 {user_reply, Error} -> 288 Pretty = pretty_error(Error), 289 do_filter_contents(T, E, ConnData, [["USER REPLY ERROR: \n", Pretty, "\n"], Contents]); 290 {actionReplies, ARS} -> 291 Pretty = [[pretty(ConnData, AR), "\n"] || AR <- ARS], 292 do_filter_contents(T, E, ConnData, [["ACTION REPLIES: \n", Pretty, "\n"], Contents]); 293 MegaMsg when is_record(MegaMsg, 'MegacoMessage') -> 294 Pretty = pretty(ConnData, MegaMsg), 295 do_filter_contents(T, E, ConnData, [Pretty, "\n", Contents]); 296 {message, MegaMsg} when is_record(MegaMsg, 'MegacoMessage') -> 297 Pretty = pretty(ConnData, MegaMsg), 298 do_filter_contents(T, E, ConnData, [Pretty, "\n", Contents]); 299 {bytes, Bin} when is_binary(Bin) -> 300 E2 = 301 case E#event.label of 302 [$s, $e, $n, $d, $ , $b, $y, $t, $e, $s | Tail] -> 303 L = lists:concat(["send ", size(Bin), " bytes", Tail]), 304 E#event{label = L}; 305 [$r, $e, $c, $e, $i, $v, $e, $ , $b, $y, $t, $e, $s | Tail] -> 306 L = lists:concat(["receive ", size(Bin), " bytes", Tail]), 307 E#event{label = L}; 308 _ -> 309 E 310 end, 311 CharList = erlang:binary_to_list(Bin), 312 do_filter_contents(T, E2, ConnData, [[CharList , "\n"], Contents]); 313 List when is_list(List) -> 314 %% BUGBUG: Workaround as megaco_messenger puts nested lists in its traces 315 do_filter_contents(List ++ T, E, ConnData, Contents); 316 Int when is_integer(Int) -> 317 %% BUGBUG: Workaround as megaco_messenger puts nested lists in its traces 318 do_filter_contents(T, E, ConnData, Contents); 319 {line, _Mod, _Line} -> 320 do_filter_contents(T, E, ConnData, Contents); 321 {orig_conn_handle, _CH} -> 322 do_filter_contents(T, E, ConnData, Contents); 323 {pid, Pid} when is_pid(Pid) -> 324 do_filter_contents(T, E, ConnData, Contents); 325 pending -> 326 do_filter_contents(T, E, ConnData, Contents); 327 reply -> 328 do_filter_contents(T, E, ConnData, Contents); 329 {test_lib, _Mod, _Fun} -> 330 do_filter_contents(T, E, ConnData, Contents); 331 {trans_id, _TransId} -> 332 do_filter_contents(T, E, ConnData, Contents); 333 {send_func, _FunName} -> 334 do_filter_contents(T, E, ConnData, Contents); 335 Pid when is_pid(Pid) -> 336 do_filter_contents(T, E, ConnData, Contents); 337 Other -> 338 Pretty = pretty(ConnData, Other), 339 do_filter_contents(T, E, ConnData, [[Pretty, "\n"], Contents]) 340 end; 341do_filter_contents([], E, _ConnData, Contents) -> 342 E#event{contents = lists:flatten(lists:reverse(Contents))}. 343 344append_serial(Serial, E) when is_integer(Serial) -> 345 Label = term_to_string(E#event.label), 346 E#event{label = Label ++ " #" ++ integer_to_list(Serial)}; 347append_serial(_Serial, E) -> 348 E. 349 350prepend_error(E) -> 351 Label = term_to_string(E#event.label), 352 E#event{label = "<ERROR> " ++ Label}. 353 354pretty(_ConnData, {context_id, ContextId}) -> 355 if 356 ContextId =:= ?megaco_null_context_id -> 357 ["CONTEXT ID: -\n"]; 358 ContextId =:= ?megaco_choose_context_id -> 359 ["CONTEXT ID: $\n"]; 360 ContextId =:= ?megaco_all_context_id -> 361 ["CONTEXT ID: *\n"]; 362 is_integer(ContextId) -> 363 ["CONTEXT ID: ",integer_to_list(ContextId), "\n"] 364 end; 365pretty(_ConnData, MegaMsg) when is_record(MegaMsg, 'MegacoMessage') -> 366 {ok, Bin} = megaco_pretty_text_encoder:encode_message([], MegaMsg), 367 term_to_string(Bin); 368pretty(_ConnData, CmdReq) when is_record(CmdReq, 'CommandRequest') -> 369 {ok, Bin} = megaco_pretty_text_encoder:encode_command_request(CmdReq), 370 term_to_string(Bin); 371pretty(_ConnData, {complete_success, ContextId, RepList}) -> 372 ActRep = #'ActionReply'{contextId = ContextId, 373 commandReply = RepList}, 374 {ok, Bin} = megaco_pretty_text_encoder:encode_action_reply(ActRep), 375 term_to_string(Bin); 376pretty(_ConnData, AR) when is_record(AR, 'ActionReply') -> 377 {ok, Bin} = megaco_pretty_text_encoder:encode_action_reply(AR), 378 term_to_string(Bin); 379pretty(_ConnData, {partial_failure, ContextId, RepList}) -> 380 ActRep = #'ActionReply'{contextId = ContextId, 381 commandReply = RepList}, 382 {ok, Bin} = megaco_pretty_text_encoder:encode_action_reply(ActRep), 383 term_to_string(Bin); 384pretty(_ConnData, {trans, Trans}) -> 385 {ok, Bin} = megaco_pretty_text_encoder:encode_transaction(Trans), 386 term_to_string(Bin); 387pretty(__ConnData, Other) -> 388 term_to_string(Other). 389 390pretty_error({error, Reason}) -> 391 ["ERROR: ", pretty_error(Reason)]; 392pretty_error({'EXIT', Reason}) -> 393 ["EXIT: ", pretty_error(Reason)]; 394pretty_error({'ErrorDescriptor', Code, Reason}) -> 395 ["CODE: ", integer_to_list(Code), " TEXT: ", pretty_error(Reason)]; 396pretty_error(Ugly) -> 397 case string_to_term(Ugly) of 398 {ok, Pretty} -> ["\n", Pretty]; 399 _ -> ["\n", term_to_string(Ugly)] 400 end. 401 402string_to_term(Chars) -> 403 do_string_to_term([], Chars, 1). 404 405do_string_to_term(Cont, Chars, Line) -> 406 case catch erl_scan:tokens(Cont, Chars, Line) of 407 {done, {ok, Tokens, _EndLine}, _Rest} -> 408 case erl_parse:parse_term(Tokens) of 409 {ok, Term} -> 410 {ok, Term}; 411 {error, Reason} -> 412 {error, Reason} 413 end; 414 {more, Cont2} -> 415 do_string_to_term(Cont2, ". ", Line); 416 Other -> 417 {error, Other} 418 end. 419 420term_to_string(Bin) when is_binary(Bin) -> 421 binary_to_list(Bin); 422term_to_string(Term) -> 423 case catch io_lib:format("~s", [Term]) of 424 {'EXIT', _} -> lists:flatten(io_lib:format("~p", [Term])); 425 GoodString -> lists:flatten(GoodString) 426 end. 427