907 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			907 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // 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 necessary.
 | |
| package stack
 | |
| 
 | |
| import (
 | |
| 	"bufio"
 | |
| 	"bytes"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"log"
 | |
| 	"math"
 | |
| 	"net/url"
 | |
| 	"os"
 | |
| 	"os/user"
 | |
| 	"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+) \\[([^\\]]+)\\]\\:\r?\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]+)\r?\n$")
 | |
| 	// Sadly, it doesn't note the goroutine number so we could cascade them per
 | |
| 	// parenthood.
 | |
| 	reCreated = regexp.MustCompile("^created by (.+)\r?\n$")
 | |
| 	reFunc    = regexp.MustCompile("^(.+)\\((.*)\\)\r?\n$")
 | |
| 	reElided  = regexp.MustCompile("^\\.\\.\\.additional frames elided\\.\\.\\.\r?\n$")
 | |
| 
 | |
| 	// TODO(maruel): This is a global state, affected by ParseDump(). This will
 | |
| 	// be refactored in v2.
 | |
| 
 | |
| 	// goroot is the GOROOT as detected in the traceback, not the on the host.
 | |
| 	//
 | |
| 	// It can be empty if no root was determined, for example the traceback
 | |
| 	// contains only non-stdlib source references.
 | |
| 	goroot string
 | |
| 	// gopaths is the GOPATH as detected in the traceback, with the value being
 | |
| 	// the corresponding path mapped to the host.
 | |
| 	//
 | |
| 	// It can be empty if only stdlib code is in the traceback or if no local
 | |
| 	// sources were matched up. In the general case there is only one.
 | |
| 	gopaths map[string]string
 | |
| 	// Corresponding local values on the host.
 | |
| 	localgoroot  = runtime.GOROOT()
 | |
| 	localgopaths = getGOPATHs()
 | |
| )
 | |
| 
 | |
| // 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 as seen in the trace
 | |
| 	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)
 | |
| }
 | |
| 
 | |
| // LocalSourcePath is the full path name of the source file as seen in the host.
 | |
| func (c *Call) LocalSourcePath() string {
 | |
| 	// TODO(maruel): Call needs members goroot and gopaths.
 | |
| 	if strings.HasPrefix(c.SourcePath, goroot) {
 | |
| 		return filepath.Join(localgoroot, c.SourcePath[len(goroot):])
 | |
| 	}
 | |
| 	for prefix, dest := range gopaths {
 | |
| 		if strings.HasPrefix(c.SourcePath, prefix) {
 | |
| 			return filepath.Join(dest, c.SourcePath[len(prefix):])
 | |
| 		}
 | |
| 	}
 | |
| 	return c.SourcePath
 | |
| }
 | |
| 
 | |
| // FullSourceLine returns "/path/to/source.go:line".
 | |
| //
 | |
| // This file path is mutated to look like the local path.
 | |
| 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 {
 | |
| 	// Consider _test/_testmain.go as stdlib since it's injected by "go test".
 | |
| 	return (goroot != "" && strings.HasPrefix(c.SourcePath, goroot)) || 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.
 | |
| }
 | |
| 
 | |
| // 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" || line == "\r\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)
 | |
| 	// Mutate global state.
 | |
| 	// TODO(maruel): Make this part of the context instead of a global.
 | |
| 	if goroot == "" {
 | |
| 		findRoots(goroutines)
 | |
| 	}
 | |
| 	return goroutines, scanner.Err()
 | |
| }
 | |
| 
 | |
| // NoRebase disables GOROOT and GOPATH guessing in ParseDump().
 | |
| //
 | |
| // BUG: This function will be removed in v2, as ParseDump() will accept a flag
 | |
| // explicitly.
 | |
