diff --git a/stacktrace/capture.go b/stacktrace/capture.go new file mode 100644 index 00000000..9fc75f8a --- /dev/null +++ b/stacktrace/capture.go @@ -0,0 +1,25 @@ +package stacktrace + +import "runtime" + +// Caputure captures a stacktrace for the current calling go program +// +// skip is the number of frames to skip +func Capture(userSkip int) Stacktrace { + var ( + skip = userSkip + 1 // add one for our own function + frames []Frame + ) + + for i := skip; ; i++ { + pc, file, line, ok := runtime.Caller(i) + if !ok { + break + } + frames = append(frames, NewFrame(pc, file, line)) + } + + return Stacktrace{ + Frames: frames, + } +} diff --git a/stacktrace/capture_test.go b/stacktrace/capture_test.go new file mode 100644 index 00000000..3f435d51 --- /dev/null +++ b/stacktrace/capture_test.go @@ -0,0 +1,27 @@ +package stacktrace + +import "testing" + +func captureFunc() Stacktrace { + return Capture(0) +} + +func TestCaptureTestFunc(t *testing.T) { + stack := captureFunc() + + if len(stack.Frames) == 0 { + t.Fatal("expected stack frames to be returned") + } + + // the first frame is the caller + frame := stack.Frames[0] + if expected := "captureFunc"; frame.Function != expected { + t.Fatalf("expteced function %q but recevied %q", expected, frame.Function) + } + if expected := "github.com/docker/libcontainer/stacktrace"; frame.Package != expected { + t.Fatalf("expected package %q but received %q", expected, frame.Package) + } + if expected := "capture_test.go"; frame.File != expected { + t.Fatalf("expected file %q but received %q", expected, frame.File) + } +} diff --git a/stacktrace/frame.go b/stacktrace/frame.go new file mode 100644 index 00000000..5edea1b7 --- /dev/null +++ b/stacktrace/frame.go @@ -0,0 +1,35 @@ +package stacktrace + +import ( + "path/filepath" + "runtime" + "strings" +) + +// NewFrame returns a new stack frame for the provided information +func NewFrame(pc uintptr, file string, line int) Frame { + fn := runtime.FuncForPC(pc) + pack, name := parseFunctionName(fn.Name()) + return Frame{ + Line: line, + File: filepath.Base(file), + Package: pack, + Function: name, + } +} + +func parseFunctionName(name string) (string, string) { + i := strings.LastIndex(name, ".") + if i == -1 { + return "", name + } + return name[:i], name[i+1:] +} + +// Frame contains all the information for a stack frame within a go program +type Frame struct { + File string + Function string + Package string + Line int +} diff --git a/stacktrace/frame_test.go b/stacktrace/frame_test.go new file mode 100644 index 00000000..ae95ec48 --- /dev/null +++ b/stacktrace/frame_test.go @@ -0,0 +1,20 @@ +package stacktrace + +import "testing" + +func TestParsePackageName(t *testing.T) { + var ( + name = "github.com/docker/libcontainer/stacktrace.captureFunc" + expectedPackage = "github.com/docker/libcontainer/stacktrace" + expectedFunction = "captureFunc" + ) + + pack, funcName := parseFunctionName(name) + if pack != expectedPackage { + t.Fatalf("expected package %q but received %q", expectedPackage, pack) + } + + if funcName != expectedFunction { + t.Fatalf("expected function %q but received %q", expectedFunction, funcName) + } +} diff --git a/stacktrace/stacktrace.go b/stacktrace/stacktrace.go new file mode 100644 index 00000000..5e8b58d2 --- /dev/null +++ b/stacktrace/stacktrace.go @@ -0,0 +1,5 @@ +package stacktrace + +type Stacktrace struct { + Frames []Frame +}