aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/github.com/maruel/panicparse/stack
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/maruel/panicparse/stack')
-rw-r--r--vendor/github.com/maruel/panicparse/stack/source.go291
-rw-r--r--vendor/github.com/maruel/panicparse/stack/stack.go832
-rw-r--r--vendor/github.com/maruel/panicparse/stack/ui.go139
3 files changed, 1262 insertions, 0 deletions
diff --git a/vendor/github.com/maruel/panicparse/stack/source.go b/vendor/github.com/maruel/panicparse/stack/source.go
new file mode 100644
index 000000000..f09e67336
--- /dev/null
+++ b/vendor/github.com/maruel/panicparse/stack/source.go
@@ -0,0 +1,291 @@
+// Copyright 2015 Marc-Antoine Ruel. All rights reserved.
+// Use of this source code is governed under the Apache License, Version 2.0
+// that can be found in the LICENSE file.
+
+// This file contains the code to process sources, to be able to deduct the
+// original types.
+
+package stack
+
+import (
+ "bytes"
+ "fmt"
+ "go/ast"
+ "go/parser"
+ "go/token"
+ "io/ioutil"
+ "log"
+ "math"
+ "strings"
+)
+
+// cache is a cache of sources on the file system.
+type cache struct {
+ files map[string][]byte
+ parsed map[string]*parsedFile
+}
+
+// Augment processes source files to improve calls to be more descriptive.
+//
+// It modifies goroutines in place.
+func Augment(goroutines []Goroutine) {
+ c := &cache{}
+ for i := range goroutines {
+ c.augmentGoroutine(&goroutines[i])
+ }
+}
+
+// augmentGoroutine processes source files to improve call to be more
+// descriptive.
+//
+// It modifies the routine.
+func (c *cache) augmentGoroutine(goroutine *Goroutine) {
+ if c.files == nil {
+ c.files = map[string][]byte{}
+ }
+ if c.parsed == nil {
+ c.parsed = map[string]*parsedFile{}
+ }
+ // For each call site, look at the next call and populate it. Then we can
+ // walk back and reformat things.
+ for i := range goroutine.Stack.Calls {
+ c.load(goroutine.Stack.Calls[i].SourcePath)
+ }
+
+ // Once all loaded, we can look at the next call when available.
+ for i := 1; i < len(goroutine.Stack.Calls); i++ {
+ // Get the AST from the previous call and process the call line with it.
+ if f := c.getFuncAST(&goroutine.Stack.Calls[i]); f != nil {
+ processCall(&goroutine.Stack.Calls[i], f)
+ }
+ }
+}
+
+// Private stuff.
+
+// load loads a source file and parses the AST tree. Failures are ignored.
+func (c *cache) load(fileName string) {
+ if _, ok := c.parsed[fileName]; ok {
+ return
+ }
+ c.parsed[fileName] = nil
+ if !strings.HasSuffix(fileName, ".go") {
+ // Ignore C and assembly.
+ c.files[fileName] = nil
+ return
+ }
+ log.Printf("load(%s)", fileName)
+ if _, ok := c.files[fileName]; !ok {
+ var err error
+ if c.files[fileName], err = ioutil.ReadFile(fileName); err != nil {
+ log.Printf("Failed to read %s: %s", fileName, err)
+ c.files[fileName] = nil
+ return
+ }
+ }
+ fset := token.NewFileSet()
+ src := c.files[fileName]
+ parsed, err := parser.ParseFile(fset, fileName, src, 0)
+ if err != nil {
+ log.Printf("Failed to parse %s: %s", fileName, err)
+ return
+ }
+ // Convert the line number into raw file offset.
+ offsets := []int{0, 0}
+ start := 0
+ for l := 1; start < len(src); l++ {
+ start += bytes.IndexByte(src[start:], '\n') + 1
+ offsets = append(offsets, start)
+ }
+ c.parsed[fileName] = &parsedFile{offsets, parsed}
+}
+
+func (c *cache) getFuncAST(call *Call) *ast.FuncDecl {
+ if p := c.parsed[call.SourcePath]; p != nil {
+ return p.getFuncAST(call.Func.Name(), call.Line)
+ }
+ return nil
+}
+
+type parsedFile struct {
+ lineToByteOffset []int
+ parsed *ast.File
+}
+
+// getFuncAST gets the callee site function AST representation for the code
+// inside the function f at line l.
+func (p *parsedFile) getFuncAST(f string, l int) (d *ast.FuncDecl) {
+ // Walk the AST to find the lineToByteOffset that fits the line number.
+ var lastFunc *ast.FuncDecl
+ var found ast.Node
+ // Inspect() goes depth first. This means for example that a function like:
+ // func a() {
+ // b := func() {}
+ // c()
+ // }
+ //
+ // Were we are looking at the c() call can return confused values. It is
+ // important to look at the actual ast.Node hierarchy.
+ ast.Inspect(p.parsed, func(n ast.Node) bool {
+ if d != nil {
+ return false
+ }
+ if n == nil {
+ return true
+ }
+ if found != nil {
+ // We are walking up.
+ }
+ if int(n.Pos()) >= p.lineToByteOffset[l] {
+ // We are expecting a ast.CallExpr node. It can be harder to figure out
+ // when there are multiple calls on a single line, as the stack trace
+ // doesn't have file byte offset information, only line based.
+ // gofmt will always format to one function call per line but there can
+ // be edge cases, like:
+ // a = A{Foo(), Bar()}
+ d = lastFunc
+ //p.processNode(call, n)
+ return false
+ } else if f, ok := n.(*ast.FuncDecl); ok {
+ lastFunc = f
+ }
+ return true
+ })
+ return
+}
+
+func name(n ast.Node) string {
+ if _, ok := n.(*ast.InterfaceType); ok {
+ return "interface{}"
+ }
+ if i, ok := n.(*ast.Ident); ok {
+ return i.Name
+ }
+ if _, ok := n.(*ast.FuncType); ok {
+ return "func"
+ }
+ if s, ok := n.(*ast.SelectorExpr); ok {
+ return s.Sel.Name
+ }
+ // TODO(maruel): Implement anything missing.
+ return "<unknown>"
+}
+
+// fieldToType returns the type name and whether if it's an ellipsis.
+func fieldToType(f *ast.Field) (string, bool) {
+ switch arg := f.Type.(type) {
+ case *ast.ArrayType:
+ return "[]" + name(arg.Elt), false
+ case *ast.Ellipsis:
+ return name(arg.Elt), true
+ case *ast.FuncType:
+ // Do not print the function signature to not overload the trace.
+ return "func", false
+ case *ast.Ident:
+ return arg.Name, false
+ case *ast.InterfaceType:
+ return "interface{}", false
+ case *ast.SelectorExpr:
+ return arg.Sel.Name, false
+ case *ast.StarExpr:
+ return "*" + name(arg.X), false
+ default:
+ // TODO(maruel): Implement anything missing.
+ return "<unknown>", false
+ }
+}
+
+// extractArgumentsType returns the name of the type of each input argument.
+func extractArgumentsType(f *ast.FuncDecl) ([]string, bool) {
+ var fields []*ast.Field
+ if f.Recv != nil {
+ if len(f.Recv.List) != 1 {
+ panic("Expect only one receiver; please fix panicparse's code")
+ }
+ // If it is an object receiver (vs a pointer receiver), its address is not
+ // printed in the stack trace so it needs to be ignored.
+ if _, ok := f.Recv.List[0].Type.(*ast.StarExpr); ok {
+ fields = append(fields, f.Recv.List[0])
+ }
+ }
+ var types []string
+ extra := false
+ for _, arg := range append(fields, f.Type.Params.List...) {
+ // Assert that extra is only set on the last item of fields?
+ var t string
+ t, extra = fieldToType(arg)
+ mult := len(arg.Names)
+ if mult == 0 {
+ mult = 1
+ }
+ for i := 0; i < mult; i++ {
+ types = append(types, t)
+ }
+ }
+ return types, extra
+}
+
+// processCall walks the function and populate call accordingly.
+func processCall(call *Call, f *ast.FuncDecl) {
+ values := make([]uint64, len(call.Args.Values))
+ for i := range call.Args.Values {
+ values[i] = call.Args.Values[i].Value
+ }
+ index := 0
+ pop := func() uint64 {
+ if len(values) != 0 {
+ x := values[0]
+ values = values[1:]
+ index++
+ return x
+ }
+ return 0
+ }
+ popName := func() string {
+ n := call.Args.Values[index].Name
+ v := pop()
+ if len(n) == 0 {
+ return fmt.Sprintf("0x%x", v)
+ }
+ return n
+ }
+
+ types, extra := extractArgumentsType(f)
+ for i := 0; len(values) != 0; i++ {
+ var t string
+ if i >= len(types) {
+ if !extra {
+ // These are unexpected value! Print them as hex.
+ call.Args.Processed = append(call.Args.Processed, popName())
+ continue
+ }
+ t = types[len(types)-1]
+ } else {
+ t = types[i]
+ }
+ switch t {
+ case "float32":
+ call.Args.Processed = append(call.Args.Processed, fmt.Sprintf("%g", math.Float32frombits(uint32(pop()))))
+ case "float64":
+ call.Args.Processed = append(call.Args.Processed, fmt.Sprintf("%g", math.Float64frombits(pop())))
+ case "int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64":
+ call.Args.Processed = append(call.Args.Processed, fmt.Sprintf("%d", pop()))
+ case "string":
+ call.Args.Processed = append(call.Args.Processed, fmt.Sprintf("%s(%s, len=%d)", t, popName(), pop()))
+ default:
+ if strings.HasPrefix(t, "*") {
+ call.Args.Processed = append(call.Args.Processed, fmt.Sprintf("%s(%s)", t, popName()))
+ } else if strings.HasPrefix(t, "[]") {
+ call.Args.Processed = append(call.Args.Processed, fmt.Sprintf("%s(%s len=%d cap=%d)", t, popName(), pop(), pop()))
+ } else {
+ // Assumes it's an interface. For now, discard the object value, which
+ // is probably not a good idea.
+ call.Args.Processed = append(call.Args.Processed, fmt.Sprintf("%s(%s)", t, popName()))
+ pop()
+ }
+ }
+ if len(values) == 0 && call.Args.Elided {
+ return
+ }
+ }
+}
diff --git a/vendor/github.com/maruel/panicparse/stack/stack.go b/vendor/github.com/maruel/panicparse/stack/stack.go
new file mode 100644
index 000000000..cfb502e66
--- /dev/null
+++ b/vendor/github.com/maruel/panicparse/stack/stack.go
@@ -0,0 +1,832 @@
+// Copyright 2015 Marc-Antoine Ruel. All rights reserved.
+// Use of this source code is governed under the Apache License, Version 2.0
+// that can be found in the LICENSE file.
+
+// Package stack analyzes stack dump of Go processes and simplifies it.
+//
+// It is mostly useful on servers will large number of identical goroutines,
+// making the crash dump harder to read than strictly necesary.
+package stack
+
+import (
+ "bufio"
+ "bytes"
+ "errors"
+ "fmt"
+ "io"
+ "math"
+ "net/url"
+ "os"
+ "path/filepath"
+ "regexp"
+ "runtime"
+ "sort"
+ "strconv"
+ "strings"
+ "unicode"
+ "unicode/utf8"
+)
+
+const lockedToThread = "locked to thread"
+
+var (
+ // TODO(maruel): Handle corrupted stack cases:
+ // - missed stack barrier
+ // - found next stack barrier at 0x123; expected
+ // - runtime: unexpected return pc for FUNC_NAME called from 0x123
+
+ reRoutineHeader = regexp.MustCompile("^goroutine (\\d+) \\[([^\\]]+)\\]\\:\n$")
+ reMinutes = regexp.MustCompile("^(\\d+) minutes$")
+ reUnavail = regexp.MustCompile("^(?:\t| +)goroutine running on other thread; stack unavailable")
+ // See gentraceback() in src/runtime/traceback.go for more information.
+ // - Sometimes the source file comes up as "<autogenerated>". It is the
+ // compiler than generated these, not the runtime.
+ // - The tab may be replaced with spaces when a user copy-paste it, handle
+ // this transparently.
+ // - "runtime.gopanic" is explicitly replaced with "panic" by gentraceback().
+ // - The +0x123 byte offset is printed when frame.pc > _func.entry. _func is
+ // generated by the linker.
+ // - The +0x123 byte offset is not included with generated code, e.g. unnamed
+ // functions "func·006()" which is generally go func() { ... }()
+ // statements. Since the _func is generated at runtime, it's probably why
+ // _func.entry is not set.
+ // - C calls may have fp=0x123 sp=0x123 appended. I think it normally happens
+ // when a signal is not correctly handled. It is printed with m.throwing>0.
+ // These are discarded.
+ // - For cgo, the source file may be "??".
+ reFile = regexp.MustCompile("^(?:\t| +)(\\?\\?|\\<autogenerated\\>|.+\\.(?:c|go|s))\\:(\\d+)(?:| \\+0x[0-9a-f]+)(?:| fp=0x[0-9a-f]+ sp=0x[0-9a-f]+)\n$")
+ // Sadly, it doesn't note the goroutine number so we could cascade them per
+ // parenthood.
+ reCreated = regexp.MustCompile("^created by (.+)\n$")
+ reFunc = regexp.MustCompile("^(.+)\\((.*)\\)\n$")
+ reElided = regexp.MustCompile("^\\.\\.\\.additional frames elided\\.\\.\\.\n$")
+ // Include frequent GOROOT value on Windows, distro provided and user
+ // installed path. This simplifies the user's life when processing a trace
+ // generated on another VM.
+ // TODO(maruel): Guess the path automatically via traces containing the
+ // 'runtime' package, which is very frequent. This would be "less bad" than
+ // throwing up random values at the parser.
+ goroots = []string{runtime.GOROOT(), "c:/go", "/usr/lib/go", "/usr/local/go"}
+)
+
+// Similarity is the level at which two call lines arguments must match to be
+// considered similar enough to coalesce them.
+type Similarity int
+
+const (
+ // ExactFlags requires same bits (e.g. Locked).
+ ExactFlags Similarity = iota
+ // ExactLines requests the exact same arguments on the call line.
+ ExactLines
+ // AnyPointer considers different pointers a similar call line.
+ AnyPointer
+ // AnyValue accepts any value as similar call line.
+ AnyValue
+)
+
+// Function is a function call.
+//
+// Go stack traces print a mangled function call, this wrapper unmangle the
+// string before printing and adds other filtering methods.
+type Function struct {
+ Raw string
+}
+
+// String is the fully qualified function name.
+//
+// Sadly Go is a bit confused when the package name doesn't match the directory
+// containing the source file and will use the directory name instead of the
+// real package name.
+func (f Function) String() string {
+ s, _ := url.QueryUnescape(f.Raw)
+ return s
+}
+
+// Name is the naked function name.
+func (f Function) Name() string {
+ parts := strings.SplitN(filepath.Base(f.Raw), ".", 2)
+ if len(parts) == 1 {
+ return parts[0]
+ }
+ return parts[1]
+}
+
+// PkgName is the package name for this function reference.
+func (f Function) PkgName() string {
+ parts := strings.SplitN(filepath.Base(f.Raw), ".", 2)
+ if len(parts) == 1 {
+ return ""
+ }
+ s, _ := url.QueryUnescape(parts[0])
+ return s
+}
+
+// PkgDotName returns "<package>.<func>" format.
+func (f Function) PkgDotName() string {
+ parts := strings.SplitN(filepath.Base(f.Raw), ".", 2)
+ s, _ := url.QueryUnescape(parts[0])
+ if len(parts) == 1 {
+ return parts[0]
+ }
+ if s != "" || parts[1] != "" {
+ return s + "." + parts[1]
+ }
+ return ""
+}
+
+// IsExported returns true if the function is exported.
+func (f Function) IsExported() bool {
+ name := f.Name()
+ parts := strings.Split(name, ".")
+ r, _ := utf8.DecodeRuneInString(parts[len(parts)-1])
+ if unicode.ToUpper(r) == r {
+ return true
+ }
+ return f.PkgName() == "main" && name == "main"
+}
+
+// Arg is an argument on a Call.
+type Arg struct {
+ Value uint64 // Value is the raw value as found in the stack trace
+ Name string // Name is a pseudo name given to the argument
+}
+
+// IsPtr returns true if we guess it's a pointer. It's only a guess, it can be
+// easily be confused by a bitmask.
+func (a *Arg) IsPtr() bool {
+ // Assumes all pointers are above 16Mb and positive.
+ return a.Value > 16*1024*1024 && a.Value < math.MaxInt64
+}
+
+func (a Arg) String() string {
+ if a.Name != "" {
+ return a.Name
+ }
+ if a.Value == 0 {
+ return "0"
+ }
+ return fmt.Sprintf("0x%x", a.Value)
+}
+
+// Args is a series of function call arguments.
+type Args struct {
+ Values []Arg // Values is the arguments as shown on the stack trace. They are mangled via simplification.
+ Processed []string // Processed is the arguments generated from processing the source files. It can have a length lower than Values.
+ Elided bool // If set, it means there was a trailing ", ..."
+}
+
+func (a Args) String() string {
+ var v []string
+ if len(a.Processed) != 0 {
+ v = make([]string, 0, len(a.Processed))
+ for _, item := range a.Processed {
+ v = append(v, item)
+ }
+ } else {
+ v = make([]string, 0, len(a.Values))
+ for _, item := range a.Values {
+ v = append(v, item.String())
+ }
+ }
+ if a.Elided {
+ v = append(v, "...")
+ }
+ return strings.Join(v, ", ")
+}
+
+// Equal returns true only if both arguments are exactly equal.
+func (a *Args) Equal(r *Args) bool {
+ if a.Elided != r.Elided || len(a.Values) != len(r.Values) {
+ return false
+ }
+ for i, l := range a.Values {
+ if l != r.Values[i] {
+ return false
+ }
+ }
+ return true
+}
+
+// Similar returns true if the two Args are equal or almost but not quite
+// equal.
+func (a *Args) Similar(r *Args, similar Similarity) bool {
+ if a.Elided != r.Elided || len(a.Values) != len(r.Values) {
+ return false
+ }
+ if similar == AnyValue {
+ return true
+ }
+ for i, l := range a.Values {
+ switch similar {
+ case ExactFlags, ExactLines:
+ if l != r.Values[i] {
+ return false
+ }
+ default:
+ if l.IsPtr() != r.Values[i].IsPtr() || (!l.IsPtr() && l != r.Values[i]) {
+ return false
+ }
+ }
+ }
+ return true
+}
+
+// Merge merges two similar Args, zapping out differences.
+func (a *Args) Merge(r *Args) Args {
+ out := Args{
+ Values: make([]Arg, len(a.Values)),
+ Elided: a.Elided,
+ }
+ for i, l := range a.Values {
+ if l != r.Values[i] {
+ out.Values[i].Name = "*"
+ out.Values[i].Value = l.Value
+ } else {
+ out.Values[i] = l
+ }
+ }
+ return out
+}
+
+// Call is an item in the stack trace.
+type Call struct {
+ SourcePath string // Full path name of the source file
+ Line int // Line number
+ Func Function // Fully qualified function name (encoded).
+ Args Args // Call arguments
+}
+
+// Equal returns true only if both calls are exactly equal.
+func (c *Call) Equal(r *Call) bool {
+ return c.SourcePath == r.SourcePath && c.Line == r.Line && c.Func == r.Func && c.Args.Equal(&r.Args)
+}
+
+// Similar returns true if the two Call are equal or almost but not quite
+// equal.
+func (c *Call) Similar(r *Call, similar Similarity) bool {
+ return c.SourcePath == r.SourcePath && c.Line == r.Line && c.Func == r.Func && c.Args.Similar(&r.Args, similar)
+}
+
+// Merge merges two similar Call, zapping out differences.
+func (c *Call) Merge(r *Call) Call {
+ return Call{
+ SourcePath: c.SourcePath,
+ Line: c.Line,
+ Func: c.Func,
+ Args: c.Args.Merge(&r.Args),
+ }
+}
+
+// SourceName returns the base file name of the source file.
+func (c *Call) SourceName() string {
+ return filepath.Base(c.SourcePath)
+}
+
+// SourceLine returns "source.go:line", including only the base file name.
+func (c *Call) SourceLine() string {
+ return fmt.Sprintf("%s:%d", c.SourceName(), c.Line)
+}
+
+// FullSourceLine returns "/path/to/source.go:line".
+func (c *Call) FullSourceLine() string {
+ return fmt.Sprintf("%s:%d", c.SourcePath, c.Line)
+}
+
+// PkgSource is one directory plus the file name of the source file.
+func (c *Call) PkgSource() string {
+ return filepath.Join(filepath.Base(filepath.Dir(c.SourcePath)), c.SourceName())
+}
+
+const testMainSource = "_test" + string(os.PathSeparator) + "_testmain.go"
+
+// IsStdlib returns true if it is a Go standard library function. This includes
+// the 'go test' generated main executable.
+func (c *Call) IsStdlib() bool {
+ for _, goroot := range goroots {
+ if strings.HasPrefix(c.SourcePath, goroot) {
+ return true
+ }
+ }
+ // Consider _test/_testmain.go as stdlib since it's injected by "go test".
+ return c.PkgSource() == testMainSource
+}
+
+// IsPkgMain returns true if it is in the main package.
+func (c *Call) IsPkgMain() bool {
+ return c.Func.PkgName() == "main"
+}
+
+// Stack is a call stack.
+type Stack struct {
+ Calls []Call // Call stack. First is original function, last is leaf function.
+ Elided bool // Happens when there's >100 items in Stack, currently hardcoded in package runtime.
+}
+
+// Equal returns true on if both call stacks are exactly equal.
+func (s *Stack) Equal(r *Stack) bool {
+ if len(s.Calls) != len(r.Calls) || s.Elided != r.Elided {
+ return false
+ }
+ for i := range s.Calls {
+ if !s.Calls[i].Equal(&r.Calls[i]) {
+ return false
+ }
+ }
+ return true
+}
+
+// Similar returns true if the two Stack are equal or almost but not quite
+// equal.
+func (s *Stack) Similar(r *Stack, similar Similarity) bool {
+ if len(s.Calls) != len(r.Calls) || s.Elided != r.Elided {
+ return false
+ }
+ for i := range s.Calls {
+ if !s.Calls[i].Similar(&r.Calls[i], similar) {
+ return false
+ }
+ }
+ return true
+}
+
+// Merge merges two similar Stack, zapping out differences.
+func (s *Stack) Merge(r *Stack) *Stack {
+ // Assumes similar stacks have the same length.
+ out := &Stack{
+ Calls: make([]Call, len(s.Calls)),
+ Elided: s.Elided,
+ }
+ for i := range s.Calls {
+ out.Calls[i] = s.Calls[i].Merge(&r.Calls[i])
+ }
+ return out
+}
+
+// Less compares two Stack, where the ones that are less are more
+// important, so they come up front. A Stack with more private functions is
+// 'less' so it is at the top. Inversely, a Stack with only public
+// functions is 'more' so it is at the bottom.
+func (s *Stack) Less(r *Stack) bool {
+ lStdlib := 0
+ lPrivate := 0
+ for _, c := range s.Calls {
+ if c.IsStdlib() {
+ lStdlib++
+ } else {
+ lPrivate++
+ }
+ }
+ rStdlib := 0
+ rPrivate := 0
+ for _, s := range r.Calls {
+ if s.IsStdlib() {
+ rStdlib++
+ } else {
+ rPrivate++
+ }
+ }
+ if lPrivate > rPrivate {
+ return true
+ }
+ if lPrivate < rPrivate {
+ return false
+ }
+ if lStdlib > rStdlib {
+ return false
+ }
+ if lStdlib < rStdlib {
+ return true
+ }
+
+ // Stack lengths are the same.
+ for x := range s.Calls {
+ if s.Calls[x].Func.Raw < r.Calls[x].Func.Raw {
+ return true
+ }
+ if s.Calls[x].Func.Raw > r.Calls[x].Func.Raw {
+ return true
+ }
+ if s.Calls[x].PkgSource() < r.Calls[x].PkgSource() {
+ return true
+ }
+ if s.Calls[x].PkgSource() > r.Calls[x].PkgSource() {
+ return true
+ }
+ if s.Calls[x].Line < r.Calls[x].Line {
+ return true
+ }
+ if s.Calls[x].Line > r.Calls[x].Line {
+ return true
+ }
+ }
+ return false
+}
+
+// Signature represents the signature of one or multiple goroutines.
+//
+// It is effectively the stack trace plus the goroutine internal bits, like
+// it's state, if it is thread locked, which call site created this goroutine,
+// etc.
+type Signature struct {
+ // Use git grep 'gopark(|unlock)\(' to find them all plus everything listed
+ // in runtime/traceback.go. Valid values includes:
+ // - chan send, chan receive, select
+ // - finalizer wait, mark wait (idle),
+ // - Concurrent GC wait, GC sweep wait, force gc (idle)
+ // - IO wait, panicwait
+ // - semacquire, semarelease
+ // - sleep, timer goroutine (idle)
+ // - trace reader (blocked)
+ // Stuck cases:
+ // - chan send (nil chan), chan receive (nil chan), select (no cases)
+ // Runnable states:
+ // - idle, runnable, running, syscall, waiting, dead, enqueue, copystack,
+ // Scan states:
+ // - scan, scanrunnable, scanrunning, scansyscall, scanwaiting, scandead,
+ // scanenqueue
+ State string
+ CreatedBy Call // Which other goroutine which created this one.
+ SleepMin int // Wait time in minutes, if applicable.
+ SleepMax int // Wait time in minutes, if applicable.
+ Stack Stack
+ Locked bool // Locked to an OS thread.
+}
+
+// Equal returns true only if both signatures are exactly equal.
+func (s *Signature) Equal(r *Signature) bool {
+ if s.State != r.State || !s.CreatedBy.Equal(&r.CreatedBy) || s.Locked != r.Locked || s.SleepMin != r.SleepMin || s.SleepMax != r.SleepMax {
+ return false
+ }
+ return s.Stack.Equal(&r.Stack)
+}
+
+// Similar returns true if the two Signature are equal or almost but not quite
+// equal.
+func (s *Signature) Similar(r *Signature, similar Similarity) bool {
+ if s.State != r.State || !s.CreatedBy.Similar(&r.CreatedBy, similar) {
+ return false
+ }
+ if similar == ExactFlags && s.Locked != r.Locked {
+ return false
+ }
+ return s.Stack.Similar(&r.Stack, similar)
+}
+
+// Merge merges two similar Signature, zapping out differences.
+func (s *Signature) Merge(r *Signature) *Signature {
+ min := s.SleepMin
+ if r.SleepMin < min {
+ min = r.SleepMin
+ }
+ max := s.SleepMax
+ if r.SleepMax > max {
+ max = r.SleepMax
+ }
+ return &Signature{
+ State: s.State, // Drop right side.
+ CreatedBy: s.CreatedBy, // Drop right side.
+ SleepMin: min,
+ SleepMax: max,
+ Stack: *s.Stack.Merge(&r.Stack),
+ Locked: s.Locked || r.Locked, // TODO(maruel): This is weirdo.
+ }
+}
+
+// Less compares two Signature, where the ones that are less are more
+// important, so they come up front. A Signature with more private functions is
+// 'less' so it is at the top. Inversely, a Signature with only public
+// functions is 'more' so it is at the bottom.
+func (s *Signature) Less(r *Signature) bool {
+ if s.Stack.Less(&r.Stack) {
+ return true
+ }
+ if r.Stack.Less(&s.Stack) {
+ return false
+ }
+ if s.Locked && !r.Locked {
+ return true
+ }
+ if r.Locked && !s.Locked {
+ return false
+ }
+ if s.State < r.State {
+ return true
+ }
+ if s.State > r.State {
+ return false
+ }
+ return false
+}
+
+// Goroutine represents the state of one goroutine, including the stack trace.
+type Goroutine struct {
+ Signature // It's stack trace, internal bits, state, which call site created it, etc.
+ ID int // Goroutine ID.
+ First bool // First is the goroutine first printed, normally the one that crashed.
+}
+
+// Bucketize returns the number of similar goroutines.
+func Bucketize(goroutines []Goroutine, similar Similarity) map[*Signature][]Goroutine {
+ out := map[*Signature][]Goroutine{}
+ // O(n²). Fix eventually.
+ for _, routine := range goroutines {
+ found := false
+ for key := range out {
+ // When a match is found, this effectively drops the other goroutine ID.
+ if key.Similar(&routine.Signature, similar) {
+ found = true
+ if !key.Equal(&routine.Signature) {
+ // Almost but not quite equal. There's different pointers passed
+ // around but the same values. Zap out the different values.
+ newKey := key.Merge(&routine.Signature)
+ out[newKey] = append(out[key], routine)
+ delete(out, key)
+ } else {
+ out[key] = append(out[key], routine)
+ }
+ break
+ }
+ }
+ if !found {
+ key := &Signature{}
+ *key = routine.Signature
+ out[key] = []Goroutine{routine}
+ }
+ }
+ return out
+}
+
+// Bucket is a stack trace signature and the list of goroutines that fits this
+// signature.
+type Bucket struct {
+ Signature
+ Routines []Goroutine
+}
+
+// First returns true if it contains the first goroutine, e.g. the ones that
+// likely generated the panic() call, if any.
+func (b *Bucket) First() bool {
+ for _, r := range b.Routines {
+ if r.First {
+ return true
+ }
+ }
+ return false
+}
+
+// Less does reverse sort.
+func (b *Bucket) Less(r *Bucket) bool {
+ if b.First() {
+ return true
+ }
+ if r.First() {
+ return false
+ }
+ return b.Signature.Less(&r.Signature)
+}
+
+// Buckets is a list of Bucket sorted by repeation count.
+type Buckets []Bucket
+
+func (b Buckets) Len() int {
+ return len(b)
+}
+
+func (b Buckets) Less(i, j int) bool {
+ return b[i].Less(&b[j])
+}
+
+func (b Buckets) Swap(i, j int) {
+ b[j], b[i] = b[i], b[j]
+}
+
+// SortBuckets creates a list of Bucket from each goroutine stack trace count.
+func SortBuckets(buckets map[*Signature][]Goroutine) Buckets {
+ out := make(Buckets, 0, len(buckets))
+ for signature, count := range buckets {
+ out = append(out, Bucket{*signature, count})
+ }
+ sort.Sort(out)
+ return out
+}
+
+// scanLines is similar to bufio.ScanLines except that it:
+// - doesn't drop '\n'
+// - doesn't strip '\r'
+// - returns when the data is bufio.MaxScanTokenSize bytes
+func scanLines(data []byte, atEOF bool) (advance int, token []byte, err error) {
+ if atEOF && len(data) == 0 {
+ return 0, nil, nil
+ }
+ if i := bytes.IndexByte(data, '\n'); i >= 0 {
+ return i + 1, data[0 : i+1], nil
+ }
+ if atEOF {
+ return len(data), data, nil
+ }
+ if len(data) >= bufio.MaxScanTokenSize {
+ // Returns the line even if it is not at EOF nor has a '\n', otherwise the
+ // scanner will return bufio.ErrTooLong which is definitely not what we
+ // want.
+ return len(data), data, nil
+ }
+ return 0, nil, nil
+}
+
+// ParseDump processes the output from runtime.Stack().
+//
+// It supports piping from another command and assumes there is junk before the
+// actual stack trace. The junk is streamed to out.
+func ParseDump(r io.Reader, out io.Writer) ([]Goroutine, error) {
+ goroutines := make([]Goroutine, 0, 16)
+ var goroutine *Goroutine
+ scanner := bufio.NewScanner(r)
+ scanner.Split(scanLines)
+ // TODO(maruel): Use a formal state machine. Patterns follows:
+ // - reRoutineHeader
+ // Either:
+ // - reUnavail
+ // - reFunc + reFile in a loop
+ // - reElided
+ // Optionally ends with:
+ // - reCreated + reFile
+ // Between each goroutine stack dump: an empty line
+ created := false
+ // firstLine is the first line after the reRoutineHeader header line.
+ firstLine := false
+ for scanner.Scan() {
+ line := scanner.Text()
+ if line == "\n" {
+ if goroutine != nil {
+ goroutine = nil
+ continue
+ }
+ } else if line[len(line)-1] == '\n' {
+ if goroutine == nil {
+ if match := reRoutineHeader.FindStringSubmatch(line); match != nil {
+ if id, err := strconv.Atoi(match[1]); err == nil {
+ // See runtime/traceback.go.
+ // "<state>, \d+ minutes, locked to thread"
+ items := strings.Split(match[2], ", ")
+ sleep := 0
+ locked := false
+ for i := 1; i < len(items); i++ {
+ if items[i] == lockedToThread {
+ locked = true
+ continue
+ }
+ // Look for duration, if any.
+ if match2 := reMinutes.FindStringSubmatch(items[i]); match2 != nil {
+ sleep, _ = strconv.Atoi(match2[1])
+ }
+ }
+ goroutines = append(goroutines, Goroutine{
+ Signature: Signature{
+ State: items[0],
+ SleepMin: sleep,
+ SleepMax: sleep,
+ Locked: locked,
+ },
+ ID: id,
+ First: len(goroutines) == 0,
+ })
+ goroutine = &goroutines[len(goroutines)-1]
+ firstLine = true
+ continue
+ }
+ }
+ } else {
+ if firstLine {
+ firstLine = false
+ if match := reUnavail.FindStringSubmatch(line); match != nil {
+ // Generate a fake stack entry.
+ goroutine.Stack.Calls = []Call{{SourcePath: "<unavailable>"}}
+ continue
+ }
+ }
+
+ if match := reFile.FindStringSubmatch(line); match != nil {
+ // Triggers after a reFunc or a reCreated.
+ num, err := strconv.Atoi(match[2])
+ if err != nil {
+ return goroutines, fmt.Errorf("failed to parse int on line: \"%s\"", line)
+ }
+ if created {
+ created = false
+ goroutine.CreatedBy.SourcePath = match[1]
+ goroutine.CreatedBy.Line = num
+ } else {
+ i := len(goroutine.Stack.Calls) - 1
+ if i < 0 {
+ return goroutines, errors.New("unexpected order")
+ }
+ goroutine.Stack.Calls[i].SourcePath = match[1]
+ goroutine.Stack.Calls[i].Line = num
+ }
+ continue
+ }
+
+ if match := reCreated.FindStringSubmatch(line); match != nil {
+ created = true
+ goroutine.CreatedBy.Func.Raw = match[1]
+ continue
+ }
+
+ if match := reFunc.FindStringSubmatch(line); match != nil {
+ args := Args{}
+ for _, a := range strings.Split(match[2], ", ") {
+ if a == "..." {
+ args.Elided = true
+ continue
+ }
+ if a == "" {
+ // Remaining values were dropped.
+ break
+ }
+ v, err := strconv.ParseUint(a, 0, 64)
+ if err != nil {
+ return goroutines, fmt.Errorf("failed to parse int on line: \"%s\"", line)
+ }
+ args.Values = append(args.Values, Arg{Value: v})
+ }
+ goroutine.Stack.Calls = append(goroutine.Stack.Calls, Call{Func: Function{match[1]}, Args: args})
+ continue
+ }
+
+ if match := reElided.FindStringSubmatch(line); match != nil {
+ goroutine.Stack.Elided = true
+ continue
+ }
+ }
+ }
+ _, _ = io.WriteString(out, line)
+ goroutine = nil
+ }
+ nameArguments(goroutines)
+ return goroutines, scanner.Err()
+}
+
+// Private stuff.
+
+func nameArguments(goroutines []Goroutine) {
+ // Set a name for any pointer occuring more than once.
+ type object struct {
+ args []*Arg
+ inPrimary bool
+ id int
+ }
+ objects := map[uint64]object{}
+ // Enumerate all the arguments.
+ for i := range goroutines {
+ for j := range goroutines[i].Stack.Calls {
+ for k := range goroutines[i].Stack.Calls[j].Args.Values {
+ arg := goroutines[i].Stack.Calls[j].Args.Values[k]
+ if arg.IsPtr() {
+ objects[arg.Value] = object{
+ args: append(objects[arg.Value].args, &goroutines[i].Stack.Calls[j].Args.Values[k]),
+ inPrimary: objects[arg.Value].inPrimary || i == 0,
+ }
+ }
+ }
+ }
+ // CreatedBy.Args is never set.
+ }
+ order := uint64Slice{}
+ for k, obj := range objects {
+ if len(obj.args) > 1 && obj.inPrimary {
+ order = append(order, k)
+ }
+ }
+ sort.Sort(order)
+ nextID := 1
+ for _, k := range order {
+ for _, arg := range objects[k].args {
+ arg.Name = fmt.Sprintf("#%d", nextID)
+ }
+ nextID++
+ }
+
+ // Now do the rest. This is done so the output is deterministic.
+ order = uint64Slice{}
+ for k := range objects {
+ order = append(order, k)
+ }
+ sort.Sort(order)
+ for _, k := range order {
+ // Process the remaining pointers, they were not referenced by primary
+ // thread so will have higher IDs.
+ if objects[k].inPrimary {
+ continue
+ }
+ for _, arg := range objects[k].args {
+ arg.Name = fmt.Sprintf("#%d", nextID)
+ }
+ nextID++
+ }
+}
+
+type uint64Slice []uint64
+
+func (a uint64Slice) Len() int { return len(a) }
+func (a uint64Slice) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
+func (a uint64Slice) Less(i, j int) bool { return a[i] < a[j] }
diff --git a/vendor/github.com/maruel/panicparse/stack/ui.go b/vendor/github.com/maruel/panicparse/stack/ui.go
new file mode 100644
index 000000000..b125fc940
--- /dev/null
+++ b/vendor/github.com/maruel/panicparse/stack/ui.go
@@ -0,0 +1,139 @@
+// Copyright 2016 Marc-Antoine Ruel. All rights reserved.
+// Use of this source code is governed under the Apache License, Version 2.0
+// that can be found in the LICENSE file.
+
+package stack
+
+import (
+ "fmt"
+ "strings"
+)
+
+// Palette defines the color used.
+//
+// An empty object Palette{} can be used to disable coloring.
+type Palette struct {
+ EOLReset string
+
+ // Routine header.
+ RoutineFirst string // The first routine printed.
+ Routine string // Following routines.
+ CreatedBy string
+
+ // Call line.
+ Package string
+ SourceFile string
+ FunctionStdLib string
+ FunctionStdLibExported string
+ FunctionMain string
+ FunctionOther string
+ FunctionOtherExported string
+ Arguments string
+}
+
+// CalcLengths returns the maximum length of the source lines and package names.
+func CalcLengths(buckets Buckets, fullPath bool) (int, int) {
+ srcLen := 0
+ pkgLen := 0
+ for _, bucket := range buckets {
+ for _, line := range bucket.Signature.Stack.Calls {
+ l := 0
+ if fullPath {
+ l = len(line.FullSourceLine())
+ } else {
+ l = len(line.SourceLine())
+ }
+ if l > srcLen {
+ srcLen = l
+ }
+ l = len(line.Func.PkgName())
+ if l > pkgLen {
+ pkgLen = l
+ }
+ }
+ }
+ return srcLen, pkgLen
+}
+
+// functionColor returns the color to be used for the function name based on
+// the type of package the function is in.
+func (p *Palette) functionColor(line *Call) string {
+ if line.IsStdlib() {
+ if line.Func.IsExported() {
+ return p.FunctionStdLibExported
+ }
+ return p.FunctionStdLib
+ } else if line.IsPkgMain() {
+ return p.FunctionMain
+ } else if line.Func.IsExported() {
+ return p.FunctionOtherExported
+ }
+ return p.FunctionOther
+}
+
+// routineColor returns the color for the header of the goroutines bucket.
+func (p *Palette) routineColor(bucket *Bucket, multipleBuckets bool) string {
+ if bucket.First() && multipleBuckets {
+ return p.RoutineFirst
+ }
+ return p.Routine
+}
+
+// BucketHeader prints the header of a goroutine signature.
+func (p *Palette) BucketHeader(bucket *Bucket, fullPath, multipleBuckets bool) string {
+ extra := ""
+ if bucket.SleepMax != 0 {
+ if bucket.SleepMin != bucket.SleepMax {
+ extra += fmt.Sprintf(" [%d~%d minutes]", bucket.SleepMin, bucket.SleepMax)
+ } else {
+ extra += fmt.Sprintf(" [%d minutes]", bucket.SleepMax)
+ }
+ }
+ if bucket.Locked {
+ extra += " [locked]"
+ }
+ created := bucket.CreatedBy.Func.PkgDotName()
+ if created != "" {
+ created += " @ "
+ if fullPath {
+ created += bucket.CreatedBy.FullSourceLine()
+ } else {
+ created += bucket.CreatedBy.SourceLine()
+ }
+ extra += p.CreatedBy + " [Created by " + created + "]"
+ }
+ return fmt.Sprintf(
+ "%s%d: %s%s%s\n",
+ p.routineColor(bucket, multipleBuckets), len(bucket.Routines),
+ bucket.State, extra,
+ p.EOLReset)
+}
+
+// callLine prints one stack line.
+func (p *Palette) callLine(line *Call, srcLen, pkgLen int, fullPath bool) string {
+ src := ""
+ if fullPath {
+ src = line.FullSourceLine()
+ } else {
+ src = line.SourceLine()
+ }
+ return fmt.Sprintf(
+ " %s%-*s %s%-*s %s%s%s(%s)%s",
+ p.Package, pkgLen, line.Func.PkgName(),
+ p.SourceFile, srcLen, src,
+ p.functionColor(line), line.Func.Name(),
+ p.Arguments, line.Args,
+ p.EOLReset)
+}
+
+// StackLines prints one complete stack trace, without the header.
+func (p *Palette) StackLines(signature *Signature, srcLen, pkgLen int, fullPath bool) string {
+ out := make([]string, len(signature.Stack.Calls))
+ for i := range signature.Stack.Calls {
+ out[i] = p.callLine(&signature.Stack.Calls[i], srcLen, pkgLen, fullPath)
+ }
+ if signature.Stack.Elided {
+ out = append(out, " (...)")
+ }
+ return strings.Join(out, "\n") + "\n"
+}