github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/cmd/trace/v2/gen.go (about)

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