mirror of
https://github.com/golang/go
synced 2024-11-05 14:56:10 -07:00
a02cf32866
This can be used either to directly parse runtime.Stack output or process text that includes stack dumps, like test timeouts or panics. It includes a binary, gostacks that processes stdin to stdout replacing stack dumps in place. Change-Id: Id7b1cfd69b8aea36c66f12ec0bdf38b68cba5afb Reviewed-on: https://go-review.googlesource.com/c/tools/+/232658 Run-TryBot: Ian Cottrell <iancottrell@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Robert Findley <rfindley@google.com>
113 lines
2.6 KiB
Go
113 lines
2.6 KiB
Go
// 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},
|
|
})
|
|
}
|