Sequential tracing makes it possible to trace all messages
resulting from one initial message. Sequential tracing is
independent of the ordinary tracing in Erlang, which
is controlled by the
An opaque term (a tuple) representing a trace token.
Sets the trace token for the calling process to
OldToken = seq_trace:set_token([]), % set to empty and save
% old value
% do something that should not be part of the trace
io:format("Exclude the signalling caused by this~n"),
seq_trace:set_token(OldToken), % activate the trace token again
...
Returns the previous value of the trace token.
Sets the individual
The
Labels were restricted to small signed integers (28 bits) prior to OTP 21. The trace token will be silenty dropped if it crosses over to a node that does not support the label.
A trace token flag (
A trace token flag (
A trace token flag (
A trace token flag (
A trace token flag (
A trace token flag (
A trace token flag (
If multiple timestamp flags are passed,
Returns the value of the trace token for the calling process.
If
Returns the value of the trace token component
Puts the Erlang term
Same as
Sets the trace token to empty for all processes on the local node. The process internal counters used to create the serial of the trace token is set to 0. The trace token is set to empty for all messages in message queues. Together this will effectively stop all ongoing sequential tracing in the local node.
Sets the system tracer. The system tracer can be either a
process, port or
Failure:
Returns the pid, port identifier or tracer module of the current system
tracer or
The format of the messages is one of the following, depending on if
flag
{seq_trace, Label, SeqTraceInfo, TimeStamp}
or
{seq_trace, Label, SeqTraceInfo}
Where:
Label = int()
TimeStamp = {Seconds, Milliseconds, Microseconds}
Seconds = Milliseconds = Microseconds = int()
Used when a process
Used when a process
Used when a process
Used when a process
Integer
Integer
Sequential tracing is a way to trace a sequence of messages sent between different local or remote processes, where the sequence is initiated by a single message. In short, it works as follows:
Each process has a trace token, which can be empty or
not empty. When not empty, the trace token can be seen as
the tuple
To start a sequential trace, the user must explicitly set the trace token in the process that will send the first message in a sequence.
The trace token of a process is set each time the process matches a message in a receive statement, according to the trace token carried by the received message, empty or not.
On each Erlang node, a process can be set as the system tracer.
This process will receive trace messages each time
a message with a trace token is sent or received (if the trace
token flag
The system tracer only receives those trace events that occur locally within the Erlang node. To get the whole picture of a sequential trace, involving processes on many Erlang nodes, the output from the system tracer on each involved node must be merged (offline).
The following sections describe sequential tracing and its most fundamental concepts.
Each process has a current trace token, which is copied from the process that spawned it. When a process sends a message to another process, a copy of the current token is sent "invisibly" along with the message.
The current token of a process is set in one of the following two ways:
Explicitly by the process itself, through a call to
When a message is received
In both cases, the current token is set. In particular, if the token of a received message is empty, the current token of the process is set to empty.
A trace token contains a label and a set of flags. Both the label and the flags are set in both alternatives above.
The trace token contains a component called
The algorithm for updating
Let each process have two counters,
When the process is about to send a message and the trace token is not empty.
Let the serial of the trace token be
curr_cnt := curr_cnt + 1 tprev := prev_cnt tcurr := curr_cnt
The trace token with
When the process is about to spawn another process and the trace token is not empty.
The counters of the parent process are updated in the same way as for send above. The trace token is then passed to the child process, whose counters will be set as follows:
curr_cnt := tcurr
prev_cnt := tcurr
When the process calls
The algorithm is the same as for send above.
When a message is received and contains a non-empty trace token.
The process trace token is set to the trace token from the message.
Let the serial of the trace token be
curr_cnt := tcurr
prev_cnt := tcurr
The performance degradation for a system that is enabled for sequential tracing is negligible as long as no tracing is activated. When tracing is activated, there is an extra cost for each traced message, but all other messages are unaffected.
Sequential tracing is not performed across ports.
If the user for some reason wants to pass the trace token to a
port, this must be done manually in the code of the port
controlling process. The port controlling processes have to check
the appropriate sequential trace settings (as obtained from
Similarly, for messages received from a port, a port controller
has to retrieve trace-specific information, and set appropriate
sequential trace flags through calls to
Sequential tracing between nodes is performed transparently.
This applies to C-nodes built with
This example gives a rough idea of how the new primitives can be used and what kind of output it produces.
Assume that you have an initiating process with
-module(seqex).
-compile(export_all).
loop(Port) ->
receive
{Port,Message} ->
seq_trace:set_token(label,17),
seq_trace:set_token('receive',true),
seq_trace:set_token(print,true),
seq_trace:print(17,"**** Trace Started ****"),
call_server ! {self(),the_message};
{ack,Ack} ->
ok
end,
loop(Port).
And a registered process
loop() ->
receive
{PortController,Message} ->
Ack = {received, Message},
seq_trace:print(17,"We are here now"),
PortController ! {ack,Ack}
end,
loop().
A possible output from the system's
17:<0.30.0> Info {0,1} WITH "**** Trace Started ****" 17:<0.31.0> Received {0,2} FROM <0.30.0> WITH {<0.30.0>,the_message} 17:<0.31.0> Info {2,3} WITH "We are here now" 17:<0.30.0> Received {2,4} FROM <0.31.0> WITH {ack,{received,the_message}}
The implementation of a system tracer process that produces this printout can look like this:
tracer() ->
receive
{seq_trace,Label,TraceInfo} ->
print_trace(Label,TraceInfo,false);
{seq_trace,Label,TraceInfo,Ts} ->
print_trace(Label,TraceInfo,Ts);
_Other -> ignore
end,
tracer().
print_trace(Label,TraceInfo,false) ->
io:format("~p:",[Label]),
print_trace(TraceInfo);
print_trace(Label,TraceInfo,Ts) ->
io:format("~p ~p:",[Label,Ts]),
print_trace(TraceInfo).
print_trace({print,Serial,From,_,Info}) ->
io:format("~p Info ~p WITH~n~p~n", [From,Serial,Info]);
print_trace({'receive',Serial,From,To,Message}) ->
io:format("~p Received ~p FROM ~p WITH~n~p~n",
[To,Serial,From,Message]);
print_trace({send,Serial,From,To,Message}) ->
io:format("~p Sent ~p TO ~p WITH~n~p~n",
[From,Serial,To,Message]);
print_trace({spawn,Serial,Parent,Child,_}) ->
io:format("~p Spawned ~p AT ~p~n",
[Parent,Child,Serial]).
The code that creates a process that runs this tracer function and sets that process as the system tracer can look like this:
start() ->
Pid = spawn(?MODULE,tracer,[]),
seq_trace:set_system_tracer(Pid), % set Pid as the system tracer
ok.
With a function like
test() ->
P = spawn(?MODULE, loop, [port]),
register(call_server, spawn(?MODULE, loop, [])),
start(),
P ! {port,message}.