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