Skip to content

Data visualization

vadimcn edited this page Dec 2, 2022 · 6 revisions

This example uses the Matplotlib library for plotting, so please make sure you have it installed (pip install --user matplotlib after opening a terminal via LLDB: Command Prompt command in VSCode). Of course, you may use any other plotting framework, as long as it can produce output that can be displayed by an HTML renderer.

Here's a program that generates an image of the Mandelbrot set then prints it to the stdout:

// mandelbrot.cpp
#include <cstdio>
#include <complex>

void mandelbrot(int image[], int xdim, int ydim, int max_iter) {
    for (int y = 0; y < ydim; ++y) {
        for (int x = 0; x < xdim; ++x) { // <<<<< Breakpoint here
            std::complex<float> xy(-2.05 + x * 3.0 / xdim, -1.5 + y * 3.0 / ydim);
            std::complex<float> z(0, 0);
            int count = max_iter;
            for (int i = 0; i < max_iter; ++i) {
                z = z * z + xy;
                if (std::abs(z) >= 2) {
                    count = i;
                    break;
                }
            }
            image[y * xdim + x] = count;
        }
    }
}

int main() {
    const int xdim = 500;
    const int ydim = 500;
    const int max_iter = 100;
    int image[xdim * ydim] = {0};
    mandelbrot(image, xdim, ydim, max_iter);
    for (int y = 0; y < ydim; y += 10) {
        for (int x = 0; x < xdim; x += 5) {
            putchar(image[y * xdim + x] < max_iter ? '.' : '#');
        }
        putchar('\n');
    }
    return 0;
}

Wouldn't it be nice to observe the image as it is being generated? Glad you asked! Thanks to the HTML display feature of CodeLLDB we can do just that!

First let's compile the program with debug info:

c++ -g mandelbrot.cpp -o mandelbrot

Next, we'll need a visualization script that generates a plot of our image:

# debugvis.py
import io
import lldb
import debugger
import base64
import numpy as np
import matplotlib
matplotlib.use('agg')
import matplotlib.pyplot as plt

def show():
    image_bytes = io.BytesIO()
    plt.savefig(image_bytes, format='png', bbox_inches='tight')
    document = '<html><img src="data:image/png;base64,%s"></html>' % base64.b64encode(image_bytes.getvalue()).decode('utf-8')
    debugger.display_html(document, position=2)

def plot_image(image, xdim, ydim, cmap='nipy_spectral_r'):
    image = debugger.unwrap(image)
    if image.TypeIsPointerType():
        image_addr = image.GetValueAsUnsigned()
    else:
        image_addr = image.AddressOf().GetValueAsUnsigned()
    data = lldb.process.ReadMemory(image_addr, int(xdim * ydim) * 4, lldb.SBError())
    data = np.frombuffer(data, dtype=np.int32).reshape((ydim,xdim))
    plt.imshow(data, cmap=cmap, interpolation='nearest')
    show()

Save it in your workspace as debugvis.py.

Finally, we'll need to make the visualizer available in your debug session:

// launch.json
    {
        "name": "Launch Mandelbrot",
        "type": "lldb",
        "request": "launch",
        "program": "${workspaceRoot}/mandelbrot",
        "initCommands": [
            "command script import ${workspaceRoot}/debugvis.py" // <<<<< This is the important bit
        ]
    }

Let's get ready for debugging.
Set a conditional breakpoint on line 7 of mandelbrot.cpp using this expression for the condition:

/py debugvis.plot_image($image, $xdim, $ydim) if $y % 50 == 0 else False

Setting conditional breakpoint.

Hit F5. ...and again...

After a few stops, you should see an image similar to this:

plotting

What's going on here?
Let's look again at that breakpoint condition:

/py debugvis.plot_image($image, $xdim, $ydim) if $y % 50 == 0 else False
  • The /py prefix indicates that this expression uses the "full" Python syntax. (If we'd only wanted to stop every 50th iteration, the breakpoint condition could have been simply y % 50 == 0.)
  • The $y and other $-prefixed variables are expanded into the values of debuggee's variables from the current stack frame.
  • The if expression evaluates $y % 50 == 0:
    • If true, it executes the debugvis.plot_image(...) block, which plots current state of the image and asks VSCode to display it.
    • If false, the entire breakpoint condition expression evaluates to False as well, which tells the debugger to keep going. (In fact, False is the only value which does that, any other value will cause a stop. This is why plot_image does not bother returning True, None works just as well.)
Clone this wiki locally