1
2
3
4
5 package trace
6
7 import (
8 "fmt"
9 "internal/trace"
10 "internal/trace/traceviewer"
11 tracev2 "internal/trace/v2"
12 "strings"
13 )
14
15
16
17
18 type generator interface {
19
20 Sync()
21 StackSample(ctx *traceContext, ev *tracev2.Event)
22 GlobalRange(ctx *traceContext, ev *tracev2.Event)
23 GlobalMetric(ctx *traceContext, ev *tracev2.Event)
24
25
26 GoroutineLabel(ctx *traceContext, ev *tracev2.Event)
27 GoroutineRange(ctx *traceContext, ev *tracev2.Event)
28 GoroutineTransition(ctx *traceContext, ev *tracev2.Event)
29
30
31 ProcRange(ctx *traceContext, ev *tracev2.Event)
32 ProcTransition(ctx *traceContext, ev *tracev2.Event)
33
34
35 Log(ctx *traceContext, ev *tracev2.Event)
36
37
38 Finish(ctx *traceContext)
39 }
40
41
42 func runGenerator(ctx *traceContext, g generator, parsed *parsedTrace, opts *genOpts) {
43 for i := range parsed.events {
44 ev := &parsed.events[i]
45
46 switch ev.Kind() {
47 case tracev2.EventSync:
48 g.Sync()
49 case tracev2.EventStackSample:
50 g.StackSample(ctx, ev)
51 case tracev2.EventRangeBegin, tracev2.EventRangeActive, tracev2.EventRangeEnd:
52 r := ev.Range()
53 switch r.Scope.Kind {
54 case tracev2.ResourceGoroutine:
55 g.GoroutineRange(ctx, ev)
56 case tracev2.ResourceProc:
57 g.ProcRange(ctx, ev)
58 case tracev2.ResourceNone:
59 g.GlobalRange(ctx, ev)
60 }
61 case tracev2.EventMetric:
62 g.GlobalMetric(ctx, ev)
63 case tracev2.EventLabel:
64 l := ev.Label()
65 if l.Resource.Kind == tracev2.ResourceGoroutine {
66 g.GoroutineLabel(ctx, ev)
67 }
68 case tracev2.EventStateTransition:
69 switch ev.StateTransition().Resource.Kind {
70 case tracev2.ResourceProc:
71 g.ProcTransition(ctx, ev)
72 case tracev2.ResourceGoroutine:
73 g.GoroutineTransition(ctx, ev)
74 }
75 case tracev2.EventLog:
76 g.Log(ctx, ev)
77 }
78 }
79 for i, task := range opts.tasks {
80 emitTask(ctx, task, i)
81 if opts.mode&traceviewer.ModeGoroutineOriented != 0 {
82 for _, region := range task.Regions {
83 emitRegion(ctx, region)
84 }
85 }
86 }
87 g.Finish(ctx)
88 }
89
90
91
92
93
94 func emitTask(ctx *traceContext, task *trace.UserTaskSummary, sortIndex int) {
95
96 var startStack, endStack tracev2.Stack
97 var startG, endG tracev2.GoID
98 startTime, endTime := ctx.startTime, ctx.endTime
99 if task.Start != nil {
100 startStack = task.Start.Stack()
101 startG = task.Start.Goroutine()
102 startTime = task.Start.Time()
103 }
104 if task.End != nil {
105 endStack = task.End.Stack()
106 endG = task.End.Goroutine()
107 endTime = task.End.Time()
108 }
109 arg := struct {
110 ID uint64 `json:"id"`
111 StartG uint64 `json:"start_g,omitempty"`
112 EndG uint64 `json:"end_g,omitempty"`
113 }{
114 ID: uint64(task.ID),
115 StartG: uint64(startG),
116 EndG: uint64(endG),
117 }
118
119
120 ctx.Task(uint64(task.ID), fmt.Sprintf("T%d %s", task.ID, task.Name), sortIndex)
121 ctx.TaskSlice(traceviewer.SliceEvent{
122 Name: task.Name,
123 Ts: ctx.elapsed(startTime),
124 Dur: endTime.Sub(startTime),
125 Resource: uint64(task.ID),
126 Stack: ctx.Stack(viewerFrames(startStack)),
127 EndStack: ctx.Stack(viewerFrames(endStack)),
128 Arg: arg,
129 })
130
131 if task.Parent != nil && task.Start != nil && task.Start.Kind() == tracev2.EventTaskBegin {
132 ctx.TaskArrow(traceviewer.ArrowEvent{
133 Name: "newTask",
134 Start: ctx.elapsed(task.Start.Time()),
135 End: ctx.elapsed(task.Start.Time()),
136 FromResource: uint64(task.Parent.ID),
137 ToResource: uint64(task.ID),
138 FromStack: ctx.Stack(viewerFrames(task.Start.Stack())),
139 })
140 }
141 }
142
143
144
145
146
147
148
149 func emitRegion(ctx *traceContext, region *trace.UserRegionSummary) {
150 if region.Name == "" {
151 return
152 }
153
154 var startStack, endStack tracev2.Stack
155 goroutine := tracev2.NoGoroutine
156 startTime, endTime := ctx.startTime, ctx.endTime
157 if region.Start != nil {
158 startStack = region.Start.Stack()
159 startTime = region.Start.Time()
160 goroutine = region.Start.Goroutine()
161 }
162 if region.End != nil {
163 endStack = region.End.Stack()
164 endTime = region.End.Time()
165 goroutine = region.End.Goroutine()
166 }
167 if goroutine == tracev2.NoGoroutine {
168 return
169 }
170 arg := struct {
171 TaskID uint64 `json:"taskid"`
172 }{
173 TaskID: uint64(region.TaskID),
174 }
175 ctx.AsyncSlice(traceviewer.AsyncSliceEvent{
176 SliceEvent: traceviewer.SliceEvent{
177 Name: region.Name,
178 Ts: ctx.elapsed(startTime),
179 Dur: endTime.Sub(startTime),
180 Resource: uint64(goroutine),
181 Stack: ctx.Stack(viewerFrames(startStack)),
182 EndStack: ctx.Stack(viewerFrames(endStack)),
183 Arg: arg,
184 },
185 Category: "Region",
186 Scope: fmt.Sprintf("%x", region.TaskID),
187 TaskColorIndex: uint64(region.TaskID),
188 })
189 }
190
191
192
193
194
195 type stackSampleGenerator[R resource] struct {
196
197 getResource func(*tracev2.Event) R
198 }
199
200
201 func (g *stackSampleGenerator[R]) StackSample(ctx *traceContext, ev *tracev2.Event) {
202 id := g.getResource(ev)
203 if id == R(noResource) {
204
205 return
206 }
207 ctx.Instant(traceviewer.InstantEvent{
208 Name: "CPU profile sample",
209 Ts: ctx.elapsed(ev.Time()),
210 Resource: uint64(id),
211 Stack: ctx.Stack(viewerFrames(ev.Stack())),
212 })
213 }
214
215
216
217 type globalRangeGenerator struct {
218 ranges map[string]activeRange
219 seenSync bool
220 }
221
222
223 func (g *globalRangeGenerator) Sync() {
224 g.seenSync = true
225 }
226
227
228
229 func (g *globalRangeGenerator) GlobalRange(ctx *traceContext, ev *tracev2.Event) {
230 if g.ranges == nil {
231 g.ranges = make(map[string]activeRange)
232 }
233 r := ev.Range()
234 switch ev.Kind() {
235 case tracev2.EventRangeBegin:
236 g.ranges[r.Name] = activeRange{ev.Time(), ev.Stack()}
237 case tracev2.EventRangeActive:
238
239 if !g.seenSync {
240
241 g.ranges[r.Name] = activeRange{ctx.startTime, ev.Stack()}
242 }
243 case tracev2.EventRangeEnd:
244
245
246 ar := g.ranges[r.Name]
247 if strings.Contains(r.Name, "GC") {
248 ctx.Slice(traceviewer.SliceEvent{
249 Name: r.Name,
250 Ts: ctx.elapsed(ar.time),
251 Dur: ev.Time().Sub(ar.time),
252 Resource: trace.GCP,
253 Stack: ctx.Stack(viewerFrames(ar.stack)),
254 EndStack: ctx.Stack(viewerFrames(ev.Stack())),
255 })
256 }
257 delete(g.ranges, r.Name)
258 }
259 }
260
261
262 func (g *globalRangeGenerator) Finish(ctx *traceContext) {
263 for name, ar := range g.ranges {
264 if !strings.Contains(name, "GC") {
265 continue
266 }
267 ctx.Slice(traceviewer.SliceEvent{
268 Name: name,
269 Ts: ctx.elapsed(ar.time),
270 Dur: ctx.endTime.Sub(ar.time),
271 Resource: trace.GCP,
272 Stack: ctx.Stack(viewerFrames(ar.stack)),
273 })
274 }
275 }
276
277
278 type globalMetricGenerator struct {
279 }
280
281
282 func (g *globalMetricGenerator) GlobalMetric(ctx *traceContext, ev *tracev2.Event) {
283 m := ev.Metric()
284 switch m.Name {
285 case "/memory/classes/heap/objects:bytes":
286 ctx.HeapAlloc(ctx.elapsed(ev.Time()), m.Value.Uint64())
287 case "/gc/heap/goal:bytes":
288 ctx.HeapGoal(ctx.elapsed(ev.Time()), m.Value.Uint64())
289 case "/sched/gomaxprocs:threads":
290 ctx.Gomaxprocs(m.Value.Uint64())
291 }
292 }
293
294
295
296 type procRangeGenerator struct {
297 ranges map[tracev2.Range]activeRange
298 seenSync bool
299 }
300
301
302 func (g *procRangeGenerator) Sync() {
303 g.seenSync = true
304 }
305
306
307
308 func (g *procRangeGenerator) ProcRange(ctx *traceContext, ev *tracev2.Event) {
309 if g.ranges == nil {
310 g.ranges = make(map[tracev2.Range]activeRange)
311 }
312 r := ev.Range()
313 switch ev.Kind() {
314 case tracev2.EventRangeBegin:
315 g.ranges[r] = activeRange{ev.Time(), ev.Stack()}
316 case tracev2.EventRangeActive:
317
318 if !g.seenSync {
319
320 g.ranges[r] = activeRange{ctx.startTime, ev.Stack()}
321 }
322 case tracev2.EventRangeEnd:
323
324 ar := g.ranges[r]
325 ctx.Slice(traceviewer.SliceEvent{
326 Name: r.Name,
327 Ts: ctx.elapsed(ar.time),
328 Dur: ev.Time().Sub(ar.time),
329 Resource: uint64(r.Scope.Proc()),
330 Stack: ctx.Stack(viewerFrames(ar.stack)),
331 EndStack: ctx.Stack(viewerFrames(ev.Stack())),
332 })
333 delete(g.ranges, r)
334 }
335 }
336
337
338 func (g *procRangeGenerator) Finish(ctx *traceContext) {
339 for r, ar := range g.ranges {
340 ctx.Slice(traceviewer.SliceEvent{
341 Name: r.Name,
342 Ts: ctx.elapsed(ar.time),
343 Dur: ctx.endTime.Sub(ar.time),
344 Resource: uint64(r.Scope.Proc()),
345 Stack: ctx.Stack(viewerFrames(ar.stack)),
346 })
347 }
348 }
349
350
351 type activeRange struct {
352 time tracev2.Time
353 stack tracev2.Stack
354 }
355
356
357 type completedRange struct {
358 name string
359 startTime tracev2.Time
360 endTime tracev2.Time
361 startStack tracev2.Stack
362 endStack tracev2.Stack
363 arg any
364 }
365
366 type logEventGenerator[R resource] struct {
367
368 getResource func(*tracev2.Event) R
369 }
370
371
372 func (g *logEventGenerator[R]) Log(ctx *traceContext, ev *tracev2.Event) {
373 id := g.getResource(ev)
374 if id == R(noResource) {
375
376 return
377 }
378
379
380 log := ev.Log()
381 name := log.Message
382 if log.Category != "" {
383 name = "[" + log.Category + "] " + name
384 }
385
386
387 ctx.Instant(traceviewer.InstantEvent{
388 Name: name,
389 Ts: ctx.elapsed(ev.Time()),
390 Category: "user event",
391 Resource: uint64(id),
392 Stack: ctx.Stack(viewerFrames(ev.Stack())),
393 })
394 }
395
View as plain text