-
Notifications
You must be signed in to change notification settings - Fork 251
Data visualization
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
Hit F5
.
...and again...
After a few stops, you should see an image similar to this:
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 simplyy % 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 whyplot_image
does not bother returning True, None works just as well.)
- If true, it executes the