recon_trace
is a module that handles tracing in a safe manner for single
Erlang nodes, currently for function calls only.
Authors: Fred Hebert (mononcqc@ferd.ca) [web site: http://ferd.ca/].
recon_trace
is a module that handles tracing in a safe manner for single
Erlang nodes, currently for function calls only. Functionality includes:
dbg
or trace BIFs.The Erlang Trace BIFs allow to trace any Erlang code at all. They work in two parts: pid specifications, and trace patterns.
Pid specifications let you decide which processes to target. They can be
specific pids, all
pids, existing
pids, or new
pids (those not
spawned at the time of the function call).
The trace patterns represent functions. Functions can be specified in two
parts: specifying the modules, functions, and arguments, and then with
Erlang match specifications to add constraints to arguments (see
calls/3
for details).
What defines whether you get traced or not is the intersection of both:
_,--------,_ _,--------,_ ,-' `-,,-' `-, ,-' ,-' '-, `-, | Matching -' '- Matching | | Pids | Getting | Trace | | | Traced | Patterns | | -, ,- | '-, '-, ,-' ,-' '-,_ _,-''-,_ _,-' '--------' '--------'
If either the pid specification excludes a process or a trace pattern excludes a given call, no trace will be received.
First let's trace the queue:new
functions in any process:
1> recon_trace:calls({queue, new, '_'}, 1). 1 13:14:34.086078 <0.44.0> queue:new() Recon tracer rate limit tripped.
The limit was set to 1
trace message at most, and recon
let us
know when that limit was reached.
Let's instead look for all the queue:in/2
calls, to see what it is
we're inserting in queues:
2> recon_trace:calls({queue, in, 2}, 1). 1 13:14:55.365157 <0.44.0> queue:in(a, {[],[]}) Recon tracer rate limit tripped.
In order to see the content we want, we should change the trace patterns
to use a fun
that matches on all arguments in a list (_
) and returns
return_trace()
. This last part will generate a second trace for each
call that includes the return value:
3> recon_trace:calls({queue, in, fun(_) -> return_trace() end}, 3). 1 13:15:27.655132 <0.44.0> queue:in(a, {[],[]}) 13:15:27.655467 <0.44.0> queue:in/2 --> {[a],[]} 13:15:27.757921 <0.44.0> queue:in(a, {[],[]}) Recon tracer rate limit tripped.
Matching on argument lists can be done in a more complex manner:
4> recon_trace:calls( 4> {queue, '_', fun([A,_]) when is_list(A); is_integer(A) andalso A > 1 -> return_trace() end}, 4> {10,100} 4> ). 32 13:24:21.324309 <0.38.0> queue:in(3, {[],[]}) 13:24:21.371473 <0.38.0> queue:in/2 --> {[3],[]} 13:25:14.694865 <0.53.0> queue:split(4, {[10,9,8,7],[1,2,3,4,5,6]}) 13:25:14.695194 <0.53.0> queue:split/2 --> {{[4,3,2],[1]},{[10,9,8,7],[5,6]}} 5> recon_trace:clear(). ok
Note that in the pattern above, no specific function ('_'
) was
matched against. Instead, the fun
used restricted functions to those
having two arguments, the first of which is either a list or an integer
greater than 1
.
The limit was also set using {10,100}
instead of an integer, making the
rate-limitting at 10 messages per 100 milliseconds, instead of an absolute
value.
Any tracing can be manually interrupted by calling recon_trace:clear()
,
or killing the shell process.
Be aware that extremely broad patterns with lax rate-limitting (or very
high absolute limits) may impact your node's stability in ways
recon_trace
cannot easily help you with.
In doubt, start with the most restrictive tracing possible, with low limits, and progressively increase your scope.
See calls/3
for more details and tracing possibilities.
This library is production-safe due to taking the following structure for tracing:
[IO/Group leader] <---------------------, | | [shell] ---> [tracer process] ----> [formatter]
The tracer process receives trace messages from the node, and enforces limits in absolute terms or trace rates, before forwarding the messages to the formatter. This is done so the tracer can do as little work as possible and never block while building up a large mailbox.
The tracer process is linked to the shell, and the formatter to the tracer process. The formatter also traps exits to be able to handle all received trace messages until the tracer termination, but will then shut down as soon as possible.
In case the operator is tracing from a remote shell which gets disconnected, the links between the shell and the tracer should make it so tracing is automatically turned off once you disconnect.
If sending output to the Group Leader is not desired, you may specify
a different pid() via the option io_server
in the calls/3
function.
For instance to write the traces to a file you can do something like
1> {ok, Dev} = file:open("/tmp/trace",[write]). 2> recon_trace:calls({queue, in, fun(_) -> return_trace() end}, 3, [{io_server, Dev}]). 1 3> Recon tracer rate limit tripped. 4> file:close(Dev).
The only output still sent to the Group Leader is the rate limit being tripped, and any errors. The rest will be sent to the other IO server (see http://erlang.org/doc/apps/stdlib/io_protocol.html).
recon_rec:import([Module, ...])
, after which the records declared in
the module list will be supported.
args() = '_' | 0..255 | return_trace | matchspec() | shellfun()
fn() = '_' | atom()
formatterfun() = fun((term()) -> iodata())
matchspec() = [{[term()], [term()], [term()]}]
max() = max_traces() | max_rate()
max_rate() = {max_traces(), millisecs()}
max_traces() = non_neg_integer()
millisecs() = non_neg_integer()
mod() = '_' | module()
num_matches() = non_neg_integer()
options() = [{pid, pidspec() | [pidspec(), ...]} | {timestamp, formatter | trace} | {args, args | arity} | {io_server, pid()} | {formatter, formatterfun()} | return_to | {return_to, boolean()} | {scope, global | local}]
pidspec() = all | existing | new | recon:pid_term()
shellfun() = fun((term()) -> term())
tspec() = {mod(), fn(), args()}
calls/2 | Equivalent to calls({Mod, Fun, Args}, Max, []). |
calls/3 | Allows to set trace patterns and pid specifications to trace function calls. |
clear/0 | Stops all tracing at once. |
format/1 | |
format_trace_output/1 | formats call arguments and return values - most types are just printed out, except for tuples recognised as records, which mimic the source code syntax. |
format_trace_output/2 |
calls(TSpecs::tspec() | [tspec(), ...], Max::max()) -> num_matches()
Equivalent to calls({Mod, Fun, Args}, Max, []).
calls(TSpecs::tspec() | [tspec(), ...], Max::max(), Opts::options()) -> num_matches()
Allows to set trace patterns and pid specifications to trace function calls.
The basic calls take the trace patterns as tuples of the form
{Module, Function, Args}
where:
Module
is any atom representing a moduleFunction
is any atom representing a function, or the wildcard
'_'
Args
is either the arity of a function (0..255
), a wildcard
pattern ('_'
), a
match specification,
or a function from a shell session that can be transformed into
a match specificationThere is also an argument specifying either a maximal count (a number)
of trace messages to be received, or a maximal frequency ({Num, Millisecs}
).
Here are examples of things to trace:
queue
module, with 10 calls printed at most:
recon_trace:calls({queue, '_', '_'}, 10)
lists:seq(A,B)
, with 100 calls printed at most:
recon_trace:calls({lists, seq, 2}, 100)
lists:seq(A,B)
, with 100 calls per second at most:
recon_trace:calls({lists, seq, 2}, {100, 1000})
lists:seq(A,B,2)
(all sequences increasing by two)
with 100 calls at most:
recon_trace:calls({lists, seq, fun([_,_,2]) -> ok end}, 100)
iolist_to_binary/1
made with a binary as an argument
already (kind of useless conversion!):
recon_trace:calls({erlang, iolist_to_binary, fun([X]) when is_binary(X) -> ok end}, 10)
Pid
, at a rate
of 50 per second at most:
recon_trace:calls({queue, '_', '_'}, {50,1000}, [{pid, Pid}])
recon_trace:calls(TSpec, Max, [{args, arity}])
filter/2
functions of both dict
and lists
modules,
across new processes only:
recon_trace:calls([{dict,filter,2},{lists,filter,2}], 10, [{pid, new}])
handle_call/3
functions of a given module for all new processes,
and those of an existing one registered with gproc
:
recon_trace:calls({Mod,handle_call,3}, {10,100}, [{pid, [{via, gproc, Name}, new]}
recon_trace:calls({Mod,Fun,fun(_) -> return_trace() end}, Max, Opts)
or
recon_trace:calls({Mod,Fun,[{'_', [], [{return_trace}]}]}, Max, Opts)
,
the important bit being the return_trace()
call or the
{return_trace}
match spec value.
A short-hand version for this pattern of 'match anything, trace everything'
for a function is recon_trace:calls({Mod, Fun, return_trace})
. There's a few more combination possible, with multiple trace patterns per call, and more options:
{pid, PidSpec}
: which processes to trace. Valid options is any of
all
, new
, existing
, or a process descriptor ({A,B,C}
,
"<A.B.C>"
, an atom representing a name, {global, Name}
,
{via, Registrar, Name}
, or a pid). It's also possible to specify
more than one by putting them in a list.{timestamp, formatter | trace}
: by default, the formatter process
adds timestamps to messages received. If accurate timestamps are
required, it's possible to force the usage of timestamps within
trace messages by adding the option {timestamp, trace}
.{args, arity | args}
: whether to print arity in function calls
or their (by default) literal representation.{scope, global | local}
: by default, only 'global' (fully qualified
function calls) are traced, not calls made internally. To force tracing
of local calls, pass in {scope, local}
. This is useful whenever
you want to track the changes of code in a process that isn't called
with Module:Fun(Args)
, but just Fun(Args)
.{formatter, fun(Term) -> io_data() end}
: override the default
formatting functionality provided by recon.{io_server, pid() | atom()}
: by default, recon logs to the current
group leader, usually the shell. This option allows to redirect
trace output to a different IO server (such as a file handle).return_to
: If this option is set (in conjunction with the match
option {scope, local}
), the function to which the value is returned
is output in a trace. Note that this is distinct from giving the
*caller* since exception handling or calls in tail position may
hide the original caller.Max
values (i.e. 99999999
or
{10000,1}
) will probably negate most of the safe-guarding this library
does and be dangerous to your node. Similarly, tracing extremely large
amounts of function calls (all of them, or all of io
for example)
can be risky if more trace messages are generated than any process on
the node could ever handle, despite the precautions taken by this library.
clear() -> ok
Stops all tracing at once.
format(TraceMsg) -> any()
format_trace_output(Args) -> any()
formats call arguments and return values - most types are just printed out, except for tuples recognised as records, which mimic the source code syntax
format_trace_output(Recs, Args) -> any()
Generated by EDoc