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