[erlang-questions] Tracing and debugging
Mon Jan 19 13:21:53 CET 2015
> On 13 Jan 2015, at 13:36, Vlad Dumitrescu <vladdu55@REDACTED> wrote:
> The main reason I use the debugger is to check that the intermediary values in a computation are the expected ones. The alternative (which works without messing up timeouts) is to print out values at points of interest, but it is messy (there's a lot of boilerplate to type and the interesting code becomes hard to read).
> So, I thought, what if, instead of interpreting a module in order to debug it, we compile it with a special parse transform that inserts tracing calls after each expression in the code, automatically keeping track of the variables visible in the scope and their values?
I don’t consider myself qualified to discuss the details of a bytecode-level debugger, but it seems to me as if it would be a very difficult thing to get right, balancing usability during debugging with efficiency of bytecode while not debugging (not to mention the effects of aggressive compile-time optimizations).
Then again, anything written after “I dont consider myself qualified… but” can probably safely be ignored.
In terms of writing debuggable code, there are some things that can be done without hacking the VM:
* Write many small functions
* Separate out side-effects, keeping as many functions as possible side-effect free
After this, tracing can help you capture function inputs, and you can then single-step through a troublesome function at your leisure.
* If your functions take records as input, use the shell function rr(Module) so you can use record syntax from the shell.
For me, this takes care of the vast majority of cases where single-stepping can come into play. Possibly due to my own personal bent, I always end up trying to debug distributed coordination logic, in which case, this is would be my current recommendation:
* Instead of io:format() debugging, call an empty function that can be traced when needed
I’ve been using this when debugging the ‘locks’ application, and while it didn’t make debugging leader election during netsplits exactly easy, it helped a great deal. Example below.
In the locks application, I wrote a module, locks_ttb.erl, which is a thin wrapper around the trace tool ttb.
To begin with, I use a debug macro I call ?event() in the code:
-define(event(E), event(?LINE, E, none)).
-define(event(E, S), event(?LINE, E, S)).
The event/3 function itself is as light as possible:
event(_Line, _Event, _State) ->
It contains a convenient function for starting a multi-node trace on my strategic ‘debug functions’. Another function, locks_ttb:format(File, OutFile) takes a merged trace log (containing timestamped traces from all nodes) and pretty-prints the traces, not least checking for record field metadata and using record syntax if possible.
In particular, I use this in the unit test for leader_election with netsplit:
The setup call to locks_ttb enables tracing on the test nodes as well as the test function itself (which also uses ‘event’ function debug entries). The trace data is collected only if the test fails. It can then be analyzed with the help of locks_ttb:format/2.
I’ve actually been meaning to test this style of trace/debug for years, but never quite got around to it. There are quite a few things that could be done beyond this - those to remember the PROTEST project may recall a few more or less wild ideas. What I’ve done in ‘locks' is actually quite basic.
I’ve been thinking about taking this a step further, writing it up and perhaps giving a presentation about it, but I’ve been having too much fun.
The only thing locks-specific in locks_ttb.erl, BTW, is the function default_pattern/0.
Ulf Wiger, Co-founder & Developer Advocate, Feuerlabs Inc.
-------------- next part --------------
An HTML attachment was scrubbed...
More information about the erlang-questions