123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382 |
- package stack
- import (
- "bufio"
- "bytes"
- "fmt"
- "io"
- "os"
- "os/user"
- "path/filepath"
- "runtime"
- "sort"
- "strconv"
- "strings"
- )
- type Context struct {
-
-
-
- Goroutines []*Goroutine
-
-
-
-
-
-
- GOROOT string
-
-
-
-
-
-
-
-
- GOPATHs map[string]string
- localgoroot string
- localgopaths []string
- }
- func ParseDump(r io.Reader, out io.Writer, guesspaths bool) (*Context, error) {
- goroutines, err := parseDump(r, out)
- if len(goroutines) == 0 {
- return nil, err
- }
- c := &Context{
- Goroutines: goroutines,
- localgoroot: runtime.GOROOT(),
- localgopaths: getGOPATHs(),
- }
- nameArguments(goroutines)
-
- if guesspaths {
- c.findRoots()
- for _, r := range c.Goroutines {
-
-
- r.updateLocations(c.GOROOT, c.localgoroot, c.GOPATHs)
- }
- }
- return c, err
- }
- func parseDump(r io.Reader, out io.Writer) ([]*Goroutine, error) {
- scanner := bufio.NewScanner(r)
- scanner.Split(scanLines)
- s := scanningState{}
- for scanner.Scan() {
- line, err := s.scan(scanner.Text())
- if line != "" {
- _, _ = io.WriteString(out, line)
- }
- if err != nil {
- return s.goroutines, err
- }
- }
- return s.goroutines, scanner.Err()
- }
- 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 {
-
-
-
- return len(data), data, nil
- }
- return 0, nil, nil
- }
- type scanningState struct {
- goroutines []*Goroutine
- goroutine *Goroutine
- created bool
- firstLine bool
- }
- func (s *scanningState) scan(line string) (string, error) {
- if line == "\n" || line == "\r\n" {
- if s.goroutine != nil {
-
- s.goroutine = nil
- return "", nil
- }
- } else if line[len(line)-1] == '\n' {
- if s.goroutine == nil {
- if match := reRoutineHeader.FindStringSubmatch(line); match != nil {
- if id, err := strconv.Atoi(match[1]); err == nil {
-
-
- items := strings.Split(match[2], ", ")
- sleep := 0
- locked := false
- for i := 1; i < len(items); i++ {
- if items[i] == lockedToThread {
- locked = true
- continue
- }
-
- if match2 := reMinutes.FindStringSubmatch(items[i]); match2 != nil {
- sleep, _ = strconv.Atoi(match2[1])
- }
- }
- g := &Goroutine{
- Signature: Signature{
- State: items[0],
- SleepMin: sleep,
- SleepMax: sleep,
- Locked: locked,
- },
- ID: id,
- First: len(s.goroutines) == 0,
- }
- s.goroutines = append(s.goroutines, g)
- s.goroutine = g
- s.firstLine = true
- return "", nil
- }
- }
- } else {
- if s.firstLine {
- s.firstLine = false
- if match := reUnavail.FindStringSubmatch(line); match != nil {
-
- s.goroutine.Stack.Calls = []Call{{SrcPath: "<unavailable>"}}
- return "", nil
- }
- }
- if match := reFile.FindStringSubmatch(line); match != nil {
-
- num, err := strconv.Atoi(match[2])
- if err != nil {
- return "", fmt.Errorf("failed to parse int on line: %q", strings.TrimSpace(line))
- }
- if s.created {
- s.created = false
- s.goroutine.CreatedBy.SrcPath = match[1]
- s.goroutine.CreatedBy.Line = num
- } else {
- i := len(s.goroutine.Stack.Calls) - 1
- if i < 0 {
- return "", fmt.Errorf("unexpected order on line: %q", strings.TrimSpace(line))
- }
- s.goroutine.Stack.Calls[i].SrcPath = match[1]
- s.goroutine.Stack.Calls[i].Line = num
- }
- return "", nil
- }
- if match := reCreated.FindStringSubmatch(line); match != nil {
- s.created = true
- s.goroutine.CreatedBy.Func.Raw = match[1]
- return "", nil
- }
- if match := reFunc.FindStringSubmatch(line); match != nil {
- args := Args{}
- for _, a := range strings.Split(match[2], ", ") {
- if a == "..." {
- args.Elided = true
- continue
- }
- if a == "" {
-
- break
- }
- v, err := strconv.ParseUint(a, 0, 64)
- if err != nil {
- return "", fmt.Errorf("failed to parse int on line: %q", strings.TrimSpace(line))
- }
- args.Values = append(args.Values, Arg{Value: v})
- }
- s.goroutine.Stack.Calls = append(s.goroutine.Stack.Calls, Call{Func: Func{Raw: match[1]}, Args: args})
- return "", nil
- }
- if match := reElided.FindStringSubmatch(line); match != nil {
- s.goroutine.Stack.Elided = true
- return "", nil
- }
- }
- }
- s.goroutine = nil
- return line, nil
- }
- func hasPathPrefix(p string, s map[string]string) bool {
- for prefix := range s {
- if strings.HasPrefix(p, prefix+"/") {
- return true
- }
- }
- return false
- }
- func getFiles(goroutines []*Goroutine) []string {
- files := map[string]struct{}{}
- for _, g := range goroutines {
- for _, c := range g.Stack.Calls {
- files[c.SrcPath] = struct{}{}
- }
- }
- out := make([]string, 0, len(files))
- for f := range files {
- out = append(out, f)
- }
- sort.Strings(out)
- return out
- }
- 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
- }
- func isFile(p string) bool {
-
-
- i, err := os.Stat(p)
- return err == nil && !i.IsDir()
- }
- func rootedIn(root string, parts []string) string {
-
- for i := 1; i < len(parts); i++ {
- suffix := filepath.Join(parts[i:]...)
- if isFile(filepath.Join(root, suffix)) {
- return filepath.Join(parts[:i]...)
- }
- }
- return ""
- }
- func (c *Context) findRoots() {
- c.GOPATHs = map[string]string{}
- for _, f := range getFiles(c.Goroutines) {
-
-
-
- if c.GOROOT != "" && strings.HasPrefix(f, c.GOROOT+"/") {
- continue
- }
- if hasPathPrefix(f, c.GOPATHs) {
- continue
- }
- parts := splitPath(f)
- if c.GOROOT == "" {
- if r := rootedIn(c.localgoroot, parts); r != "" {
- c.GOROOT = r
-
- continue
- }
- }
- found := false
- for _, l := range c.localgopaths {
- if r := rootedIn(l, parts); r != "" {
-
- c.GOPATHs[r] = l
- found = true
- break
- }
- }
- if !found {
-
-
- }
- }
- }
- func getGOPATHs() []string {
- var out []string
- for _, v := range filepath.SplitList(os.Getenv("GOPATH")) {
-
- 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
- }
|