Making new program visualizations with “Execution Records”


Debugging a program means trying to understand it, but our understanding is limited by the visibility we have into these often opaque processes.  GDB-style stepping debuggers give us some visibility, but are limited to show us one snapshot at a time.

The goal of this work is to enable:
  1. New kinds of interactive visualizations and debuggers that surface different kinds of information about program behavior.  Examples: flamegraphs of the call stack over time; force-directed-graph visualizations for object graphs
  1. Experimentation in debuggers and domain specific debuggers, both without having to change the code they’re debugging code (eg. add logging or expose debugger hooks), nor having to know how to pause/start/step arbitrary processes. 
  1. a mechanism to query program behavior in the way SQL is used to query real-world data (once it’s been stored in a db)
  1. framework/library error messages on incorrect usage which can point to more specific mistakes in user code than the line of the calling function.
  1. omniscient debugging of spurious failures in the wild

I introduce the Evaluation Record, which record the value of every expression in a single execution of a program.  Separate runs produce separate records.  These records are plain objects in an easy to understand representation, so they are easily queried by writing programs that process them.   Visualizations like flamegraphs are easy to build on top of these projections of the records (proof of concept:

This eases innovation in debuggers because they can be backed by Evaluation Records without understanding how to start/pause/control program execution.  An omniscient debugger can be implemented as just a visualization/exploration of an Evaluation Record, since the Evaluation Record contains all the information about the program over time.  (Proof of concept: a classical GDB-style debugger

The Evaluation Record representation of a run of a program

At its simplest, a Record contains the inputs to an expression and the resulting value of the expression.  For example if you have
a = 4
b = 5
the record for the expression
a + b
would contain at least
{expr: +, lhs: 4, rhs: 5, value: 9}

The magic happens when records recursively contain records for their subexpressions.

Then the record for
a + (2 * b)
might be
{expr: +, value: 14
  lhs: {
    expr: a
    value: 4
  rhs: {
    expr: *, value: 10
    lhs: {
      expr: ['lit', 2]
      value: 2
    rhs: {