dbg(3erl)

๐Ÿ“ฆ
version 25.3.2.8 ยท 3 versions
, in debian bookworm, ubuntu noble

โœจ NAME โœจ

Okay so this is dbg โ€” The Text Based Trace Facility โ€” aka dbg(3erl) from the Erlang Module Definition docs. Itโ€™s the text-based tracing bestie, and she is literally here to help you see functions, processes, ports, and messages doing their thing. ๐Ÿ’…โœจ

๐Ÿ” DESCRIPTION ๐Ÿ”

So dbg implements a text based interface to the trace/3 and the trace_pattern/2 BIFs. That means you can trace functions, processes, ports, and messages โ€” the whole main-character lineup. ๐Ÿ‘‘

To quickly get started on tracing function calls, you can use the following code in the Erlang shell:

1> dbg:tracer(). %% Start the default trace message receiver
{ok,<0.36.0>}
2> dbg:p(all, c). %% Setup call (c) tracing on all processes
{ok,[{matched,nonode@nohost,26}]}
3> dbg:tp(lists, seq, x). %% Setup an exception return trace (x) on lists:seq
{ok,[{matched,nonode@nohost,2},{saved,x}]}
4> lists:seq(1,10).
(<0.34.0>) call lists:seq(1,10)
(<0.34.0>) returned from lists:seq/2 -> [1,2,3,4,5,6,7,8,9,10]
[1,2,3,4,5,6,7,8,9,10]

For more examples of how to use dbg from the Erlang shell, see the simple example section.

These utilities are also suitable to use in system testing on large systems, where other tools have too much impact on the system performance. Some primitive support for sequential tracing is also included โ€” see the advanced topics section. ๐ŸŒธ

๐Ÿ› ๏ธ EXPORTS ๐Ÿ› ๏ธ

๐ŸŒŸ fun2ms(LiteralFun) -> MatchSpec ๐ŸŒŸ

Types:

LiteralFun = fun() literal
MatchSpec = term()

Pseudo function that, by means of a parse_transform, translates the literal fun() you typed as the parameter in the function call into a match specification as described in the match_spec manual of the ERTS users guide. (And when I say literal, I mean the fun() needs to textually be written as the parameter of the function โ€” it cannot be held in a variable which in turn is passed to the function. Thatโ€™s the rule, bestie. No cap.) ๐ŸŽ€

The parse transform is implemented in the module ms_transform, and the source must include the file ms_transform.hrl in STDLIB for this pseudo function to work. Failing to include the hrl file in the source will result in a runtime error, not a compile time ditto. The include file is easiest included by adding the line -include_lib("stdlib/include/ms_transform.hrl"). to the source file.

The fun() is very restricted: it can take only a single parameter (the parameter list to match), a sole variable, or a list. It needs to use the is_XXX guard tests, and one cannot use language constructs that have no representation in a match_spec (like if, case, receive, etc.). The return value from the fun will be the return value of the resulting match_spec.

Example:

1> dbg:fun2ms(fun([M,N]) when N > 3 -> return_trace() end).
[{['$1','$2'],[{'>','$2',3}],[{return_trace}]}]

Variables from the environment can be imported, so that this works:

2> X=3.
3
3> dbg:fun2ms(fun([M,N]) when N > X -> return_trace() end).
[{['$1','$2'],[{'>','$2',{const,3}}],[{return_trace}]}]

The imported variables will be replaced by match_spec const expressions, which is consistent with the static scoping for Erlang fun()s. Local or global function calls cannot be in the guard or body of the fun, however. Calls to builtin match_spec functions are, of course, allowed:

4> dbg:fun2ms(fun([M,N]) when N > X, is_atomm(M) -> return_trace() end).
Error: fun containing local erlang function calls ('is_atomm' called in guard)\
 cannot be translated into match_spec
{error,transform_error}
5> dbg:fun2ms(fun([M,N]) when N > X, is_atom(M) -> return_trace() end).
[{['$1','$2'],[{'>','$2',{const,3}},{is_atom,'$1'}],[{return_trace}]}]

As you can see by the example, the function can be called from the shell too. The fun() needs to be literally in the call when used from the shell as well. Other means than the parse_transform are used in the shell case, but more or less the same restrictions apply (the exception being records, as they are not handled by the shell).

โš ๏ธ Warning: If the parse_transform is not applied to a module which calls this pseudo function, the call will fail in runtime (with a badarg). The module dbg actually exports a function with this name, but it should never really be called except for when using the function in the shell. If the parse_transform is properly applied by including the ms_transform.hrl header file, compiled code will never call the function โ€” instead the function call is replaced by a literal match_spec. More information is provided by the ms_transform manual page in STDLIB.

๐ŸŒŸ h() -> ok ๐ŸŒŸ

h stands for help. Gives a list of items for brief online help. Cute, right? ๐ŸŽ€

๐ŸŒŸ h(Item) -> ok ๐ŸŒŸ

Types:

Item = atom()

h stands for help. Gives a brief help text for functions in the dbg module. The available items can be listed with dbg:h/0.

