blob: 8812de9521cb3faa0be882eefa8d953becdedd8d [file] [log] [blame]
// 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 (
"bytes"
"fmt"
"io"
"runtime"
"sort"
)
// Capture get the current stack traces from the runtime.
func Capture() Dump {
buf := make([]byte, 2<<20)
buf = buf[:runtime.Stack(buf, true)]
scanner := NewScanner(bytes.NewReader(buf))
dump, _ := Parse(scanner)
return dump
}
// Summarize a dump for easier consumption.
// This collates goroutines with equivalent stacks.
func Summarize(dump Dump) Summary {
s := Summary{
Total: len(dump),
}
for _, gr := range dump {
s.addGoroutine(gr)
}
return s
}
// Process and input stream to an output stream, summarizing any stacks that
// are detected in place.
func Process(out io.Writer, in io.Reader) error {
scanner := NewScanner(in)
for {
dump, err := Parse(scanner)
summary := Summarize(dump)
switch {
case len(dump) > 0:
fmt.Fprintf(out, "%+v\n\n", summary)
case err != nil:
return err
case scanner.Done():
return scanner.Err()
default:
// must have been a line that is not part of a dump
fmt.Fprintln(out, scanner.Next())
}
}
}
// Diff calculates the delta between two dumps.
func Diff(before, after Dump) Delta {
result := Delta{}
processed := make(map[int]bool)
for _, gr := range before {
processed[gr.ID] = false
}
for _, gr := range after {
if _, found := processed[gr.ID]; found {
result.Shared = append(result.Shared, gr)
} else {
result.After = append(result.After, gr)
}
processed[gr.ID] = true
}
for _, gr := range before {
if done := processed[gr.ID]; !done {
result.Before = append(result.Before, gr)
}
}
return result
}
// TODO: do we want to allow contraction of stacks before comparison?
func (s *Summary) addGoroutine(gr Goroutine) {
index := sort.Search(len(s.Calls), func(i int) bool {
return !s.Calls[i].Stack.less(gr.Stack)
})
if index >= len(s.Calls) || !s.Calls[index].Stack.equal(gr.Stack) {
// insert new stack, first increase the length
s.Calls = append(s.Calls, Call{})
// move the top part upward to make space
copy(s.Calls[index+1:], s.Calls[index:])
// insert the new call
s.Calls[index] = Call{
Stack: gr.Stack,
}
}
// merge the goroutine into the matched call
s.Calls[index].merge(gr)
}
// TODO: do we want other grouping strategies?
func (c *Call) merge(gr Goroutine) {
for i := range c.Groups {
canditate := &c.Groups[i]
if canditate.State == gr.State {
canditate.Goroutines = append(canditate.Goroutines, gr)
return
}
}
c.Groups = append(c.Groups, Group{
State: gr.State,
Goroutines: []Goroutine{gr},
})
}