Trace Tool Builder is a base for building trace tools for single node or distributed Erlang systems. It requires the Runtime_Tools application to be available on the traced node.
The following are the main features of Trace Tool Builder:
The intention of Trace Tool Builder is to serve
as a base for tailor-made trace tools, but it can also be used directly
from the Erlang shell (it can mimic
Module
To get started, the least you need to do is to
start a tracer with
When the tracing is completed, stop the tracer with
Useful functions:
Opens a trace port on each node to be traced. By default, trace messages are written to binary files on remote nodes (the binary trace log).
Specifies the processes to be traced. Trace flags specified in this call specify what to trace on each process. This function can be called many times if you like different trace flags to be set on different processes.
If you want to trace function calls (that is, if you have
trace flag
Stops tracing on all nodes, deletes all trace patterns, and flushes the trace port buffer.
Translates the binary trace logs into something readable.
By default,
If option
The following small module is used in the subsequent example:
-module(m).
-export([f/0]).
f() ->
receive
From when is_pid(From) ->
Now = erlang:now(),
From ! {self(),Now}
end.
The following example shows the basic use of
(tiger@durin)47> %% First I spawn a process running my test function (tiger@durin)47> Pid = spawn(m,f,[]). <0.125.0> (tiger@durin)48> (tiger@durin)48> %% Then I start a tracer... (tiger@durin)48> ttb:tracer(). {ok,[tiger@durin]} (tiger@durin)49> (tiger@durin)49> %% and activate the new process for tracing (tiger@durin)49> %% function calls and sent messages. (tiger@durin)49> ttb:p(Pid,[call,send]). {ok,[{<0.125.0>,[{matched,tiger@durin,1}]}]} (tiger@durin)50> (tiger@durin)50> %% Here I set a trace pattern on erlang:now/0 (tiger@durin)50> %% The trace pattern is a simple match spec (tiger@durin)50> %% indicating that the return value should be (tiger@durin)50> %% traced. Refer to the reference_manual for (tiger@durin)50> %% the full list of match spec shortcuts (tiger@durin)50> %% available. (tiger@durin)51> ttb:tp(erlang,now,return). {ok,[{matched,tiger@durin,1},{saved,1}]} (tiger@durin)52> (tiger@durin)52> %% I run my test (i.e. send a message to (tiger@durin)52> %% my new process) (tiger@durin)52> Pid ! self(). <0.72.0> (tiger@durin)53> (tiger@durin)53> %% And then I have to stop ttb in order to flush (tiger@durin)53> %% the trace port buffer (tiger@durin)53> ttb:stop([return, {fetch_dir, "fetch"}]). {stopped, "fetch"} (tiger@durin)54> (tiger@durin)54> %% Finally I format my trace log (tiger@durin)54> ttb:format("fetch"). ({<0.125.0>,{m,f,0},tiger@durin}) call erlang:now() ({<0.125.0>,{m,f,0},tiger@durin}) returned from erlang:now/0 -> {1031,133451,667611} ({<0.125.0>,{m,f,0},tiger@durin}) <0.72.0> ! {<0.125.0>,{1031,133451,667611}} ok
The following example shows a simple tool for "debug tracing", that is, tracing of function calls with return values:
%% The options specify that the binary log shall be named
%% -debug_log and that the print/4 function in this
%% module shall be used as format handler
ttb:tracer(all,[{file,"debug_log"},{handler,{{?MODULE,print},0}}]),
%% All processes (existing and new) shall trace function calls
%% We want trace messages to be sorted upon format, which requires
%% timestamp flag. The flag is however enabled by default in ttb.
ttb:p(all,call).
%%% Set trace pattern on function(s)
trc(M) when is_atom(M) ->
trc({M,'_','_'});
trc({M,F}) when is_atom(M), is_atom(F) ->
trc({M,F,'_'});
trc({M,F,_A}=MFA) when is_atom(M), is_atom(F) ->
%% This match spec shortcut specifies that return values shall
%% be traced.
MatchSpec = dbg:fun2ms(fun(_) -> return_trace() end),
ttb:tpl(MFA,MatchSpec).
%%% Format a binary trace log
format(Dir) ->
ttb:format(Dir).
%%% Stop the "mydebug" tool
stop() ->
ttb:stop(return).
%%% --------Internal functions--------
%%% ----------------------------------
%%% Format handler
print(_Out,end_of_trace,_TI,N) ->
N;
print(Out,Trace,_TI,N) ->
do_print(Out,Trace,N),
N+1.
do_print(Out,{trace_ts,P,call,{M,F,A},Ts},N) ->
io:format(Out,
"~w: ~w, ~w:~n"
"Call : ~w:~w/~w~n"
"Arguments :~p~n~n",
[N,Ts,P,M,F,length(A),A]);
do_print(Out,{trace_ts,P,return_from,{M,F,A},R,Ts},N) ->
io:format(Out,
"~w: ~w, ~w:~n"
"Return from : ~w:~w/~w~n"
"Return value :~p~n~n",
[N,Ts,P,M,F,A,R]). ]]>
To distinguish trace logs produced with this tool from other
logs, option
By using option
Trace flag
The Observer application might not always be available on the node to be traced (in the following called the "traced node"). However, Trace Tool Builder can still be run from another node (in the following called the "trace control node") as long as the following is fulfilled:
If Trace Tool Builder is to be used against a remote node,
it is highly recommended to start the trace control node as
hidden. This way it can connect to the traced node
without being "seen" by it, that is, if the
% erl -sname trace_control -hidden
If the traced node is diskless,
(trace_control@durin)1> ttb:tracer(mynode@diskless, {file,{local,{wrap,"mytrace"}}}). {ok,[mynode@diskless]}
When setting up a trace, the following features can also be activated:
It can sometimes be helpful to enable trace for a
specified period of time (for example, to monitor a system for 24 hours
or half a second). This can be done with option
The timer is started with
The following example shows how to set up a trace that is automatically stopped and formatted after 5 seconds:
(tiger@durin)1> ttb:start_trace([node()], [{erlang, now,[]}], {all, call}, [{timer, {5000, format}}]).
Because of network and processing delays, the period of tracing is approximate.
When tracing live systems, always take special care to not
overload a node with too heavy tracing.
Overload protection activated on one node does not
affect other nodes, where the tracing continues as normal.
It is not allowed to change trace details
(with
-module(overload).
-export([check/1]).
check(init) ->
Pid = sophisticated_module:start(),
put(pid, Pid);
check(check) ->
get(pid) ! is_overloaded,
receive
Reply ->
Reply
after 5000 ->
true
end;
check(stop) ->
get(pid) ! stop.
A node can crash (probably a buggy one, hence traced).
Use
To not lose the data that the failing node stored
up to the point of crash, the control node tries to fetch
it before restarting trace. This must occur within the allowed
time frame, otherwise it is aborted (default is 10 seconds, but it
can be changed with
The autostart feature requires more data to be stored on
traced nodes. By default, the data is stored automatically
to the file named "ttb_autostart.bin" in the currect working directory
(cwd) of the traced node.
Users can change this behaviour (that is, on diskless
nodes) by specifying their own module to handle autostart data
storage and retrieval (
-module(ttb_autostart).
-export([read_config/0,
write_config/1,
delete_config/0]).
-define(AUTOSTART_FILENAME, "ttb_autostart.bin").
delete_config() ->
file:delete(?AUTOSTART_FILENAME).
read_config() ->
case file:read_file(?AUTOSTART_FILENAME) of
{ok, Data} -> {ok, binary_to_term(Data)};
Error -> Error
end.
write_config(Data) ->
file:write_file(?AUTOSTART_FILENAME, term_to_binary(Data)).
Remember that file trace ports buffer the data
by default. If the node crashes, trace messages are not
flushed to the binary log. If the risk of failure is
high, it can be a good idea to flush the buffers every
now and then automatically. Passing
Option
Command
In addition to the trace log file(s), a file with extension
Except for the process information, everything in the trace
information file is passed on to the handler function when
formatting. Parameter
Information to the trace information file by
can be added by calling
Example:
If you want to limit the size of the trace logs, you can use
wrap logs. This works almost like a circular buffer. You can
specify the maximum number of binary logs and the maximum size of
each log.
The overall size of data generated by
Wrap logs can be formatted one by one or all at once. See
Formatting can be done automatically when stopping
Formatting means to read a binary log and present it in a
readable format. You can use the default format handler in
The first argument to
The second argument to
Specifies the destination to write the formatted text.
Default destination is
Specifies the format handler to use. If this option is
not specified, option
Indicates that the logs are not to be merged according to time-stamp, but processed one file after another (this can be a bit faster).
A format handler is a fun taking four arguments. This fun is called for each trace message in the binary log(s). A simple example that only prints each trace message can be as follows:
fun(Fd, Trace, _TraceInfo, State) ->
io:format(Fd, "Trace: ~p~n", [Trace]),
State
end.
Here,
ttb:format("tiger@durin-ttb", [{handler, {{Mod,Fun}, initial_state}}])
^^^^^^^^^^^^^
Another format handler can be used to calculate the time spent by the garbage collector:
fun(_Fd,{trace_ts,P,gc_start,_Info,StartTs},_TraceInfo,State) ->
[{P,StartTs}|State];
(Fd,{trace_ts,P,gc_end,_Info,EndTs},_TraceInfo,State) ->
{value,{P,StartTs}} = lists:keysearch(P,1,State),
Time = diff(StartTs,EndTs),
io:format("GC in process ~w: ~w milliseconds~n", [P,Time]),
State -- [{P,StartTs}]
end
A more refined version of this format handler is function
The trace message is passed as the second argument (
By giving the format handler
You can always decide not to format the whole trace data contained
in the fetch directory, but analyze single files instead. To do so,
a single file (or list of files) must be passed as the first argument
to
Wrap logs can be formatted one by one or all at once. To
format one of the wrap logs in a set, specify the exact file name.
To format the whole set of wrap logs, specify the name with
Example:
Start tracing:
(tiger@durin)1> ttb:tracer(node(),{file,{wrap,"trace"}}). {ok,[tiger@durin]} (tiger@durin)2> ttb:p(...) ...
This gives a set of binary logs, for example:
tiger@durin-trace.0.wrp
tiger@durin-trace.1.wrp
tiger@durin-trace.2.wrp
...
Format the whole set of logs:
1> ttb:format("tiger@durin-trace.*.wrp"). .... ok 2>
Format only the first log:
1> ttb:format("tiger@durin-trace.0.wrp"). .... ok 2>
To merge all wrap logs from two nodes:
1> ttb:format(["tiger@durin-trace.*.wrp","lion@durin-trace.*.wrp"]). .... ok 2>
For detailed information about the Event Tracer, see the
By giving the format handler
The
The remaining filters only show function calls and
function returns. All other trace message are discarded. To get
the most out of these filters,
The same result can be obtained by using the flag
1> dbg:fun2ms(fun(_) -> return_trace(),message(caller()) end). [{'_',[],[{return_trace},{message,{caller}}]}]
This must however be done with care, as function
The
The
The
In the following example, modules
-module(foo).
-export([start/0,go/0]).
start() ->
spawn(?MODULE, go, []).
go() ->
receive
stop ->
ok;
go ->
bar:f1(),
go()
end.
-module(bar).
-export([f1/0,f3/0]).
f1() ->
f2(),
ok.
f2() ->
spawn(?MODULE,f3,[]).
f3() ->
ok.
Setting up the trace:
(tiger@durin)1> %%First we retrieve the Pid to limit traced processes set (tiger@durin)1> Pid = foo:start(). (tiger@durin)2> %%Now we set up tracing (tiger@durin)2> ttb:tracer(). (tiger@durin)3> ttb:p(Pid, [call, return_to, procs, set_on_spawn]). (tiger@durin)4> ttb:tpl(bar, []). (tiger@durin)5> %%Invoke our test function and see output with et viewer (tiger@durin)5> Pid ! go. (tiger@durin)6> ttb:stop({format, {handler, ttb:get_et_handler()}}).
This renders a result similar to the following:
Notice that function
(tiger@durin)1> Pid = foo:start(). (tiger@durin)2> ttb:start_trace([node()], [{bar,[]}], {Pid, [call, return_to, procs, set_on_spawn]} {handler, ttb:get_et_handler()}). (tiger@durin)3> Pid ! go. (tiger@durin)4> ttb:stop(format).
By default,
If option
For the tracing functionality,
Use
The main purpose of the history buffer is the possibility to create configuration files. Any function stored in the history buffer can be written to a configuration file and used for creating a specific configuration at any time with a single function call.
A configuration file is created or extended with
The complete content of the history buffer can be written to a
configuration file by calling
User-defined entries can also be written to a configuration file
by calling function
Any existing file
Example:
See the content of the history buffer:
(tiger@durin)191> ttb:tracer(). {ok,[tiger@durin]} (tiger@durin)192> ttb:p(self(),[garbage_collection,call]). {ok,{[<0.1244.0>],[garbage_collection,call]}} (tiger@durin)193> ttb:tp(ets,new,2,[]). {ok,[{matched,1}]} (tiger@durin)194> ttb:list_history(). [{1,{ttb,tracer,[tiger@durin,[]]}}, {2,{ttb,p,[<0.1244.0>,[garbage_collection,call]]}}, {3,{ttb,tp,[ets,new,2,[]]}}]
Execute an entry from the history buffer:
(tiger@durin)195> ttb:ctp(ets,new,2). {ok,[{matched,1}]} (tiger@durin)196> ttb:list_history(). [{1,{ttb,tracer,[tiger@durin,[]]}}, {2,{ttb,p,[<0.1244.0>,[garbage_collection,call]]}}, {3,{ttb,tp,[ets,new,2,[]]}}, {4,{ttb,ctp,[ets,new,2]}}] (tiger@durin)197> ttb:run_history(3). ttb:tp(ets,new,2,[]) -> {ok,[{matched,1}]}
Write the content of the history buffer to a configuration file:
(tiger@durin)198> ttb:write_config("myconfig",all). ok (tiger@durin)199> ttb:list_config("myconfig"). [{1,{ttb,tracer,[tiger@durin,[]]}}, {2,{ttb,p,[<0.1244.0>,[garbage_collection,call]]}}, {3,{ttb,tp,[ets,new,2,[]]}}, {4,{ttb,ctp,[ets,new,2]}}, {5,{ttb,tp,[ets,new,2,[]]}}]
Extend an existing configuration:
(tiger@durin)200> ttb:write_config("myconfig",[{ttb,tp,[ets,delete,1,[]]}], [append]). ok (tiger@durin)201> ttb:list_config("myconfig"). [{1,{ttb,tracer,[tiger@durin,[]]}}, {2,{ttb,p,[<0.1244.0>,[garbage_collection,call]]}}, {3,{ttb,tp,[ets,new,2,[]]}}, {4,{ttb,ctp,[ets,new,2]}}, {5,{ttb,tp,[ets,new,2,[]]}}, {6,{ttb,tp,[ets,delete,1,[]]}}]
Go back to a previous configuration after stopping Trace Tool Builder:
(tiger@durin)202> ttb:stop(). ok (tiger@durin)203> ttb:run_config("myconfig"). ttb:tracer(tiger@durin,[]) -> {ok,[tiger@durin]} ttb:p(<0.1244.0>,[garbage_collection,call]) -> {ok,{[<0.1244.0>],[garbage_collection,call]}} ttb:tp(ets,new,2,[]) -> {ok,[{matched,1}]} ttb:ctp(ets,new,2) -> {ok,[{matched,1}]} ttb:tp(ets,new,2,[]) -> {ok,[{matched,1}]} ttb:tp(ets,delete,1,[]) -> {ok,[{matched,1}]} ok
Write selected entries from the history buffer to a configuration file:
(tiger@durin)204> ttb:list_history(). [{1,{ttb,tracer,[tiger@durin,[]]}}, {2,{ttb,p,[<0.1244.0>,[garbage_collection,call]]}}, {3,{ttb,tp,[ets,new,2,[]]}}, {4,{ttb,ctp,[ets,new,2]}}, {5,{ttb,tp,[ets,new,2,[]]}}, {6,{ttb,tp,[ets,delete,1,[]]}}] (tiger@durin)205> ttb:write_config("myconfig",[1,2,3,6]). ok (tiger@durin)206> ttb:list_config("myconfig"). [{1,{ttb,tracer,[tiger@durin,[]]}}, {2,{ttb,p,[<0.1244.0>,[garbage_collection,call]]}}, {3,{ttb,tp,[ets,new,2,[]]}}, {4,{ttb,tp,[ets,delete,1,[]]}}] (tiger@durin)207>
To learn what sequential tracing is and how it can be used,
see the Reference Manual for
The support for sequential tracing provided by Trace Tool Builder includes the following:
Starting sequential tracing requires that a tracer is
started with function
Example 1:
In the following example, function
(tiger@durin)110> ttb:tracer(). {ok,[tiger@durin]} (tiger@durin)111> ttb:p(self(),call). {ok,{[<0.158.0>],[call]}} (tiger@durin)112> ttb:tp(dbg,get_tracer,0,ttb:seq_trigger_ms(send)). {ok,[{matched,1},{saved,1}]} (tiger@durin)113> dbg:get_tracer(), seq_trace:reset_trace(). true (tiger@durin)114> ttb:stop(format). ({<0.158.0>,{shell,evaluator,3},tiger@durin}) call dbg:get_tracer() SeqTrace [0]: ({<0.158.0>,{shell,evaluator,3},tiger@durin}) {<0.237.0>,dbg,tiger@durin} ! {<0.158.0>,{get_tracer,tiger@durin}} [Serial: {0,1}] SeqTrace [0]: ({<0.237.0>,dbg,tiger@durin}) {<0.158.0>,{shell,evaluator,3},tiger@durin} ! {dbg,{ok,#Port<0.222>}} [Serial: {1,2}] ok (tiger@durin)116>
Example 2:
Starting sequential tracing with a trigger is more useful if the trigger function is not called directly from the shell, but rather implicitly within a larger system. When calling a function from the shell, it is simpler to start sequential tracing directly, for example, as follows:
(tiger@durin)116> ttb:tracer(). {ok,[tiger@durin]} (tiger@durin)117> seq_trace:set_token(send,true), dbg:get_tracer(), seq_trace:reset_trace(). true (tiger@durin)118> ttb:stop(format). SeqTrace [0]: ({<0.158.0>,{shell,evaluator,3},tiger@durin}) {<0.246.0>,dbg,tiger@durin} ! {<0.158.0>,{get_tracer,tiger@durin}} [Serial: {0,1}] SeqTrace [0]: ({<0.246.0>,dbg,tiger@durin}) {<0.158.0>,{shell,evaluator,3},tiger@durin} ! {dbg,{ok,#Port<0.229>}} [Serial: {1,2}] ok (tiger@durin)120>
In both previous examples,
All functions in module
Module
Start calltrace on all processes and trace the specified
function(s). The format handler used is
Trace garbage collection on the specified process(es). The
format handler used is
Trace in-scheduling and out-scheduling on the specified process(es).
The format handler used is