๐ŸŒŸ p(Item) -> {ok, MatchDesc} | {error, term()} ๐ŸŒŸ

Equivalent to p(Item, [m]).

๐ŸŒŸ p(Item, Flags) -> {ok, MatchDesc} | {error, term()} ๐ŸŒŸ

Types:

MatchDesc = [MatchNum]
MatchNum = {matched, node(), integer()} | {matched, node(), 0, RPCError}
RPCError = term()

p stands for process. Traces Item in accordance to the value specified by Flags. The variation of Item is listed below:

  • pid() or port(): The corresponding process or port is traced. The process or port may be a remote process or port (on another Erlang node). The node must be in the list of traced nodes (see n/1 and tracer/3).
  • all: All processes and ports in the system as well as all processes and ports created hereafter are to be traced.
  • processes: All processes in the system as well as all processes created hereafter are to be traced.
  • ports: All ports in the system as well as all ports created hereafter are to be traced.
  • new: All processes and ports created after the call are to be traced.
  • new_processes: All processes created after the call are to be traced.
  • new_ports: All ports created after the call are to be traced.
  • existing: All existing processes and ports are traced.
  • existing_processes: All existing processes are traced.
  • existing_ports: All existing ports are traced.
  • atom(): The process or port with the corresponding registered name is traced. The process or port may be a remote process (on another Erlang node). The node must be added with the n/1 or tracer/3 function.
  • integer(): The process <0.Item.0> is traced.
  • {X, Y, Z}: The process <X.Y.Z> is traced.
  • string(): If the Item is a string "<X.Y.Z>" as returned from pid_to_list/1, the process <X.Y.Z> is traced.

When enabling an Item that represents a group of processes, the Item is enabled on all nodes added with the n/1 or tracer/3 function.

Flags can be a single atom, or a list of flags. The available flags are:

  • s (send): Traces the messages the process or port sends.
  • r (receive): Traces the messages the process or port receives.
  • m (messages): Traces the messages the process or port receives and sends.
  • c (call): Traces global function calls for the process according to the trace patterns set in the system (see tp/2).
  • p (procs): Traces process related events to the process.
  • ports: Traces port related events to the port.
  • sos (set on spawn): Lets all processes created by the traced process inherit the trace flags of the traced process.
  • sol (set on link): Lets another process, P2, inherit the trace flags of the traced process whenever the traced process links to P2.
  • sofs (set on first spawn): This is the same as sos, but only for the first process spawned by the traced process.
  • sofl (set on first link): This is the same as sol, but only for the first call to link/1 by the traced process.
  • all: Sets all flags except silent.
  • clear: Clears all flags.

The list can also include any of the flags allowed in erlang:trace/3.

The function returns either an error tuple or a tuple {ok, List}. The List consists of specifications of how many processes and ports that matched (in the case of a pure pid() exactly 1). The specification of matched processes is {matched, Node, N}. If the remote processor call, rpc, to a remote node fails, the rpc error message is delivered as a fourth argument and the number of matched processes are 0. Note that the result {ok, List} may contain a list where rpc calls to one, several or even all nodes failed.

๐ŸŒŸ c(Mod, Fun, Args) ๐ŸŒŸ

Equivalent to c(Mod, Fun, Args, all).

๐ŸŒŸ c(Mod, Fun, Args, Flags) ๐ŸŒŸ

c stands for call. Evaluates the expression apply(Mod, Fun, Args) with the trace flags in Flags set. This is a convenient way to trace processes from the Erlang shell.

๐ŸŒŸ i() -> ok ๐ŸŒŸ

i stands for information. Displays information about all traced processes and ports.

๐ŸŒŸ tp(Module,MatchSpec) ๐ŸŒŸ

Same as tp({Module, '_', '_'}, MatchSpec)

๐ŸŒŸ tp(Module,Function,MatchSpec) ๐ŸŒŸ

Same as tp({Module, Function, '_'}, MatchSpec)

๐ŸŒŸ tp(Module, Function, Arity, MatchSpec) ๐ŸŒŸ

Same as tp({Module, Function, Arity}, MatchSpec)

๐ŸŒŸ tp({Module, Function, Arity}, MatchSpec) -> {ok, MatchDesc} | {error, term()} ๐ŸŒŸ

Types:

Module = atom() | '_'
Function = atom() | '_'
Arity = integer() |'_'
MatchSpec = integer() | Built-inAlias | [] | match_spec()
Built-inAlias = x | c | cx
MatchDesc = [MatchInfo]
MatchInfo = {saved, integer()} | MatchNum
MatchNum = {matched, node(), integer()} | {matched, node(), 0, RPCError}

okay so tp stands for trace pattern, and it's literally the moment the group chat gets interesting ๐Ÿ‘‘. This function enables call trace for one or more functions. All exported functions matching the {Module, Function, Arity} argument will be concerned, but the match_spec() may further narrow down the set of function calls generating trace messages. ๐Ÿ’…

