If it won't be simple, it simply won't be. [Hire me, source code] by Miki Tebeka, CEO, 353Solutions

Tuesday, August 30, 2016

"Manual" Breakpoints in Go

When debugging, sometimes you need to set conditional breakpoints. This option is available both in gdb and delve. However sometimes when the condition is complicated, it's hard or even impossible to set it. A way around is to temporary write the condition in Go and set breakpoint "manually".

I Python we do it with pdb.set_trace(), in Go we'll need to work a little harder. The main idea is that breakpoints are special signal called SIGTRAP.

Here's the code to do this:
// "Manual" breapoing in go
// Compile with
// go build -gcflags "-N -l" manual-bp.go
// Then
// gdb manual-bp
// (gdb) run
// When you hit breakpoint, call "fin" twice to get to the current location
// (gdb) fin
// (gdb) fin
// After that it's usuall gdb commands
// (gdb) p i
// $1 = 3
package main
import (
"fmt"
"syscall"
)
func foo() {
for i := 0; i < 5; i++ {
if i == 3 { // Some complicated condition
// Initiate a breakpoint
syscall.Kill(syscall.Getpid(), syscall.SIGTRAP)
}
fmt.Printf("i = %d\n", i)
}
}
func main() {
foo()
}
view raw manual-bp.go hosted with ❤ by GitHub
You'll need tell the go tool not to optimize and keep variable information:

$ go build -gcflags "-N -l" manual-bp

Then run a gdb session

$ gdb manual-bp 
(gdb) run 

 When you hit the breakpoint, you'll be in assembly code. Exit two functions to get to your code

(gdb) fin
(gdb) fin

Then you'll be in your code and can run gdb commands

(gdb) p i
$1 = 3

This scheme also works with delve

$ dlv debug manual-bp.go 
(dlv) c 

Sadly delve don't have "fin" command so you'll need to hit "n" (next) until you reach your code. 

That's it, happy debugging.

Oh - and in the very old days we did about the same trick in C code. There we manually inserted asm("int $3)" to the code. You can do with with cgo but sending a signal seems easier.

Wednesday, August 24, 2016

Generate Relation Diagram from GAE ndb Model

Working with GAE, we wanted to create relation diagram from out ndb model. By deferring the rendering to dot and using Python's reflection this became an easy task.
#!/usr/bin/env python2
"""Export GAE model to dot format."""
# Usage example:
# ./ndb2dot.py cool.model | dot -Tpng -o model.png
# Install graphviz to get the dot command line utility
from inspect import isclass
from os import environ
import imp
import sys
# Inject GAE SDK path
sys.path.insert(0, environ.get('GAE_PY_SDK', '/opt/google_appengine'))
import dev_appserver # noqa
dev_appserver.fix_sys_path()
from google.appengine.ext import ndb # noqa
header = '''\
digraph G {
graph [rankdir=LR];
node [shape=none];
'''
def load_module(name):
"""Load module from name"""
mod = None
for mod_name in name.split('.'):
file, pathname, desc = imp.find_module(mod_name, mod and mod.__path__)
mod = imp.load_module(mod_name, file, pathname, desc)
return mod
def models(module):
"""Sorted list (by name) of models in module"""
for attr in dir(module):
obj = getattr(module, attr)
if isclass(obj) and issubclass(obj, ndb.Model):
yield obj
def prop_type(prop):
"""Property type (string)"""
if isinstance(prop, ndb.StructuredProperty):
return prop._modelclass.__name__
name = prop.__class__.__name__
suffix = 'Property'
if name.endswith(suffix):
name = name[:-len(suffix)]
return name
def model2dot(model):
"""dot represtantation of model"""
cls = model.__name__
print('''\
%s [
label = <<table>
<tr><td colspan="2"><b>%s</b></td></tr>''' % (cls, cls))
links = []
for name, prop in sorted(model._properties.iteritems()):
if isinstance(prop, ndb.StructuredProperty):
port = 'port="%s"' % name
links.append((name, prop))
else:
port = ''
print('%s<tr><td %s>%s</td><td>%s</td></tr>' % (
' ' * 8, port, name, prop_type(prop)))
print('''\
</table>>
];''')
for name, prop in links:
print(' %s:%s -> %s;' % (cls, name, prop_type(prop)))
if __name__ == '__main__':
from operator import attrgetter
from argparse import ArgumentParser
parser = ArgumentParser(description=__doc__)
parser.add_argument('module', help='module name (e.g. "cool.model")')
args = parser.parse_args()
try:
mod = load_module(args.module)
except ImportError as err:
raise SystemExit('error: cannot load %r - %s' % (args.module, err))
print(header)
for model in sorted(models(mod), key=attrgetter('__name__')):
model2dot(model)
print('}')
view raw ndb2dot.py hosted with ❤ by GitHub
Some links are still missing since we're using ancestor queries, but this can be handled by some class docstring syntax or just manually editing the resulting dot file.

Blog Archive