1
0
mirror of https://github.com/golang/go synced 2024-11-18 13:14:47 -07:00
go/internal/stack/process.go
Ian Cottrell a02cf32866 internal/stack: adding an internal stack dump parsing library
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>
2020-05-27 14:25:21 +00:00

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},
})
}