Source file src/cmd/trace/v2/procgen.go

     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  	"internal/trace/traceviewer"
    10  	"internal/trace/traceviewer/format"
    11  	tracev2 "internal/trace/v2"
    12  )
    13  
    14  var _ generator = &procGenerator{}
    15  
    16  type procGenerator struct {
    17  	globalRangeGenerator
    18  	globalMetricGenerator
    19  	procRangeGenerator
    20  	stackSampleGenerator[tracev2.ProcID]
    21  	logEventGenerator[tracev2.ProcID]
    22  
    23  	gStates   map[tracev2.GoID]*gState[tracev2.ProcID]
    24  	inSyscall map[tracev2.ProcID]*gState[tracev2.ProcID]
    25  	maxProc   tracev2.ProcID
    26  }
    27  
    28  func newProcGenerator() *procGenerator {
    29  	pg := new(procGenerator)
    30  	rg := func(ev *tracev2.Event) tracev2.ProcID {
    31  		return ev.Proc()
    32  	}
    33  	pg.stackSampleGenerator.getResource = rg
    34  	pg.logEventGenerator.getResource = rg
    35  	pg.gStates = make(map[tracev2.GoID]*gState[tracev2.ProcID])
    36  	pg.inSyscall = make(map[tracev2.ProcID]*gState[tracev2.ProcID])
    37  	return pg
    38  }
    39  
    40  func (g *procGenerator) Sync() {
    41  	g.globalRangeGenerator.Sync()
    42  	g.procRangeGenerator.Sync()
    43  }
    44  
    45  func (g *procGenerator) GoroutineLabel(ctx *traceContext, ev *tracev2.Event) {
    46  	l := ev.Label()
    47  	g.gStates[l.Resource.Goroutine()].setLabel(l.Label)
    48  }
    49  
    50  func (g *procGenerator) GoroutineRange(ctx *traceContext, ev *tracev2.Event) {
    51  	r := ev.Range()
    52  	switch ev.Kind() {
    53  	case tracev2.EventRangeBegin:
    54  		g.gStates[r.Scope.Goroutine()].rangeBegin(ev.Time(), r.Name, ev.Stack())
    55  	case tracev2.EventRangeActive:
    56  		g.gStates[r.Scope.Goroutine()].rangeActive(r.Name)
    57  	case tracev2.EventRangeEnd:
    58  		gs := g.gStates[r.Scope.Goroutine()]
    59  		gs.rangeEnd(ev.Time(), r.Name, ev.Stack(), ctx)
    60  	}
    61  }
    62  
    63  func (g *procGenerator) GoroutineTransition(ctx *traceContext, ev *tracev2.Event) {
    64  	st := ev.StateTransition()
    65  	goID := st.Resource.Goroutine()
    66  
    67  	// If we haven't seen this goroutine before, create a new
    68  	// gState for it.
    69  	gs, ok := g.gStates[goID]
    70  	if !ok {
    71  		gs = newGState[tracev2.ProcID](goID)
    72  		g.gStates[goID] = gs
    73  	}
    74  	// If we haven't already named this goroutine, try to name it.
    75  	gs.augmentName(st.Stack)
    76  
    77  	// Handle the goroutine state transition.
    78  	from, to := st.Goroutine()
    79  	if from == to {
    80  		// Filter out no-op events.
    81  		return
    82  	}
    83  	if from == tracev2.GoRunning && !to.Executing() {
    84  		if to == tracev2.GoWaiting {
    85  			// Goroutine started blocking.
    86  			gs.block(ev.Time(), ev.Stack(), st.Reason, ctx)
    87  		} else {
    88  			gs.stop(ev.Time(), ev.Stack(), ctx)
    89  		}
    90  	}
    91  	if !from.Executing() && to == tracev2.GoRunning {
    92  		start := ev.Time()
    93  		if from == tracev2.GoUndetermined {
    94  			// Back-date the event to the start of the trace.
    95  			start = ctx.startTime
    96  		}
    97  		gs.start(start, ev.Proc(), ctx)
    98  	}
    99  
   100  	if from == tracev2.GoWaiting {
   101  		// Goroutine was unblocked.
   102  		gs.unblock(ev.Time(), ev.Stack(), ev.Proc(), ctx)
   103  	}
   104  	if from == tracev2.GoNotExist && to == tracev2.GoRunnable {
   105  		// Goroutine was created.
   106  		gs.created(ev.Time(), ev.Proc(), ev.Stack())
   107  	}
   108  	if from == tracev2.GoSyscall && to != tracev2.GoRunning {
   109  		// Goroutine exited a blocked syscall.
   110  		gs.blockedSyscallEnd(ev.Time(), ev.Stack(), ctx)
   111  	}
   112  
   113  	// Handle syscalls.
   114  	if to == tracev2.GoSyscall && ev.Proc() != tracev2.NoProc {
   115  		start := ev.Time()
   116  		if from == tracev2.GoUndetermined {
   117  			// Back-date the event to the start of the trace.
   118  			start = ctx.startTime
   119  		}
   120  		// Write down that we've entered a syscall. Note: we might have no P here
   121  		// if we're in a cgo callback or this is a transition from GoUndetermined
   122  		// (i.e. the G has been blocked in a syscall).
   123  		gs.syscallBegin(start, ev.Proc(), ev.Stack())
   124  		g.inSyscall[ev.Proc()] = gs
   125  	}
   126  	// Check if we're exiting a non-blocking syscall.
   127  	_, didNotBlock := g.inSyscall[ev.Proc()]
   128  	if from == tracev2.GoSyscall && didNotBlock {
   129  		gs.syscallEnd(ev.Time(), false, ctx)
   130  		delete(g.inSyscall, ev.Proc())
   131  	}
   132  
   133  	// Note down the goroutine transition.
   134  	_, inMarkAssist := gs.activeRanges["GC mark assist"]
   135  	ctx.GoroutineTransition(ctx.elapsed(ev.Time()), viewerGState(from, inMarkAssist), viewerGState(to, inMarkAssist))
   136  }
   137  
   138  func (g *procGenerator) ProcTransition(ctx *traceContext, ev *tracev2.Event) {
   139  	st := ev.StateTransition()
   140  	proc := st.Resource.Proc()
   141  
   142  	g.maxProc = max(g.maxProc, proc)
   143  	viewerEv := traceviewer.InstantEvent{
   144  		Resource: uint64(proc),
   145  		Stack:    ctx.Stack(viewerFrames(ev.Stack())),
   146  	}
   147  
   148  	from, to := st.Proc()
   149  	if from == to {
   150  		// Filter out no-op events.
   151  		return
   152  	}
   153  	if to.Executing() {
   154  		start := ev.Time()
   155  		if from == tracev2.ProcUndetermined {
   156  			start = ctx.startTime
   157  		}
   158  		viewerEv.Name = "proc start"
   159  		viewerEv.Arg = format.ThreadIDArg{ThreadID: uint64(ev.Thread())}
   160  		viewerEv.Ts = ctx.elapsed(start)
   161  		ctx.IncThreadStateCount(ctx.elapsed(start), traceviewer.ThreadStateRunning, 1)
   162  	}
   163  	if from.Executing() {
   164  		start := ev.Time()
   165  		viewerEv.Name = "proc stop"
   166  		viewerEv.Ts = ctx.elapsed(start)
   167  		ctx.IncThreadStateCount(ctx.elapsed(start), traceviewer.ThreadStateRunning, -1)
   168  
   169  		// Check if this proc was in a syscall before it stopped.
   170  		// This means the syscall blocked. We need to emit it to the
   171  		// viewer at this point because we only display the time the
   172  		// syscall occupied a P when the viewer is in per-P mode.
   173  		//
   174  		// TODO(mknyszek): We could do better in a per-M mode because
   175  		// all events have to happen on *some* thread, and in v2 traces
   176  		// we know what that thread is.
   177  		gs, ok := g.inSyscall[proc]
   178  		if ok {
   179  			// Emit syscall slice for blocked syscall.
   180  			gs.syscallEnd(start, true, ctx)
   181  			gs.stop(start, ev.Stack(), ctx)
   182  			delete(g.inSyscall, proc)
   183  		}
   184  	}
   185  	// TODO(mknyszek): Consider modeling procs differently and have them be
   186  	// transition to and from NotExist when GOMAXPROCS changes. We can emit
   187  	// events for this to clearly delineate GOMAXPROCS changes.
   188  
   189  	if viewerEv.Name != "" {
   190  		ctx.Instant(viewerEv)
   191  	}
   192  }
   193  
   194  func (g *procGenerator) Finish(ctx *traceContext) {
   195  	ctx.SetResourceType("PROCS")
   196  
   197  	// Finish off ranges first. It doesn't really matter for the global ranges,
   198  	// but the proc ranges need to either be a subset of a goroutine slice or
   199  	// their own slice entirely. If the former, it needs to end first.
   200  	g.procRangeGenerator.Finish(ctx)
   201  	g.globalRangeGenerator.Finish(ctx)
   202  
   203  	// Finish off all the goroutine slices.
   204  	for _, gs := range g.gStates {
   205  		gs.finish(ctx)
   206  	}
   207  
   208  	// Name all the procs to the emitter.
   209  	for i := uint64(0); i <= uint64(g.maxProc); i++ {
   210  		ctx.Resource(i, fmt.Sprintf("Proc %v", i))
   211  	}
   212  }
   213  

View as plain text