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 5package main 6 7import ( 8 "fmt" 9 "internal/trace" 10 "internal/trace/traceviewer" 11 "internal/trace/traceviewer/format" 12) 13 14var _ generator = &procGenerator{} 15 16type procGenerator struct { 17 globalRangeGenerator 18 globalMetricGenerator 19 procRangeGenerator 20 stackSampleGenerator[trace.ProcID] 21 logEventGenerator[trace.ProcID] 22 23 gStates map[trace.GoID]*gState[trace.ProcID] 24 inSyscall map[trace.ProcID]*gState[trace.ProcID] 25 maxProc trace.ProcID 26} 27 28func newProcGenerator() *procGenerator { 29 pg := new(procGenerator) 30 rg := func(ev *trace.Event) trace.ProcID { 31 return ev.Proc() 32 } 33 pg.stackSampleGenerator.getResource = rg 34 pg.logEventGenerator.getResource = rg 35 pg.gStates = make(map[trace.GoID]*gState[trace.ProcID]) 36 pg.inSyscall = make(map[trace.ProcID]*gState[trace.ProcID]) 37 return pg 38} 39 40func (g *procGenerator) Sync() { 41 g.globalRangeGenerator.Sync() 42 g.procRangeGenerator.Sync() 43} 44 45func (g *procGenerator) GoroutineLabel(ctx *traceContext, ev *trace.Event) { 46 l := ev.Label() 47 g.gStates[l.Resource.Goroutine()].setLabel(l.Label) 48} 49 50func (g *procGenerator) GoroutineRange(ctx *traceContext, ev *trace.Event) { 51 r := ev.Range() 52 switch ev.Kind() { 53 case trace.EventRangeBegin: 54 g.gStates[r.Scope.Goroutine()].rangeBegin(ev.Time(), r.Name, ev.Stack()) 55 case trace.EventRangeActive: 56 g.gStates[r.Scope.Goroutine()].rangeActive(r.Name) 57 case trace.EventRangeEnd: 58 gs := g.gStates[r.Scope.Goroutine()] 59 gs.rangeEnd(ev.Time(), r.Name, ev.Stack(), ctx) 60 } 61} 62 63func (g *procGenerator) GoroutineTransition(ctx *traceContext, ev *trace.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[trace.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 == trace.GoRunning && !to.Executing() { 84 if to == trace.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 == trace.GoRunning { 92 start := ev.Time() 93 if from == trace.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 == trace.GoWaiting { 101 // Goroutine was unblocked. 102 gs.unblock(ev.Time(), ev.Stack(), ev.Proc(), ctx) 103 } 104 if from == trace.GoNotExist && to == trace.GoRunnable { 105 // Goroutine was created. 106 gs.created(ev.Time(), ev.Proc(), ev.Stack()) 107 } 108 if from == trace.GoSyscall && to != trace.GoRunning { 109 // Goroutine exited a blocked syscall. 110 gs.blockedSyscallEnd(ev.Time(), ev.Stack(), ctx) 111 } 112 113 // Handle syscalls. 114 if to == trace.GoSyscall && ev.Proc() != trace.NoProc { 115 start := ev.Time() 116 if from == trace.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 == trace.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 138func (g *procGenerator) ProcTransition(ctx *traceContext, ev *trace.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 == trace.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 194func (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