Source file
src/cmd/trace/pprof.go
1
2
3
4
5
6
7 package main
8
9 import (
10 "cmp"
11 "fmt"
12 "internal/trace"
13 "internal/trace/traceviewer"
14 "net/http"
15 "slices"
16 "strings"
17 "time"
18 )
19
20 func pprofByGoroutine(compute computePprofFunc, t *parsedTrace) traceviewer.ProfileFunc {
21 return func(r *http.Request) ([]traceviewer.ProfileRecord, error) {
22 name := r.FormValue("name")
23 gToIntervals, err := pprofMatchingGoroutines(name, t)
24 if err != nil {
25 return nil, err
26 }
27 return compute(gToIntervals, t.events)
28 }
29 }
30
31 func pprofByRegion(compute computePprofFunc, t *parsedTrace) traceviewer.ProfileFunc {
32 return func(r *http.Request) ([]traceviewer.ProfileRecord, error) {
33 filter, err := newRegionFilter(r)
34 if err != nil {
35 return nil, err
36 }
37 gToIntervals, err := pprofMatchingRegions(filter, t)
38 if err != nil {
39 return nil, err
40 }
41 return compute(gToIntervals, t.events)
42 }
43 }
44
45
46
47 func pprofMatchingGoroutines(name string, t *parsedTrace) (map[trace.GoID][]interval, error) {
48 res := make(map[trace.GoID][]interval)
49 for _, g := range t.summary.Goroutines {
50 if name != "" && g.Name != name {
51 continue
52 }
53 endTime := g.EndTime
54 if g.EndTime == 0 {
55 endTime = t.endTime()
56 }
57 res[g.ID] = []interval{{start: g.StartTime, end: endTime}}
58 }
59 if len(res) == 0 {
60 return nil, fmt.Errorf("failed to find matching goroutines for name: %s", name)
61 }
62 return res, nil
63 }
64
65
66
67 func pprofMatchingRegions(filter *regionFilter, t *parsedTrace) (map[trace.GoID][]interval, error) {
68 if filter == nil {
69 return nil, nil
70 }
71
72 gToIntervals := make(map[trace.GoID][]interval)
73 for _, g := range t.summary.Goroutines {
74 for _, r := range g.Regions {
75 if !filter.match(t, r) {
76 continue
77 }
78 gToIntervals[g.ID] = append(gToIntervals[g.ID], regionInterval(t, r))
79 }
80 }
81
82 for g, intervals := range gToIntervals {
83
84
85
86
87 slices.SortFunc(intervals, func(a, b interval) int {
88 if c := cmp.Compare(a.start, b.start); c != 0 {
89 return c
90 }
91 return cmp.Compare(a.end, b.end)
92 })
93 var lastTimestamp trace.Time
94 var n int
95
96 for _, i := range intervals {
97 if lastTimestamp <= i.start {
98 intervals[n] = i
99 lastTimestamp = i.end
100 n++
101 }
102
103 }
104 gToIntervals[g] = intervals[:n]
105 }
106 return gToIntervals, nil
107 }
108
109 type computePprofFunc func(gToIntervals map[trace.GoID][]interval, events []trace.Event) ([]traceviewer.ProfileRecord, error)
110
111
112
113 func computePprofIO() computePprofFunc {
114 return makeComputePprofFunc(trace.GoWaiting, func(reason string) bool {
115 return reason == "network"
116 })
117 }
118
119
120
121 func computePprofBlock() computePprofFunc {
122 return makeComputePprofFunc(trace.GoWaiting, func(reason string) bool {
123 return strings.Contains(reason, "chan") || strings.Contains(reason, "sync") || strings.Contains(reason, "select")
124 })
125 }
126
127
128
129 func computePprofSyscall() computePprofFunc {
130 return makeComputePprofFunc(trace.GoSyscall, func(_ string) bool {
131 return true
132 })
133 }
134
135
136
137 func computePprofSched() computePprofFunc {
138 return makeComputePprofFunc(trace.GoRunnable, func(_ string) bool {
139 return true
140 })
141 }
142
143
144
145 func makeComputePprofFunc(state trace.GoState, trackReason func(string) bool) computePprofFunc {
146 return func(gToIntervals map[trace.GoID][]interval, events []trace.Event) ([]traceviewer.ProfileRecord, error) {
147 stacks := newStackMap()
148 tracking := make(map[trace.GoID]*trace.Event)
149 for i := range events {
150 ev := &events[i]
151
152
153 if ev.Kind() != trace.EventStateTransition {
154 continue
155 }
156 stack := ev.Stack()
157 if stack == trace.NoStack {
158 continue
159 }
160
161
162 st := ev.StateTransition()
163 if st.Resource.Kind != trace.ResourceGoroutine {
164 continue
165 }
166 id := st.Resource.Goroutine()
167 _, new := st.Goroutine()
168
169
170 startEv := tracking[id]
171 if startEv == nil {
172
173
174
175 if new == state && trackReason(st.Reason) {
176 tracking[id] = ev
177 }
178 continue
179 }
180
181 if new == state {
182
183
184 continue
185 }
186
187
188 delete(tracking, id)
189
190 overlapping := pprofOverlappingDuration(gToIntervals, id, interval{startEv.Time(), ev.Time()})
191 if overlapping > 0 {
192 rec := stacks.getOrAdd(startEv.Stack())
193 rec.Count++
194 rec.Time += overlapping
195 }
196 }
197 return stacks.profile(), nil
198 }
199 }
200
201
202
203
204 func pprofOverlappingDuration(gToIntervals map[trace.GoID][]interval, id trace.GoID, sample interval) time.Duration {
205 if gToIntervals == nil {
206 return sample.duration()
207 }
208 intervals := gToIntervals[id]
209 if len(intervals) == 0 {
210 return 0
211 }
212
213 var overlapping time.Duration
214 for _, i := range intervals {
215 if o := i.overlap(sample); o > 0 {
216 overlapping += o
217 }
218 }
219 return overlapping
220 }
221
222
223 type interval struct {
224 start, end trace.Time
225 }
226
227 func (i interval) duration() time.Duration {
228 return i.end.Sub(i.start)
229 }
230
231 func (i1 interval) overlap(i2 interval) time.Duration {
232
233 if i1.end < i2.start || i2.end < i1.start {
234 return 0
235 }
236 if i1.start < i2.start {
237 i1.start = i2.start
238 }
239 if i1.end > i2.end {
240 i1.end = i2.end
241 }
242 return i1.duration()
243 }
244
245
246
247
248
249
250
251 const pprofMaxStack = 128
252
253
254 type stackMap struct {
255
256
257
258
259
260 stacks map[trace.Stack]*traceviewer.ProfileRecord
261
262
263
264 pcs map[[pprofMaxStack]uint64]trace.Stack
265 }
266
267 func newStackMap() *stackMap {
268 return &stackMap{
269 stacks: make(map[trace.Stack]*traceviewer.ProfileRecord),
270 pcs: make(map[[pprofMaxStack]uint64]trace.Stack),
271 }
272 }
273
274 func (m *stackMap) getOrAdd(stack trace.Stack) *traceviewer.ProfileRecord {
275
276 if rec, ok := m.stacks[stack]; ok {
277 return rec
278 }
279
280
281
282 var pcs [pprofMaxStack]uint64
283 pcsForStack(stack, &pcs)
284
285
286 var rec *traceviewer.ProfileRecord
287 if existing, ok := m.pcs[pcs]; ok {
288
289 rec = m.stacks[existing]
290 delete(m.stacks, existing)
291 } else {
292
293 rec = new(traceviewer.ProfileRecord)
294 }
295
296
297
298
299
300 m.pcs[pcs] = stack
301 m.stacks[stack] = rec
302 return rec
303 }
304
305 func (m *stackMap) profile() []traceviewer.ProfileRecord {
306 prof := make([]traceviewer.ProfileRecord, 0, len(m.stacks))
307 for stack, record := range m.stacks {
308 rec := *record
309 i := 0
310 stack.Frames(func(frame trace.StackFrame) bool {
311 rec.Stack = append(rec.Stack, &trace.Frame{
312 PC: frame.PC,
313 Fn: frame.Func,
314 File: frame.File,
315 Line: int(frame.Line),
316 })
317 i++
318
319
320 return i < pprofMaxStack
321 })
322 prof = append(prof, rec)
323 }
324 return prof
325 }
326
327
328 func pcsForStack(stack trace.Stack, pcs *[pprofMaxStack]uint64) {
329 i := 0
330 stack.Frames(func(frame trace.StackFrame) bool {
331 pcs[i] = frame.PC
332 i++
333 return i < len(pcs)
334 })
335 }
336
View as plain text