// 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 provides support for parsing standard goroutine stack traces. package stack import ( "fmt" "text/tabwriter" ) // Dump is a raw set of goroutines and their stacks. type Dump []Goroutine // Goroutine is a single parsed goroutine dump. type Goroutine struct { State string // state that the goroutine is in. ID int // id of the goroutine. Stack Stack // call frames that make up the stack } // Stack is a set of frames in a callstack. type Stack []Frame // Frame is a point in a call stack. type Frame struct { Function Function Position Position } // Function is the function called at a frame. type Function struct { Package string // package name of function if known Type string // if set function is a method of this type Name string // function name of the frame } // Position is the file position for a frame. type Position struct { Filename string // source filename Line int // line number within file } // Summary is a set of stacks processed and collated into Calls. type Summary struct { Total int // the total count of goroutines in the summary Calls []Call // the collated stack traces } // Call is set of goroutines that all share the same callstack. // They will be grouped by state. type Call struct { Stack Stack // the shared callstack information Groups []Group // the sets of goroutines with the same state } // Group is a set of goroutines with the same stack that are in the same state. type Group struct { State string // the shared state of the goroutines Goroutines []Goroutine // the set of goroutines in this group } // Delta represents the difference between two stack dumps. type Delta struct { Before Dump // The goroutines that were only in the before set. Shared Dump // The goroutines that were in both sets. After Dump // The goroutines that were only in the after set. } func (s Stack) equal(other Stack) bool { if len(s) != len(other) { return false } for i, frame := range s { if !frame.equal(other[i]) { return false } } return true } func (s Stack) less(other Stack) bool { for i, frame := range s { if i >= len(other) { return false } if frame.less(other[i]) { return true } if !frame.equal(other[i]) { return false } } return len(s) < len(other) } func (f Frame) equal(other Frame) bool { return f.Position.equal(other.Position) } func (f Frame) less(other Frame) bool { return f.Position.less(other.Position) } func (p Position) equal(other Position) bool { return p.Filename == other.Filename && p.Line == other.Line } func (p Position) less(other Position) bool { if p.Filename < other.Filename { return true } if p.Filename > other.Filename { return false } return p.Line < other.Line } func (s Summary) Format(w fmt.State, r rune) { tw := tabwriter.NewWriter(w, 0, 0, 1, ' ', 0) for i, c := range s.Calls { if i > 0 { fmt.Fprintf(tw, "\n\n") tw.Flush() } fmt.Fprint(tw, c) } tw.Flush() if s.Total > 0 && w.Flag('+') { fmt.Fprintf(w, "\n\n%d goroutines, %d unique", s.Total, len(s.Calls)) } } func (c Call) Format(w fmt.State, r rune) { for i, g := range c.Groups { if i > 0 { fmt.Fprint(w, " ") } fmt.Fprint(w, g) } for _, f := range c.Stack { fmt.Fprintf(w, "\n%v", f) } } func (g Group) Format(w fmt.State, r rune) { fmt.Fprintf(w, "[%v]: ", g.State) for i, gr := range g.Goroutines { if i > 0 { fmt.Fprint(w, ", ") } fmt.Fprintf(w, "$%d", gr.ID) } } func (f Frame) Format(w fmt.State, c rune) { fmt.Fprintf(w, "%v:\t%v", f.Position, f.Function) } func (f Function) Format(w fmt.State, c rune) { if f.Type != "" { fmt.Fprintf(w, "(%v).", f.Type) } fmt.Fprintf(w, "%v", f.Name) } func (p Position) Format(w fmt.State, c rune) { fmt.Fprintf(w, "%v:%v", p.Filename, p.Line) }