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 = &threadGenerator{} 15 16type 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 26func 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 38func (g *threadGenerator) Sync() { 39 g.globalRangeGenerator.Sync() 40} 41 42func (g *threadGenerator) GoroutineLabel(ctx *traceContext, ev *trace.Event) { 43 l := ev.Label() 44 g.gStates[l.Resource.Goroutine()].setLabel(l.Label) 45} 46 47func (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 60func (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 134func (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 185func (g *threadGenerator) ProcRange(ctx *traceContext, ev *trace.Event) { 186 // TODO(mknyszek): Extend procRangeGenerator to support rendering proc ranges on threads. 187} 188 189func (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