| 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 provides support for parsing standard goroutine stack traces. |
| 6 | package stack |
| 7 | |
| 8 | import ( |
| 9 | "fmt" |
| 10 | "text/tabwriter" |
| 11 | ) |
| 12 | |
| 13 | // Dump is a raw set of goroutines and their stacks. |
| 14 | type Dump []Goroutine |
| 15 | |
| 16 | // Goroutine is a single parsed goroutine dump. |
| 17 | type Goroutine struct { |
| 18 | State string // state that the goroutine is in. |
| 19 | ID int // id of the goroutine. |
| 20 | Stack Stack // call frames that make up the stack |
| 21 | } |
| 22 | |
| 23 | // Stack is a set of frames in a callstack. |
| 24 | type Stack []Frame |
| 25 | |
| 26 | // Frame is a point in a call stack. |
| 27 | type Frame struct { |
| 28 | Function Function |
| 29 | Position Position |
| 30 | } |
| 31 | |
| 32 | // Function is the function called at a frame. |
| 33 | type Function struct { |
| 34 | Package string // package name of function if known |
| 35 | Type string // if set function is a method of this type |
| 36 | Name string // function name of the frame |
| 37 | } |
| 38 | |
| 39 | // Position is the file position for a frame. |
| 40 | type Position struct { |
| 41 | Filename string // source filename |
| 42 | Line int // line number within file |
| 43 | } |
| 44 | |
| 45 | // Summary is a set of stacks processed and collated into Calls. |
| 46 | type Summary struct { |
| 47 | Total int // the total count of goroutines in the summary |
| 48 | Calls []Call // the collated stack traces |
| 49 | } |
| 50 | |
| 51 | // Call is set of goroutines that all share the same callstack. |
| 52 | // They will be grouped by state. |
| 53 | type Call struct { |
| 54 | Stack Stack // the shared callstack information |
| 55 | Groups []Group // the sets of goroutines with the same state |
| 56 | } |
| 57 | |
| 58 | // Group is a set of goroutines with the same stack that are in the same state. |
| 59 | type Group struct { |
| 60 | State string // the shared state of the goroutines |
| 61 | Goroutines []Goroutine // the set of goroutines in this group |
| 62 | } |
| 63 | |
| 64 | // Delta represents the difference between two stack dumps. |
| 65 | type Delta struct { |
| 66 | Before Dump // The goroutines that were only in the before set. |
| 67 | Shared Dump // The goroutines that were in both sets. |
| 68 | After Dump // The goroutines that were only in the after set. |
| 69 | } |
| 70 | |
| 71 | func (s Stack) equal(other Stack) bool { |
| 72 | if len(s) != len(other) { |
| 73 | return false |
| 74 | } |
| 75 | for i, frame := range s { |
| 76 | if !frame.equal(other[i]) { |
| 77 | return false |
| 78 | } |
| 79 | } |
| 80 | return true |
| 81 | } |
| 82 | |
| 83 | func (s Stack) less(other Stack) bool { |
| 84 | for i, frame := range s { |
| 85 | if i >= len(other) { |
| 86 | return false |
| 87 | } |
| 88 | if frame.less(other[i]) { |
| 89 | return true |
| 90 | } |
| 91 | if !frame.equal(other[i]) { |
| 92 | return false |
| 93 | } |
| 94 | } |
| 95 | return len(s) < len(other) |
| 96 | } |
| 97 | |
| 98 | func (f Frame) equal(other Frame) bool { |
| 99 | return f.Position.equal(other.Position) |
| 100 | } |
| 101 | |
| 102 | func (f Frame) less(other Frame) bool { |
| 103 | return f.Position.less(other.Position) |
| 104 | } |
| 105 | |
| 106 | func (p Position) equal(other Position) bool { |
| 107 | return p.Filename == other.Filename && p.Line == other.Line |
| 108 | } |
| 109 | |
| 110 | func (p Position) less(other Position) bool { |
| 111 | if p.Filename < other.Filename { |
| 112 | return true |
| 113 | } |
| 114 | if p.Filename > other.Filename { |
| 115 | return false |
| 116 | } |
| 117 | return p.Line < other.Line |
| 118 | } |
| 119 | |
| 120 | func (s Summary) Format(w fmt.State, r rune) { |
| 121 | tw := tabwriter.NewWriter(w, 0, 0, 1, ' ', 0) |
| 122 | for i, c := range s.Calls { |
| 123 | if i > 0 { |
| 124 | fmt.Fprintf(tw, "\n\n") |
| 125 | tw.Flush() |
| 126 | } |
| 127 | fmt.Fprint(tw, c) |
| 128 | } |
| 129 | tw.Flush() |
| 130 | if s.Total > 0 && w.Flag('+') { |
| 131 | fmt.Fprintf(w, "\n\n%d goroutines, %d unique", s.Total, len(s.Calls)) |
| 132 | } |
| 133 | } |
| 134 | |
| 135 | func (c Call) Format(w fmt.State, r rune) { |
| 136 | for i, g := range c.Groups { |
| 137 | if i > 0 { |
| 138 | fmt.Fprint(w, " ") |
| 139 | } |
| 140 | fmt.Fprint(w, g) |
| 141 | } |
| 142 | for _, f := range c.Stack { |
| 143 | fmt.Fprintf(w, "\n%v", f) |
| 144 | } |
| 145 | } |
| 146 | |
| 147 | func (g Group) Format(w fmt.State, r rune) { |
| 148 | fmt.Fprintf(w, "[%v]: ", g.State) |
| 149 | for i, gr := range g.Goroutines { |
| 150 | if i > 0 { |
| 151 | fmt.Fprint(w, ", ") |
| 152 | } |
| 153 | fmt.Fprintf(w, "$%d", gr.ID) |
| 154 | } |
| 155 | } |
| 156 | |
| 157 | func (f Frame) Format(w fmt.State, c rune) { |
| 158 | fmt.Fprintf(w, "%v:\t%v", f.Position, f.Function) |
| 159 | } |
| 160 | |
| 161 | func (f Function) Format(w fmt.State, c rune) { |
| 162 | if f.Type != "" { |
| 163 | fmt.Fprintf(w, "(%v).", f.Type) |
| 164 | } |
| 165 | fmt.Fprintf(w, "%v", f.Name) |
| 166 | } |
| 167 | |
| 168 | func (p Position) Format(w fmt.State, c rune) { |
| 169 | fmt.Fprintf(w, "%v:%v", p.Filename, p.Line) |
| 170 | } |
| 171 |
Members