Erlang's
dbg
module provides the ability to trace functions, processes, and
messages from within the terminal. I'd not played with it before tonight -
let's see what all it can do.
Shell
So we're just going to do all of this from within an iex session. Fire up
iex
.
Debugger help
The
dbg
module ships with its own tiny help feature. Let's look at that
briefly:
:dbg.h
(output)
The following help items are available:
p, c
- Set trace flags for processes
tp, tpl, ctp, ctpl, ctpg, ltp, dtp, wtp, rtp
- Manipulate trace patterns for functions
n, cn, ln
- Add/remove traced nodes.
tracer, trace_port, trace_client, get_tracer, stop, stop_clear
- Manipulate tracer process/port
i
- Info
call dbg:h(Item) for brief help a brief description
of one of the items above.
Alright, so here are the things you can do with this module. Let's look at the help for :tracer:
:dbg.h(:tracer)
(output)
iex(25)> :dbg.h(:tracer)
tracer() -> {ok, pid()} | {error, already_started}
- Starts a tracer server that handles trace messages.
tracer(Type, Data) -> {ok, pid()} | {error, Error}
- Starts a tracer server with additional parameters
Alright, I won't go through all of these, but it's neat enough that this help facility is built in. On to the debugging.
Basic Tracing
Let's start off by defining a module:
defmodule Foo do
def bar do
IO.puts "baz"
end
end
Alright, so this is about as simple of a situation as we could have - this is
just a function that calls a function on the
IO
module. Let's trace it to see
what happens when we call it.
:dbg.c(:"Elixir.Foo", :bar, []) # do not hit enter yet.
Alright, so here we're calling the
c/3
function on the
:dbg
module. This is
a native erlang module, which is why we reference it as its atom. It takes a
traditional MFA-style triplet of arguments. The first argument is the atom
representing our
Foo
module - since we're calling it from erlang code, we have
to provide its fully qualified name. If you weren't already aware, elixir
modules are just their module names, prefixed with "Elixir.", as an atom.
Anyway, this will call the
Foo.bar/0
function, and provide the trace. Go
ahead and run it. (hit enter)
Here, you can see all of the functions that were called and messages that were passed in order to fulfill this function invocation - it's a lot of detailed information that you just can't get without instrumentation in a lot of other languages. This is surprising to me, and it's pretty cool.
Setting trace flags on a pid
Alright, so say you wanted to trace just one particular pid - for instance, any messages or process events that occurred for that pid. You can do this too. First, we'll spawn a process:
defmodule Echo do
def start do
receive do
{from, message} ->
send(from, String.upcase(message))
end
end
end
pid = spawn(Echo, :start, [])
Next, we'll debug any messages or process events on that pid:
:dbg.p(pid, [:m, :procs])
Finally, we'll send it a message:
send(pid, {self, "hai"})
(output)
iex(22)> send(pid, {self, "hai"})
{#PID<0.42.0>, "hai"}
(<0.105.0>) << {<0.42.0>,<<"hai">>}
(<0.105.0>) <0.42.0> ! <<"HAI">>
(<0.105.0>) exit normal
iex(23)> flush
"HAI"
"HAI"
:ok
So here you can see that a message was sent to the pid we just spawned (pid 105) with the contents of our message (our pid, then the binary "hai"). You can see that it proceeded to send the upcased "HAI" binary back to us, and then exited normally. Finally, you can see that we received the message, if you flush this shell process's messages:
flush
Pretty cool! What else can we do?
Find out what's being traced
So say you wanted to know what was being traced at present. You would do this:
:dbg.i
It's showing no traces set up, because the pid that we were tracing exited already. Let's set up a trace for a little echo server again:
pid = spawn(Echo, :start, [])
:dbg.p(pid, [:m, :procs])
Alright, now run
:dbg.i
again. Now you can see the details on the current set
traces.
Summary
Looking through the docs, there's a lot more that the
dbg
module can do, but
this gets you a good feel for it without getting too boring and repetitive. See
you soon!
Comments for Tracing