--- /dev/null
+// 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)
+}