Sequential tracing makes it possible
to trace all messages resulting from one initial
message. Sequential tracing is completely independent of the ordinary tracing
in Erlang, which is controlled by the erlang:trace/3
BIF. See the chapter
"What is Sequential Trace" below for more
information about what sequential tracing is and how it can be used.
seq_trace
provides functions which control all aspects of
sequential tracing. There are functions for activation, deactivation,
inspection and for collection of the trace output.
The implementation of sequential tracing is in beta status. This means that the programming interface still might undergo minor adjustments (possibly incompatible) based on feedback from users. |
set_token(Component, ComponentValue) -> {Component, PreviousValue}
Component = label | serial | Flag
Flag = send | 'receive' | print | timestamp
ComponentValue = FlagValue | LabelValue | SerialValue
FlagValue = bool() (for Flag)
LabelValue = integer() (for label)
SerialValue = {Previous, Current}
Previous = Current = integer()
Sets the individual Component
of the trace token to ComponentValue
.
Returns the previous value of the trace token Component
.
The valid Component, ComponentValue
combinations are:
label, integer()
label
component is an integer which identifies all
events belonging
to the same sequential trace.
If several sequential traces can be active simultaneously label
is used to identify the separate traces. Default is 0.
send, bool()
true | false
) which enables/disables
tracing on message sending. Default is false
.
'receive', bool()
true | false
) which enables/disables
tracing on message reception. Default is false
.
print, bool()
true | false
) which enables/disables
tracing on
explicit calls to seq_trace:print/1
. Default is false
.
timestamp, bool()
true | false
) which enables/disables
a timestamp
to be generated for each traced event.
Default is false
.
serial, SerialValue
SerialValue
= {Previous, Current}
. The
serial
component contains counters which enables the traced messages
to be sorted, should never be set explicitly by the user as these counters
are updated automatically. Default is {0, 0}
.
set_token(Token) -> PreviousToken
Token = PreviousToken = term() | []
Sets the trace token for the current process to Token
.
If Token = []
then tracing is disabled,
otherwise Token
should be an
Erlang term returned from get_token/0
or set_token/1
.
set_token/1
can be used to temporarily exclude message passing
from the trace by setting
the trace token to empty like this:
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.
get_token(Component) -> {Component, ComponentValue}
Component = label | serial | Flag
ComponentValue = FlagValue | LabelValue | SerialValue
Flag = send | 'receive' | print | timestamp
FlagValue = bool() (for Flag)
LabelValue = integer() (for label)
SerialValue = {Previous, Current} (for serial)
Previous = Current = integer()
Returns the value of the trace token componentComponent
.
TraceToken = term() | []
Returns the value of the trace token for the current process.
If []
is returned it means that tracing is not active.
Any other value returned
is the value of an active trace token. The value returned can be used
as input to the set_token/1
function.
TraceInfo = term()
Puts the Erlang term TraceInfo
into the sequential trace output
if the process currently is executing within a sequential trace and
the print
flag of the trace token is set.
Sets the trace token to empty for all processes in the 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 Erlang node.
set_system_tracer(ProcessOrPortId) -> PreviousId
Pid = PreviousId = pid() | port() | false
Sets the system tracer.
The system tracer can be either a pid or port denoted by ProcessOrPortId
.
Returns the previous value (which can be false
if no
system tracer is active). The function will generate
{'EXIT',{badarg,Info}}
if Pid
is not the pid of an existing local process.
get_system_tracer() -> pid() | port() | false
Returns the pid or port identifier of the current
system tracer or false
if no system tracer is activated.
The format of the messages are:
{seq_trace, Label, SeqTraceInfo, TimeStamp}
or
{seq_trace, Label, SeqTraceInfo}
depending on whether the timestamp
flag of the trace token is set to true
or false
.
Where :
Label = integer() TimeStamp = {Seconds, Milliseconds, Microseconds} Seconds = Milliseconds = Microseconds = integer()
The SeqTraceInfo
can have the following formats:
{send, Serial, From, To, Message}
From
with its trace token flag print
set to
true
has sent a message.
{'receive', Serial, From, To, Message}
To
receives a message with
a trace token that has the 'receive'
flag set to true
.
{print, Serial, From, _, Info}
From
has called
seq_trace:print(Label,Info)
and has a trace token with print
set to true
and label
set to Label
.
Serial = {PreviousSerial, ThisSerial} PreviousSerial = ThisSerial = integer() From = To = pid()
Serial
is a tuple consisting of two integers where the first
PreviousSerial
denotes
the serial counter passed in the last received message which carried a trace
token. If the process is the first one in a new sequential trace the
PreviousSerial
is set to the value of the process internal
"trace clock". The second integer ThisSerial
is the serial
counter that a process sets on outgoing messages and it is based on
the process internal "trace clock" which is incremented by one before
it is attached to the trace token in the message.
Sequential tracing is a way to trace a sequence of messages sent between different local or distributed processes where the sequence is initiated by one single message. In short it works like this:
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
{Label, Flags, Serial, From}
. The trace token is passed invisibly
with each message.
In order 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 automatically set to empty each time the process enters a receive statement but will be set to a value again if the received message carries a nonempty trace token.
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 send
or
'receive'
is set). The system tracer can then print each
trace event, write it to a file or whatever suitable.
The system tracer will only receive those trace events that occur locally within the Erlang node. To get the whole picture of a sequential trace that involves processes on several Erlang nodes, the output from the system tracer on each involved node must be merged (off line). |
In the following sections Sequential Tracing and its most fundamental concepts are described.
Each process has a current trace token. Initially the token is empty. When the process sends a message to another process, a copy of the current token will be sent "invisibly" along with the message. The current token of a process is set in two ways, either
seq_trace:set_token
, or
In both cases the current token will be set. In particular, if the token of a message received 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 1 and 2 above.
The trace token contains a component which we call the Serial
which consists of two integers Previous
and Current
.
The purpose of Serial
is uniquely identify each
traced event within a trace sequence and to order the messages chronologically
and in the different
branches if any.
The algorithm for updating Serial
can be described as follows:
Let each process have two counters prev_cnt
and curr_cnt
which
both are set to 0 when a process is created. The counters are updated at the
following occasions:
tprev
and tcurr
.
curr_cnt := curr_cnt + 1
tprev := prev_cnt
tcurr := curr_cnt
tprev
and tcurr
is then passed along
with the message.
seq_trace:print(Label,Info)
the Label
matches the label part of the trace token and the trace token print flag
is true.
tprev
and tcurr
.
if (curr_cnt < tcurr )
curr_cnt := tcurr
prev_cnt := tprev
The curr_cnt
of a process is incremented each time the process is
involved in a sequential trace. The counter can reach its limit (27 bits)
if a process is very long-lived and is involved in much sequential
tracing. If the counter overflows it will not be possible to use the Serial for
ordering of the trace events. To prevent the counter from overflowing in the middle
of a sequential trace the function seq_trace:reset_trace/0
can be called
to reset the prev_cnt
and curr_cnt
of all processes in the
Erlang node. This function will also set all trace tokens in processes and their
message queues to empty and will thus stop all ongoing sequential tracing.
The performance degradation for a system which is enabled for Sequential tracing is negligible as long as no tracing is activated. When tracing is activated there will of course be an extra cost for each traced message but all other messages will be unaffected.
Sequential tracing is not performed across ports.
If the user for some reason wants to pass the trace token to a port this
has to 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
seq_trace:get_token/1
and include
trace information in the message data sent to their respective
ports.
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 seq_trace:set_token/2
.
Sequential tracing between nodes is performed transparently. This applies to C-nodes built with Erl_interface too. A C-node built with Erl_interface only maintains one trace token which means that the C-node will appear as one process from the sequential tracing point of view.
In order to be able to perform sequential tracing between distributed Erlang nodes, the distribution protocol has been extended (in a backward compatible way). An Erlang node which supports sequential tracing can communicate with an older (OTP R3B) node but messages passed within that node can of course not be traced.
The example shown here will give rough idea of how the new primitives can be used and what kind of output it will produce.
Assume that we have an initiating process with Pid = <0.30.0> like this:
-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 'call_server' with Pid = <0.31.0> like this:
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 sequential_tracer (inspired by AXE-10 and MD-110) could look like:
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 the printout above could 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]).
The code that creates a process that runs the tracer function above and sets that process as the system tracer could 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/0
below the whole example can be
started.
test() -> P = spawn(?MODULE, loop, [port]), register(call_server, spawn(?MODULE, loop, [])), start(), P ! {port,message}.