Sync’ up! … without getting drained

apr 21

Farewell, io:format

Well, it’s a dark day indeed. I’m saying goodbye to Erlang’s family of io:format/n routines. We’ve had a good long run, and I’m sad that I may never ‘print-to-screen’ again.

Looking back, it was pretty nice having io:format/n around. We had some good times, for sure. Heck, it got me out of many jams & pickles; how can I ever say thanks enough.

The bugs ahead

It’s hard to imagine turning one’s back on such a mainstay routine as ‘print-to-screen.’ But after an afternoon with the Erlang runtime-tool ‘dbg,’ it’s unlikely one would ever want to debug OTP code (and systems) any other way.

So, here’s the scoop: any place you would like to peer into your Erlang code and see what it’s up to, instead of putting some ‘print-to-screen’ lines in your source-code, employ ‘dbg’ instead.

Want a ‘dbg’ ice-breaker? Then read ahead!

Holy moly, ‘dbg’

I’ve used ‘redbug’ in the past to debug production systems, and have read Fred’s treatise in support of his ‘recon’ libraries (which do sound fantastic). But nothing feels better than using those low-level tools Erlang provides right out of the box. With that, how about we write some code and take ‘dbg’ for a spin?

Let’s create a dummy Erlang module, compile it & then debug it using ‘dbg’ in the Erlang shell. Here’s a module called ‘foo.erl’ :

-module(foo).
-export([manydiv/2]).

%% Given a list of, we assume,
%% numbers, divide them all
%% by the second parameter to 
%% return their new values.
manydiv([], _) -> nothing_to_do;
manydiv(L, N) when is_list(L) ->
    [ K / N || K <- N ].

You can compile this single module with a quick erlc foo.erl on the command-line. No need for anything fancier here!

Now, let’s fire up an Erlang shell and play around with ‘foo’ alongside ‘dbg.’

erl -sname debug

Assuming all went well, you should be greeted with the typical Erlang banner. We can fire up ‘dbg’ right after we load & test our compiled code in the shell.

1> l(foo).
2> A = [9.0, 9, 2, -9].
3> foo:manydiv(A, 2).

Oops. That last line throws an exception. Time for ‘dbg.’ Let’s trace this function to see what’s going on…

The following is a typical way of getting ‘dbg’ started succinctly & safely. Others may have their pet way of doing it, but this works well for me:

4> dbg:stop_clear().
5> dbg:tracer(), dbg:p(all, c).

Let’s pause and quickly look at what’s happening. On line four, we make sure to start a trace with a clean slate; this will annihilate any other ‘dbg’ sessions which may be running (a great habit to get into).

On line five, we start a tracer, and tell said tracer to consider calls to all processes. Both tracer and p can take oodles of options (as you’ll quickly see by looking in the ‘dbg’ manual), but we don’t need anything fancy for what we’re up to.

Now, to get some information on our buggy routine, ‘manydiv,’ we’ll want to observe inputs & return values at very least. We do this the following way:

6> dbg:tpl(foo, manydiv, x).

Again, the manual will give inquiring minds more information on what’s happening behind the scenes. But from a high level, we are now tracing the ‘foo:manydiv/2’ routine!

Let’s debug

With our function now being traced, let’s run our code again:

7> foo:manydiv(A, 2).

There. Now we are able to observe the arguments passed to the routine, and what was returned, all thanks to tracing. So, we are passing the list as expected; and the number ‘2’ as the second argument. And we are getting an {error,function_clause} exception on line ten.

Looking at the source-code on line ten, we spot the problem. Our list comprehension is using the wrong variable! It should be iterating over the list, not the second argument. With our editor, we make the fix as follows:

...

manydiv(L, N) when is_list(L) ->
    [ K / N || K <- L ].

...

Let’s stay in the Erlang shell to compile this time. Running this all again: did we fix the bug?

8>  c(foo).
9>  dbg:tpl(foo, manydiv, x).
10> foo:manydiv(A, 2). 

There, that’s playing nice, finally. You can see that both the function & tracer are returning the expected value now. Bug fixed!

Before we adjourn this exercise, let’s try to break our routine again, this time by dividing by zero:

11> foo:manydiv(A, 0). 

With this, the new exception is {error,badarith}. To fix this bug, We can make a small adjustment to the code by adding another clause to manydiv/2 :

...

manydiv(_, 0)  -> {error, div_by_zero};
manydiv([], _) -> nothing_to_do; 
manydiv(L, N) when is_list(L) ->
    [ K / N || K <- L ].

...

It’s debatable whether this is defensive programming; in fact I would vote to keep this new clause out of a real code-base. But, just so we can play with ‘dbg’ some more, let’s go with it.

Again, we compile the code, and try it out:

12> c(foo).
13> dbg:tpl(foo, manydiv, x).
14> foo:manydiv(A, 0). 

Great! Everything looks correct. Let’s now drop the tracing of this routine, as there’s no more debugging needed to be done:

15> dbg:ctpl(foo, manydiv).

And of course, when your session is over, go ahead and stop the tool entirely via:

16> dbg:stop_clear().

A pair of stop_clear/0 routines should be the bookends for your debugging session, generally. Especially in production!

Wrapping up

So, there you have it. The world’s coolest companion for a functional language, ‘dbg.’ This is just the nickel tour, but it should whet your appetite for more. And honestly, after getting these few runtime commands under your belt, you just may not need to ‘print-to-screen’ any longer.

Give ‘dbg’ a try & fall in love with Erlang all over again!

Update Sep. 2016

It is not possible to trace on what are called ‘guard bifs,’ i.e. functions in the ‘erlang’ module that can be used in guards.

Click here for a full list of those functions.