Source file src/cmd/trace/threadgen.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 main
     6  
     7  import (
     8  	"fmt"
     9  	"internal/trace"
    10  	"internal/trace/traceviewer"
    11  	"internal/trace/traceviewer/format"
    12  )
    13  
    14  var _ generator = &threadGenerator{}
    15  
    16  type threadGenerator struct {
    17  	globalRangeGenerator
    18  	globalMetricGenerator
    19  	stackSampleGenerator[trace.ThreadID]
    20  	logEventGenerator[trace.ThreadID]
    21  
    22  	gStates map[trace.GoID]*gState[trace.ThreadID]
    23  	threads map[trace.ThreadID]struct{}
    24  }
    25  
    26  func newThreadGenerator() *threadGenerator {
    27  	tg := new(threadGenerator)
    28  	rg := func(ev *trace.Event) trace.ThreadID {
    29  		return ev.Thread()
    30  	}
    31  	tg.stackSampleGenerator.getResource = rg
    32  	tg.logEventGenerator.getResource = rg
    33  	tg.gStates = make(map[trace.GoID]*gState[trace.ThreadID])
    34  	tg.threads = make(map[trace.ThreadID]struct{})
    35  	return tg
    36  }
    37  
    38  func (g *threadGenerator) Sync() {
    39  	g.globalRangeGenerator.Sync()
    40  }
    41  
    42  func (g *threadGenerator) GoroutineLabel(ctx *traceContext, ev *trace.Event) {
    43  	l := ev.Label()
    44  	g.gStates[l.Resource.Goroutine()].setLabel(l.Label)
    45  }
    46  
    47  func (g *threadGenerator) GoroutineRange(ctx *traceContext, ev *trace.Event) {
    48  	r := ev.Range()
    49  	switch ev.Kind() {
    50  	case trace.EventRangeBegin:
    51  		g.gStates[r.Scope.Goroutine()].rangeBegin(ev.Time(), r.Name, ev.Stack())
    52  	case trace.EventRangeActive:
    53  		g.gStates[r.Scope.Goroutine()].rangeActive(r.Name)
    54  	case trace.EventRangeEnd:
    55  		gs := g.gStates[r.Scope.Goroutine()]
    56  		gs.rangeEnd(ev.Time(), r.Name, ev.Stack(), ctx)
    57  	}
    58  }
    59  
    60  func (g *threadGenerator) GoroutineTransition(ctx *traceContext, ev *trace.Event) {
    61  	if ev.Thread() != trace.NoThread {
    62  		if _, ok := g.threads[ev.Thread()]; !ok {
    63  			g.threads[ev.Thread()] = struct{}{}
    64  		}
    65  	}
    66  
    67  	st := ev.StateTransition()
    68  	goID := st.Resource.Goroutine()
    69  
    70  	// If we haven't seen this goroutine before, create a new
    71  	// gState for it.
    72  	gs, ok := g.gStates[goID]
    73  	if !ok {
    74  		gs = newGState[trace.ThreadID](goID)
    75  		g.gStates[goID] = gs
    76  	}
    77  	// If we haven't already named this goroutine, try to name it.
    78  	gs.augmentName(st.Stack)
    79  
    80  	// Handle the goroutine state transition.
    81  	from, to := st.Goroutine()
    82  	if from == to {
    83  		// Filter out no-op events.
    84  		return
    85  	}
    86  	if from.Executing() && !to.Executing() {
    87  		if to == trace.GoWaiting {
    88  			// Goroutine started blocking.
    89  			gs.block(ev.Time(), ev.Stack(), st.Reason, ctx)
    90  		} else {
    91  			gs.stop(ev.Time(), ev.Stack(), ctx)
    92  		}
    93  	}
    94  	if !from.Executing() && to.Executing() {
    95  		start := ev.Time()
    96  		if from == trace.GoUndetermined {
    97  			// Back-date the event to the start of the trace.
    98  			start = ctx.startTime
    99  		}
   100  		gs.start(start, ev.Thread(), ctx)
   101  	}
   102  
   103  	if from == trace.GoWaiting {
   104  		// Goroutine was unblocked.
   105  		gs.unblock(ev.Time(), ev.Stack(), ev.Thread(), ctx)
   106  	}
   107  	if from == trace.GoNotExist && to == trace.GoRunnable {
   108  		// Goroutine was created.
   109  		gs.created(ev.Time(), ev.Thread(), ev.Stack())
   110  	}
   111  	if from == trace.GoSyscall {
   112  		// Exiting syscall.
   113  		gs.syscallEnd(ev.Time(), to != trace.GoRunning, ctx)
   114  	}
   115  
   116  	// Handle syscalls.
   117  	if to == trace.GoSyscall {
   118  		start := ev.Time()
   119  		if from == trace.GoUndetermined {
   120  			// Back-date the event to the start of the trace.
   121  			start = ctx.startTime
   122  		}
   123  		// Write down that we've entered a syscall. Note: we might have no P here
   124  		// if we're in a cgo callback or this is a transition from GoUndetermined
   125  		// (i.e. the G has been blocked in a syscall).
   126  		gs.syscallBegin(start, ev.Thread(), ev.Stack())
   127  	}
   128  
   129  	// Note down the goroutine transition.
   130  	_, inMarkAssist := gs.activeRanges["GC mark assist"]
   131  	ctx.GoroutineTransition(ctx.elapsed(ev.Time()), viewerGState(from, inMarkAssist), viewerGState(to, inMarkAssist))
   132  }
   133  
   134  func (g *threadGenerator) ProcTransition(ctx *traceContext, ev *trace.Event) {
   135  	if ev.Thread() != trace.NoThread {
   136  		if _, ok := g.threads[ev.Thread()]; !ok {
   137  			g.threads[ev.Thread()] = struct{}{}
   138  		}
   139  	}
   140  
   141  	type procArg struct {
   142  		Proc uint64 `json:"proc,omitempty"`
   143  	}
   144  	st := ev.StateTransition()
   145  	viewerEv := traceviewer.InstantEvent{
   146  		Resource: uint64(ev.Thread()),
   147  		Stack:    ctx.Stack(viewerFrames(ev.Stack())),
   148  		Arg:      procArg{Proc: uint64(st.Resource.Proc())},
   149  	}
   150  
   151  	from, to := st.Proc()
   152  	if from == to {
   153  		// Filter out no-op events.
   154  		return
   155  	}
   156  	if to.Executing() {
   157  		start := ev.Time()
   158  		if from == trace.ProcUndetermined {
   159  			start = ctx.startTime
   160  		}
   161  		viewerEv.Name = "proc start"
   162  		viewerEv.Arg = format.ThreadIDArg{ThreadID: uint64(ev.Thread())}
   163  		viewerEv.Ts = ctx.elapsed(start)
   164  		// TODO(mknyszek): We don't have a state machine for threads, so approximate
   165  		// running threads with running Ps.
   166  		ctx.IncThreadStateCount(ctx.elapsed(start), traceviewer.ThreadStateRunning, 1)
   167  	}
   168  	if from.Executing() {
   169  		start := ev.Time()
   170  		viewerEv.Name = "proc stop"
   171  		viewerEv.Ts = ctx.elapsed(start)
   172  		// TODO(mknyszek): We don't have a state machine for threads, so approximate
   173  		// running threads with running Ps.
   174  		ctx.IncThreadStateCount(ctx.elapsed(start), traceviewer.ThreadStateRunning, -1)
   175  	}
   176  	// TODO(mknyszek): Consider modeling procs differently and have them be
   177  	// transition to and from NotExist when GOMAXPROCS changes. We can emit
   178  	// events for this to clearly delineate GOMAXPROCS changes.
   179  
   180  	if viewerEv.Name != "" {
   181  		ctx.Instant(viewerEv)
   182  	}
   183  }
   184  
   185  func (g *threadGenerator) ProcRange(ctx *traceContext, ev *trace.Event) {
   186  	// TODO(mknyszek): Extend procRangeGenerator to support rendering proc ranges on threads.
   187  }
   188  
   189  func (g *threadGenerator) Finish(ctx *traceContext) {
   190  	ctx.SetResourceType("OS THREADS")
   191  
   192  	// Finish off global ranges.
   193  	g.globalRangeGenerator.Finish(ctx)
   194  
   195  	// Finish off all the goroutine slices.
   196  	for _, gs := range g.gStates {
   197  		gs.finish(ctx)
   198  	}
   199  
   200  	// Name all the threads to the emitter.
   201  	for id := range g.threads {
   202  		ctx.Resource(uint64(id), fmt.Sprintf("Thread %d", id))
   203  	}
   204  }
   205  

View as plain text