-
Notifications
You must be signed in to change notification settings - Fork 18k
/
Copy pathgen.go
393 lines (362 loc) · 11.5 KB
/
gen.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
// Copyright 2023 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 main
import (
"fmt"
"internal/trace"
"internal/trace/traceviewer"
"strings"
)
// generator is an interface for generating a JSON trace for the trace viewer
// from a trace. Each method in this interface is a handler for a kind of event
// that is interesting to render in the UI via the JSON trace.
type generator interface {
// Global parts.
Sync() // Notifies the generator of an EventSync event.
StackSample(ctx *traceContext, ev *trace.Event)
GlobalRange(ctx *traceContext, ev *trace.Event)
GlobalMetric(ctx *traceContext, ev *trace.Event)
// Goroutine parts.
GoroutineLabel(ctx *traceContext, ev *trace.Event)
GoroutineRange(ctx *traceContext, ev *trace.Event)
GoroutineTransition(ctx *traceContext, ev *trace.Event)
// Proc parts.
ProcRange(ctx *traceContext, ev *trace.Event)
ProcTransition(ctx *traceContext, ev *trace.Event)
// User annotations.
Log(ctx *traceContext, ev *trace.Event)
// Finish indicates the end of the trace and finalizes generation.
Finish(ctx *traceContext)
}
// runGenerator produces a trace into ctx by running the generator over the parsed trace.
func runGenerator(ctx *traceContext, g generator, parsed *parsedTrace, opts *genOpts) {
for i := range parsed.events {
ev := &parsed.events[i]
switch ev.Kind() {
case trace.EventSync:
g.Sync()
case trace.EventStackSample:
g.StackSample(ctx, ev)
case trace.EventRangeBegin, trace.EventRangeActive, trace.EventRangeEnd:
r := ev.Range()
switch r.Scope.Kind {
case trace.ResourceGoroutine:
g.GoroutineRange(ctx, ev)
case trace.ResourceProc:
g.ProcRange(ctx, ev)
case trace.ResourceNone:
g.GlobalRange(ctx, ev)
}
case trace.EventMetric:
g.GlobalMetric(ctx, ev)
case trace.EventLabel:
l := ev.Label()
if l.Resource.Kind == trace.ResourceGoroutine {
g.GoroutineLabel(ctx, ev)
}
case trace.EventStateTransition:
switch ev.StateTransition().Resource.Kind {
case trace.ResourceProc:
g.ProcTransition(ctx, ev)
case trace.ResourceGoroutine:
g.GoroutineTransition(ctx, ev)
}
case trace.EventLog:
g.Log(ctx, ev)
}
}
for i, task := range opts.tasks {
emitTask(ctx, task, i)
if opts.mode&traceviewer.ModeGoroutineOriented != 0 {
for _, region := range task.Regions {
emitRegion(ctx, region)
}
}
}
g.Finish(ctx)
}
// emitTask emits information about a task into the trace viewer's event stream.
//
// sortIndex sets the order in which this task will appear related to other tasks,
// lowest first.
func emitTask(ctx *traceContext, task *trace.UserTaskSummary, sortIndex int) {
// Collect information about the task.
var startStack, endStack trace.Stack
var startG, endG trace.GoID
startTime, endTime := ctx.startTime, ctx.endTime
if task.Start != nil {
startStack = task.Start.Stack()
startG = task.Start.Goroutine()
startTime = task.Start.Time()
}
if task.End != nil {
endStack = task.End.Stack()
endG = task.End.Goroutine()
endTime = task.End.Time()
}
arg := struct {
ID uint64 `json:"id"`
StartG uint64 `json:"start_g,omitempty"`
EndG uint64 `json:"end_g,omitempty"`
}{
ID: uint64(task.ID),
StartG: uint64(startG),
EndG: uint64(endG),
}
// Emit the task slice and notify the emitter of the task.
ctx.Task(uint64(task.ID), fmt.Sprintf("T%d %s", task.ID, task.Name), sortIndex)
ctx.TaskSlice(traceviewer.SliceEvent{
Name: task.Name,
Ts: ctx.elapsed(startTime),
Dur: endTime.Sub(startTime),
Resource: uint64(task.ID),
Stack: ctx.Stack(viewerFrames(startStack)),
EndStack: ctx.Stack(viewerFrames(endStack)),
Arg: arg,
})
// Emit an arrow from the parent to the child.
if task.Parent != nil && task.Start != nil && task.Start.Kind() == trace.EventTaskBegin {
ctx.TaskArrow(traceviewer.ArrowEvent{
Name: "newTask",
Start: ctx.elapsed(task.Start.Time()),
End: ctx.elapsed(task.Start.Time()),
FromResource: uint64(task.Parent.ID),
ToResource: uint64(task.ID),
FromStack: ctx.Stack(viewerFrames(task.Start.Stack())),
})
}
}
// emitRegion emits goroutine-based slice events to the UI. The caller
// must be emitting for a goroutine-oriented trace.
//
// TODO(mknyszek): Make regions part of the regular generator loop and
// treat them like ranges so that we can emit regions in traces oriented
// by proc or thread.
func emitRegion(ctx *traceContext, region *trace.UserRegionSummary) {
if region.Name == "" {
return
}
// Collect information about the region.
var startStack, endStack trace.Stack
goroutine := trace.NoGoroutine
startTime, endTime := ctx.startTime, ctx.endTime
if region.Start != nil {
startStack = region.Start.Stack()
startTime = region.Start.Time()
goroutine = region.Start.Goroutine()
}
if region.End != nil {
endStack = region.End.Stack()
endTime = region.End.Time()
goroutine = region.End.Goroutine()
}
if goroutine == trace.NoGoroutine {
return
}
arg := struct {
TaskID uint64 `json:"taskid"`
}{
TaskID: uint64(region.TaskID),
}
ctx.AsyncSlice(traceviewer.AsyncSliceEvent{
SliceEvent: traceviewer.SliceEvent{
Name: region.Name,
Ts: ctx.elapsed(startTime),
Dur: endTime.Sub(startTime),
Resource: uint64(goroutine),
Stack: ctx.Stack(viewerFrames(startStack)),
EndStack: ctx.Stack(viewerFrames(endStack)),
Arg: arg,
},
Category: "Region",
Scope: fmt.Sprintf("%x", region.TaskID),
TaskColorIndex: uint64(region.TaskID),
})
}
// Building blocks for generators.
// stackSampleGenerator implements a generic handler for stack sample events.
// The provided resource is the resource the stack sample should count against.
type stackSampleGenerator[R resource] struct {
// getResource is a function to extract a resource ID from a stack sample event.
getResource func(*trace.Event) R
}
// StackSample implements a stack sample event handler. It expects ev to be one such event.
func (g *stackSampleGenerator[R]) StackSample(ctx *traceContext, ev *trace.Event) {
id := g.getResource(ev)
if id == R(noResource) {
// We have nowhere to put this in the UI.
return
}
ctx.Instant(traceviewer.InstantEvent{
Name: "CPU profile sample",
Ts: ctx.elapsed(ev.Time()),
Resource: uint64(id),
Stack: ctx.Stack(viewerFrames(ev.Stack())),
})
}
// globalRangeGenerator implements a generic handler for EventRange* events that pertain
// to trace.ResourceNone (the global scope).
type globalRangeGenerator struct {
ranges map[string]activeRange
seenSync bool
}
// Sync notifies the generator of an EventSync event.
func (g *globalRangeGenerator) Sync() {
g.seenSync = true
}
// GlobalRange implements a handler for EventRange* events whose Scope.Kind is ResourceNone.
// It expects ev to be one such event.
func (g *globalRangeGenerator) GlobalRange(ctx *traceContext, ev *trace.Event) {
if g.ranges == nil {
g.ranges = make(map[string]activeRange)
}
r := ev.Range()
switch ev.Kind() {
case trace.EventRangeBegin:
g.ranges[r.Name] = activeRange{ev.Time(), ev.Stack()}
case trace.EventRangeActive:
// If we've seen a Sync event, then Active events are always redundant.
if !g.seenSync {
// Otherwise, they extend back to the start of the trace.
g.ranges[r.Name] = activeRange{ctx.startTime, ev.Stack()}
}
case trace.EventRangeEnd:
// Only emit GC events, because we have nowhere to
// put other events.
ar := g.ranges[r.Name]
if strings.Contains(r.Name, "GC") {
ctx.Slice(traceviewer.SliceEvent{
Name: r.Name,
Ts: ctx.elapsed(ar.time),
Dur: ev.Time().Sub(ar.time),
Resource: traceviewer.GCP,
Stack: ctx.Stack(viewerFrames(ar.stack)),
EndStack: ctx.Stack(viewerFrames(ev.Stack())),
})
}
delete(g.ranges, r.Name)
}
}
// Finish flushes any outstanding ranges at the end of the trace.
func (g *globalRangeGenerator) Finish(ctx *traceContext) {
for name, ar := range g.ranges {
if !strings.Contains(name, "GC") {
continue
}
ctx.Slice(traceviewer.SliceEvent{
Name: name,
Ts: ctx.elapsed(ar.time),
Dur: ctx.endTime.Sub(ar.time),
Resource: traceviewer.GCP,
Stack: ctx.Stack(viewerFrames(ar.stack)),
})
}
}
// globalMetricGenerator implements a generic handler for Metric events.
type globalMetricGenerator struct {
}
// GlobalMetric implements an event handler for EventMetric events. ev must be one such event.
func (g *globalMetricGenerator) GlobalMetric(ctx *traceContext, ev *trace.Event) {
m := ev.Metric()
switch m.Name {
case "/memory/classes/heap/objects:bytes":
ctx.HeapAlloc(ctx.elapsed(ev.Time()), m.Value.ToUint64())
case "/gc/heap/goal:bytes":
ctx.HeapGoal(ctx.elapsed(ev.Time()), m.Value.ToUint64())
case "/sched/gomaxprocs:threads":
ctx.Gomaxprocs(m.Value.ToUint64())
}
}
// procRangeGenerator implements a generic handler for EventRange* events whose Scope.Kind is
// ResourceProc.
type procRangeGenerator struct {
ranges map[trace.Range]activeRange
seenSync bool
}
// Sync notifies the generator of an EventSync event.
func (g *procRangeGenerator) Sync() {
g.seenSync = true
}
// ProcRange implements a handler for EventRange* events whose Scope.Kind is ResourceProc.
// It expects ev to be one such event.
func (g *procRangeGenerator) ProcRange(ctx *traceContext, ev *trace.Event) {
if g.ranges == nil {
g.ranges = make(map[trace.Range]activeRange)
}
r := ev.Range()
switch ev.Kind() {
case trace.EventRangeBegin:
g.ranges[r] = activeRange{ev.Time(), ev.Stack()}
case trace.EventRangeActive:
// If we've seen a Sync event, then Active events are always redundant.
if !g.seenSync {
// Otherwise, they extend back to the start of the trace.
g.ranges[r] = activeRange{ctx.startTime, ev.Stack()}
}
case trace.EventRangeEnd:
// Emit proc-based ranges.
ar := g.ranges[r]
ctx.Slice(traceviewer.SliceEvent{
Name: r.Name,
Ts: ctx.elapsed(ar.time),
Dur: ev.Time().Sub(ar.time),
Resource: uint64(r.Scope.Proc()),
Stack: ctx.Stack(viewerFrames(ar.stack)),
EndStack: ctx.Stack(viewerFrames(ev.Stack())),
})
delete(g.ranges, r)
}
}
// Finish flushes any outstanding ranges at the end of the trace.
func (g *procRangeGenerator) Finish(ctx *traceContext) {
for r, ar := range g.ranges {
ctx.Slice(traceviewer.SliceEvent{
Name: r.Name,
Ts: ctx.elapsed(ar.time),
Dur: ctx.endTime.Sub(ar.time),
Resource: uint64(r.Scope.Proc()),
Stack: ctx.Stack(viewerFrames(ar.stack)),
})
}
}
// activeRange represents an active EventRange* range.
type activeRange struct {
time trace.Time
stack trace.Stack
}
// completedRange represents a completed EventRange* range.
type completedRange struct {
name string
startTime trace.Time
endTime trace.Time
startStack trace.Stack
endStack trace.Stack
arg any
}
type logEventGenerator[R resource] struct {
// getResource is a function to extract a resource ID from a Log event.
getResource func(*trace.Event) R
}
// Log implements a log event handler. It expects ev to be one such event.
func (g *logEventGenerator[R]) Log(ctx *traceContext, ev *trace.Event) {
id := g.getResource(ev)
if id == R(noResource) {
// We have nowhere to put this in the UI.
return
}
// Construct the name to present.
log := ev.Log()
name := log.Message
if log.Category != "" {
name = "[" + log.Category + "] " + name
}
// Emit an instant event.
ctx.Instant(traceviewer.InstantEvent{
Name: name,
Ts: ctx.elapsed(ev.Time()),
Category: "user event",
Resource: uint64(id),
Stack: ctx.Stack(viewerFrames(ev.Stack())),
})
}