1 | // Copyright 2020 The Go Authors. All rights reserved. |
---|---|
2 | // Use of this source code is governed by a BSD-style |
3 | // license that can be found in the LICENSE file. |
4 | |
5 | package stack |
6 | |
7 | import ( |
8 | "bytes" |
9 | "fmt" |
10 | "io" |
11 | "runtime" |
12 | "sort" |
13 | ) |
14 | |
15 | // Capture get the current stack traces from the runtime. |
16 | func Capture() Dump { |
17 | buf := make([]byte, 2<<20) |
18 | buf = buf[:runtime.Stack(buf, true)] |
19 | scanner := NewScanner(bytes.NewReader(buf)) |
20 | dump, _ := Parse(scanner) |
21 | return dump |
22 | } |
23 | |
24 | // Summarize a dump for easier consumption. |
25 | // This collates goroutines with equivalent stacks. |
26 | func Summarize(dump Dump) Summary { |
27 | s := Summary{ |
28 | Total: len(dump), |
29 | } |
30 | for _, gr := range dump { |
31 | s.addGoroutine(gr) |
32 | } |
33 | return s |
34 | } |
35 | |
36 | // Process and input stream to an output stream, summarizing any stacks that |
37 | // are detected in place. |
38 | func Process(out io.Writer, in io.Reader) error { |
39 | scanner := NewScanner(in) |
40 | for { |
41 | dump, err := Parse(scanner) |
42 | summary := Summarize(dump) |
43 | switch { |
44 | case len(dump) > 0: |
45 | fmt.Fprintf(out, "%+v\n\n", summary) |
46 | case err != nil: |
47 | return err |
48 | case scanner.Done(): |
49 | return scanner.Err() |
50 | default: |
51 | // must have been a line that is not part of a dump |
52 | fmt.Fprintln(out, scanner.Next()) |
53 | } |
54 | } |
55 | } |
56 | |
57 | // Diff calculates the delta between two dumps. |
58 | func Diff(before, after Dump) Delta { |
59 | result := Delta{} |
60 | processed := make(map[int]bool) |
61 | for _, gr := range before { |
62 | processed[gr.ID] = false |
63 | } |
64 | for _, gr := range after { |
65 | if _, found := processed[gr.ID]; found { |
66 | result.Shared = append(result.Shared, gr) |
67 | } else { |
68 | result.After = append(result.After, gr) |
69 | } |
70 | processed[gr.ID] = true |
71 | } |
72 | for _, gr := range before { |
73 | if done := processed[gr.ID]; !done { |
74 | result.Before = append(result.Before, gr) |
75 | } |
76 | } |
77 | return result |
78 | } |
79 | |
80 | // TODO: do we want to allow contraction of stacks before comparison? |
81 | func (s *Summary) addGoroutine(gr Goroutine) { |
82 | index := sort.Search(len(s.Calls), func(i int) bool { |
83 | return !s.Calls[i].Stack.less(gr.Stack) |
84 | }) |
85 | if index >= len(s.Calls) || !s.Calls[index].Stack.equal(gr.Stack) { |
86 | // insert new stack, first increase the length |
87 | s.Calls = append(s.Calls, Call{}) |
88 | // move the top part upward to make space |
89 | copy(s.Calls[index+1:], s.Calls[index:]) |
90 | // insert the new call |
91 | s.Calls[index] = Call{ |
92 | Stack: gr.Stack, |
93 | } |
94 | } |
95 | // merge the goroutine into the matched call |
96 | s.Calls[index].merge(gr) |
97 | } |
98 | |
99 | // TODO: do we want other grouping strategies? |
100 | func (c *Call) merge(gr Goroutine) { |
101 | for i := range c.Groups { |
102 | canditate := &c.Groups[i] |
103 | if canditate.State == gr.State { |
104 | canditate.Goroutines = append(canditate.Goroutines, gr) |
105 | return |
106 | } |
107 | } |
108 | c.Groups = append(c.Groups, Group{ |
109 | State: gr.State, |
110 | Goroutines: []Goroutine{gr}, |
111 | }) |
112 | } |
113 |
Members