1 // Copyright 2020 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
16 reBlank = regexp.MustCompile(`^\s*$`)
17 reGoroutine = regexp.MustCompile(`^\s*goroutine (\d+) \[([^\]]*)\]:\s*$`)
18 reCall = regexp.MustCompile(`^\s*` +
19 `(created by )?` + //marker
20 `(([\w/.]+/)?[\w]+)\.` + //package
21 `(\(([^:.)]*)\)\.)?` + //optional type
22 `([\w\.]+)` + //function
25 rePos = regexp.MustCompile(`^\s*(.*):(\d+)( .*)?$`)
27 errBreakParse = errors.New("break parse")
30 // Scanner splits an input stream into lines in a way that is consumable by
37 // NewScanner creates a scanner on top of a reader.
38 func NewScanner(r io.Reader) *Scanner {
40 lines: bufio.NewScanner(r),
46 // Peek returns the next line without consuming it.
47 func (s *Scanner) Peek() string {
54 // Skip consumes the next line without looking at it.
55 // Normally used after it has already been looked at using Peek.
56 func (s *Scanner) Skip() {
62 // Next consumes and returns the next line.
63 func (s *Scanner) Next() string {
69 // Done returns true if the scanner has reached the end of the underlying
71 func (s *Scanner) Done() bool {
75 // Err returns true if the scanner has reached the end of the underlying
77 func (s *Scanner) Err() error {
81 // Match returns the submatchs of the regular expression against the next line.
82 // If it matched the line is also consumed.
83 func (s *Scanner) Match(re *regexp.Regexp) []string {
87 match := re.FindStringSubmatch(s.Peek())
94 // SkipBlank skips any number of pure whitespace lines.
95 func (s *Scanner) SkipBlank() {
98 if len(line) != 0 && !reBlank.MatchString(line) {
105 // Parse the current contiguous block of goroutine stack traces until the
106 // scanned content no longer matches.
107 func Parse(scanner *Scanner) (Dump, error) {
110 gr, ok := parseGoroutine(scanner)
114 dump = append(dump, gr)
118 func parseGoroutine(scanner *Scanner) (Goroutine, bool) {
119 match := scanner.Match(reGoroutine)
121 return Goroutine{}, false
123 id, _ := strconv.ParseInt(match[1], 0, 32)
129 frame, ok := parseFrame(scanner)
134 if frame.Position.Filename != "" {
135 gr.Stack = append(gr.Stack, frame)
140 func parseFrame(scanner *Scanner) (Frame, bool) {
141 fun, ok := parseFunction(scanner)
143 return Frame{}, false
148 frame.Position, ok = parsePosition(scanner)
149 // if ok is false, then this is a broken state.
150 // we got the func but not the file that must follow
151 // the consumed line can be recovered from the frame
152 //TODO: push back the fun raw
156 func parseFunction(scanner *Scanner) (Function, bool) {
157 match := scanner.Match(reCall)
159 return Function{}, false
168 func parsePosition(scanner *Scanner) (Position, bool) {
169 match := scanner.Match(rePos)
171 return Position{}, false
173 line, _ := strconv.ParseInt(match[2], 0, 32)
174 return Position{Filename: match[1], Line: int(line)}, true