mirror of
https://github.com/golang/go
synced 2024-11-18 14:04:45 -07:00
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
|
||
|
}
|