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. |
4 | |
5 | package stack |
6 | |
7 | import ( |
8 | "bufio" |
9 | "errors" |
10 | "io" |
11 | "regexp" |
12 | "strconv" |
13 | ) |
14 | |
15 | var ( |
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 |
23 | `(\(.*\))?` + // args |
24 | `\s*$`) |
25 | rePos = regexp.MustCompile(`^\s*(.*):(\d+)( .*)?$`) |
26 | |
27 | errBreakParse = errors.New("break parse") |
28 | ) |
29 | |
30 | // Scanner splits an input stream into lines in a way that is consumable by |
31 | // the parser. |
32 | type Scanner struct { |
33 | lines *bufio.Scanner |
34 | done bool |
35 | } |
36 | |
37 | // NewScanner creates a scanner on top of a reader. |
38 | func NewScanner(r io.Reader) *Scanner { |
39 | s := &Scanner{ |
40 | lines: bufio.NewScanner(r), |
41 | } |
42 | s.Skip() // prefill |
43 | return s |
44 | } |
45 | |
46 | // Peek returns the next line without consuming it. |
47 | func (s *Scanner) Peek() string { |
48 | if s.done { |
49 | return "" |
50 | } |
51 | return s.lines.Text() |
52 | } |
53 | |
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() { |
57 | if !s.lines.Scan() { |
58 | s.done = true |
59 | } |
60 | } |
61 | |
62 | // Next consumes and returns the next line. |
63 | func (s *Scanner) Next() string { |
64 | line := s.Peek() |
65 | s.Skip() |
66 | return line |
67 | } |
68 | |
69 | // Done returns true if the scanner has reached the end of the underlying |
70 | // stream. |
71 | func (s *Scanner) Done() bool { |
72 | return s.done |
73 | } |
74 | |
75 | // Err returns true if the scanner has reached the end of the underlying |
76 | // stream. |
77 | func (s *Scanner) Err() error { |
78 | return s.lines.Err() |
79 | } |
80 | |
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 { |
84 | if s.done { |
85 | return nil |
86 | } |
87 | match := re.FindStringSubmatch(s.Peek()) |
88 | if match != nil { |
89 | s.Skip() |
90 | } |
91 | return match |
92 | } |
93 | |
94 | // SkipBlank skips any number of pure whitespace lines. |
95 | func (s *Scanner) SkipBlank() { |
96 | for !s.done { |
97 | line := s.Peek() |
98 | if len(line) != 0 && !reBlank.MatchString(line) { |
99 | return |
100 | } |
101 | s.Skip() |
102 | } |
103 | } |
104 | |
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) { |
108 | dump := Dump{} |
109 | for { |
110 | gr, ok := parseGoroutine(scanner) |
111 | if !ok { |
112 | return dump, nil |
113 | } |
114 | dump = append(dump, gr) |
115 | } |
116 | } |
117 | |
118 | func parseGoroutine(scanner *Scanner) (Goroutine, bool) { |
119 | match := scanner.Match(reGoroutine) |
120 | if match == nil { |
121 | return Goroutine{}, false |
122 | } |
123 | id, _ := strconv.ParseInt(match[1], 0, 32) |
124 | gr := Goroutine{ |
125 | ID: int(id), |
126 | State: match[2], |
127 | } |
128 | for { |
129 | frame, ok := parseFrame(scanner) |
130 | if !ok { |
131 | scanner.SkipBlank() |
132 | return gr, true |
133 | } |
134 | if frame.Position.Filename != "" { |
135 | gr.Stack = append(gr.Stack, frame) |
136 | } |
137 | } |
138 | } |
139 | |
140 | func parseFrame(scanner *Scanner) (Frame, bool) { |
141 | fun, ok := parseFunction(scanner) |
142 | if !ok { |
143 | return Frame{}, false |
144 | } |
145 | frame := Frame{ |
146 | Function: fun, |
147 | } |
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 |
153 | return frame, ok |
154 | } |
155 | |
156 | func parseFunction(scanner *Scanner) (Function, bool) { |
157 | match := scanner.Match(reCall) |
158 | if match == nil { |
159 | return Function{}, false |
160 | } |
161 | return Function{ |
162 | Package: match[2], |
163 | Type: match[5], |
164 | Name: match[6], |
165 | }, true |
166 | } |
167 | |
168 | func parsePosition(scanner *Scanner) (Position, bool) { |
169 | match := scanner.Match(rePos) |
170 | if match == nil { |
171 | return Position{}, false |
172 | } |
173 | line, _ := strconv.ParseInt(match[2], 0, 32) |
174 | return Position{Filename: match[1], Line: int(line)}, true |
175 | } |
176 |
Members