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 }