For the match_spec() syntax, bestie, turn to the User's guide part of the online documentation for the runtime system (erts). The chapter Match Specifications in Erlang explains the general match specification "language". The most common generic match specifications used can be found as Built-inAlias โ€” see ltp/0 below for details. โœจ

The Module, Function and/or Arity parts of the tuple may be specified as the atom '_', which is a "wild-card" matching all modules/functions/arities. Note: if Module is specified as '_', the Function and Arity parts have to be specified as '_' too. The same holds for the Function relation to the Arity. No cap. ๐Ÿซถ

All nodes added with n/1 or tracer/3 will be affected by this call, and if Module is not '_' the module will be loaded on all nodes. ๐ŸŒˆ

The function returns either an error tuple or a tuple {ok, List}. The List consists of specifications of how many functions that matched, in the same way as the processes and ports are presented in the return value of p/2. ๐Ÿ’–

There may be a tuple {saved, N} in the return value, if the MatchSpec is other than []. The integer N may then be used in subsequent calls to this function and will stand as an "alias" for the given expression. There are also a couple of built-in aliases for common expressions, see ltp/0 below for details. ๐ŸŽ€

If an error is returned, it can be due to errors in compilation of the match specification. Such errors are presented as a list of tuples {error, string()} where the string is a textual explanation of the compilation error. An example:

