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