Commits

Miki Tebeka committed a7baac6

slides and testing

Comments (0)

Files changed (17)

+import "C"
+Calling C From Go
+27 Mar 2013
+Tags: go c
+
+Miki Tebeka
+Technical Lead, Adconion
+miki.tebeka@gmail.com
+http://mikitebeka.com
+@tebeka
+
+* Why C?
+
+After all we have Go, which is much better :)
+
+However, there is a *huge* volume of software written in C.
+We'd like to re-use it and not re-write it.
+
+* import "C" (A.K.A cgo)
+
+Provides an easy bridge between Go and C
+
+The Downside
+- Hard to find docs (see reference slide)
+- Pay attention to memory leaks
+- Compilation time grows
+- For C++ you need `extern`"C"` and C like wrapper
+- Users need C compilers and libraries installed
+- Cross compilation [[https://code.google.com/p/go/issues/detail?id=4714][does not work]]
+
+Note that are other options (such as [[http://www.swig.org/][swig]])
+
+
+
+* Minimal Example
+
+.play import-c/sqrt.go
+
+
+* Strings
+
+.play import-c/str.go
+
+* Ahhhhhh!
+
+.image import-c/picard1.jpg 400 600
+
+* Strings
+
+.play import-c/str2.go
+
+* But ...
+
+.image import-c/picard2.jpg 400 600
+
+* Strings
+
+.play import-c/str3.go
+
+* Structs
+
+.play import-c/struct.go
+
+* Calling Go from C
+
+`callback.c`
+
+.code import-c/callback/callback.c
+
+`callback.h`
+
+.code import-c/callback/callback.h
+
+* Calling Go from C
+
+`callback.go`
+
+.code import-c/callback/callback.go
+
+* Go Style Errors
+
+.play import-c/err.go
+
+* Case Study - snowball
+
+- [[https://bitbucket.org/tebeka/snowball]]
+- Port of [[http://snowball.tartarus.org/][snowball]] stemmer to Go
+- Had to flatten directories and rewrite #include
+- Wasn't that painful
+
+.play import-c/stem.go
+
+* snowball - imports and definition
+
+.code import-c/snowball.go  /START_IMPORT/,/END_IMPORT/
+
+* snowball - New and free
+
+.code import-c/snowball.go  /START_NEW/,/END_NEW/
+
+
+* snowball - Stem
+
+snowball uses `sb_symbol*` for string
+
+.code import-c/snowball.go  /START_STEM/,/END_STEM/
+
+* snowball - List
+
+The list of languages is a `static`char*` array with a `NULL` sentinal
+
+.code import-c/snowball.go  /START_LIST/,/END_LIST/
+
+
+* References
+
+- [[http://golang.org/cmd/cgo/][cgo docs]]
+- [[http://golang.org/doc/articles/c_go_cgo.html][C? Go? Cgo!]]
+- [[http://golang.org/misc/cgo/][cgo examples]]
+- [[https://code.google.com/p/go-wiki/wiki/cgo][cgo wiki]]
+- [[http://stackoverflow.com/search?q=%5Bgo%5D+cgo][StackOverflow]]
+
+* Lets Hack!
+
+.image import-c/real-programmers.jpg 400 600
+
+nrsc
+Packing Resource in Go Executables
+Tags: go
+
+Miki Tebeka
+Lead Software Engineer, Demand Media
+miki.tebeka@gmail.com
+http://mikitebeka.com
+@tebeka
+
+* The Good, the Bad and the Ugly
+- Good:  Go is statically linked, we need to ship *one* binary.
+- Bad: Web application (and others) need some external resources
+- Ugly: `nrsc` lets you embed resources in Go executables
+
+* Method
+- Use `cat` to append zipped resource to executable
+- Use `zip`-A` to "fix" offset
+- Use `nrsc` library to access resources
+
+(Original idea from [[http://bit.ly/SmYXXm][Carlos Castillo in golang-nuts list]])
+
+* nrsc shell script
+.code nrsc/nrsc  /START/,/END/
+
+* Resource
+.code nrsc/nrsc.go  /START_DEF/,/END_DEF/
+
+* Loading (1)
+.code nrsc/nrsc.go  /START_MAP_1/,/END_MAP_1/
+
+* Loading (2)
+.code nrsc/nrsc.go  /START_MAP_2/,/END_MAP_2/
+
+* Get
+.code nrsc/nrsc.go  /START_GET/,/END_GET/
+
+* Masking
+.code nrsc/nrsc.go  /START_MASK/,/END_MASK/
+
+* Serving (1)
+.code nrsc/nrsc.go  /START_SERVE1/,/END_SERVE1/
+
+* Serving (2)
+.code nrsc/nrsc.go  /START_SERVE2/,/END_SERVE2/
+
+* Handle
+.code nrsc/nrsc.go  /START_HANDLE/,/END_HANDLE/
+
+
+* Testing
+
+- Create an executable with embedded resources in /tmp
+- Run it (it's a web server)
+- Test methods (hit, miss, mask ...)
+- Kill server
+
+See [[https://bitbucket.org/tebeka/nrsc/src/tip/nrsc_test.go][nrsc_test.go]]
+
+
+* Future Work
+
+Help is highly appreciated :)
+
+- Convert nrsc bash script to Go code ([[https://bitbucket.org/tebeka/nrsc/issue/2][issue #2]])
+    - Make it "go gettable"
+- Remove external depedency on zip ([[https://bitbucket.org/tebeka/nrsc/issue/4][issue #4]])
+    - Windows anyone?
+- Use `fuse` and provide file system API to resource ([[https://bitbucket.org/tebeka/nrsc/issue/3][issue #3]])
+    - Simplify a lot of things (LoadTemplate ...)
+    - However totally different API
+- Get rid of `Initialize` ([[https://bitbucket.org/tebeka/nrsc/issue/5][issue #5]])
+    - `loadMap` in `init` does not work
+
+* References
+- [[https://bitbucket.org/tebeka/nrsc]]
+

test-debug-prof.slide

+Testing, Debugging and Profiling
+23 Apr 2013
+Tags: go test profile debug
+
+Fabrizio Milo, Miki Tebeka
+@fabmilo 
+@tebeka
+
+* Testing
+
+* Testing Recipe
+
+- Write `test_<module>.go` (discovery based test system)
+- With the same package as the one being tested
+- import [[http://golang.org/pkg/testing/][testing]]
+- Write `TestFoo(t`*testing.T)` functions
+- Use `init` for setup
+- Use `defer` for poor mans teardown (in *every* test function)
+- Run `go`test`-v`
+- Run `go`test`-run='.*Mul.*'`-v` to select tests
+- Read `go`help`testflag` output for more options
+
+* math.go
+
+.code test-debug-prof/math/math.go
+
+* math_test.go
+
+.code test-debug-prof/math/math_test.go /START1/,/END1/
+
+.code test-debug-prof/math/math_test.go /START2/,/END2/
+
+* Table Driven Testing
+
+.code test-debug-prof/table/math_test.go /START1/,/END1/
+
+* Parallel Execution
+
+.code test-debug-prof/parallel/math_test.go /START1/,/END1/
+
+- `time`go`test` (~2sec)
+- `time`go`test`-parallel`2` (~1sec)
+- If you have CPU intensive tests try `go`test`-parallel`2`-cpu`2`
+
+
+* Benchmarks
+
+.code test-debug-prof/bench/math_test.go /START1/,/END1/
+
+- `go`test`-bench`'.*'`
+
+
+* Debugging
+
+
+* GDB 7.5 on Mac 
+
+- You need GDB 7.5 OS X has a branch of 6.X 
+
+	$gdb --version
+
+	GNU gdb (GDB) 7.5
+	Copyright (C) 2012 Free Software Foundation, Inc.
+	License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
+	This is free software: you are free to change and redistribute it.
+	There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
+	and "show warranty" for details.
+	This GDB was configured as "x86_64-apple-darwin11.4.2".
+	For bug reporting instructions, please see:
+	<http://www.gnu.org/software/gdb/bugs/>.
+
+* If you need to upgrade on OS X 
+
+- Download latest stable release source code from
+	http://sourceware.org/gdb/download/
+
+- Unpack and compile:
+
+	tar xjvf gdb-7.4.tar.bz2
+	cd gdb-7.4
+	less gdb/README
+	./configure
+	make
+
+- Sign GDB (more info in the link)
+
+.link http://sourceware.org/gdb/wiki/BuildingOnDarwin
+
+
+* Compiling go programs for debugging
+
+- you can build a development version of dependent pkgs.
+
+	go install -a -v -gcflags "-N -l" <pkgs-list>
+
+
+Load GDB golang extensions:
+
+	(gdb)source http://golang.org/src/pkg/runtime/runtime-gdb.py
+
+* Gdb Configuration
+
+    (gdb)source <go-installation>/src/pkg/runtime/runtime-gdb.py 
+    (gdb)skip malloc.goc:436
+
+you can add commands in your .gdbinit inside your GOPATH
+
+	(GOPATH)$cat .gdbinit
+	source runtime-gdb.py 
+	skip malloc.goc:436
+
+* If GDB is properly configured for go if you read these lines:
+
+.code test-debug-prof/debug/gdbout
+
+
+* Let's use gdb on geodns:
+
+	mkdir gdbex
+	cd gdbex
+	mkdir bin lib src
+	export GOPATH=`pwd`
+	brew install geoip # OSX
+	yum install geoip-devel # RedHat / CentOs
+	go get github.com/abh/geodns
+	go build -a -v -gcflags "-N -l" github.com/abh/geodns
+
+.link https://github.com/abh/geodns
+
+
+*  Configure Environment
+
+- How to set an environemnt variable:
+
+	set environment GOGCTRACE=1
+	set environmnet GOMAXPROCS=1
+	set environment GOTRACEBACK=2
+
+note that we can add these variables to the `.gdbinit` file as well
+
+- How to set arguments:
+
+	set args -port=5300
+
+
+* Breakpoints
+
+- Breakpoint on module function:
+
+	(gdb) b main.main
+
+- Breakpoint on file line:
+
+	(gdb) b serve.go:212
+
+- Breakpoint on library function
+
+	(gdb) b net/http.ListenAndServe
+
+* How to inspect variables
+
+- info locals  // locals only 
+- info variables  // all variables including globals / static
+- info args
+- whatis <variablename>
+- info variables <regexp>
+- p <variablename>
+
+
+* GDB Commands
+
+- list
+- list line
+
+- list file.go:line
+- break line
+- break file.go:line
+- disas
+
+* Go Extensions (2)
+
+- bt
+- frame n
+- p var
+- p $len(var)
+- p $dtype(var)
+- iface var
+
+- info goroutines
+- goroutine n cmd
+- help goroutine
+
+
+* NOTE: You can't call a Go function
+
+	(gdb) call 'main.greet'("hello")
+	infrun.c:5777: internal-error: normal_stop: Assertion `get_frame_type (frame) == DUMMY_FRAME' failed.
+	A problem internal to GDB has been detected,
+	further debugging may prove unreliable.
+	Quit this debugging session? (y or n) y
+
+This is due to the differences on how Go calls a function and the way GDB does
+
+* User Interfaces for GDB
+
+- gdb -tui
+- ddd
+- Affinic's
+
+* Hopwatch
+
+.play test-debug-prof/hopwatch.go
+
+* Go Manual
+
+- import "runtime/debug"
+
+.link http://golang.org/pkg/runtime/debug/#PrintStack
+
+	func PrintStack()
+
+.link http://golang.org/pkg/runtime/#Stack
+
+	func Stack(buf []byte, all bool) int
+
+- import "runtime"
+
+.link http://golang.org/pkg/runtime/#Breakpoint
+
+	func Breakpoint()
+
+* Example
+
+.code test-debug-prof/debug/break.go
+
+* Custom Exceptions: Extending Error's interface
+
+.link https://code.google.com/p/biogo/source/browse/errors/errors.go
+
+
+* Profiling
+* Running the Memory Profiler
+
+* Notes
+
+in pre Go 1.0 the memory profiler requires that the garbage collection has run at least once.
+To force the GC use runtime.GC() i.e:
+
+    DumpHeapProfile("pre-gc-mem.prof")
+	runtime.GC() // force Garbage Collection
+	DumpHeapProfile("post-gc-mem.prof")
+
+The value of runtime.MemProfileRate is important to tweak as it adjusts the granularity of the stats, the default is to only consider allocations of 512kb or bigger. 
+
+.link http://golang.org/pkg/runtime/#pkg-variables
+
+	go tool pprof <Executable> executable.prof
+
+install graphviz
+
+.link http://www.graphviz.org/
+
+
+
+* Testing References
+
+- [[http://golang.org/pkg/testing/][testing]]
+- [[http://golang.org/doc/code.html#Testing][Testing]] section in "How To Write Go Code"
+- [[http://labix.org/gocheck][gocheck]]
+- [[https://bitbucket.org/tebeka/go2xunit][go2xunit]] Jenkins integration
+
+
+* Debugging References
+
+- [[http://golang.org/doc/gdb][gdb]]
+- [[http://www.affinic.com/?page_id=109][Visual GDB Debugger]]
+- [[https://github.com/emicklei/hopwatch][HopWatch]]
+
+
+
+
+
+

test-debug-prof/.gdbinit

+source runtime-gdb.py
+skip malloc.goc:436

test-debug-prof/bench/bench.test

Binary file added.

test-debug-prof/bench/math.go

+package math
+
+func Mul(x, y float64) float64 {
+	return x * y
+}
+
+func Div(x, y float64) float64 {
+	return x / y
+}

test-debug-prof/bench/math_test.go

+package math
+
+import "testing"
+
+// START1 OMIT
+func BenchmarkDiv(b *testing.B) { // HL
+	for i := 0; i < b.N; i++ { // go test will increase N to get good timing 
+		Div(10.0, 3.0)
+	}
+}
+// END1 OMIT
+

test-debug-prof/debug/break.go

+package main
+
+import (
+	"runtime"
+	"runtime/debug"
+)
+
+func call() {
+	debug.PrintStack()
+	runtime.Breakpoint()
+}
+
+func main() {
+	call()
+}

test-debug-prof/debug/gdbout

+GNU gdb (GDB) 7.5
+Copyright (C) 2012 Free Software Foundation, Inc.
+License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
+This is free software: you are free to change and redistribute it.
+There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
+and "show warranty" for details.
+This GDB was configured as "x86_64-apple-darwin11.4.2".
+For bug reporting instructions, please see:
+<http://www.gnu.org/software/gdb/bugs/>...
+Reading symbols from /Users/fabrizio/Documents/workspace/go-sandbox/bin/apns_client...done.
+Loading Go Runtime support.  // HL
+Function runtime.new at 0x1741c will be skipped when stepping. // HL

test-debug-prof/hopwatch.go

+package main
+
+import "github.com/emicklei/hopwatch"
+
+func main() {
+	for i := 0; i < 6; i++ {
+		hopwatch.Display("i", i)
+		j := i * i
+		hopwatch.Display("i", i, "j", j).Break(j > 10)
+		hopwatch.Break()
+	}
+}

test-debug-prof/math/math.go

+package math
+
+func Mul(x, y float64) float64 {
+	return x * y
+}
+
+func Div(x, y float64) float64 {
+	return x / y
+}

test-debug-prof/math/math_test.go

+package math
+
+// START1 OMIT
+import "testing" // HL
+
+func TestMul(t *testing.T) {
+	a, b, expected := 2.0, 3.0, 6.0
+	result := Mul(a, b)
+	if result != expected {
+		t.Fatalf("%v * %v -> %v (expected %v)", a, b, result, expected) // HL
+	}
+}
+// END1 OMIT
+
+// START2 OMIT
+func TestDiv(t *testing.T) {
+	a, b, expected := 6.0, 3.0, 3.0
+	result := Div(a, b)
+	if result != expected {
+		t.Fatalf("%v / %v -> %v (expected %v)", a, b, result, expected) // HL
+	}
+}
+// END2 OMIT

test-debug-prof/parallel/math.go

+package math
+
+import "time"
+
+func Mul(x, y float64) float64 {
+	time.Sleep(1 * time.Second)
+	return x * y
+}
+
+func Div(x, y float64) float64 {
+	time.Sleep(1 * time.Second)
+	return x / y
+}

test-debug-prof/parallel/math_test.go

+package math
+
+import "testing"
+
+// START1 OMIT
+func TestOne(t *testing.T) {
+	t.Parallel() // HL
+	if Mul(1.0, 2.0) != 2.0 {
+		t.Fatal("Double(1, 2) failed") // FIXME: Better log
+	}
+}
+
+func TestTwo(t *testing.T) {
+	t.Parallel() // HL
+	if Mul(2.0, 2.0) != 4 {
+		t.Fatal("Double(2, 2) failed") // FIXME: Better log
+	}
+}
+// END1 OMIT

test-debug-prof/runtime-gdb.py

+# Copyright 2010 The Go Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file.
+
+"""GDB Pretty printers and convenience functions for Go's runtime structures.
+
+This script is loaded by GDB when it finds a .debug_gdb_scripts
+section in the compiled binary. The [68]l linkers emit this with a
+path to this file based on the path to the runtime package.
+"""
+
+# Known issues:
+#    - pretty printing only works for the 'native' strings. E.g. 'type
+#      foo string' will make foo a plain struct in the eyes of gdb,
+#      circumventing the pretty print triggering.
+
+
+import sys, re
+
+print >>sys.stderr, "Loading Go Runtime support."
+
+# allow to manually reload while developing
+goobjfile = gdb.current_objfile() or gdb.objfiles()[0]
+goobjfile.pretty_printers = []
+
+#
+#  Pretty Printers
+#
+
+class StringTypePrinter:
+	"Pretty print Go strings."
+
+	pattern = re.compile(r'^struct string$')
+
+	def __init__(self, val):
+		self.val = val
+
+	def display_hint(self):
+		return 'string'
+
+	def to_string(self):
+		l = int(self.val['len'])
+		return self.val['str'].string("utf-8", "ignore", l)
+
+
+class SliceTypePrinter:
+	"Pretty print slices."
+
+	pattern = re.compile(r'^struct \[\]')
+
+	def __init__(self, val):
+		self.val = val
+
+	def display_hint(self):
+		return 'array'
+
+	def to_string(self):
+		return str(self.val.type)[6:]  # skip 'struct '
+
+	def children(self):
+		if self.val["len"] > self.val["cap"]:
+			return
+		ptr = self.val["array"]
+		for idx in range(self.val["len"]):
+			yield ('[%d]' % idx, (ptr + idx).dereference())
+
+
+class MapTypePrinter:
+	"""Pretty print map[K]V types.
+
+	Map-typed go variables are really pointers. dereference them in gdb
+	to inspect their contents with this pretty printer.
+	"""
+
+	pattern = re.compile(r'^struct hash<.*>$')
+
+	def __init__(self, val):
+		self.val = val
+
+	def display_hint(self):
+		return 'map'
+
+	def to_string(self):
+		return str(self.val.type)
+
+	def children(self):
+		stab = self.val['st']
+		i = 0
+		for v in self.traverse_hash(stab):
+			yield ("[%d]" % i, v['key'])
+			yield ("[%d]" % (i + 1), v['val'])
+			i += 2
+
+	def traverse_hash(self, stab):
+		ptr = stab['entry'].address
+		last = stab['last']
+		while ptr <= last:
+			v = ptr.dereference()
+			ptr = ptr + 1
+			if v['hash'] == 0: continue
+			if v['hash'] & 63 == 63:   # subtable
+				for v in self.traverse_hash(v['key'].cast(self.val['st'].type)):
+					yield v
+			else:
+				yield v
+
+
+class ChanTypePrinter:
+	"""Pretty print chan[T] types.
+
+	Chan-typed go variables are really pointers. dereference them in gdb
+	to inspect their contents with this pretty printer.
+	"""
+
+	pattern = re.compile(r'^struct hchan<.*>$')
+
+	def __init__(self, val):
+		self.val = val
+
+	def display_hint(self):
+		return 'array'
+
+	def to_string(self):
+		return str(self.val.type)
+
+	def children(self):
+		# see chan.c chanbuf(). et is the type stolen from hchan<T>::recvq->first->elem
+		et = [x.type for x in self.val['recvq']['first'].type.target().fields() if x.name == 'elem'][0]
+		ptr = (self.val.address + 1).cast(et.pointer())
+		for i in range(self.val["qcount"]):
+			j = (self.val["recvx"] + i) % self.val["dataqsiz"]
+			yield ('[%d]' % i, (ptr + j).dereference())
+
+
+#
+#  Register all the *Printer classes above.
+#
+
+def makematcher(klass):
+	def matcher(val):
+		try:
+			if klass.pattern.match(str(val.type)):
+				return klass(val)
+		except:
+			pass
+	return matcher
+
+goobjfile.pretty_printers.extend([makematcher(k) for k in vars().values() if hasattr(k, 'pattern')])
+
+#
+#  For reference, this is what we're trying to do:
+#  eface: p *(*(struct 'runtime.commonType'*)'main.e'->type_->data)->string
+#  iface: p *(*(struct 'runtime.commonType'*)'main.s'->tab->Type->data)->string
+#
+# interface types can't be recognized by their name, instead we check
+# if they have the expected fields.  Unfortunately the mapping of
+# fields to python attributes in gdb.py isn't complete: you can't test
+# for presence other than by trapping.
+
+
+def is_iface(val):
+	try:
+		return str(val['tab'].type) == "struct runtime.itab *" \
+		      and str(val['data'].type) == "void *"
+	except:
+		pass
+
+def is_eface(val):
+	try:
+		return str(val['_type'].type) == "struct runtime._type *" \
+		      and str(val['data'].type) == "void *"
+	except:
+		pass
+
+def lookup_type(name):
+	try:
+		return gdb.lookup_type(name)
+	except:
+		pass
+	try:
+		return gdb.lookup_type('struct ' + name)
+	except:
+		pass
+	try:
+		return gdb.lookup_type('struct ' + name[1:]).pointer()
+	except:
+		pass
+
+_rctp_type = gdb.lookup_type("struct runtime.commonType").pointer()
+_rtp_type = gdb.lookup_type("struct runtime._type").pointer()
+
+def iface_commontype(obj):
+	if is_iface(obj):
+		go_type_ptr = obj['tab']['_type']
+	elif is_eface(obj):
+		go_type_ptr = obj['_type']
+	else:
+		return
+
+	# sanity check: reflection type description ends in a loop.
+	tt = go_type_ptr['_type'].cast(_rtp_type).dereference()['_type']
+	if tt != tt.cast(_rtp_type).dereference()['_type']:
+		return
+	
+	return go_type_ptr['ptr'].cast(_rctp_type).dereference()
+	
+
+def iface_dtype(obj):
+	"Decode type of the data field of an eface or iface struct."
+	# known issue: dtype_name decoded from runtime.commonType is "nested.Foo"
+	# but the dwarf table lists it as "full/path/to/nested.Foo"
+
+	dynamic_go_type = iface_commontype(obj)
+	if dynamic_go_type is None:
+		return
+	dtype_name = dynamic_go_type['string'].dereference()['str'].string()
+
+	dynamic_gdb_type = lookup_type(dtype_name)
+	if dynamic_gdb_type is None:
+		return
+	
+	type_size = int(dynamic_go_type['size'])
+	uintptr_size = int(dynamic_go_type['size'].type.sizeof)	 # size is itself an uintptr
+	if type_size > uintptr_size:
+			dynamic_gdb_type = dynamic_gdb_type.pointer()
+
+	return dynamic_gdb_type
+
+def iface_dtype_name(obj):
+	"Decode type name of the data field of an eface or iface struct."
+
+	dynamic_go_type = iface_commontype(obj)
+	if dynamic_go_type is None:
+		return
+	return dynamic_go_type['string'].dereference()['str'].string()
+
+
+class IfacePrinter:
+	"""Pretty print interface values
+
+	Casts the data field to the appropriate dynamic type."""
+
+	def __init__(self, val):
+		self.val = val
+
+	def display_hint(self):
+		return 'string'
+
+	def to_string(self):
+		if self.val['data'] == 0:
+			return 0x0
+		try:
+			dtype = iface_dtype(self.val)
+		except:
+			return "<bad dynamic type>"
+
+		if dtype is None:  # trouble looking up, print something reasonable
+			return "(%s)%s" % (iface_dtype_name(self.val), self.val['data'])
+
+		try:
+			return self.val['data'].cast(dtype).dereference()
+		except:
+			pass
+		return self.val['data'].cast(dtype)
+
+
+def ifacematcher(val):
+	if is_iface(val) or is_eface(val):
+		return IfacePrinter(val)
+
+goobjfile.pretty_printers.append(ifacematcher)
+
+#
+#  Convenience Functions
+#
+
+class GoLenFunc(gdb.Function):
+	"Length of strings, slices, maps or channels"
+
+	how = ((StringTypePrinter, 'len'),
+	       (SliceTypePrinter, 'len'),
+	       (MapTypePrinter, 'count'),
+	       (ChanTypePrinter, 'qcount'))
+
+	def __init__(self):
+		super(GoLenFunc, self).__init__("len")
+
+	def invoke(self, obj):
+		typename = str(obj.type)
+		for klass, fld in self.how:
+			if klass.pattern.match(typename):
+				return obj[fld]
+
+class GoCapFunc(gdb.Function):
+	"Capacity of slices or channels"
+
+	how = ((SliceTypePrinter, 'cap'),
+	       (ChanTypePrinter, 'dataqsiz'))
+
+	def __init__(self):
+		super(GoCapFunc, self).__init__("cap")
+
+	def invoke(self, obj):
+		typename = str(obj.type)
+		for klass, fld in self.how:
+			if klass.pattern.match(typename):
+				return obj[fld]
+
+class DTypeFunc(gdb.Function):
+	"""Cast Interface values to their dynamic type.
+
+	For non-interface types this behaves as the identity operation.
+	"""
+
+	def __init__(self):
+		super(DTypeFunc, self).__init__("dtype")
+
+	def invoke(self, obj):
+		try:
+			return obj['data'].cast(iface_dtype(obj))
+		except:
+			pass
+		return obj
+
+#
+#  Commands
+#
+
+sts = ('idle', 'runnable', 'running', 'syscall', 'waiting', 'moribund', 'dead', 'recovery')
+
+def linked_list(ptr, linkfield):
+	while ptr:
+		yield ptr
+		ptr = ptr[linkfield]
+
+
+class GoroutinesCmd(gdb.Command):
+	"List all goroutines."
+
+	def __init__(self):
+		super(GoroutinesCmd, self).__init__("info goroutines", gdb.COMMAND_STACK, gdb.COMPLETE_NONE)
+
+	def invoke(self, arg, from_tty):
+		# args = gdb.string_to_argv(arg)
+		vp = gdb.lookup_type('void').pointer()
+		for ptr in linked_list(gdb.parse_and_eval("'runtime.allg'"), 'alllink'):
+			if ptr['status'] == 6:	# 'gdead'
+				continue
+			s = ' '
+			if ptr['m']:
+				s = '*'
+			pc = ptr['sched']['pc'].cast(vp)
+			sp = ptr['sched']['sp'].cast(vp)
+			blk = gdb.block_for_pc(long((pc)))
+			print s, ptr['goid'], "%8s" % sts[long((ptr['status']))], blk.function
+
+def find_goroutine(goid):
+	vp = gdb.lookup_type('void').pointer()
+	for ptr in linked_list(gdb.parse_and_eval("'runtime.allg'"), 'alllink'):
+		if ptr['status'] == 6:	# 'gdead'
+			continue
+		if ptr['goid'] == goid:
+			return [ptr['sched'][x].cast(vp) for x in 'pc', 'sp']
+	return None, None
+
+
+class GoroutineCmd(gdb.Command):
+	"""Execute gdb command in the context of goroutine <goid>.
+
+	Switch PC and SP to the ones in the goroutine's G structure,
+	execute an arbitrary gdb command, and restore PC and SP.
+
+	Usage: (gdb) goroutine <goid> <gdbcmd>
+
+	Note that it is ill-defined to modify state in the context of a goroutine.
+	Restrict yourself to inspecting values.
+	"""
+
+	def __init__(self):
+		super(GoroutineCmd, self).__init__("goroutine", gdb.COMMAND_STACK, gdb.COMPLETE_NONE)
+
+	def invoke(self, arg, from_tty):
+		goid, cmd = arg.split(None, 1)
+		pc, sp = find_goroutine(int(goid))
+		if not pc:
+			print "No such goroutine: ", goid
+			return
+		save_frame = gdb.selected_frame()
+		gdb.parse_and_eval('$save_pc = $pc')
+		gdb.parse_and_eval('$save_sp = $sp')
+		gdb.parse_and_eval('$pc = 0x%x' % long(pc))
+		gdb.parse_and_eval('$sp = 0x%x' % long(sp))
+		try:
+			gdb.execute(cmd)
+		finally:
+			gdb.parse_and_eval('$pc = $save_pc')
+			gdb.parse_and_eval('$sp = $save_sp')
+			save_frame.select()
+
+
+class GoIfaceCmd(gdb.Command):
+	"Print Static and dynamic interface types"
+
+	def __init__(self):
+		super(GoIfaceCmd, self).__init__("iface", gdb.COMMAND_DATA, gdb.COMPLETE_SYMBOL)
+
+	def invoke(self, arg, from_tty):
+		for obj in gdb.string_to_argv(arg):
+			try:
+				#TODO fix quoting for qualified variable names
+				obj = gdb.parse_and_eval("%s" % obj)
+			except Exception, e:
+				print "Can't parse ", obj, ": ", e
+				continue
+
+			if obj['data'] == 0:
+				dtype = "nil"
+			else:
+				dtype = iface_dtype(obj)
+				
+			if dtype is None:
+				print "Not an interface: ", obj.type
+				continue
+
+			print "%s: %s" % (obj.type, dtype)
+
+# TODO: print interface's methods and dynamic type's func pointers thereof.
+#rsc: "to find the number of entries in the itab's Fn field look at itab.inter->numMethods
+#i am sure i have the names wrong but look at the interface type and its method count"
+# so Itype will start with a commontype which has kind = interface
+
+#
+# Register all convenience functions and CLI commands
+#
+for k in vars().values():
+	if hasattr(k, 'invoke'):
+		k()

test-debug-prof/table/math.go

+package math
+
+func Mul(x, y float64) float64 {
+	return x * y
+}
+
+func Div(x, y float64) float64 {
+	return x / y
+}

test-debug-prof/table/math_test.go

+package math
+
+import "testing"
+
+// START1 OMIT
+func checkMul(t *testing.T, a, b, expected float64) {
+	t.Logf("Testing Mul(%f, %f)", a, b)
+	if Mul(a, b) != expected {
+		t.Fatalf("Mul(%f, %f) failed", a, b) // FIXME: Better error
+	}
+}
+
+var mulTestCases = []struct {
+	a, b, expected float64
+}{
+	{1., 2., 2.},
+	{-2., -3., 6.},
+}
+
+func TestMul(t *testing.T) {  // HL
+	for _, tc := range mulTestCases {
+		checkMul(t, tc.a, tc.b, tc.expected)
+	}
+}
+// END1 OMIT