(x@y)4> dbg:tp({dbg,ltp,0},[{[],[],[{message, two, arguments}, {noexist}]}]).
{error,
 [{error,"Special form 'message' called with wrong number of
          arguments in {message,two,arguments}."},
  {error,"Function noexist/1 does_not_exist."}]}

tpl(Module,MatchSpec)

Same as tpl({Module, '_', '_'}, MatchSpec).

tpl(Module,Function,MatchSpec)

Same as tpl({Module, Function, '_'}, MatchSpec).

tpl(Module, Function, Arity, MatchSpec)

Same as tpl({Module, Function, Arity}, MatchSpec).

tpl({Module, Function, Arity}, MatchSpec) -> {ok, MatchDesc} | {error, term()}

tpl stands for trace pattern local, and it's giving "I see you, local calls" ๐Ÿ’…. This function works as tp/2, but enables tracing for local calls (and local functions) as well as for global calls (and functions).

tpe(Event, MatchSpec) -> {ok, MatchDesc} | {error, term()}

Types:

Event = send | 'receive'
MatchSpec = integer() | Built-inAlias | [] | match_spec()
Built-inAlias = x | c | cx
MatchDesc = [MatchInfo]
MatchInfo = {saved, integer()} | MatchNum
MatchNum = {matched, node(), 1} | {matched, node(), 0,
RPCError}

tpe stands for trace pattern event. This function associates a match specification with trace event send or 'receive'. By default all executed send and 'receive' events are traced if enabled for a process. A match specification can be used to filter traced events based on sender, receiver and/or message content. ๐Ÿฆ‹

For the match_spec() syntax, turn to the User's guide part of the online documentation for the runtime system (erts). The chapter Match Specifications in Erlang explains the general match specification "language". โœจ

For send, the matching is done on the list [Receiver, Msg]. Receiver is the process or port identity of the receiver and Msg is the message term. The pid of the sending process can be accessed with the guard function self/0. ๐Ÿ’Œ

For 'receive', the matching is done on the list [Node, Sender, Msg]. Node is the node name of the sender. Sender is the process or port identity of the sender, or the atom undefined if the sender is not known (which may be the case for remote senders). Msg is the message term. The pid of the receiving process can be accessed with the guard function self/0. ๐Ÿ“ฅ

All nodes added with n/1 or tracer/3 will be affected by this call. ๐ŸŒˆ

The return value is the same as for tp/2. The number of matched events are never larger than 1 as tpe/2 does not accept any form of wildcards for argument Event. Periodt. ๐Ÿ‘‘

ctp()

Same as ctp({'_', '_', '_'}).

ctp(Module)

Same as ctp({Module, '_', '_'}).

ctp(Module, Function)

Same as ctp({Module, Function, '_'}).

ctp(Module, Function, Arity)

Same as ctp({Module, Function, Arity}).

ctp({Module, Function, Arity}) -> {ok, MatchDesc} | {error, term()}

Types:

Module = atom() | '_'
Function = atom() | '_'
Arity = integer() | '_'
MatchDesc = [MatchNum]
MatchNum = {matched, node(), integer()} | {matched, node(), 0,
RPCError}

ctp stands for clear trace pattern. This function disables call tracing on the specified functions. The semantics of the parameter is the same as for the corresponding function specification in tp/2 or tpl/2. Both local and global call trace is disabled. ๐Ÿงน

The return value reflects how many functions that matched, and is constructed as described in tp/2. No tuple {saved, N} is however ever returned (for obvious reasons). ๐Ÿ’€

ctpl()

Same as ctpl({'_', '_', '_'}).

ctpl(Module)

Same as ctpl({Module, '_', '_'}).

ctpl(Module, Function)

Same as ctpl({Module, Function, '_'}).

ctpl(Module, Function, Arity)

Same as ctpl({Module, Function, Arity}).

ctpl({Module, Function, Arity}) -> {ok, MatchDesc} | {error, term()}

ctpl stands for clear trace pattern local. This function works as ctp/1, but only disables tracing set up with tpl/2 (not with tp/2). โœจ

ctpg()

Same as ctpg({'_', '_', '_'}).

ctpg(Module)

Same as ctpg({Module, '_', '_'}).

ctpg(Module, Function)

Same as ctpg({Module, Function, '_'}).

ctpg(Module, Function, Arity)

Same as ctpg({Module, Function, Arity}).

ctpg({Module, Function, Arity}) -> {ok, MatchDesc} | {error, term()}

ctpg stands for clear trace pattern global. This function works as ctp/1, but only disables tracing set up with tp/2 (not with tpl/2). ๐ŸŒ

ctpe(Event) -> {ok, MatchDesc} | {error, term()}

Types:

Event = send | 'receive'
MatchDesc = [MatchNum]
MatchNum = {matched, node(), 1} | {matched, node(), 0,
RPCError}

ctpe stands for clear trace pattern event. This function clears match specifications for the specified trace event (send or 'receive'). It will revert back to the default behavior of tracing all triggered events. ๐ŸŽ€

The return value follow the same style as for ctp/1.

ltp() -> ok

ltp stands for list trace patterns. Use this function to recall all match specifications previously used in the session (i.e. previously saved during calls to tp/2, and built-in match specifications). This is very useful, as a complicated match_spec can be quite awkward to write. Note that the match specifications are lost if stop/0 is called. ๐Ÿ˜ญ

Match specifications used can be saved in a file (if a read-write file system is present) for use in later debugging sessions, see wtp/1 and rtp/1. ๐Ÿ’พ

There are three built-in trace patterns: exception_trace, caller_trace and caller_exception_trace (or x, c and cx respectively). Exception trace sets a trace which will show function names, parameters, return values and exceptions thrown from functions. Caller traces display function names, parameters and information about which function called it. An example using a built-in alias:

(x@y)4> dbg:tp(lists,sort,cx).
{ok,[{matched,nonode@nohost,2},{saved,cx}]}
(x@y)4> lists:sort([2,1]).
(<0.32.0>) call lists:sort([2,1]) ({erl_eval,do_apply,5})
(<0.32.0>) returned from lists:sort/1 -> [1,2]
[1,2]

dtp() -> ok

dtp stands for delete trace patterns. Use this function to "forget" all match specifications saved during calls to tp/2. This is useful when one wants to restore other match specifications from a file with rtp/1. Use dtp/1 to delete specific saved match specifications. ๐Ÿ—‘๏ธ

dtp(N) -> ok

Types:

N = integer()

dtp stands for delete trace pattern. Use this function to "forget" a specific match specification saved during calls to tp/2. ๐Ÿ™ˆ

wtp(Name) -> ok | {error, IOError}

Types:

Name = string()
IOError = term()

wtp stands for write trace patterns. This function will save all match specifications saved during the session (during calls to tp/2) and built-in match specifications in a text file with the name designated by Name. The format of the file is textual, which is why it can be edited with an ordinary text editor, and then restored with rtp/1. ๐Ÿ“

Each match spec in the file ends with a full stop (.) and new (syntactically correct) match specifications can be added to the file manually. โœจ

The function returns ok or an error tuple where the second element contains the I/O error that made the writing impossible. ๐Ÿ’”

rtp(Name) -> ok | {error, Error}

Types:

Name = string()
Error = term()

rtp stands for read trace patterns. This function reads match specifications from a file (possibly) generated by the wtp/1 function. It checks the syntax of all match specifications and verifies that they are correct. The error handling principle is "all or nothing", i.e. if some of the match specifications are wrong, none of the specifications are added to the list of saved match specifications for the running system. ๐Ÿซถ

The match specifications in the file get merged with the current match specifications, so that no duplicates are generated. Use ltp/0 to see what numbers were assigned to the specifications from the file. ๐Ÿ’…

The function will return an error, either because of I/O problems (like a non existing or non readable file) or because of file format problems. The errors from a bad format file are in a more or less textual format, which will give a hint to what's causing the problem. ๐Ÿ”


n(Nodename) -> {ok, Nodename} | {error, Reason}

Types:

Nodename = atom()
Reason = term()

n stands for node. The dbg server keeps a list of nodes where tracing should be performed. Whenever a tp/2 call or a p/2 call is made, it is executed for all nodes in this list including the local node (except for p/2 with a specific pid() or port() as first argument, in which case the command is executed only on the node where the designated process or port resides). ๐Ÿซถ

This function adds a remote node (Nodename) to the list of nodes where tracing is performed. It starts a tracer process on the remote node, which will send all trace messages to the tracer process on the local node (via the Erlang distribution). If no tracer process is running on the local node, the error reason no_local_tracer is returned. The tracer process on the local node must be started with the tracer/0/2 function. ๐Ÿ˜ค

If Nodename is the local node, the error reason cant_add_local_node is returned.

If a trace port (see trace_port/2) is running on the local node, remote nodes cannot be traced with a tracer process. The error reason cant_trace_remote_pid_to_local_port is returned. A trace port can however be started on the remote node with the tracer/3 function.

The function will also return an error if the node Nodename is not reachable. ๐Ÿ“ก


cn(Nodename) -> ok

Types:

Nodename = atom()

cn stands for clear node. Clears a node from the list of traced nodes. Subsequent calls to tp/2 and p/2 will not consider that node, but tracing already activated on the node will continue to be in effect. ๐Ÿ‘€

Returns ok, cannot fail. Slay, a function that literally cannot fail. ๐Ÿ’…


ln() -> ok

ln stands for list nodes. Shows the list of traced nodes on the console. ๐Ÿ–ฅ๏ธ


tracer() -> {ok, pid()} | {error, already_started}

This function starts a server on the local node that will be the recipient of all trace messages. All subsequent calls to p/2 will result in messages sent to the newly started trace server. โœจ

A trace server started in this way will simply display the trace messages in a formatted way in the Erlang shell (i. e. use io:format). See tracer/2 for a description of how the trace message handler can be customized.

To start a similar tracer on a remote node, use n/1.


tracer(Type, Data) -> {ok, pid()} | {error, Error}

Types:

Type = port | process | module
Data = PortGenerator | HandlerSpec | ModuleSpec
PortGenerator = fun() (no arguments)
Error = term()
HandlerSpec = {HandlerFun, InitialData}
HandlerFun = fun() (two arguments)
ModuleSpec = fun() (no arguments) | {TracerModule, TracerState}
TracerModule = atom()
InitialData = TracerState = term()

This function starts a tracer server with additional parameters on the local node. The first parameter, the Type, indicates if trace messages should be handled by a receiving process (process), by a tracer port (port) or by a tracer module (module). For a description about tracer ports see trace_port/2 and for a tracer modules see erl_tracer. ๐ŸŒˆ

If Type is process, a message handler function can be specified (HandlerSpec). The handler function, which should be a fun taking two arguments, will be called for each trace message, with the first argument containing the message as it is and the second argument containing the return value from the last invocation of the fun. The initial value of the second parameter is specified in the InitialData part of the HandlerSpec. The HandlerFun may choose any appropriate action to take when invoked, and can save a state for the next invocation by returning it. ๐Ÿ’–

If Type is port, then the second parameter should be a fun which takes no arguments and returns a newly opened trace port when called. Such a fun is preferably generated by calling trace_port/2.

if Type is module, then the second parameter should be either a tuple describing the erl_tracer module to be used for tracing and the state to be used for that tracer module or a fun returning the same tuple. ๐Ÿงš

If an error is returned, it can either be due to a tracer server already running ({error,already_started}) or due to the HandlerFun throwing an exception. ๐Ÿ’€

To start a similar tracer on a remote node, use tracer/3.


tracer(Nodename, Type, Data) -> {ok, Nodename} | {error, Reason}

Types:

Nodename = atom()

This function is equivalent to tracer/2, but acts on the given node. A tracer is started on the node (Nodename) and the node is added to the list of traced nodes. ๐ŸŽ€

Note: This function is not equivalent to n/1. While n/1 starts a process tracer which redirects all trace information to a process tracer on the local node (i.e. the trace control node), tracer/3 starts a tracer of any type which is independent of the tracer on the trace control node.

For details, see tracer/2.


trace_port(Type, Parameters) -> fun()

Types:

Type = ip | file
Parameters = Filename | WrapFilesSpec | IPPortSpec
Filename = string() | [string()] | atom()
WrapFilesSpec = {Filename, wrap, Suffix} | {Filename, wrap, Suffix, WrapSize} | {Filename, wrap, Suffix, WrapSize, WrapCnt}
Suffix = string()
WrapSize = integer() >= 0 | {time, WrapTime}
WrapTime = integer() >= 1
WrapCnt = integer() >= 1
IpPortSpec = PortNumber | {PortNumber, QueSize}
PortNumber = integer()
QueSize = integer()

This function creates a trace port generating fun. The fun takes no arguments and returns a newly opened trace port. The return value from this function is suitable as a second parameter to tracer/2, i.e. dbg:tracer(port, dbg:trace_port(ip, 4711)). ๐Ÿ”ฅ

A trace port is an Erlang port to a dynamically linked in driver that handles trace messages directly, without the overhead of sending them as messages in the Erlang virtual machine. โœจ

Two trace drivers are currently implemented, the file and the ip trace drivers. The file driver sends all trace messages into one or several binary files, from where they later can be fetched and processed with the trace_client/2 function. The ip driver opens a TCP/IP port where it listens for connections. When a client (preferably started by calling trace_client/2 on another Erlang node) connects, all trace messages are sent over the IP network for further processing by the remote client. ๐ŸŒ

Using a trace port significantly lowers the overhead imposed by using tracing.

The file trace driver expects a filename or a wrap files specification as parameter. A file is written with a high degree of buffering, why all trace messages are not guaranteed to be saved in the file in case of a system crash. That is the price to pay for low tracing overhead. ๐Ÿ˜ญ

A wrap files specification is used to limit the disk space consumed by the trace. The trace is written to a limited number of files each with a limited size. The actual filenames are Filename ++ SeqCnt ++ Suffix, where SeqCnt counts as a decimal string from 0 to WrapCnt and then around again from 0. When a trace term written to the current file makes it longer than WrapSize, that file is closed, if the number of files in this wrap trace is as many as WrapCnt the oldest file is deleted then a new file is opened to become the current. Thus, when a wrap trace has been stopped, there are at most WrapCnt trace files saved with a size of at least WrapSize (but not much bigger), except for the last file that might even be empty. The default values are WrapSize = 128*1024 and WrapCnt = 8. ๐Ÿ—‚๏ธ

The SeqCnt values in the filenames are all in the range 0 through WrapCnt with a gap in the circular sequence. The gap is needed to find the end of the trace. ๐Ÿ‘‘

If the WrapSize is specified as {time, WrapTime}, the current file is closed when it has been open more than WrapTime milliseconds, regardless of it being empty or not. โฐ

The ip trace driver has a queue of QueSize messages waiting to be delivered. If the driver cannot deliver messages as fast as they are produced by the runtime system, a special message is sent, which indicates how many messages that are dropped. That message will arrive at the handler function specified in trace_client/3 as the tuple {drop, N} where N is the number of consecutive messages dropped. In case of heavy tracing, drop's are likely to occur, and they surely occur if no client is reading the trace messages. The default value of QueSize is 200. ๐Ÿ˜ฎโ€๐Ÿ’จ


flush_trace_port()

Equivalent to flush_trace_port(node()).


flush_trace_port(Nodename) -> ok | {error, Reason}

Equivalent to trace_port_control(Nodename,flush).


trace_port_control(Operation)

Equivalent to trace_port_control(node(),Operation).


trace_port_control(Nodename,Operation) -> ok | {ok, Result} | {error, Reason}

Types:

Nodename = atom()

This function is used to do a control operation on the active trace port driver on the given node (Nodename). Which operations are allowed as well as their return values depend on which trace driver is used. ๐ŸŽ›๏ธ

Returns either ok or {ok, Result} if the operation was successful, or {error, Reason} if the current tracer is a process or if it is a port not supporting the operation. ๐Ÿ’…

  • flush โ€” flushes out the internal buffers that the trace port driver is holding onto. Right now only the file trace driver supports this op. Returns ok. ๐Ÿงผ
  • get_listen_port โ€” returns {ok, IpPort} where IpPort is the IP port number used by the driver's listen socket. Only the ip trace driver supports this. ๐ŸŽง

trace_client(Type, Parameters) -> pid()

Types:

Type = ip | file | follow_file
Parameters = Filename | WrapFilesSpec | IPClientPortSpec
Filename = string() | [string()] | atom()
WrapFilesSpec = see trace_port/2
Suffix = string()
IpClientPortSpec = PortNumber | {Hostname, PortNumber}
PortNumber = integer()
Hostname = string()

okay so this one starts a trace client that reads whatever output a trace port driver made and handles it basically the same way a tracer process made by tracer/0 would. iconic behavior honestly ๐Ÿ’…

if Type is file, the client reads every trace message stored in the file named Filename, or one specified by WrapFilesSpec (and it must be the same one used when the trace was created โ€” see trace_port/2), and then lets the default handler function format the messages on the console. this is literally one way to interpret the data a file trace port driver stored. ๐Ÿ“‚โœจ

if Type is follow_file, the client acts like the file case but keeps trying to read (and process) more data from the file until it's stopped by stop_trace_client/1. heads up bestie: WrapFilesSpec is not allowed as the second argument for this Type. ๐Ÿ™…โ€โ™€๏ธ

if Type is ip, the client connects to the TCP/IP port PortNumber on the host Hostname, and reads trace messages from there until the TCP/IP connection closes. no Hostname? then the local host is assumed. ๐Ÿ–ฅ๏ธ๐Ÿ’ซ

as an example, you can let trace messages be sent over the network to another Erlang node (preferably not distributed), and do the formatting over there:

on the node stack there's an Erlang node ant@stack โ€” in the shell, type this:

ant@stack> dbg:tracer(port, dbg:trace_port(ip,4711)).
<0.17.0>
ant@stack> dbg:p(self(), send).
{ok,1}

now all trace messages are sent to the trace port driver, which in turn listens for connections on TCP/IP port 4711. if we wanna see the messages on another node โ€” preferably on another host โ€” we do this:

-> dbg:trace_client(ip, {"stack", 4711}).
<0.42.0>

and if we now send a message from the shell on the node ant@stack, where all sends from the shell are traced:

ant@stack> self() ! hello.
hello

the following will appear at the console on the node that started the trace client:

(<0.23.0>) <0.23.0> ! hello
(<0.23.0>) <0.22.0> ! {shell_rep,<0.23.0>,{value,hello,[],[]}}

the last line is generated because of internal message passing in the Erlang shell. the process ids will vary, no cap. ๐Ÿซถ

trace_client(Type, Parameters, HandlerSpec) -> pid()

Types:

Type = ip | file | follow_file
Parameters = Filename | WrapFilesSpec | IPClientPortSpec
Filename = string() | [string()] | atom()
WrapFilesSpec = see trace_port/2
Suffix = string()
IpClientPortSpec = PortNumber | {Hostname, PortNumber}
PortNumber = integer()
Hostname = string()
HandlerSpec = {HandlerFun, InitialData}
HandlerFun = fun() (two arguments)
InitialData = term()

this works exactly like trace_client/2 but lets you write your own handler function. ๐ŸŽจ the handler function works mostly like the one described in tracer/2, but it also has to be prepared to handle trace messages shaped like {drop, N}, where N is the number of dropped messages. this pseudo trace message only ever shows up if the ip trace driver is being used. ๐Ÿ’€

for trace type file, the pseudo trace message end_of_trace will appear at the end of the trace. the return value from the handler function is ignored in this case. ๐Ÿ™ˆ

stop_trace_client(Pid) -> ok

Types:

Pid = pid()

this shuts down a trace client that was started earlier. the Pid argument is the process id you got back from the trace_client/2 or trace_client/3 call. bye bestie ๐Ÿ‘‹โœจ

get_tracer()

equivalent to get_tracer(node()). same energy, less typing.

get_tracer(Nodename) -> {ok, Tracer}

Types:

Nodename = atom()
Tracer = port() | pid() | {module(), term()}

returns the process, port, or tracer module that all trace messages are being sent to. ๐Ÿ‘‘

stop() -> ok

stops the dbg server, clears all trace flags for all processes, clears all trace patterns for all functions, clears trace patterns for send/receive, shuts down all trace clients, and closes all trace ports. full reset, main character cleanup montage ๐Ÿงน๐Ÿ’…


๐ŸŒธ SIMPLE EXAMPLES โ€” TRACING FROM THE SHELL ๐ŸŒธ

the simplest way of tracing from the Erlang shell is to use dbg:c/3 or dbg:c/4, like tracing the function dbg:get_tracer/0:

(tiger@durin)84> dbg:c(dbg,get_tracer,[]).
(<0.154.0>) <0.152.0> ! {<0.154.0>,{get_tracer,tiger@durin}}
(<0.154.0>) out {dbg,req,1}
(<0.154.0>) << {dbg,{ok,<0.153.0>}}
(<0.154.0>) in {dbg,req,1}
(<0.154.0>) << timeout
{ok,<0.153.0>}
(tiger@durin)85>

another way of tracing from the shell is to explicitly start a tracer and then set whatever trace flags you want on the processes you wanna trace โ€” like trace messages and process events:

(tiger@durin)66> Pid = spawn(fun() -> receive {From,Msg} -> From ! Msg end end).
<0.126.0>
(tiger@durin)67> dbg:tracer().
{ok,<0.128.0>}
(tiger@durin)68> dbg:p(Pid,[m,procs]).
{ok,[{matched,tiger@durin,1}]}
(tiger@durin)69> Pid ! {self(),hello}.
(<0.126.0>) << {<0.116.0>,hello}
{<0.116.0>,hello}
(<0.126.0>) << timeout
(<0.126.0>) <0.116.0> ! hello
(<0.126.0>) exit normal
(tiger@durin)70> flush().
Shell got hello
ok
(tiger@durin)71>

if you set the call trace flag, you also have to set a trace pattern for the functions you wanna trace:

(tiger@durin)77> dbg:tracer().
{ok,<0.142.0>}
(tiger@durin)78> dbg:p(all,call).
{ok,[{matched,tiger@durin,3}]}
(tiger@durin)79> dbg:tp(dbg,get_tracer,0,[]).
{ok,[{matched,tiger@durin,1}]}
(tiger@durin)80> dbg:get_tracer().
(<0.116.0>) call dbg:get_tracer()
{ok,<0.143.0>}
(tiger@durin)81> dbg:tp(dbg,get_tracer,0,[{'_',[],[{return_trace}]}]).
{ok,[{matched,tiger@durin,1},{saved,1}]}
(tiger@durin)82> dbg:get_tracer().
(<0.116.0>) call dbg:get_tracer()
(<0.116.0>) returned from dbg:get_tracer/0 -> {ok,<0.143.0>}
{ok,<0.143.0>}
(tiger@durin)83>

๐Ÿฆ‹ ADVANCED TOPICS โ€” COMBINING WITH SEQ_TRACE ๐Ÿฆ‹

the dbg module is primarily targeted towards tracing through the erlang:trace/3 function. but sometimes you wanna trace messages in a more delicate way, and that's where the seq_trace module comes in clutch. ๐Ÿ’ซ

seq_trace implements sequential tracing (known in the AXE10 world, and sometimes called "forlopp tracing"). dbg can interpret messages generated from seq_trace, and you can use the same tracer function for both types of tracing. the seq_trace messages can even be sent to a trace port for further analysis. ๐Ÿ”ฌ

and because a match specification can turn on sequential tracing, combining dbg and seq_trace can be, like, genuinely powerful. this brief example shows a session where sequential tracing is used:

1> dbg:tracer().
{ok,<0.30.0>}
2> {ok, Tracer} = dbg:get_tracer().
{ok,<0.31.0>}
3> seq_trace:set_system_tracer(Tracer).
false
4> dbg:tp(dbg, get_tracer, 0, [{[],[],[{set_seq_token, send, true}]}]).
{ok,[{matched,nonode@nohost,1},{saved,1}]}
5> dbg:p(all,call).
{ok,[{matched,nonode@nohost,22}]}
6> dbg:get_tracer(), seq_trace:set_token([]).
(<0.25.0>) call dbg:get_tracer()
SeqTrace [0]: (<0.25.0>) <0.30.0> ! {<0.25.0>,get_tracer} [Serial: {2,4}]
SeqTrace [0]: (<0.30.0>) <0.25.0> ! {dbg,{ok,<0.31.0>}} [Serial: {4,5}]
{1,0,5,<0.30.0>,4}

so this session sets the system_tracer to the same process as the ordinary tracer process (i.e. <0.31.0>) and sets the trace pattern for the function dbg:get_tracer to one that has the action of setting a sequential token. when that function is called by a traced process (all processes are traced in this case), the process gets "contaminated" by the token, and seq_trace messages are sent both for the server request and the response. ๐Ÿงต

the seq_trace:set_token([]) after the call clears the seq_trace token โ€” which is why no messages are sent when the answer propagates via the shell to the console port. otherwise the output would've been way more noisy. ๐Ÿ’€๐Ÿ”‡

๐Ÿšจ NOTE OF CAUTION ๐Ÿšจ

Okay bestie, listen up, because this part is important ๐Ÿ’…. When you're tracing function calls on a group leader process (that's an IO process, iykyk), there is a very real risk of causing a deadlock. ๐Ÿ˜ญ

