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