|  | // 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 | 
|  | } |