| func NoRebase() {
 | |
| 	goroot = runtime.GOROOT()
 | |
| 	gopaths = map[string]string{}
 | |
| 	for _, p := range getGOPATHs() {
 | |
| 		gopaths[p] = p
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Private stuff.
 | |
| 
 | |
| func nameArguments(goroutines []Goroutine) {
 | |
| 	// Set a name for any pointer occurring 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 := make(uint64Slice, 0, len(objects)/2)
 | |
| 	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 = make(uint64Slice, 0, len(objects))
 | |
| 	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++
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // hasPathPrefix returns true if any of s is the prefix of p.
 | |
| func hasPathPrefix(p string, s map[string]string) bool {
 | |
| 	for prefix := range s {
 | |
| 		if strings.HasPrefix(p, prefix+"/") {
 | |
| 			return true
 | |
| 		}
 | |
| 	}
 | |
| 	return false
 | |
| }
 | |
| 
 | |
| // getFiles returns all the source files deduped and ordered.
 | |
| func getFiles(goroutines []Goroutine) []string {
 | |
| 	files := map[string]struct{}{}
 | |
| 	for _, g := range goroutines {
 | |
| 		for _, c := range g.Stack.Calls {
 | |
| 			files[c.SourcePath] = struct{}{}
 | |
| 		}
 | |
| 	}
 | |
| 	out := make([]string, 0, len(files))
 | |
| 	for f := range files {
 | |
| 		out = append(out, f)
 | |
| 	}
 | |
| 	sort.Strings(out)
 | |
| 	return out
 | |
| }
 | |
| 
 | |
| // splitPath splits a path into its components.
 | |
| //
 | |
| // The first item has its initial path separator kept.
 | |
| func splitPath(p string) []string {
 | |
| 	if p == "" {
 | |
| 		return nil
 | |
| 	}
 | |
| 	var out []string
 | |
| 	s := ""
 | |
| 	for _, c := range p {
 | |
| 		if c != '/' || (len(out) == 0 && strings.Count(s, "/") == len(s)) {
 | |
| 			s += string(c)
 | |
| 		} else if s != "" {
 | |
| 			out = append(out, s)
 | |
| 			s = ""
 | |
| 		}
 | |
| 	}
 | |
| 	if s != "" {
 | |
| 		out = append(out, s)
 | |
| 	}
 | |
| 	return out
 | |
| }
 | |
| 
 | |
| // isFile returns true if the path is a valid file.
 | |
| func isFile(p string) bool {
 | |
| 	// TODO(maruel): Is it faster to open the file or to stat it? Worth a perf
 | |
| 	// test on Windows.
 | |
| 	i, err := os.Stat(p)
 | |
| 	return err == nil && !i.IsDir()
 | |
| }
 | |
| 
 | |
| // isRootIn returns a root if the file split in parts is rooted in root.
 | |
| func rootedIn(root string, parts []string) string {
 | |
| 	//log.Printf("rootIn(%s, %v)", root, parts)
 | |
| 	for i := 1; i < len(parts); i++ {
 | |
| 		suffix := filepath.Join(parts[i:]...)
 | |
| 		if isFile(filepath.Join(root, suffix)) {
 | |
| 			return filepath.Join(parts[:i]...)
 | |
| 		}
 | |
| 	}
 | |
| 	return ""
 | |
| }
 | |
| 
 | |
| // findRoots sets global variables goroot and gopath.
 | |
| //
 | |
| // TODO(maruel): In v2, it will be a property of the new struct that will
 | |
| // contain the goroutines.
 | |
| func findRoots(goroutines []Goroutine) {
 | |
| 	gopaths = map[string]string{}
 | |
| 	for _, f := range getFiles(goroutines) {
 | |
| 		// TODO(maruel): Could a stack dump have mixed cases? I think it's
 | |
| 		// possible, need to confirm and handle.
 | |
| 		//log.Printf("  Analyzing %s", f)
 | |
| 		if goroot != "" && strings.HasPrefix(f, goroot+"/") {
 | |
| 			continue
 | |
| 		}
 | |
| 		if gopaths != nil && hasPathPrefix(f, gopaths) {
 | |
| 			continue
 | |
| 		}
 | |
| 		parts := splitPath(f)
 | |
| 		if goroot == "" {
 | |
| 			if r := rootedIn(localgoroot, parts); r != "" {
 | |
| 				goroot = r
 | |
| 				log.Printf("Found GOROOT=%s", goroot)
 | |
| 				continue
 | |
| 			}
 | |
| 		}
 | |
| 		found := false
 | |
| 		for _, l := range localgopaths {
 | |
| 			if r := rootedIn(l, parts); r != "" {
 | |
| 				log.Printf("Found GOPATH=%s", r)
 | |
| 				gopaths[r] = l
 | |
| 				found = true
 | |
| 				break
 | |
| 			}
 | |
| 		}
 | |
| 		if !found {
 | |
| 			// If the source is not found, just too bad.
 | |
| 			//log.Printf("Failed to find locally: %s / %s", f, goroot)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func getGOPATHs() []string {
 | |
| 	var out []string
 | |
| 	for _, v := range filepath.SplitList(os.Getenv("GOPATH")) {
 | |
| 		// Disallow non-absolute paths?
 | |
| 		if v != "" {
 | |
| 			out = append(out, v)
 | |
| 		}
 | |
| 	}
 | |
| 	if len(out) == 0 {
 | |
| 		homeDir := ""
 | |
| 		u, err := user.Current()
 | |
| 		if err != nil {
 | |
| 			homeDir = os.Getenv("HOME")
 | |
| 			if homeDir == "" {
 | |
| 				panic(fmt.Sprintf("Could not get current user or $HOME: %s\n", err.Error()))
 | |
| 			}
 | |
| 		} else {
 | |
| 			homeDir = u.HomeDir
 | |
| 		}
 | |
| 		out = []string{homeDir + "go"}
 | |
| 	}
 | |
| 	return out
 | |
| }
 | |
| 
 | |
| 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] }
 |