1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 2002-2017. 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-module(ttb). 21-author('siri@erix.ericsson.se'). 22-author('bartlomiej.puzon@erlang-solutions.com'). 23 24%% API 25-export([tracer/0,tracer/1,tracer/2,p/2,stop/0,stop/1,start_trace/4]). 26-export([get_et_handler/0]). 27-export([tp/2, tp/3, tp/4, ctp/0, ctp/1, ctp/2, ctp/3, tpl/2, tpl/3, tpl/4, 28 ctpl/0, ctpl/1, ctpl/2, ctpl/3, ctpg/0, ctpg/1, ctpg/2, ctpg/3, 29 tpe/2, ctpe/1]). 30-export([seq_trigger_ms/0,seq_trigger_ms/1]). 31-export([write_trace_info/2]). 32-export([write_config/2,write_config/3,run_config/1,run_config/2,list_config/1]). 33-export([list_history/0,run_history/1]). 34-export([format/1,format/2]). 35 36%% For debugging 37-export([dump_ti/1]). 38 39-include_lib("kernel/include/file.hrl"). 40-define(meta_time,5000). 41-define(fetch_time, 10000). 42-define(history_table,ttb_history_table). 43-define(seq_trace_flags,[send,'receive',print,timestamp]). 44-define(upload_dir(Logname),"ttb_upload_"++Logname). 45-define(last_config, "ttb_last_config"). 46-define(partial_dir, "ttb_partial_result"). 47-ifdef(debug). 48-define(get_status,;get_status -> erlang:display(dict:to_list(NodeInfo),loop(NodeInfo, TraceInfo)). 49-else. 50-define(get_status,). 51-endif. 52 53%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 54%%% Shortcut 55start_trace(Nodes, Patterns, {Procs, Flags}, Options) -> 56 {ok, _} = tracer(Nodes, Options), 57 [{ok, _} = apply(?MODULE, tpl, tuple_to_list(Args)) || Args <- Patterns], 58 {ok, _} = p(Procs, Flags). 59 60 61%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 62%%% Open a trace port on all given nodes and create the meta data file 63tracer() -> tracer(node()). 64tracer(shell) -> tracer(node(), shell); 65tracer(dbg) -> tracer(node(), {shell, only}); 66tracer(Nodes) -> tracer(Nodes,[]). 67tracer(Nodes,Opt) -> 68 {PI,Client,Traci} = opt(Opt), 69 %%We use initial Traci as SessionInfo for loop/2 70 Pid = start(Traci), 71 store(tracer,[Nodes,Opt]), 72 do_tracer(Nodes,PI,Client,[{ttb_control, Pid}|Traci]). 73 74do_tracer(Nodes0,PI,Client,Traci) -> 75 Nodes = nods(Nodes0), 76 Clients = clients(Nodes,Client), 77 do_tracer(Clients,PI,Traci). 78 79do_tracer(Clients,PI,Traci) -> 80 Shell = proplists:get_value(shell, Traci, false), 81 IpPortSpec = 82 case proplists:get_value(queue_size, Traci) of 83 undefined -> 0; 84 QS -> {0,QS} 85 end, 86 DefShell = fun(Trace) -> dbg:dhandler(Trace, standard_io) end, 87 {ClientSucc,Succ} = 88 lists:foldl( 89 fun({N,{local,File},TF},{CS,S}) -> 90 {TF2, FileInfo, ShellOutput} = 91 case Shell of 92 only -> {none, shell_only, DefShell}; 93 true -> {TF, {file,File}, DefShell}; 94 {only,Fun} -> {none, shell_only, Fun}; 95 Fun when is_function(Fun) -> {TF, {file,File}, Fun}; 96 _ -> {TF, {file,File}, false} 97 end, 98 Host = case N of 99 nonode@nohost -> 100 {ok, H} = inet:gethostname(), 101 H; 102 _ -> 103 [_,H] = string:lexemes(atom_to_list(N),"@"), 104 H 105 end, 106 case catch dbg:tracer(N,port,dbg:trace_port(ip,IpPortSpec)) of 107 {ok,N} -> 108 {ok,Port} = dbg:trace_port_control(N,get_listen_port), 109 {ok,T} = dbg:get_tracer(N), 110 rpc:call(N,seq_trace,set_system_tracer,[T]), 111 dbg:trace_client(ip,{Host,Port}, 112 {fun ip_to_file/2,{FileInfo, ShellOutput}}), 113 {[{N,{local,File,Port},TF2}|CS], [N|S]}; 114 Other -> 115 display_warning(N,{cannot_open_ip_trace_port, 116 Host, 117 Other}), 118 {CS, S} 119 end; 120 ({N,C,_}=Client,{CS,S}) -> 121 case catch dbg:tracer(N,port,dbg:trace_port(file,C)) of 122 {ok,N} -> 123 {ok,T} = dbg:get_tracer(N), 124 rpc:call(N,seq_trace,set_system_tracer,[T]), 125 {[Client|CS], [N|S]}; 126 Other -> 127 display_warning(N,Other), 128 {CS,S} 129 end 130 end, 131 {[],[]}, 132 Clients), 133 case Succ of 134 [] -> 135 {ok,Succ}; 136 _list -> 137 write_info(ClientSucc,PI,Traci), 138 {ok,Succ} 139 end. 140 141opt(Opt) when is_list(Opt) -> 142 opt(Opt,{true,?MODULE,[]}); 143opt(Opt) -> 144 opt([Opt]). 145 146opt([{process_info,PI}|O],{_,Client,Traci}) -> 147 opt(O,{PI,Client,Traci}); 148opt([{file,Client}|O],{PI,_,Traci}) -> 149 opt(O,{PI,Client,[{logfile,get_logname(Client)}|Traci]}); 150opt([{handler,Handler}|O],{PI,Client,Traci}) -> 151 opt(O,{PI,Client,[{handler,Handler}|Traci]}); 152opt([{timer, {MSec, StopOpts}}|O],{PI,Client,Traci}) -> 153 opt(O,{PI,Client,[{timer,{MSec, StopOpts}}|Traci]}); 154opt([{timer, MSec}|O],{PI,Client,Traci}) -> 155 opt(O,{PI,Client,[{timer,{MSec, []}}|Traci]}); 156opt([{overload_check, {MSec,M,F}}|O],{PI,Client,Traci}) -> 157 opt(O,{PI,Client,[{overload_check,{MSec,M,F}}|Traci]}); 158opt([shell|O],{PI,Client,Traci}) -> 159 opt(O,{PI,Client,[{shell, true}|Traci]}); 160opt([{shell,Type}|O],{PI,Client,Traci}) -> 161 opt(O,{PI,Client,[{shell, Type}|Traci]}); 162opt([resume|O],{PI,Client,Traci}) -> 163 opt(O,{PI,Client,[{resume, {true, ?fetch_time}}|Traci]}); 164opt([{resume,MSec}|O],{PI,Client,Traci}) -> 165 opt(O,{PI,Client,[{resume, {true, MSec}}|Traci]}); 166opt([{flush,MSec}|O],{PI,Client,Traci}) -> 167 opt(O,{PI,Client,[{flush, MSec}|Traci]}); 168opt([{queue_size,QueueSize}|O],{PI,Client,Traci}) -> 169 opt(O,{PI,Client,[{queue_size,QueueSize}|Traci]}); 170opt([],Opt) -> 171 ensure_opt(Opt). 172 173ensure_opt({PI,Client,Traci}) -> 174 case {proplists:get_value(flush, Traci), Client} of 175 {undefined, _} -> ok; 176 {_, {local, _}} -> exit(flush_unsupported_with_ip_trace_port); 177 {_,_} -> ok 178 end, 179 NeedIpTracer = proplists:get_value(shell, Traci, false) /= false, 180 case {NeedIpTracer, Client} of 181 {false, _} -> {PI, Client, Traci}; 182 {true, ?MODULE} -> {PI, {local, ?MODULE}, Traci}; 183 {true, {local, File}} -> {PI, {local, File}, Traci}; 184 {true, _} -> exit(local_client_required_on_shell_tracing) 185 end. 186 187get_logname({local, F}) -> get_logname(F); 188get_logname({wrap, F}) -> filename:basename(F); 189get_logname({wrap, F, _, _}) -> filename:basename(F); 190get_logname(F) -> filename:basename(F). 191 192nods(all) -> 193 Nodes1 = remove_active([node()|nodes()]), 194 remove_faulty_runtime_tools_vsn(Nodes1); 195nods(Node) when is_atom(Node) -> 196 nods([Node]); 197nods(Nodes) when is_list(Nodes) -> 198 Nodes1 = remove_active(Nodes), 199 Nodes2 = remove_noexist(Nodes1), 200 remove_faulty_runtime_tools_vsn(Nodes2). 201 202remove_active(Nodes) -> 203 Active = get_nodes(), 204 lists:filter( 205 fun(N) -> case lists:member(N,Active) of 206 false -> true; 207 true -> display_warning(N,already_started), false 208 end 209 end, Nodes). 210 211remove_noexist(Nodes) -> 212 lists:filter( 213 fun(N) when N=:=node() -> 214 true; 215 (N) -> 216 case net_adm:ping(N) of 217 pong -> true; 218 pang -> display_warning(N,no_connection), false 219 end 220 end, Nodes). 221 222remove_faulty_runtime_tools_vsn(Nodes) -> 223 lists:filter( 224 fun(N) -> 225 case rpc:call(N,observer_backend,vsn,[]) of 226 {ok,Vsn} -> check_vsn(N,Vsn); 227 _Error -> display_warning(N,faulty_vsn_of_runtime_tools), false 228 end 229 end,Nodes). 230 231check_vsn(_Node,_Vsn) -> true. 232%check_vsn(Node,_Vsn) -> 233% display_warning(Node,faulty_vsn_of_runtime_tools), 234% false. 235 236clients(Nodes, {wrap,Name}) -> 237 F = fun(Node) -> 238 TraceFile = name(Node,Name), 239 {Node,{TraceFile++".",wrap,".wrp"},TraceFile} 240 end, 241 lists:map(F,Nodes); 242clients(Nodes, {wrap,Name,Size,Count}) -> 243 F = fun(Node) -> 244 TraceFile = name(Node,Name), 245 {Node,{TraceFile++".",wrap,".wrp",Size,Count},TraceFile} 246 end, 247 lists:map(F,Nodes); 248clients(Nodes, {local,RealClient}) -> 249 WrapClients = clients(Nodes,RealClient), 250 F = fun({Node,Client,TraceFile}) -> 251 {Node,{local,Client},TraceFile} 252 end, 253 lists:map(F,WrapClients); 254clients(Nodes, Name) -> 255 F = fun(Node) -> 256 TraceFile = name(Node,Name), 257 {Node,TraceFile,TraceFile} 258 end, 259 lists:map(F,Nodes). 260 261name(Node,Filename) -> 262 Dir = filename:dirname(Filename), 263 File = filename:basename(Filename), 264 filename:join(Dir,atom_to_list(Node) ++ "-" ++ File). 265 266%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 267%%% Handling of config file 268store(Func,Args) -> 269 Last = case ets:last(?history_table) of 270 '$end_of_table' -> 0; 271 Int when is_integer(Int) -> Int 272 end, 273 ets:insert(?history_table,{Last+1,{?MODULE,Func,Args}}). 274 275list_history() -> 276 %% the check is only to see if the tool is started. 277 case ets:info(?history_table) of 278 undefined -> {error, not_running}; 279 _info -> ets:tab2list(?history_table) 280 end. 281 282run_history([H|T]) -> 283 case run_history(H) of 284 ok -> run_history(T); 285 {error,not_found} -> {error,{not_found,H}} 286 end; 287 288run_history(all) -> 289 CurrentHist = ets:tab2list(?history_table), 290 ets:delete_all_objects(?history_table), 291 [run_printed(MFA,true) || {_, MFA} <- CurrentHist]; 292run_history(all_silent) -> 293 CurrentHist = ets:tab2list(?history_table), 294 ets:delete_all_objects(?history_table), 295 [run_printed(MFA,false) || {_, MFA} <- CurrentHist]; 296run_history([]) -> 297 ok; 298run_history(N) -> 299 case catch ets:lookup(?history_table,N) of 300 [{N,{M,F,A}}] -> 301 run_printed({M,F,A},true); 302 _ -> 303 {error, not_found} 304 end. 305 306run_printed({M,F,A},Verbose) -> 307 Verbose andalso print_func(M,F,A), 308 R = apply(M,F,A), 309 Verbose andalso print_result(R). 310 311write_config(ConfigFile,all) -> 312 write_config(ConfigFile,['_']); 313write_config(ConfigFile,Config) -> 314 write_config(ConfigFile,Config,[]). 315write_config(ConfigFile,all,Opt) -> 316 write_config(ConfigFile,['_'],Opt); 317write_config(ConfigFile,Config,Opt) when not(is_list(Opt)) -> 318 write_config(ConfigFile,Config,[Opt]); 319write_config(ConfigFile,Nums,Opt) when is_list(Nums), is_integer(hd(Nums)); 320 Nums=:=['_'] -> 321 F = fun(N) -> ets:select(?history_table, 322 [{{N,'$1'},[],['$1']}]) 323 end, 324 Config = lists:append(lists:map(F,Nums)), 325 do_write_config(ConfigFile,Config,Opt); 326write_config(ConfigFile,Config,Opt) when is_list(Config) -> 327 case check_config(Config,[]) of 328 {ok,Config1} -> do_write_config(ConfigFile,Config1,Opt); 329 Error -> Error 330 end. 331 332do_write_config(ConfigFile,Config,Opt) -> 333 case Opt of 334 [append] -> ok; 335 [] -> file:delete(ConfigFile) 336 end, 337 write_binary(ConfigFile,Config). 338 339check_config([{?MODULE=Mod,Func,Args}|Rest],Acc) -> 340 %% Check only in this module, since other modules might not 341 %% be loaded at the time of creating the config file. 342 case erlang:function_exported(Mod,Func,length(Args)) of 343 true -> check_config(Rest,[{Mod,Func,Args}|Acc]); 344 false -> {error, {not_exported,{Mod,Func,Args}}} 345 end; 346check_config([{Mod,Func,Args}|Rest],Acc) -> 347 check_config(Rest,[{Mod,Func,Args}|Acc]); 348check_config([],Acc) -> 349 {ok,lists:reverse(Acc)}; 350check_config([Other|_Rest],_Acc) -> 351 {error,{illegal_config,Other}}. 352 353 354list_config(ConfigFile) -> 355 case file:read_file(ConfigFile) of 356 {ok,B} -> read_config(B,[],1); 357 Error -> Error 358 end. 359 360read_config(<<>>,Acc,_N) -> 361 lists:reverse(Acc); 362read_config(B,Acc,N) -> 363 {{M,F,A},Rest} = get_term(B), 364 read_config(Rest,[{N,{M,F,A}}|Acc],N+1). 365 366 367run_config(ConfigFile) -> 368 case list_config(ConfigFile) of 369 Config when is_list(Config) -> 370 lists:foreach(fun({_,{M,F,A}}) -> print_func(M,F,A), 371 R = apply(M,F,A), 372 print_result(R) 373 end, 374 Config); 375 Error -> Error 376 end. 377 378run_config(ConfigFile,N) -> 379 case list_config(ConfigFile) of 380 Config when is_list(Config) -> 381 case lists:keysearch(N,1,Config) of 382 {value,{N,{M,F,A}}} -> 383 print_func(M,F,A), 384 apply(M,F,A); 385 false -> 386 {error, not_found} 387 end; 388 Error -> Error 389 end. 390 391 392print_func(M,F,A) -> 393 Args = arg_list(A,[]), 394 io:format("~w:~tw(~ts) ->~n",[M,F,Args]). 395print_result(R) -> 396 io:format("~tp~n~n",[R]). 397 398arg_list([],[]) -> 399 ""; 400arg_list([A1],Acc) -> 401 Acc++io_lib:format("~tw",[A1]); 402arg_list([A1|A],Acc) -> 403 arg_list(A,Acc++io_lib:format("~tw,",[A1])). 404 405 406%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 407%%% Set trace flags on processes 408p(ProcsPorts0,Flags0) -> 409 ensure_no_overloaded_nodes(), 410 store(p,[ProcsPorts0,Flags0]), 411 no_store_p(ProcsPorts0,Flags0). 412no_store_p(ProcsPorts0,Flags0) -> 413 case transform_flags(to_list(Flags0)) of 414 {error,Reason} -> 415 {error,Reason}; 416 Flags -> 417 ProcsPorts = procs_ports(ProcsPorts0), 418 case lists:foldl(fun(P,{PMatched,Ps}) -> case dbg:p(P,Flags) of 419 {ok,Matched} -> 420 {[{P,Matched}|PMatched],[P|Ps]}; 421 {error,Reason} -> 422 display_warning(P,Reason), 423 {PMatched,Ps} 424 end 425 end,{[],[]},ProcsPorts) of 426 {[],[]} -> {error, no_match}; 427 {SuccMatched,Succ} -> 428 no_store_write_trace_info(flags,{Succ,Flags}), 429 ?MODULE ! trace_started, 430 {ok,SuccMatched} 431 end 432 end. 433 434transform_flags([clear]) -> 435 [clear]; 436transform_flags(Flags) -> 437 dbg:transform_flags([timestamp | Flags]). 438 439 440procs_ports(Procs) when is_list(Procs) -> 441 lists:foldl(fun(P,Acc) -> proc_port(P)++Acc end,[],Procs); 442procs_ports(Proc) -> 443 proc_port(Proc). 444 445proc_port(P) when P=:=all; P=:=ports; P=:=processes; 446 P=:=existing; P=:=existing_ports; P=:=existing_processes; 447 P=:=new; P=:=new_ports; P=:=new_processes -> 448 [P]; 449proc_port(Name) when is_atom(Name) -> 450 [Name]; % can be registered on this node or other node 451proc_port(Pid) when is_pid(Pid) -> 452 [Pid]; 453proc_port(Port) when is_port(Port) -> 454 [Port]; 455proc_port({global,Name}) -> 456 case global:whereis_name(Name) of 457 Pid when is_pid(Pid) -> 458 [Pid]; 459 undefined -> 460 [] 461 end. 462 463 464%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 465%%% Trace pattern 466tp(A,B) -> 467 ensure_no_overloaded_nodes(), 468 store(tp,[A,ms(B)]), 469 dbg:tp(A,ms(B)). 470tp(A,B,C) -> 471 ensure_no_overloaded_nodes(), 472 store(tp,[A,B,ms(C)]), 473 dbg:tp(A,B,ms(C)). 474tp(A,B,C,D) -> 475 ensure_no_overloaded_nodes(), 476 store(tp,[A,B,C,ms(D)]), 477 dbg:tp(A,B,C,ms(D)). 478 479tpl(A,B) -> 480 ensure_no_overloaded_nodes(), 481 store(tpl,[A,ms(B)]), 482 dbg:tpl(A,ms(B)). 483tpl(A,B,C) -> 484 ensure_no_overloaded_nodes(), 485 store(tpl,[A,B,ms(C)]), 486 dbg:tpl(A,B,ms(C)). 487tpl(A,B,C,D) -> 488 ensure_no_overloaded_nodes(), 489 store(tpl,[A,B,C,ms(D)]), 490 dbg:tpl(A,B,C,ms(D)). 491 492tpe(A,B) -> 493 ensure_no_overloaded_nodes(), 494 store(tpe,[A,ms(B)]), 495 dbg:tpe(A,ms(B)). 496 497ctp() -> 498 store(ctp,[]), 499 dbg:ctp(). 500ctp(A) -> 501 store(ctp,[A]), 502 dbg:ctp(A). 503ctp(A,B) -> 504 store(ctp,[A,B]), 505 dbg:ctp(A,B). 506ctp(A,B,C) -> 507 store(ctp,[A,B,C]), 508 dbg:ctp(A,B,C). 509 510ctpl() -> 511 store(ctpl,[]), 512 dbg:ctpl(). 513ctpl(A) -> 514 store(ctpl,[A]), 515 dbg:ctpl(A). 516ctpl(A,B) -> 517 store(ctpl,[A,B]), 518 dbg:ctpl(A,B). 519ctpl(A,B,C) -> 520 store(ctpl,[A,B,C]), 521 dbg:ctpl(A,B,C). 522 523ctpg() -> 524 store(ctpg,[]), 525 dbg:ctpg(). 526ctpg(A) -> 527 store(ctpg,[A]), 528 dbg:ctpg(A). 529ctpg(A,B) -> 530 store(ctpg,[A,B]), 531 dbg:ctpg(A,B). 532ctpg(A,B,C) -> 533 store(ctpg,[A,B,C]), 534 dbg:ctpg(A,B,C). 535 536ctpe(A) -> 537 store(ctpe,[A]), 538 dbg:ctpe(A). 539 540ms(return) -> 541 [{'_',[],[{return_trace}]}]; 542ms(caller) -> 543 [{'_',[],[{message,{caller}}]}]; 544ms({codestr, FunStr}) -> 545 {ok, MS} = string2ms(FunStr), 546 MS; 547ms(Other) -> 548 Other. 549 550ensure_no_overloaded_nodes() -> 551 Overloaded = case whereis(?MODULE) of 552 undefined -> 553 []; 554 _ -> 555 ?MODULE ! {get_overloaded, self()}, 556 receive {overloaded,O} -> O end 557 end, 558 case Overloaded of 559 [] -> ok; 560 Overloaded -> exit({error, overload_protection_active, Overloaded}) 561 end. 562 563-spec string2ms(string()) -> {ok, list()} | {error, fun_format}. 564string2ms(FunStr) -> 565 case erl_scan:string(fix_dot(FunStr)) of 566 {ok, Tokens, _} -> 567 case erl_parse:parse_exprs(Tokens) of 568 {ok, [Expression]} -> 569 case Expression of 570 {_, _, {clauses, Clauses}} -> 571 {ok, ms_transform:transform_from_shell(dbg, Clauses, [])}; 572 _ -> 573 {error, fun_format} 574 end; 575 _ -> 576 {error, fun_format} 577 end; 578 _ ->{error, fun_format} 579 end. 580 581-spec fix_dot(string()) -> string(). 582fix_dot(FunStr) -> 583 [H | Rest] = lists:reverse(FunStr), 584 case H of 585 $. -> 586 FunStr; 587 H -> 588 lists:reverse([$., H | Rest]) 589 end. 590 591%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 592%%% Support for sequential trace 593seq_trigger_ms() -> seq_trigger_ms(all). 594seq_trigger_ms(all) -> seq_trigger_ms(?seq_trace_flags); 595seq_trigger_ms(Flag) when is_atom(Flag) -> seq_trigger_ms([Flag],[]); 596seq_trigger_ms(Flags) -> seq_trigger_ms(Flags,[]). 597seq_trigger_ms([Flag|Flags],Body) -> 598 case lists:member(Flag,?seq_trace_flags) of 599 true -> seq_trigger_ms(Flags,[{set_seq_token,Flag,true}|Body]); 600 false -> {error,{illegal_flag,Flag}} 601 end; 602seq_trigger_ms([],Body) -> 603 [{'_',[],Body}]. 604 605 606%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 607%%% Write information to the .ti file 608write_trace_info(Key,What) -> 609 store(write_trace_info,[Key,What]), 610 no_store_write_trace_info(Key,What). 611 612no_store_write_trace_info(Key,What) -> 613 case whereis(?MODULE) of 614 undefined -> ok; 615 Pid when is_pid(Pid) -> ?MODULE ! {write_trace_info,Key,What} 616 end, 617 ok. 618 619 620%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 621%%% Stop tracing on all nodes 622stop() -> 623 stop([]). 624stop(Opts) when is_list(Opts) -> 625 Fetch = stop_opts(Opts), 626 Result = 627 case whereis(?MODULE) of 628 undefined -> ok; 629 Pid when is_pid(Pid) -> 630 ?MODULE ! {stop,Fetch,self()}, 631 receive {?MODULE,R} -> R end 632 end, 633 case {Fetch, Result} of 634 {nofetch, _} -> 635 ok; 636 {_, {stopped, _}} -> 637 %% Printout moved out of the ttb loop to avoid occasional deadlock 638 io:format("Stored logs in ~ts~n", [element(2, Result)]); 639 {_, _} -> 640 ok 641 end, 642 stop_return(Result,Opts); 643stop(Opts) -> 644 stop([Opts]). 645 646stop_opts(Opts) -> 647 FetchDir = proplists:get_value(fetch_dir, Opts), 648 ensure_fetch_dir(FetchDir), 649 FormatData = case proplists:get_value(format, Opts) of 650 undefined -> false; 651 true -> {format, []}; 652 FOpts -> {format, FOpts} 653 end, 654 case {FormatData, lists:member(return_fetch_dir, Opts)} of 655 {false, true} -> 656 {fetch, FetchDir}; % if we specify return_fetch_dir, the data should be fetched 657 {false, false} -> 658 case lists:member(nofetch,Opts) of 659 false -> {fetch, FetchDir}; 660 true -> nofetch 661 end; 662 {FormatData, _} -> 663 {FormatData, FetchDir} 664 end. 665 666ensure_fetch_dir(undefined) -> ok; 667ensure_fetch_dir(Dir) -> 668 case filelib:is_file(Dir) of 669 true -> 670 throw({error, exists, Dir}); 671 false -> 672 ok 673 end. 674 675stop_return(R,Opts) -> 676 case {lists:member(return_fetch_dir,Opts),R} of 677 {true,_} -> 678 R; 679 {false,{stopped,_}} -> 680 stopped; 681 {false,_} -> 682 %% Anything other than 'stopped' would not be bw compatible... 683 stopped 684 end. 685 686 687%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 688%%% Process implementation 689start(SessionInfo) -> 690 case whereis(?MODULE) of 691 undefined -> 692 Parent = self(), 693 Pid = spawn(fun() -> init(Parent, SessionInfo) end), 694 receive {started,Pid} -> ok end, 695 Pid; 696 Pid when is_pid(Pid) -> 697 Pid 698 end. 699 700init(Parent, SessionInfo) -> 701 register(?MODULE,self()), 702 ets:new(?history_table,[ordered_set,named_table,public]), 703 Parent ! {started,self()}, 704 NewSessionInfo = [{partials, 0}, {dead_nodes, []} | SessionInfo], 705 try_send_flush_tick(NewSessionInfo), 706 loop(dict:new(), NewSessionInfo). 707 708loop(NodeInfo, SessionInfo) -> 709 receive 710 {init_node,Node,MetaFile,PI,Traci} -> 711 erlang:monitor_node(Node,true), 712 {AbsoluteMetaFile, MetaPid} = 713 case rpc:call(Node, 714 observer_backend, 715 ttb_init_node, 716 [MetaFile,PI,Traci]) of 717 {ok,MF,MP} -> 718 {MF,MP}; 719 {badrpc,nodedown} -> 720 %% We will get a nodedown message 721 {MetaFile,undefined} 722 end, 723 loop(dict:store(Node,{AbsoluteMetaFile,MetaPid},NodeInfo), SessionInfo); 724 {ip_to_file_trace_port,Port,Sender} -> 725 Ports = proplists:get_value(ip_to_file_trace_ports, SessionInfo, []), 726 NewSessionInfo = [{ip_to_file_trace_ports,[Port|Ports]}|SessionInfo], 727 Sender ! {?MODULE,ok}, 728 loop(NodeInfo, NewSessionInfo); 729 {get_nodes,Sender} -> 730 Sender ! {?MODULE,dict:fetch_keys(NodeInfo)}, 731 loop(NodeInfo, SessionInfo); 732 {write_trace_info,Key,What} -> 733 dict:fold(fun(Node,{_MetaFile,MetaPid},_) -> 734 rpc:call(Node,observer_backend, 735 ttb_write_trace_info,[MetaPid,Key,What]) 736 end, 737 ok, 738 NodeInfo), 739 loop(NodeInfo, SessionInfo); 740 {nodedown,Node} -> 741 NewState = make_node_dead(Node, NodeInfo, SessionInfo), 742 loop(dict:erase(Node,NodeInfo), NewState); 743 {noderesumed,Node,Reporter} -> 744 {MetaFile, CurrentSuffix, NewState} = make_node_alive(Node, SessionInfo), 745 fetch_partial_result(Node, MetaFile, CurrentSuffix), 746 spawn(fun() -> resume_trace(Reporter) end), 747 loop(NodeInfo, NewState); 748 {timeout, StopOpts} -> 749 spawn(?MODULE, stop, [StopOpts]), 750 loop(NodeInfo, SessionInfo); 751 {node_overloaded, Node} -> 752 io:format("Overload check activated on node: ~p.~n", [Node]), 753 {Overloaded, SI} = {proplists:get_value(overloaded, SessionInfo, []), 754 lists:keydelete(overloaded, 1, SessionInfo)}, 755 loop(NodeInfo, [{overloaded, [Node|Overloaded]} | SI]); 756 {get_overloaded, Pid} -> 757 Pid ! {overloaded,proplists:get_value(overloaded, SessionInfo, [])}, 758 loop(NodeInfo, SessionInfo); 759 trace_started -> 760 case proplists:get_value(timer, SessionInfo) of 761 undefined -> ok; 762 {MSec, StopOpts} -> erlang:send_after(MSec, self(), {timeout, StopOpts}) 763 end, 764 loop(NodeInfo, SessionInfo); 765 flush_timeout -> 766 [ dbg:flush_trace_port(Node) || Node <- dict:fetch_keys(NodeInfo) ], 767 try_send_flush_tick(SessionInfo), 768 loop(NodeInfo, SessionInfo); 769 {stop,nofetch,Sender} -> 770 do_stop(nofetch, Sender, NodeInfo, SessionInfo); 771 {stop,FetchSpec,Sender} -> 772 case proplists:get_value(shell, SessionInfo, false) of 773 only -> do_stop(nofetch, Sender, NodeInfo, SessionInfo); 774 _ -> do_stop(FetchSpec, Sender, NodeInfo, SessionInfo) 775 end 776 end. 777 778do_stop(nofetch, Sender, NodeInfo, SessionInfo) -> 779 write_config(?last_config, all), 780 dict:fold( 781 fun(Node,{_,MetaPid},_) -> 782 rpc:call(Node,observer_backend,ttb_stop,[MetaPid]) 783 end, 784 ok, 785 NodeInfo), 786 stop_ip_to_file_trace_ports(SessionInfo), 787 dbg:stop_clear(), 788 ets:delete(?history_table), 789 Sender ! {?MODULE, stopped}; 790 791do_stop({FetchOrFormat, UserDir}, Sender, NodeInfo, SessionInfo) -> 792 write_config(?last_config, all), 793 Localhost = host(node()), 794 Dir = get_fetch_dir(UserDir, proplists:get_value(logfile, SessionInfo)), 795 ok = filelib:ensure_dir(filename:join(Dir,"*")), 796 %% The nodes are traversed twice here because 797 %% the meta tracing in observer_backend must be 798 %% stopped before dbg is stopped, and dbg must 799 %% be stopped before the trace logs are moved orelse 800 %% windows complains. 801 AllNodesAndMeta = 802 dict:fold( 803 fun(Node,{MetaFile,MetaPid},Nodes) -> 804 rpc:call(Node,observer_backend,ttb_stop,[MetaPid]), 805 [{Node,MetaFile}|Nodes] 806 end, 807 [], 808 NodeInfo), 809 stop_ip_to_file_trace_ports(SessionInfo), 810 dbg:stop_clear(), 811 AllNodes = 812 lists:map( 813 fun({Node,MetaFile}) -> 814 spawn(fun() -> fetch_report(Localhost,Dir,Node,MetaFile) end), 815 Node 816 end, 817 AllNodesAndMeta), 818 ets:delete(?history_table), 819 wait_for_fetch(AllNodes), 820 copy_partials(Dir, proplists:get_value(partials, SessionInfo)), 821 Absname = filename:absname(Dir), 822 case FetchOrFormat of 823 fetch -> ok; 824 {format, Opts} -> format(Dir, Opts) 825 end, 826 Sender ! {?MODULE,{stopped,Absname}}. 827 828stop_ip_to_file_trace_ports(SessionInfo) -> 829 lists:foreach(fun(Port) -> 830 case lists:member(Port,erlang:ports()) of 831 true -> 832 dbg:deliver_and_flush(Port), 833 erlang:port_close(Port); 834 false -> 835 ok 836 end 837 end, 838 proplists:get_value(ip_to_file_trace_ports,SessionInfo,[])). 839 840 841make_node_dead(Node, NodeInfo, SessionInfo) -> 842 {MetaFile,_} = dict:fetch(Node, NodeInfo), 843 NewDeadNodes = [{Node, MetaFile} | proplists:get_value(dead_nodes, SessionInfo)], 844 [{dead_nodes, NewDeadNodes} | lists:keydelete(dead_nodes, 1, SessionInfo)]. 845 846make_node_alive(Node, SessionInfo) -> 847 DeadNodes = proplists:get_value(dead_nodes, SessionInfo), 848 Partials = proplists:get_value(partials, SessionInfo), 849 {value, {_, MetaFile}, Dn2} = lists:keytake(Node, 1, DeadNodes), 850 SessionInfo2 = lists:keyreplace(dead_nodes, 1, SessionInfo, {dead_nodes, Dn2}), 851 {MetaFile, Partials + 1, lists:keyreplace(partials, 1, SessionInfo2, {partials, Partials + 1})}. 852 853try_send_flush_tick(State) -> 854 case proplists:get_value(flush, State) of 855 undefined -> 856 ok; 857 MSec -> 858 erlang:send_after(MSec, self(), flush_timeout) 859 end. 860 861get_fetch_dir(undefined,undefined) -> ?upload_dir(?MODULE_STRING) ++ ts(); 862get_fetch_dir(undefined,Logname) -> ?upload_dir(Logname) ++ ts(); 863get_fetch_dir(Dir,_) -> Dir. 864 865resume_trace(Reporter) -> 866 ?MODULE:run_history(all_silent), 867 Reporter ! trace_resumed. 868 869get_nodes() -> 870 ?MODULE ! {get_nodes,self()}, 871 receive {?MODULE,Nodes} -> Nodes end. 872 873ts() -> 874 {{Y,M,D},{H,Min,S}} = calendar:now_to_local_time(erlang:timestamp()), 875 io_lib:format("-~4.4.0w~2.2.0w~2.2.0w-~2.2.0w~2.2.0w~2.2.0w", 876 [Y,M,D,H,Min,S]). 877 878copy_partials(_, 0) -> 879 ok; 880copy_partials(Dir, Num) -> 881 PartialDir = ?partial_dir ++ integer_to_list(Num), 882 file:rename(PartialDir, filename:join(Dir,PartialDir)), 883 copy_partials(Dir, Num - 1). 884 885fetch_partial_result(Node,MetaFile,Current) -> 886 DirName = ?partial_dir ++ integer_to_list(Current), 887 case file:list_dir(DirName) of 888 {error, enoent} -> 889 ok; 890 {ok, Files} -> 891 [ file:delete(filename:join(DirName, File)) || File <- Files ], 892 file:del_dir(DirName) 893 end, 894 file:make_dir(DirName), 895 fetch(host(node()), DirName, Node, MetaFile). 896 897fetch_report(Localhost, Dir, Node, MetaFile) -> 898 fetch(Localhost,Dir,Node,MetaFile), 899 ?MODULE ! {fetch_complete,Node}. 900 901fetch(Localhost,Dir,Node,MetaFile) -> 902 case (host(Node) == Localhost) orelse is_local(MetaFile) of 903 true -> % same host, just move the files 904 Files = get_filenames(Node,MetaFile), 905 lists:foreach( 906 fun(File0) -> 907 Dest = filename:join(Dir,filename:basename(File0)), 908 file:rename(File0, Dest) 909 end, 910 Files); 911 false -> 912 {ok, LSock} = gen_tcp:listen(0, [binary,{packet,2},{active,false}]), 913 {ok,Port} = inet:port(LSock), 914 Enc = file:native_name_encoding(), 915 Args = 916 case rpc:call(Node,erlang,function_exported, 917 [observer_backend,ttb_fetch,3]) of 918 true -> 919 [MetaFile,{Port,Localhost},Enc]; 920 false -> 921 [MetaFile,{Port,Localhost}] 922 end, 923 rpc:cast(Node,observer_backend,ttb_fetch,Args), 924 {ok, Sock} = gen_tcp:accept(LSock), 925 receive_files(Dir,Sock,undefined,Enc), 926 ok = gen_tcp:close(LSock), 927 ok = gen_tcp:close(Sock) 928 end. 929 930is_local({local, _, _}) -> 931 true; 932is_local(_) -> 933 false. 934 935get_filenames(_N, {local,F,_}) -> 936 observer_backend:ttb_get_filenames(F); 937get_filenames(N, F) -> 938 rpc:call(N, observer_backend,ttb_get_filenames,[F]). 939 940receive_files(Dir,Sock,Fd,Enc) -> 941 case gen_tcp:recv(Sock, 0) of 942 {ok, <<0,Bin/binary>>} -> 943 file:write(Fd,Bin), 944 receive_files(Dir,Sock,Fd,Enc); 945 {ok, <<Code,Bin/binary>>} when Code==1; Code==2; Code==3 -> 946 File0 = decode_filename(Code,Bin,Enc), 947 File = filename:join(Dir,File0), 948 {ok,Fd1} = file:open(File,[raw,write]), 949 receive_files(Dir,Sock,Fd1,Enc); 950 {error, closed} -> 951 ok = file:close(Fd) 952 end. 953 954decode_filename(1,Bin,_Enc) -> 955 %% Old version of observer_backend - filename encoded with 956 %% list_to_binary 957 binary_to_list(Bin); 958decode_filename(2,Bin,Enc) -> 959 %% Successfully encoded filename with correct encoding 960 unicode:characters_to_list(Bin,Enc); 961decode_filename(3,Bin,latin1) -> 962 %% Filename encoded with faulty encoding. This has to be utf8 963 %% remote and latin1 here, and the filename actually containing 964 %% characters outside the latin1 range. So making an escaped 965 %% variant of the filename and warning about it. 966 File0 = unicode:characters_to_list(Bin,utf8), 967 File = [ case X of 968 High when High > 255 -> 969 ["\\\\x{",erlang:integer_to_list(X, 16),$}]; 970 Low -> 971 Low 972 end || X <- File0 ], 973 io:format("Warning: fetching file with faulty filename encoding ~ts~n" 974 "Will be written as ~ts~n", 975 [File0,File]), 976 File. 977 978host(Node) -> 979 [_name,Host] = string:lexemes(atom_to_list(Node),"@"), 980 Host. 981 982wait_for_fetch([]) -> 983 ok; 984wait_for_fetch(Nodes) -> 985 receive 986 {fetch_complete,Node} -> 987 wait_for_fetch(lists:delete(Node,Nodes)) 988 end. 989 990%%% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 991%%% TRACE INFORMATION FILE 992%%% ====================== 993%%% The trace information file has the same name as the trace log, 994%%% but with the extension ".ti". It contains process information, 995%%% trace information and any data the user writes with the 996%%% function write_trace_info/2. 997%%% 998%%% The file is read during formatting of trace logs, and all data 999%%% except process information is included in the handler function. 1000%%% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1001 1002write_info(Nodes,PI,Traci) -> 1003 {ok, Cwd} = file:get_cwd(), 1004 lists:foreach(fun({N,{local,C,_},F}) -> 1005 MetaFile = case F of 1006 none -> 1007 none; 1008 F -> 1009 AbsFile = filename:join(Cwd, F) ++ ".ti", 1010 file:delete(AbsFile), 1011 AbsFile 1012 end, 1013 Traci1 = [{node,N},{file,C}|Traci], 1014 {ok,Port} = dbg:get_tracer(N), 1015 ?MODULE ! 1016 {init_node, N, {local,MetaFile,Port}, PI, Traci1}; 1017 ({N,C,F}) -> 1018 MetaFile = F ++ ".ti", 1019 Traci1 = [{node,N},{file,C}|Traci], 1020 ?MODULE ! {init_node, N, MetaFile, PI, Traci1} 1021 end, 1022 Nodes). 1023 1024%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1025%%% Format binary trace logs 1026get_et_handler() -> 1027 {fun ttb_et:handler/4, initial}. 1028 1029format(Files) -> 1030 format(Files,[]). 1031format(Files,Opt) -> 1032 {Out,Handler,DisableSort} = format_opt(Opt), 1033 ets:new(?MODULE,[named_table]), 1034 format(Files,Out,Handler, DisableSort). 1035format(File,Out,Handler,DisableSort) when is_list(File), is_integer(hd(File)) -> 1036 Files = 1037 case filelib:is_dir(File) of 1038 true -> % will merge all files in the directory 1039 List = filelib:wildcard(filename:join(File, ?partial_dir++"*")), 1040 lists:append(collect_files([File | List])); 1041 false -> % format one file 1042 [File] 1043 end, 1044 format(Files,Out,Handler,DisableSort); 1045format(Files,Out,Handler,DisableSort) when is_list(Files), is_list(hd(Files)) -> 1046 StopDbg = case whereis(dbg) of 1047 undefined -> true; 1048 _ -> false 1049 end, 1050 Details = lists:foldl(fun(File,Acc) -> [prepare(File)|Acc] end, 1051 [],Files), 1052 Fd = get_fd(Out), 1053 RealHandler = get_handler(Handler, Files), 1054 R = do_format(Fd,Details,DisableSort,RealHandler), 1055 file:close(Fd), 1056 ets:delete(?MODULE), 1057 case StopDbg of 1058 true -> dbg:stop_clear(); 1059 false -> ok 1060 end, 1061 R. 1062 1063collect_files(Dirs) -> 1064 lists:map(fun(Dir) -> 1065 MetaFiles = filelib:wildcard(filename:join(Dir,"*.ti")), 1066 lists:map(fun(M) -> 1067 Sub = filename:rootname(M,".ti"), 1068 case filelib:is_file(Sub) of 1069 true -> Sub; 1070 false -> Sub++".*.wrp" 1071 end 1072 end, 1073 MetaFiles) 1074 end, Dirs). 1075 1076get_handler(undefined, Files) -> 1077 %%We retrieve traci from the first available file 1078 {Traci, _} = read_traci(hd(Files)), 1079 case dict:find(handler, Traci) of 1080 error -> {fun defaulthandler/4, initial}; 1081 {ok, [Handler]} -> Handler 1082 end; 1083get_handler(Handler, _) -> 1084 Handler. 1085 1086prepare(File) -> 1087 {Traci,Proci} = read_traci(File), 1088 Node = get_node(Traci), 1089 lists:foreach(fun({Pid,PI}) -> 1090 %% The last definition for a Pid will overwrite 1091 %% any previous definitions. That should be what 1092 %% we want (we will get the registered name for 1093 %% the process, rather than the initial call if 1094 %% both are present in the list). 1095 ets:insert(?MODULE,{Pid,PI,Node}) 1096 end,Proci), 1097 FileOrWrap = get_file(File,Traci), 1098 {FileOrWrap,Traci}. 1099 1100format_opt(Opt) when is_list(Opt) -> 1101 Out = case lists:keysearch(out,1,Opt) of 1102 {value,{out,O}} -> O; 1103 _ -> standard_io 1104 end, 1105 Handler = case lists:keysearch(handler,1,Opt) of 1106 {value,{handler,H}} -> H; 1107 _ -> undefined 1108 end, 1109 DisableSort = proplists:get_value(disable_sort, Opt, false), 1110 {Out,Handler,DisableSort}; 1111format_opt(Opt) -> 1112 format_opt([Opt]). 1113 1114 1115read_traci(File) -> 1116 MetaFile = get_metafile(File), 1117 case file:read_file(MetaFile) of 1118 {ok,B} -> 1119 interpret_binary(B,dict:new(),[]); 1120 _ -> 1121 io:format("Warning: no meta data file: ~ts~n",[MetaFile]), 1122 {dict:new(),[]} 1123 end. 1124 1125get_metafile(File) -> 1126 case filename:rootname(File,".wrp") of 1127 File -> File++".ti"; 1128 Wrap -> filename:rootname(Wrap)++".ti" 1129 end. 1130 1131 1132interpret_binary(<<>>,Dict,P) -> 1133 {Dict,lists:reverse(P)}; 1134interpret_binary(B,Dict,P) -> 1135 {Term,Rest} = get_term(B), 1136 {Dict1,P1} = 1137 case Term of 1138 {pid,PI} -> 1139 {Dict,[PI|P]}; 1140 {Key,Val} -> 1141 {dict:update(Key,fun(Val0) -> [Val|Val0] end, [Val], Dict),P} 1142 end, 1143 interpret_binary(Rest,Dict1,P1). 1144 1145get_fd(Out) -> 1146 case Out of 1147 standard_io -> 1148 Out; 1149 _file -> 1150 file:delete(Out), 1151 case file:open(Out,[append,{encoding,utf8}]) of 1152 {ok,Fd} -> Fd; 1153 Error -> exit(Error) 1154 end 1155 end. 1156 1157get_node(Traci) -> 1158 case dict:find(node,Traci) of 1159 {ok,[Node]} -> Node; 1160 error -> unknown 1161 end. 1162 1163get_file(File,Traci) -> 1164 case dict:find(file,Traci) of 1165 {ok,[Client]} -> 1166 check_client(Client,File); 1167 error -> 1168 check_exists(File) 1169 end. 1170 1171check_client(Client,File) when is_list(Client) -> 1172 check_exists(File); 1173check_client(Client,File) when is_tuple(Client),element(2,Client)==wrap -> 1174 Root = filename:rootname(File,".wrp"), 1175 case filename:extension(Root) of 1176 ".*" -> 1177 Part1 = filename:rootname(Root,"*"), 1178 setelement(1,Client,Part1); 1179 _ -> 1180 check_exists(File) 1181 end. 1182 1183check_exists(File) -> 1184 case file:read_file_info(File) of 1185 {ok,#file_info{type=regular}} -> File; 1186 _ -> 1187 exit({error,no_file}) 1188 end. 1189 1190 1191do_format(Fd,Details,DisableSort,Handler) -> 1192 Clients = lists:foldl(fun({FileOrWrap,Traci},Acc) -> 1193 [start_client(FileOrWrap,Traci)|Acc] 1194 end,[],Details), 1195 init_collector(Fd,Clients,DisableSort,Handler). 1196 1197start_client(FileOrWrap,Traci) -> 1198 dbg:trace_client(file, FileOrWrap, 1199 {fun handler/2, dict:to_list(Traci)}). 1200 1201handler(Trace,Traci) -> 1202 %%We return our own Traci so that it not necesarry to look it up 1203 %%This may take time if something huge has been written to it 1204 receive 1205 {get,Collector} -> Collector ! {self(),{Trace,Traci}}; 1206 done -> ok 1207 end, 1208 Traci. 1209 1210%%Used to handle common state (the same for all clients) 1211handler2(Trace,{Fd,Traci,{Fun,State}}) when is_function(Fun) -> 1212 {Fun, Fun(Fd, Trace, Traci, State)}; 1213handler2(Trace,{Fd,Traci,{{M,F},State}}) when is_atom(M), is_atom(F) -> 1214 {{M,F}, M:F(Fd, Trace, Traci, State)}. 1215 1216defaulthandler(Fd,Trace,_Traci,initial) -> 1217 dbg:dhandler(Trace,Fd); 1218defaulthandler(_Fd,Trace,_Traci,State) -> 1219 dbg:dhandler(Trace,State). 1220 1221init_collector(Fd,Clients,DisableSort,Handler) -> 1222 Collected = get_first(Clients), 1223 case DisableSort of 1224 true -> collector(Fd,Collected, DisableSort, Handler); 1225 false -> collector(Fd,sort(Collected), DisableSort, Handler) 1226 end. 1227 1228collector(Fd,[{_,{Client,{Trace,Traci}}} |Rest], DisableSort, CommonState) -> 1229 Trace1 = update_procinfo(Trace), 1230 CommonState2 = handler2(Trace1, {Fd, Traci, CommonState}), 1231 case get_next(Client) of 1232 end_of_trace -> 1233 collector(Fd,Rest,DisableSort, CommonState2); 1234 Next -> case DisableSort of 1235 false -> collector(Fd,sort([Next|Rest]), DisableSort, CommonState2); 1236 true -> collector(Fd,[Next|Rest], DisableSort, CommonState2) 1237 end 1238 end; 1239collector(Fd,[], _, CommonState) -> 1240 handler2(end_of_trace, {Fd, end_of_trace, CommonState}), 1241 ok. 1242 1243update_procinfo({drop,_N}=Trace) -> 1244 Trace; 1245update_procinfo(Trace) when element(1,Trace)==seq_trace -> 1246 Info = element(3,Trace), 1247 Info1 = 1248 case Info of 1249 {send, Serial, From, To, Msg} -> 1250 {send, Serial, get_procinfo(From), get_procinfo(To), Msg}; 1251 {'receive', Serial, From, To, Msg} -> 1252 {'receive', Serial, get_procinfo(From), get_procinfo(To), Msg}; 1253 {print, Serial, From, Void, UserInfo} -> 1254 {print, Serial, get_procinfo(From), Void, UserInfo}; 1255 Other -> 1256 Other 1257 end, 1258 setelement(3,Trace,Info1); 1259update_procinfo(Trace) when element(3,Trace)==send -> 1260 PI = get_procinfo(element(5,Trace)), 1261 setelement(5,Trace,PI); 1262update_procinfo(Trace) -> 1263 Pid = element(2,Trace), 1264 ProcInfo = get_procinfo(Pid), 1265 setelement(2,Trace,ProcInfo). 1266 1267get_procinfo(Pid) when is_pid(Pid); is_port(Pid) -> 1268 case ets:lookup(?MODULE,Pid) of 1269 [PI] -> PI; 1270 [] -> Pid 1271 end; 1272get_procinfo(Name) when is_atom(Name) -> 1273 case ets:match_object(?MODULE,{'_',Name,node()}) of 1274 [PI] -> PI; 1275 [] -> Name 1276 end; 1277get_procinfo({Name,Node}) when is_atom(Name) -> 1278 case ets:match_object(?MODULE,{'_',Name,Node}) of 1279 [PI] -> PI; 1280 [] -> {Name,Node} 1281 end. 1282 1283get_first([Client|Clients]) -> 1284 Client ! {get,self()}, 1285 receive 1286 {Client,{end_of_trace,_}} -> 1287 get_first(Clients); 1288 {Client,{Trace,_}}=Next -> 1289 [{timestamp(Trace),Next}|get_first(Clients)] 1290 end; 1291get_first([]) -> []. 1292 1293get_next(Client) when is_pid(Client) -> 1294 Client ! {get,self()}, 1295 receive 1296 {Client,{end_of_trace,_}} -> 1297 end_of_trace; 1298 {Client,{Trace, Traci}} -> 1299 {timestamp(Trace),{Client,{Trace,Traci}}} 1300 end. 1301 1302sort(List) -> 1303 lists:keysort(1,List). 1304 1305 1306timestamp(Trace) when element(1,Trace) =:= trace_ts; 1307 element(1,Trace) =:= seq_trace, tuple_size(Trace) =:= 4 -> 1308 element(tuple_size(Trace),Trace); 1309timestamp(_Trace) -> 1310 0. 1311 1312%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1313%%% common internal functions 1314to_list(Atom) when is_atom(Atom) -> [Atom]; 1315to_list(List) when is_list(List) -> List. 1316 1317write_binary(File,TermList) -> 1318 {ok,Fd} = file:open(File,[raw,append]), 1319 %% Using the function implemented in observer_backend, only because 1320 %% is exists - so I don't have to write the same code twice. 1321 observer_backend:ttb_write_binary(Fd,TermList), 1322 file:close(Fd). 1323 1324get_term(B) -> 1325 <<S:8, B2/binary>> = B, 1326 <<T:S/binary, Rest/binary>> = B2, 1327 case binary_to_term(T) of 1328 {'$size',Sz} -> 1329 %% size of the actual term was bigger than 8 bits 1330 <<T1:Sz/binary, Rest1/binary>> = Rest, 1331 {binary_to_term(T1),Rest1}; 1332 Term -> 1333 {Term,Rest} 1334 end. 1335 1336display_warning(Item,Warning) -> 1337 io:format("Warning: {~tw,~tw}~n",[Warning,Item]). 1338 1339 1340%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1341%%% Trace client which reads an IP port and puts data directly to a file. 1342%%% This is used when tracing remote nodes with no file system. 1343ip_to_file({metadata,_,_},{shell_only, _} = State) -> 1344 State; 1345ip_to_file(Trace, {shell_only, Fun} = State) -> 1346 Fun(Trace), 1347 State; 1348ip_to_file(Trace,{{file,File}, ShellOutput}) -> 1349 Fun = dbg:trace_port(file,File), %File can be a filename or a wrap spec 1350 Port = Fun(), 1351 %% Just in case this is on the traced node, 1352 %% make sure the port is not traced. 1353 p(Port,clear), 1354 %% Store the port so it can be properly closed 1355 ?MODULE ! {ip_to_file_trace_port, Port, self()}, 1356 receive {?MODULE,ok} -> ok end, 1357 case Trace of 1358 {metadata, _, _} -> ok; 1359 Trace -> show_trace(Trace, ShellOutput) 1360 end, 1361 ip_to_file(Trace,{Port,ShellOutput}); 1362ip_to_file({metadata,MetaFile,MetaData},State) -> 1363 {ok,MetaFd} = file:open(MetaFile,[write,raw,append]), 1364 file:write(MetaFd,MetaData), 1365 file:close(MetaFd), 1366 State; 1367ip_to_file(Trace,{Port, ShellOutput}) -> 1368 show_trace(Trace, ShellOutput), 1369 B = term_to_binary(Trace), 1370 erlang:port_command(Port,B), 1371 {Port, ShellOutput}. 1372 1373show_trace(Trace, Fun) when is_function(Fun) -> 1374 Fun(Trace); 1375show_trace(_, _) -> 1376 ok. 1377 1378%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1379%%% For debugging 1380dump_ti(File) -> 1381 {ok,B} = file:read_file(File), 1382 dump_ti(B,[]). 1383 1384dump_ti(<<>>,Acc) -> 1385 lists:reverse(Acc); 1386dump_ti(B,Acc) -> 1387 {Term,Rest} = get_term(B), 1388 dump_ti(Rest,[Term|Acc]). 1389