mirror of
https://github.com/golang/go
synced 2024-11-05 18:46:11 -07:00
a02cf32866
This can be used either to directly parse runtime.Stack output or process text that includes stack dumps, like test timeouts or panics. It includes a binary, gostacks that processes stdin to stdout replacing stack dumps in place. Change-Id: Id7b1cfd69b8aea36c66f12ec0bdf38b68cba5afb Reviewed-on: https://go-review.googlesource.com/c/tools/+/232658 Run-TryBot: Ian Cottrell <iancottrell@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Robert Findley <rfindley@google.com>
176 lines
3.7 KiB
Go
176 lines
3.7 KiB
Go
// Copyright 2020 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.
|
|
|
|
package stack
|
|
|
|
import (
|
|
"bufio"
|
|
"errors"
|
|
"io"
|
|
"regexp"
|
|
"strconv"
|
|
)
|
|
|
|
var (
|
|
reBlank = regexp.MustCompile(`^\s*$`)
|
|
reGoroutine = regexp.MustCompile(`^\s*goroutine (\d+) \[([^\]]*)\]:\s*$`)
|
|
reCall = regexp.MustCompile(`^\s*` +
|
|
`(created by )?` + //marker
|
|
`(([\w/.]+/)?[\w]+)\.` + //package
|
|
`(\(([^:.)]*)\)\.)?` + //optional type
|
|
`([\w\.]+)` + //function
|
|
`(\(.*\))?` + // args
|
|
`\s*$`)
|
|
rePos = regexp.MustCompile(`^\s*(.*):(\d+)( .*)?$`)
|
|
|
|
errBreakParse = errors.New("break parse")
|
|
)
|
|
|
|
// Scanner splits an input stream into lines in a way that is consumable by
|
|
// the parser.
|
|
type Scanner struct {
|
|
lines *bufio.Scanner
|
|
done bool
|
|
}
|
|
|
|
// NewScanner creates a scanner on top of a reader.
|
|
func NewScanner(r io.Reader) *Scanner {
|
|
s := &Scanner{
|
|
lines: bufio.NewScanner(r),
|
|
}
|
|
s.Skip() // prefill
|
|
return s
|
|
}
|
|
|
|
// Peek returns the next line without consuming it.
|
|
func (s *Scanner) Peek() string {
|
|
if s.done {
|
|
return ""
|
|
}
|
|
return s.lines.Text()
|
|
}
|
|
|
|
// Skip consumes the next line without looking at it.
|
|
// Normally used after it has already been looked at using Peek.
|
|
func (s *Scanner) Skip() {
|
|
if !s.lines.Scan() {
|
|
s.done = true
|
|
}
|
|
}
|
|
|
|
// Next consumes and returns the next line.
|
|
func (s *Scanner) Next() string {
|
|
line := s.Peek()
|
|
s.Skip()
|
|
return line
|
|
}
|
|
|
|
// Done returns true if the scanner has reached the end of the underlying
|
|
// stream.
|
|
func (s *Scanner) Done() bool {
|
|
return s.done
|
|
}
|
|
|
|
// Err returns true if the scanner has reached the end of the underlying
|
|
// stream.
|
|
func (s *Scanner) Err() error {
|
|
return s.lines.Err()
|
|
}
|
|
|
|
// Match returns the submatchs of the regular expression against the next line.
|
|
// If it matched the line is also consumed.
|
|
func (s *Scanner) Match(re *regexp.Regexp) []string {
|
|
if s.done {
|
|
return nil
|
|
}
|
|
match := re.FindStringSubmatch(s.Peek())
|
|
if match != nil {
|
|
s.Skip()
|
|
}
|
|
return match
|
|
}
|
|
|
|
// SkipBlank skips any number of pure whitespace lines.
|
|
func (s *Scanner) SkipBlank() {
|
|
for !s.done {
|
|
line := s.Peek()
|
|
if len(line) != 0 && !reBlank.MatchString(line) {
|
|
return
|
|
}
|
|
s.Skip()
|
|
}
|
|
}
|
|
|
|
// Parse the current contiguous block of goroutine stack traces until the
|
|
// scanned content no longer matches.
|
|
func Parse(scanner *Scanner) (Dump, error) {
|
|
dump := Dump{}
|
|
for {
|
|
gr, ok := parseGoroutine(scanner)
|
|
if !ok {
|
|
return dump, nil
|
|
}
|
|
dump = append(dump, gr)
|
|
}
|
|
}
|
|
|
|
func parseGoroutine(scanner *Scanner) (Goroutine, bool) {
|
|
match := scanner.Match(reGoroutine)
|
|
if match == nil {
|
|
return Goroutine{}, false
|
|
}
|
|
id, _ := strconv.ParseInt(match[1], 0, 32)
|
|
gr := Goroutine{
|
|
ID: int(id),
|
|
State: match[2],
|
|
}
|
|
for {
|
|
frame, ok := parseFrame(scanner)
|
|
if !ok {
|
|
scanner.SkipBlank()
|
|
return gr, true
|
|
}
|
|
if frame.Position.Filename != "" {
|
|
gr.Stack = append(gr.Stack, frame)
|
|
}
|
|
}
|
|
}
|
|
|
|
func parseFrame(scanner *Scanner) (Frame, bool) {
|
|
fun, ok := parseFunction(scanner)
|
|
if !ok {
|
|
return Frame{}, false
|
|
}
|
|
frame := Frame{
|
|
Function: fun,
|
|
}
|
|
frame.Position, ok = parsePosition(scanner)
|
|
// if ok is false, then this is a broken state.
|
|
// we got the func but not the file that must follow
|
|
// the consumed line can be recovered from the frame
|
|
//TODO: push back the fun raw
|
|
return frame, ok
|
|
}
|
|
|
|
func parseFunction(scanner *Scanner) (Function, bool) {
|
|
match := scanner.Match(reCall)
|
|
if match == nil {
|
|
return Function{}, false
|
|
}
|
|
return Function{
|
|
Package: match[2],
|
|
Type: match[5],
|
|
Name: match[6],
|
|
}, true
|
|
}
|
|
|
|
func parsePosition(scanner *Scanner) (Position, bool) {
|
|
match := scanner.Match(rePos)
|
|
if match == nil {
|
|
return Position{}, false
|
|
}
|
|
line, _ := strconv.ParseInt(match[2], 0, 32)
|
|
return Position{Filename: match[1], Line: int(line)}, true
|
|
}
|