Source File
traceruntime.go
Belonging Package
runtime
// 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.// Runtime -> tracer API.package runtimeimport (_ // for go:linkname)// gTraceState is per-G state for the tracer.type gTraceState struct {traceSchedResourceState}// reset resets the gTraceState for a new goroutine.func ( *gTraceState) () {.seq = [2]uint64{}// N.B. s.statusTraced is managed and cleared separately.}// mTraceState is per-M state for the tracer.type mTraceState struct {seqlock atomic.Uintptr // seqlock indicating that this M is writing to a trace buffer.buf [2]*traceBuf // Per-M traceBuf for writing. Indexed by trace.gen%2.link *m // Snapshot of alllink or freelink.}// pTraceState is per-P state for the tracer.type pTraceState struct {traceSchedResourceState// mSyscallID is the ID of the M this was bound to before entering a syscall.mSyscallID int64// maySweep indicates the sweep events should be traced.// This is used to defer the sweep start event until a span// has actually been swept.maySweep bool// inSweep indicates that at least one sweep event has been traced.inSweep bool// swept and reclaimed track the number of bytes swept and reclaimed// by sweeping in the current sweep loop (while maySweep was true).swept, reclaimed uintptr}// traceLockInit initializes global trace locks.func () {// Sharing a lock rank here is fine because they should never be accessed// together. If they are, we want to find out immediately.lockInit(&trace.stringTab[0].lock, lockRankTraceStrings)lockInit(&trace.stringTab[0].tab.mem.lock, lockRankTraceStrings)lockInit(&trace.stringTab[1].lock, lockRankTraceStrings)lockInit(&trace.stringTab[1].tab.mem.lock, lockRankTraceStrings)lockInit(&trace.stackTab[0].tab.mem.lock, lockRankTraceStackTab)lockInit(&trace.stackTab[1].tab.mem.lock, lockRankTraceStackTab)lockInit(&trace.typeTab[0].tab.mem.lock, lockRankTraceTypeTab)lockInit(&trace.typeTab[1].tab.mem.lock, lockRankTraceTypeTab)lockInit(&trace.lock, lockRankTrace)}// lockRankMayTraceFlush records the lock ranking effects of a// potential call to traceFlush.//// nosplit because traceAcquire is nosplit.////go:nosplitfunc () {lockWithRankMayAcquire(&trace.lock, getLockRank(&trace.lock))}// traceBlockReason is an enumeration of reasons a goroutine might block.// This is the interface the rest of the runtime uses to tell the// tracer why a goroutine blocked. The tracer then propagates this information// into the trace however it sees fit.//// Note that traceBlockReasons should not be compared, since reasons that are// distinct by name may *not* be distinct by value.type traceBlockReason uint8const (traceBlockGeneric traceBlockReason = iotatraceBlockForevertraceBlockNettraceBlockSelecttraceBlockCondWaittraceBlockSynctraceBlockChanSendtraceBlockChanRecvtraceBlockGCMarkAssisttraceBlockGCSweeptraceBlockSystemGoroutinetraceBlockPreemptedtraceBlockDebugCalltraceBlockUntilGCEndstraceBlockSleeptraceBlockGCWeakToStrongWait)var traceBlockReasonStrings = [...]string{traceBlockGeneric: "unspecified",traceBlockForever: "forever",traceBlockNet: "network",traceBlockSelect: "select",traceBlockCondWait: "sync.(*Cond).Wait",traceBlockSync: "sync",traceBlockChanSend: "chan send",traceBlockChanRecv: "chan receive",traceBlockGCMarkAssist: "GC mark assist wait for work",traceBlockGCSweep: "GC background sweeper wait",traceBlockSystemGoroutine: "system goroutine wait",traceBlockPreempted: "preempted",traceBlockDebugCall: "wait for debug call",traceBlockUntilGCEnds: "wait until GC ends",traceBlockSleep: "sleep",traceBlockGCWeakToStrongWait: "GC weak to strong wait",}// traceGoStopReason is an enumeration of reasons a goroutine might yield.//// Note that traceGoStopReasons should not be compared, since reasons that are// distinct by name may *not* be distinct by value.type traceGoStopReason uint8const (traceGoStopGeneric traceGoStopReason = iotatraceGoStopGoSchedtraceGoStopPreempted)var traceGoStopReasonStrings = [...]string{traceGoStopGeneric: "unspecified",traceGoStopGoSched: "runtime.Gosched",traceGoStopPreempted: "preempted",}// traceEnabled returns true if the trace is currently enabled.////go:nosplitfunc () bool {return trace.enabled}// traceAllocFreeEnabled returns true if the trace is currently enabled// and alloc/free events are also enabled.////go:nosplitfunc () bool {return trace.enabledWithAllocFree}// traceShuttingDown returns true if the trace is currently shutting down.func () bool {return trace.shutdown.Load()}// traceLocker represents an M writing trace events. While a traceLocker value// is valid, the tracer observes all operations on the G/M/P or trace events being// written as happening atomically.type traceLocker struct {mp *mgen uintptr}// debugTraceReentrancy checks if the trace is reentrant.//// This is optional because throwing in a function makes it instantly// not inlineable, and we want traceAcquire to be inlineable for// low overhead when the trace is disabled.const debugTraceReentrancy = false// traceAcquire prepares this M for writing one or more trace events.//// nosplit because it's called on the syscall path when stack movement is forbidden.////go:nosplitfunc () traceLocker {if !traceEnabled() {return traceLocker{}}return traceAcquireEnabled()}// traceTryAcquire is like traceAcquire, but may return an invalid traceLocker even// if tracing is enabled. For example, it will return !ok if traceAcquire is being// called with an active traceAcquire on the M (reentrant locking). This exists for// optimistically emitting events in the few contexts where tracing is now allowed.//// nosplit for alignment with traceTryAcquire, so it can be used in the// same contexts.////go:nosplitfunc () traceLocker {if !traceEnabled() {return traceLocker{}}return traceTryAcquireEnabled()}// traceAcquireEnabled is the traceEnabled path for traceAcquire. It's explicitly// broken out to make traceAcquire inlineable to keep the overhead of the tracer// when it's disabled low.//// nosplit because it's called by traceAcquire, which is nosplit.////go:nosplitfunc () traceLocker {// Any time we acquire a traceLocker, we may flush a trace buffer. But// buffer flushes are rare. Record the lock edge even if it doesn't happen// this time.lockRankMayTraceFlush()// Prevent preemption.:= acquirem()// Acquire the trace seqlock. This prevents traceAdvance from moving forward// until all Ms are observed to be outside of their seqlock critical section.//// Note: The seqlock is mutated here and also in traceCPUSample. If you update// usage of the seqlock here, make sure to also look at what traceCPUSample is// doing.:= .trace.seqlock.Add(1)if debugTraceReentrancy && %2 != 1 {throw("bad use of trace.seqlock or tracer is reentrant")}// N.B. This load of gen appears redundant with the one in traceEnabled.// However, it's very important that the gen we use for writing to the trace// is acquired under a traceLocker so traceAdvance can make sure no stale// gen values are being used.//// Because we're doing this load again, it also means that the trace// might end up being disabled when we load it. In that case we need to undo// what we did and bail.:= trace.gen.Load()if == 0 {.trace.seqlock.Add(1)releasem()return traceLocker{}}return traceLocker{, }}// traceTryAcquireEnabled is like traceAcquireEnabled but may return an invalid// traceLocker under some conditions. See traceTryAcquire for more details.//// nosplit for alignment with traceAcquireEnabled, so it can be used in the// same contexts.////go:nosplitfunc () traceLocker {// Any time we acquire a traceLocker, we may flush a trace buffer. But// buffer flushes are rare. Record the lock edge even if it doesn't happen// this time.lockRankMayTraceFlush()// Check if we're already locked. If so, return an invalid traceLocker.if getg().m.trace.seqlock.Load()%2 == 1 {return traceLocker{}}return traceAcquireEnabled()}// ok returns true if the traceLocker is valid (i.e. tracing is enabled).//// nosplit because it's called on the syscall path when stack movement is forbidden.////go:nosplitfunc ( traceLocker) () bool {return .gen != 0}// traceRelease indicates that this M is done writing trace events.//// nosplit because it's called on the syscall path when stack movement is forbidden.////go:nosplitfunc ( traceLocker) {:= .mp.trace.seqlock.Add(1)if debugTraceReentrancy && %2 != 0 {print("runtime: seq=", , "\n")throw("bad use of trace.seqlock")}releasem(.mp)}// traceExitingSyscall marks a goroutine as exiting the syscall slow path.//// Must be paired with a traceExitedSyscall call.func () {trace.exitingSyscall.Add(1)}// traceExitedSyscall marks a goroutine as having exited the syscall slow path.func () {trace.exitingSyscall.Add(-1)}// Gomaxprocs emits a ProcsChange event.func ( traceLocker) ( int32) {.eventWriter(traceGoRunning, traceProcRunning).commit(traceEvProcsChange, traceArg(), .stack(1))}// ProcStart traces a ProcStart event.//// Must be called with a valid P.func ( traceLocker) () {:= .mp.p.ptr()// Procs are typically started within the scheduler when there is no user goroutine. If there is a user goroutine,// it must be in _Gsyscall because the only time a goroutine is allowed to have its Proc moved around from under it// is during a syscall..eventWriter(traceGoSyscall, traceProcIdle).commit(traceEvProcStart, traceArg(.id), .trace.nextSeq(.gen))}// ProcStop traces a ProcStop event.func ( traceLocker) ( *p) {// The only time a goroutine is allowed to have its Proc moved around// from under it is during a syscall..eventWriter(traceGoSyscall, traceProcRunning).commit(traceEvProcStop)}// GCActive traces a GCActive event.//// Must be emitted by an actively running goroutine on an active P. This restriction can be changed// easily and only depends on where it's currently called.func ( traceLocker) () {.eventWriter(traceGoRunning, traceProcRunning).commit(traceEvGCActive, traceArg(trace.seqGC))// N.B. Only one GC can be running at a time, so this is naturally// serialized by the caller.trace.seqGC++}// GCStart traces a GCBegin event.//// Must be emitted by an actively running goroutine on an active P. This restriction can be changed// easily and only depends on where it's currently called.func ( traceLocker) () {.eventWriter(traceGoRunning, traceProcRunning).commit(traceEvGCBegin, traceArg(trace.seqGC), .stack(3))// N.B. Only one GC can be running at a time, so this is naturally// serialized by the caller.trace.seqGC++}// GCDone traces a GCEnd event.//// Must be emitted by an actively running goroutine on an active P. This restriction can be changed// easily and only depends on where it's currently called.func ( traceLocker) () {.eventWriter(traceGoRunning, traceProcRunning).commit(traceEvGCEnd, traceArg(trace.seqGC))// N.B. Only one GC can be running at a time, so this is naturally// serialized by the caller.trace.seqGC++}// STWStart traces a STWBegin event.func ( traceLocker) ( stwReason) {// Although the current P may be in _Pgcstop here, we model the P as running during the STW. This deviates from the// runtime's state tracking, but it's more accurate and doesn't result in any loss of information..eventWriter(traceGoRunning, traceProcRunning).commit(traceEvSTWBegin, .string(.String()), .stack(2))}// STWDone traces a STWEnd event.func ( traceLocker) () {// Although the current P may be in _Pgcstop here, we model the P as running during the STW. This deviates from the// runtime's state tracking, but it's more accurate and doesn't result in any loss of information..eventWriter(traceGoRunning, traceProcRunning).commit(traceEvSTWEnd)}// GCSweepStart prepares to trace a sweep loop. This does not// emit any events until traceGCSweepSpan is called.//// GCSweepStart must be paired with traceGCSweepDone and there// must be no preemption points between these two calls.//// Must be called with a valid P.func ( traceLocker) () {// Delay the actual GCSweepBegin event until the first span// sweep. If we don't sweep anything, don't emit any events.:= .mp.p.ptr()if .trace.maySweep {throw("double traceGCSweepStart")}.trace.maySweep, .trace.swept, .trace.reclaimed = true, 0, 0}// GCSweepSpan traces the sweep of a single span. If this is// the first span swept since traceGCSweepStart was called, this// will emit a GCSweepBegin event.//// This may be called outside a traceGCSweepStart/traceGCSweepDone// pair; however, it will not emit any trace events in this case.//// Must be called with a valid P.func ( traceLocker) ( uintptr) {:= .mp.p.ptr()if .trace.maySweep {if .trace.swept == 0 {.eventWriter(traceGoRunning, traceProcRunning).commit(traceEvGCSweepBegin, .stack(1)).trace.inSweep = true}.trace.swept +=}}// GCSweepDone finishes tracing a sweep loop. If any memory was// swept (i.e. traceGCSweepSpan emitted an event) then this will emit// a GCSweepEnd event.//// Must be called with a valid P.func ( traceLocker) () {:= .mp.p.ptr()if !.trace.maySweep {throw("missing traceGCSweepStart")}if .trace.inSweep {.eventWriter(traceGoRunning, traceProcRunning).commit(traceEvGCSweepEnd, traceArg(.trace.swept), traceArg(.trace.reclaimed)).trace.inSweep = false}.trace.maySweep = false}// GCMarkAssistStart emits a MarkAssistBegin event.func ( traceLocker) () {.eventWriter(traceGoRunning, traceProcRunning).commit(traceEvGCMarkAssistBegin, .stack(1))}// GCMarkAssistDone emits a MarkAssistEnd event.func ( traceLocker) () {.eventWriter(traceGoRunning, traceProcRunning).commit(traceEvGCMarkAssistEnd)}// GoCreate emits a GoCreate event.func ( traceLocker) ( *g, uintptr, bool) {.trace.setStatusTraced(.gen):= traceEvGoCreateif {= traceEvGoCreateBlocked}.eventWriter(traceGoRunning, traceProcRunning).commit(, traceArg(.goid), .startPC(), .stack(2))}// GoStart emits a GoStart event.//// Must be called with a valid P.func ( traceLocker) () {:= getg().m.curg:= .m.p:= .eventWriter(traceGoRunnable, traceProcRunning)= .write(traceEvGoStart, traceArg(.goid), .trace.nextSeq(.gen))if .ptr().gcMarkWorkerMode != gcMarkWorkerNotWorker {= .write(traceEvGoLabel, trace.markWorkerLabels[.gen%2][.ptr().gcMarkWorkerMode])}.end()}// GoEnd emits a GoDestroy event.//// TODO(mknyszek): Rename this to GoDestroy.func ( traceLocker) () {.eventWriter(traceGoRunning, traceProcRunning).commit(traceEvGoDestroy)}// GoSched emits a GoStop event with a GoSched reason.func ( traceLocker) () {.GoStop(traceGoStopGoSched)}// GoPreempt emits a GoStop event with a GoPreempted reason.func ( traceLocker) () {.GoStop(traceGoStopPreempted)}// GoStop emits a GoStop event with the provided reason.func ( traceLocker) ( traceGoStopReason) {.eventWriter(traceGoRunning, traceProcRunning).commit(traceEvGoStop, traceArg(trace.goStopReasons[.gen%2][]), .stack(1))}// GoPark emits a GoBlock event with the provided reason.//// TODO(mknyszek): Replace traceBlockReason with waitReason. It's silly// that we have both, and waitReason is way more descriptive.func ( traceLocker) ( traceBlockReason, int) {.eventWriter(traceGoRunning, traceProcRunning).commit(traceEvGoBlock, traceArg(trace.goBlockReasons[.gen%2][]), .stack())}// GoUnpark emits a GoUnblock event.func ( traceLocker) ( *g, int) {// Emit a GoWaiting status if necessary for the unblocked goroutine.:= .eventWriter(traceGoRunning, traceProcRunning)// Careful: don't use the event writer. We never want status or in-progress events// to trigger more in-progress events..w = emitUnblockStatus(.w, , .gen).commit(traceEvGoUnblock, traceArg(.goid), .trace.nextSeq(.gen), .stack())}// GoCoroswitch emits a GoSwitch event. If destroy is true, the calling goroutine// is simultaneously being destroyed.func ( traceLocker) ( *g, bool) {// Emit a GoWaiting status if necessary for the unblocked goroutine.:= .eventWriter(traceGoRunning, traceProcRunning)// Careful: don't use the event writer. We never want status or in-progress events// to trigger more in-progress events..w = emitUnblockStatus(.w, , .gen):= traceEvGoSwitchif {= traceEvGoSwitchDestroy}.commit(, traceArg(.goid), .trace.nextSeq(.gen))}// emitUnblockStatus emits a GoStatus GoWaiting event for a goroutine about to be// unblocked to the trace writer.func ( traceWriter, *g, uintptr) traceWriter {if !.trace.statusWasTraced() && .trace.acquireStatus() {// TODO(go.dev/issue/65634): Although it would be nice to add a stack trace here of gp,// we cannot safely do so. gp is in _Gwaiting and so we don't have ownership of its stack.// We can fix this by acquiring the goroutine's scan bit.= .writeGoStatus(.goid, -1, traceGoWaiting, .inMarkAssist, 0)}return}// GoSysCall emits a GoSyscallBegin event.//// Must be called with a valid P.func ( traceLocker) () {// Scribble down the M that the P is currently attached to.:= .mp.p.ptr().trace.mSyscallID = int64(.mp.procid).eventWriter(traceGoRunning, traceProcRunning).commit(traceEvGoSyscallBegin, .trace.nextSeq(.gen), .stack(1))}// GoSysExit emits a GoSyscallEnd event, possibly along with a GoSyscallBlocked event// if lostP is true.//// lostP must be true in all cases that a goroutine loses its P during a syscall.// This means it's not sufficient to check if it has no P. In particular, it needs to be// true in the following cases:// - The goroutine lost its P, it ran some other code, and then got it back. It's now running with that P.// - The goroutine lost its P and was unable to reacquire it, and is now running without a P.// - The goroutine lost its P and acquired a different one, and is now running with that P.func ( traceLocker) ( bool) {:= traceEvGoSyscallEnd:= traceProcSyscall // Procs implicitly enter traceProcSyscall on GoSyscallBegin.if {= traceEvGoSyscallEndBlocked= traceProcRunning // If a G has a P when emitting this event, it reacquired a P and is indeed running.} else {.mp.p.ptr().trace.mSyscallID = -1}.eventWriter(traceGoSyscall, ).commit()}// ProcSteal indicates that our current M stole a P from another M.//// inSyscall indicates that we're stealing the P from a syscall context.//// The caller must have ownership of pp.func ( traceLocker) ( *p, bool) {// Grab the M ID we stole from.:= .trace.mSyscallID.trace.mSyscallID = -1// The status of the proc and goroutine, if we need to emit one here, is not evident from the// context of just emitting this event alone. There are two cases. Either we're trying to steal// the P just to get its attention (e.g. STW or sysmon retake) or we're trying to steal a P for// ourselves specifically to keep running. The two contexts look different, but can be summarized// fairly succinctly. In the former, we're a regular running goroutine and proc, if we have either.// In the latter, we're a goroutine in a syscall.:= traceGoRunning:= traceProcRunningif {= traceGoSyscall= traceProcSyscallAbandoned}:= .eventWriter(, )// Emit the status of the P we're stealing. We may have *just* done this when creating the event// writer but it's not guaranteed, even if inSyscall is true. Although it might seem like from a// syscall context we're always stealing a P for ourselves, we may have not wired it up yet (so// it wouldn't be visible to eventWriter) or we may not even intend to wire it up to ourselves// at all (e.g. entersyscall_gcwait).if !.trace.statusWasTraced(.gen) && .trace.acquireStatus(.gen) {// Careful: don't use the event writer. We never want status or in-progress events// to trigger more in-progress events..w = .w.writeProcStatus(uint64(.id), traceProcSyscallAbandoned, .trace.inSweep)}.commit(traceEvProcSteal, traceArg(.id), .trace.nextSeq(.gen), traceArg())}// HeapAlloc emits a HeapAlloc event.func ( traceLocker) ( uint64) {.eventWriter(traceGoRunning, traceProcRunning).commit(traceEvHeapAlloc, traceArg())}// HeapGoal reads the current heap goal and emits a HeapGoal event.func ( traceLocker) () {:= gcController.heapGoal()if == ^uint64(0) {// Heap-based triggering is disabled.= 0}.eventWriter(traceGoRunning, traceProcRunning).commit(traceEvHeapGoal, traceArg())}// GoCreateSyscall indicates that a goroutine has transitioned from dead to GoSyscall.//// Unlike GoCreate, the caller must be running on gp.//// This occurs when C code calls into Go. On pthread platforms it occurs only when// a C thread calls into Go code for the first time.func ( traceLocker) ( *g) {// N.B. We should never trace a status for this goroutine (which we're currently running on),// since we want this to appear like goroutine creation..trace.setStatusTraced(.gen).eventWriter(traceGoBad, traceProcBad).commit(traceEvGoCreateSyscall, traceArg(.goid))}// GoDestroySyscall indicates that a goroutine has transitioned from GoSyscall to dead.//// Must not have a P.//// This occurs when Go code returns back to C. On pthread platforms it occurs only when// the C thread is destroyed.func ( traceLocker) () {// N.B. If we trace a status here, we must never have a P, and we must be on a goroutine// that is in the syscall state..eventWriter(traceGoSyscall, traceProcBad).commit(traceEvGoDestroySyscall)}// To access runtime functions from runtime/trace.// See runtime/trace/annotation.go// trace_userTaskCreate emits a UserTaskCreate event.////go:linkname trace_userTaskCreate runtime/trace.userTaskCreatefunc (, uint64, string) {:= traceAcquire()if !.ok() {// Need to do this check because the caller won't have it.return}.eventWriter(traceGoRunning, traceProcRunning).commit(traceEvUserTaskBegin, traceArg(), traceArg(), .string(), .stack(3))traceRelease()}// trace_userTaskEnd emits a UserTaskEnd event.////go:linkname trace_userTaskEnd runtime/trace.userTaskEndfunc ( uint64) {:= traceAcquire()if !.ok() {// Need to do this check because the caller won't have it.return}.eventWriter(traceGoRunning, traceProcRunning).commit(traceEvUserTaskEnd, traceArg(), .stack(2))traceRelease()}// trace_userTaskEnd emits a UserRegionBegin or UserRegionEnd event,// depending on mode (0 == Begin, 1 == End).//// TODO(mknyszek): Just make this two functions.////go:linkname trace_userRegion runtime/trace.userRegionfunc (, uint64, string) {:= traceAcquire()if !.ok() {// Need to do this check because the caller won't have it.return}var traceEvswitch {case 0:= traceEvUserRegionBegincase 1:= traceEvUserRegionEnddefault:return}.eventWriter(traceGoRunning, traceProcRunning).commit(, traceArg(), .string(), .stack(3))traceRelease()}// trace_userTaskEnd emits a UserRegionBegin or UserRegionEnd event.////go:linkname trace_userLog runtime/trace.userLogfunc ( uint64, , string) {:= traceAcquire()if !.ok() {// Need to do this check because the caller won't have it.return}.eventWriter(traceGoRunning, traceProcRunning).commit(traceEvUserLog, traceArg(), .string(), .uniqueString(), .stack(3))traceRelease()}// traceThreadDestroy is called when a thread is removed from// sched.freem.//// mp must not be able to emit trace events anymore.//// sched.lock must be held to synchronize with traceAdvance.func ( *m) {assertLockHeld(&sched.lock)// Flush all outstanding buffers to maintain the invariant// that an M only has active buffers while on sched.freem// or allm.//// Perform a traceAcquire/traceRelease on behalf of mp to// synchronize with the tracer trying to flush our buffer// as well.:= .trace.seqlock.Add(1)if debugTraceReentrancy && %2 != 1 {throw("bad use of trace.seqlock or tracer is reentrant")}systemstack(func() {lock(&trace.lock)for := range .trace.buf {if .trace.buf[] != nil {// N.B. traceBufFlush accepts a generation, but it// really just cares about gen%2.traceBufFlush(.trace.buf[], uintptr()).trace.buf[] = nil}}unlock(&trace.lock)}):= .trace.seqlock.Add(1)if != +1 {print("runtime: seq1=", , "\n")throw("bad use of trace.seqlock")}}
![]() |
The pages are generated with Golds v0.7.6. (GOOS=linux GOARCH=amd64) Golds is a Go 101 project developed by Tapir Liu. PR and bug reports are welcome and can be submitted to the issue list. Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds. |