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