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