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 //go:build goexperiment.exectracer2 6 7 // Trace goroutine and P status management. 8 9 package runtime 10 11 import "runtime/internal/atomic" 12 13 // traceGoStatus is the status of a goroutine. 14 // 15 // They correspond directly to the various goroutine 16 // statuses. 17 type traceGoStatus uint8 18 19 const ( 20 traceGoBad traceGoStatus = iota 21 traceGoRunnable 22 traceGoRunning 23 traceGoSyscall 24 traceGoWaiting 25 ) 26 27 // traceProcStatus is the status of a P. 28 // 29 // They mostly correspond to the various P statuses. 30 type traceProcStatus uint8 31 32 const ( 33 traceProcBad traceProcStatus = iota 34 traceProcRunning 35 traceProcIdle 36 traceProcSyscall 37 38 // traceProcSyscallAbandoned is a special case of 39 // traceProcSyscall. It's used in the very specific case 40 // where the first a P is mentioned in a generation is 41 // part of a ProcSteal event. If that's the first time 42 // it's mentioned, then there's no GoSyscallBegin to 43 // connect the P stealing back to at that point. This 44 // special state indicates this to the parser, so it 45 // doesn't try to find a GoSyscallEndBlocked that 46 // corresponds with the ProcSteal. 47 traceProcSyscallAbandoned 48 ) 49 50 // writeGoStatus emits a GoStatus event as well as any active ranges on the goroutine. 51 func (w traceWriter) writeGoStatus(goid uint64, mid int64, status traceGoStatus, markAssist bool) traceWriter { 52 // The status should never be bad. Some invariant must have been violated. 53 if status == traceGoBad { 54 print("runtime: goid=", goid, "\n") 55 throw("attempted to trace a bad status for a goroutine") 56 } 57 58 // Trace the status. 59 w = w.event(traceEvGoStatus, traceArg(goid), traceArg(uint64(mid)), traceArg(status)) 60 61 // Trace any special ranges that are in-progress. 62 if markAssist { 63 w = w.event(traceEvGCMarkAssistActive, traceArg(goid)) 64 } 65 return w 66 } 67 68 // writeProcStatusForP emits a ProcStatus event for the provided p based on its status. 69 // 70 // The caller must fully own pp and it must be prevented from transitioning (e.g. this can be 71 // called by a forEachP callback or from a STW). 72 func (w traceWriter) writeProcStatusForP(pp *p, inSTW bool) traceWriter { 73 if !pp.trace.acquireStatus(w.gen) { 74 return w 75 } 76 var status traceProcStatus 77 switch pp.status { 78 case _Pidle, _Pgcstop: 79 status = traceProcIdle 80 if pp.status == _Pgcstop && inSTW { 81 // N.B. a P that is running and currently has the world stopped will be 82 // in _Pgcstop, but we model it as running in the tracer. 83 status = traceProcRunning 84 } 85 case _Prunning: 86 status = traceProcRunning 87 // There's a short window wherein the goroutine may have entered _Gsyscall 88 // but it still owns the P (it's not in _Psyscall yet). The goroutine entering 89 // _Gsyscall is the tracer's signal that the P its bound to is also in a syscall, 90 // so we need to emit a status that matches. See #64318. 91 if w.mp.p.ptr() == pp && w.mp.curg != nil && readgstatus(w.mp.curg)&^_Gscan == _Gsyscall { 92 status = traceProcSyscall 93 } 94 case _Psyscall: 95 status = traceProcSyscall 96 default: 97 throw("attempt to trace invalid or unsupported P status") 98 } 99 w = w.writeProcStatus(uint64(pp.id), status, pp.trace.inSweep) 100 return w 101 } 102 103 // writeProcStatus emits a ProcStatus event with all the provided information. 104 // 105 // The caller must have taken ownership of a P's status writing, and the P must be 106 // prevented from transitioning. 107 func (w traceWriter) writeProcStatus(pid uint64, status traceProcStatus, inSweep bool) traceWriter { 108 // The status should never be bad. Some invariant must have been violated. 109 if status == traceProcBad { 110 print("runtime: pid=", pid, "\n") 111 throw("attempted to trace a bad status for a proc") 112 } 113 114 // Trace the status. 115 w = w.event(traceEvProcStatus, traceArg(pid), traceArg(status)) 116 117 // Trace any special ranges that are in-progress. 118 if inSweep { 119 w = w.event(traceEvGCSweepActive, traceArg(pid)) 120 } 121 return w 122 } 123 124 // goStatusToTraceGoStatus translates the internal status to tracGoStatus. 125 // 126 // status must not be _Gdead or any status whose name has the suffix "_unused." 127 func goStatusToTraceGoStatus(status uint32, wr waitReason) traceGoStatus { 128 // N.B. Ignore the _Gscan bit. We don't model it in the tracer. 129 var tgs traceGoStatus 130 switch status &^ _Gscan { 131 case _Grunnable: 132 tgs = traceGoRunnable 133 case _Grunning, _Gcopystack: 134 tgs = traceGoRunning 135 case _Gsyscall: 136 tgs = traceGoSyscall 137 case _Gwaiting, _Gpreempted: 138 // There are a number of cases where a G might end up in 139 // _Gwaiting but it's actually running in a non-preemptive 140 // state but needs to present itself as preempted to the 141 // garbage collector. In these cases, we're not going to 142 // emit an event, and we want these goroutines to appear in 143 // the final trace as if they're running, not blocked. 144 tgs = traceGoWaiting 145 if status == _Gwaiting && 146 wr == waitReasonStoppingTheWorld || 147 wr == waitReasonGCMarkTermination || 148 wr == waitReasonGarbageCollection || 149 wr == waitReasonTraceProcStatus || 150 wr == waitReasonPageTraceFlush || 151 wr == waitReasonGCWorkerActive { 152 tgs = traceGoRunning 153 } 154 case _Gdead: 155 throw("tried to trace dead goroutine") 156 default: 157 throw("tried to trace goroutine with invalid or unsupported status") 158 } 159 return tgs 160 } 161 162 // traceSchedResourceState is shared state for scheduling resources (i.e. fields common to 163 // both Gs and Ps). 164 type traceSchedResourceState struct { 165 // statusTraced indicates whether a status event was traced for this resource 166 // a particular generation. 167 // 168 // There are 3 of these because when transitioning across generations, traceAdvance 169 // needs to be able to reliably observe whether a status was traced for the previous 170 // generation, while we need to clear the value for the next generation. 171 statusTraced [3]atomic.Uint32 172 173 // seq is the sequence counter for this scheduling resource's events. 174 // The purpose of the sequence counter is to establish a partial order between 175 // events that don't obviously happen serially (same M) in the stream ofevents. 176 // 177 // There are two of these so that we can reset the counter on each generation. 178 // This saves space in the resulting trace by keeping the counter small and allows 179 // GoStatus and GoCreate events to omit a sequence number (implicitly 0). 180 seq [2]uint64 181 } 182 183 // acquireStatus acquires the right to emit a Status event for the scheduling resource. 184 func (r *traceSchedResourceState) acquireStatus(gen uintptr) bool { 185 if !r.statusTraced[gen%3].CompareAndSwap(0, 1) { 186 return false 187 } 188 r.readyNextGen(gen) 189 return true 190 } 191 192 // readyNextGen readies r for the generation following gen. 193 func (r *traceSchedResourceState) readyNextGen(gen uintptr) { 194 nextGen := traceNextGen(gen) 195 r.seq[nextGen%2] = 0 196 r.statusTraced[nextGen%3].Store(0) 197 } 198 199 // statusWasTraced returns true if the sched resource's status was already acquired for tracing. 200 func (r *traceSchedResourceState) statusWasTraced(gen uintptr) bool { 201 return r.statusTraced[gen%3].Load() != 0 202 } 203 204 // setStatusTraced indicates that the resource's status was already traced, for example 205 // when a goroutine is created. 206 func (r *traceSchedResourceState) setStatusTraced(gen uintptr) { 207 r.statusTraced[gen%3].Store(1) 208 } 209 210 // nextSeq returns the next sequence number for the resource. 211 func (r *traceSchedResourceState) nextSeq(gen uintptr) traceArg { 212 r.seq[gen%2]++ 213 return traceArg(r.seq[gen%2]) 214 } 215