It happens like this: the group leader process generates a trace message, and then the tracer process โ€” by calling the trace handler function โ€” sends an IO request to that same group leader. Boom. Deadlock. ๐Ÿ’€

The problem can only occur if the trace handler prints to tty using an io function such as format/2. Also note: when dbg:p(all,call) is called, IO processes are also traced. Here's an example:

%% Using a default line editing shell
1> dbg:tracer(process, {fun(Msg,_) -> io:format("~p~n", [Msg]), 0 end, 0}).
{ok,<0.37.0>}
2> dbg:p(all, [call]).
{ok,[{matched,nonode@nohost,25}]}
3> dbg:tp(mymod,[{'_',[],[]}]).
{ok,[{matched,nonode@nohost,0},{saved,1}]}
4> mymod: % TAB pressed here
%% -- Deadlock --

Here's another example:

%% Using a shell without line editing (oldshell)
1> dbg:tracer(process).
{ok,<0.31.0>}
2> dbg:p(all, [call]).
{ok,[{matched,nonode@nohost,25}]}
3> dbg:tp(lists,[{'_',[],[]}]).
{ok,[{matched,nonode@nohost,0},{saved,1}]}
% -- Deadlock --

The reason we get a deadlock in the first example is because when TAB is pressed to expand the function name, the group leader (which handles character input) calls mymod:module_info(). This generates a trace message which, in turn, causes the tracer process to send an IO request to the group leader (by calling io:format/2). We end up in a deadlock. ๐Ÿ˜ตโ€๐Ÿ’ซ

