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