mirror of
https://github.com/golang/go
synced 2024-11-05 17:36:15 -07:00
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},
|
||
|
})
|
||
|
}
|