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