In the second example we use the default trace handler function. This handler prints to tty by sending IO requests to the user process. When Erlang is started in oldshell mode, the shell process will have user as its group leader and so will the tracer process in this example. Since user calls functions in lists we end up in a deadlock as soon as the first IO request is sent. ๐Ÿซ 

Here are a few suggestions for how to avoid deadlock:

  • Don't trace the group leader of the tracer process. If tracing has been switched on for all processes, call dbg:p(TracerGLPid,clear) to stop tracing the group leader (TracerGLPid). process_info(TracerPid,group_leader) tells you which process this is (TracerPid is returned from dbg:get_tracer/0). ๐Ÿง โœจ
  • Don't trace the user process if using the default trace handler function. ๐Ÿ™…โ€โ™€๏ธ
  • In your own trace handler function, call erlang:display/1 instead of an io function or, if user is not used as group leader, print to user instead of the default group leader. Example: io:format(user,Str,Args). ๐Ÿ’–

That's the whole page, bestie! ๐ŸŽ€ Huge thanks to Ericsson AB for this one โ€” it's the runtime_tools 1.19 version of the dbg(3erl) manual page. Bye, love you, go trace responsibly! ๐Ÿฆ‹โœจ๐Ÿ’…

๐Ÿ“ฆ where this page comes from