blob: b2aa551455f87e9ac815bfca957dbada380ccc80 [file] [log] [blame]
// 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.
package trace
import (
"fmt"
"strings"
"internal/trace/v2/event"
"internal/trace/v2/event/go122"
"internal/trace/v2/version"
)
// ordering emulates Go scheduler state for both validation and
// for putting events in the right order.
//
// The interface to ordering consists of two methods: Advance
// and Next. Advance is called to try and advance an event and
// add completed events to the ordering. Next is used to pick
// off events in the ordering.
type ordering struct {
gStates map[GoID]*gState
pStates map[ProcID]*pState // TODO: The keys are dense, so this can be a slice.
mStates map[ThreadID]*mState
activeTasks map[TaskID]taskState
gcSeq uint64
gcState gcState
initialGen uint64
queue queue[Event]
}
// Advance checks if it's valid to proceed with ev which came from thread m.
//
// It assumes the gen value passed to it is monotonically increasing across calls.
//
// If any error is returned, then the trace is broken and trace parsing must cease.
// If it's not valid to advance with ev, but no error was encountered, the caller
// should attempt to advance with other candidate events from other threads. If the
// caller runs out of candidates, the trace is invalid.
//
// If this returns true, Next is guaranteed to return a complete event. However,
// multiple events may be added to the ordering, so the caller should (but is not
// required to) continue to call Next until it is exhausted.
func (o *ordering) Advance(ev *baseEvent, evt *evTable, m ThreadID, gen uint64) (bool, error) {
if o.initialGen == 0 {
// Set the initial gen if necessary.
o.initialGen = gen
}
var curCtx, newCtx schedCtx
curCtx.M = m
newCtx.M = m
var ms *mState
if m == NoThread {
curCtx.P = NoProc
curCtx.G = NoGoroutine
newCtx = curCtx
} else {
// Pull out or create the mState for this event.
var ok bool
ms, ok = o.mStates[m]
if !ok {
ms = &mState{
g: NoGoroutine,
p: NoProc,
}
o.mStates[m] = ms
}
curCtx.P = ms.p
curCtx.G = ms.g
newCtx = curCtx
}
f := orderingDispatch[ev.typ]
if f == nil {
return false, fmt.Errorf("bad event type found while ordering: %v", ev.typ)
}
newCtx, ok, err := f(o, ev, evt, m, gen, curCtx)
if err == nil && ok && ms != nil {
// Update the mState for this event.
ms.p = newCtx.P
ms.g = newCtx.G
}
return ok, err
}
type orderingHandleFunc func(o *ordering, ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error)
var orderingDispatch = [256]orderingHandleFunc{
// Procs.
go122.EvProcsChange: (*ordering).advanceAnnotation,
go122.EvProcStart: (*ordering).advanceProcStart,
go122.EvProcStop: (*ordering).advanceProcStop,
go122.EvProcSteal: (*ordering).advanceProcSteal,
go122.EvProcStatus: (*ordering).advanceProcStatus,
// Goroutines.
go122.EvGoCreate: (*ordering).advanceGoCreate,
go122.EvGoCreateSyscall: (*ordering).advanceGoCreateSyscall,
go122.EvGoStart: (*ordering).advanceGoStart,
go122.EvGoDestroy: (*ordering).advanceGoStopExec,
go122.EvGoDestroySyscall: (*ordering).advanceGoDestroySyscall,
go122.EvGoStop: (*ordering).advanceGoStopExec,
go122.EvGoBlock: (*ordering).advanceGoStopExec,
go122.EvGoUnblock: (*ordering).advanceGoUnblock,
go122.EvGoSyscallBegin: (*ordering).advanceGoSyscallBegin,
go122.EvGoSyscallEnd: (*ordering).advanceGoSyscallEnd,
go122.EvGoSyscallEndBlocked: (*ordering).advanceGoSyscallEndBlocked,
go122.EvGoStatus: (*ordering).advanceGoStatus,
// STW.
go122.EvSTWBegin: (*ordering).advanceGoRangeBegin,
go122.EvSTWEnd: (*ordering).advanceGoRangeEnd,
// GC events.
go122.EvGCActive: (*ordering).advanceGCActive,
go122.EvGCBegin: (*ordering).advanceGCBegin,
go122.EvGCEnd: (*ordering).advanceGCEnd,
go122.EvGCSweepActive: (*ordering).advanceGCSweepActive,
go122.EvGCSweepBegin: (*ordering).advanceGCSweepBegin,
go122.EvGCSweepEnd: (*ordering).advanceGCSweepEnd,
go122.EvGCMarkAssistActive: (*ordering).advanceGoRangeActive,
go122.EvGCMarkAssistBegin: (*ordering).advanceGoRangeBegin,
go122.EvGCMarkAssistEnd: (*ordering).advanceGoRangeEnd,
go122.EvHeapAlloc: (*ordering).advanceHeapMetric,
go122.EvHeapGoal: (*ordering).advanceHeapMetric,
// Annotations.
go122.EvGoLabel: (*ordering).advanceAnnotation,
go122.EvUserTaskBegin: (*ordering).advanceUserTaskBegin,
go122.EvUserTaskEnd: (*ordering).advanceUserTaskEnd,
go122.EvUserRegionBegin: (*ordering).advanceUserRegionBegin,
go122.EvUserRegionEnd: (*ordering).advanceUserRegionEnd,
go122.EvUserLog: (*ordering).advanceAnnotation,
// Coroutines. Added in Go 1.23.
go122.EvGoSwitch: (*ordering).advanceGoSwitch,
go122.EvGoSwitchDestroy: (*ordering).advanceGoSwitch,
go122.EvGoCreateBlocked: (*ordering).advanceGoCreate,
// GoStatus event with a stack. Added in Go 1.23.
go122.EvGoStatusStack: (*ordering).advanceGoStatus,
}
func (o *ordering) advanceProcStatus(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
pid := ProcID(ev.args[0])
status := go122.ProcStatus(ev.args[1])
if int(status) >= len(go122ProcStatus2ProcState) {
return curCtx, false, fmt.Errorf("invalid status for proc %d: %d", pid, status)
}
oldState := go122ProcStatus2ProcState[status]
if s, ok := o.pStates[pid]; ok {
if status == go122.ProcSyscallAbandoned && s.status == go122.ProcSyscall {
// ProcSyscallAbandoned is a special case of ProcSyscall. It indicates a
// potential loss of information, but if we're already in ProcSyscall,
// we haven't lost the relevant information. Promote the status and advance.
oldState = ProcRunning
ev.args[1] = uint64(go122.ProcSyscall)
} else if status == go122.ProcSyscallAbandoned && s.status == go122.ProcSyscallAbandoned {
// If we're passing through ProcSyscallAbandoned, then there's no promotion
// to do. We've lost the M that this P is associated with. However it got there,
// it's going to appear as idle in the API, so pass through as idle.
oldState = ProcIdle
ev.args[1] = uint64(go122.ProcSyscallAbandoned)
} else if s.status != status {
return curCtx, false, fmt.Errorf("inconsistent status for proc %d: old %v vs. new %v", pid, s.status, status)
}
s.seq = makeSeq(gen, 0) // Reset seq.
} else {
o.pStates[pid] = &pState{id: pid, status: status, seq: makeSeq(gen, 0)}
if gen == o.initialGen {
oldState = ProcUndetermined
} else {
oldState = ProcNotExist
}
}
ev.extra(version.Go122)[0] = uint64(oldState) // Smuggle in the old state for StateTransition.
// Bind the proc to the new context, if it's running.
newCtx := curCtx
if status == go122.ProcRunning || status == go122.ProcSyscall {
newCtx.P = pid
}
// If we're advancing through ProcSyscallAbandoned *but* oldState is running then we've
// promoted it to ProcSyscall. However, because it's ProcSyscallAbandoned, we know this
// P is about to get stolen and its status very likely isn't being emitted by the same
// thread it was bound to. Since this status is Running -> Running and Running is binding,
// we need to make sure we emit it in the right context: the context to which it is bound.
// Find it, and set our current context to it.
if status == go122.ProcSyscallAbandoned && oldState == ProcRunning {
// N.B. This is slow but it should be fairly rare.
found := false
for mid, ms := range o.mStates {
if ms.p == pid {
curCtx.M = mid
curCtx.P = pid
curCtx.G = ms.g
found = true
}
}
if !found {
return curCtx, false, fmt.Errorf("failed to find sched context for proc %d that's about to be stolen", pid)
}
}
o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
return newCtx, true, nil
}
func (o *ordering) advanceProcStart(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
pid := ProcID(ev.args[0])
seq := makeSeq(gen, ev.args[1])
// Try to advance. We might fail here due to sequencing, because the P hasn't
// had a status emitted, or because we already have a P and we're in a syscall,
// and we haven't observed that it was stolen from us yet.
state, ok := o.pStates[pid]
if !ok || state.status != go122.ProcIdle || !seq.succeeds(state.seq) || curCtx.P != NoProc {
// We can't make an inference as to whether this is bad. We could just be seeing
// a ProcStart on a different M before the proc's state was emitted, or before we
// got to the right point in the trace.
//
// Note that we also don't advance here if we have a P and we're in a syscall.
return curCtx, false, nil
}
// We can advance this P. Check some invariants.
//
// We might have a goroutine if a goroutine is exiting a syscall.
reqs := event.SchedReqs{Thread: event.MustHave, Proc: event.MustNotHave, Goroutine: event.MayHave}
if err := validateCtx(curCtx, reqs); err != nil {
return curCtx, false, err
}
state.status = go122.ProcRunning
state.seq = seq
newCtx := curCtx
newCtx.P = pid
o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
return newCtx, true, nil
}
func (o *ordering) advanceProcStop(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
// We must be able to advance this P.
//
// There are 2 ways a P can stop: ProcStop and ProcSteal. ProcStop is used when the P
// is stopped by the same M that started it, while ProcSteal is used when another M
// steals the P by stopping it from a distance.
//
// Since a P is bound to an M, and we're stopping on the same M we started, it must
// always be possible to advance the current M's P from a ProcStop. This is also why
// ProcStop doesn't need a sequence number.
state, ok := o.pStates[curCtx.P]
if !ok {
return curCtx, false, fmt.Errorf("event %s for proc (%v) that doesn't exist", go122.EventString(ev.typ), curCtx.P)
}
if state.status != go122.ProcRunning && state.status != go122.ProcSyscall {
return curCtx, false, fmt.Errorf("%s event for proc that's not %s or %s", go122.EventString(ev.typ), go122.ProcRunning, go122.ProcSyscall)
}
reqs := event.SchedReqs{Thread: event.MustHave, Proc: event.MustHave, Goroutine: event.MayHave}
if err := validateCtx(curCtx, reqs); err != nil {
return curCtx, false, err
}
state.status = go122.ProcIdle
newCtx := curCtx
newCtx.P = NoProc
o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
return newCtx, true, nil
}
func (o *ordering) advanceProcSteal(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
pid := ProcID(ev.args[0])
seq := makeSeq(gen, ev.args[1])
state, ok := o.pStates[pid]
if !ok || (state.status != go122.ProcSyscall && state.status != go122.ProcSyscallAbandoned) || !seq.succeeds(state.seq) {
// We can't make an inference as to whether this is bad. We could just be seeing
// a ProcStart on a different M before the proc's state was emitted, or before we
// got to the right point in the trace.
return curCtx, false, nil
}
// We can advance this P. Check some invariants.
reqs := event.SchedReqs{Thread: event.MustHave, Proc: event.MayHave, Goroutine: event.MayHave}
if err := validateCtx(curCtx, reqs); err != nil {
return curCtx, false, err
}
// Smuggle in the P state that let us advance so we can surface information to the event.
// Specifically, we need to make sure that the event is interpreted not as a transition of
// ProcRunning -> ProcIdle but ProcIdle -> ProcIdle instead.
//
// ProcRunning is binding, but we may be running with a P on the current M and we can't
// bind another P. This P is about to go ProcIdle anyway.
oldStatus := state.status
ev.extra(version.Go122)[0] = uint64(oldStatus)
// Update the P's status and sequence number.
state.status = go122.ProcIdle
state.seq = seq
// If we've lost information then don't try to do anything with the M.
// It may have moved on and we can't be sure.
if oldStatus == go122.ProcSyscallAbandoned {
o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
return curCtx, true, nil
}
// Validate that the M we're stealing from is what we expect.
mid := ThreadID(ev.args[2]) // The M we're stealing from.
newCtx := curCtx
if mid == curCtx.M {
// We're stealing from ourselves. This behaves like a ProcStop.
if curCtx.P != pid {
return curCtx, false, fmt.Errorf("tried to self-steal proc %d (thread %d), but got proc %d instead", pid, mid, curCtx.P)
}
newCtx.P = NoProc
o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
return newCtx, true, nil
}
// We're stealing from some other M.
mState, ok := o.mStates[mid]
if !ok {
return curCtx, false, fmt.Errorf("stole proc from non-existent thread %d", mid)
}
// Make sure we're actually stealing the right P.
if mState.p != pid {
return curCtx, false, fmt.Errorf("tried to steal proc %d from thread %d, but got proc %d instead", pid, mid, mState.p)
}
// Tell the M it has no P so it can proceed.
//
// This is safe because we know the P was in a syscall and
// the other M must be trying to get out of the syscall.
// GoSyscallEndBlocked cannot advance until the corresponding
// M loses its P.
mState.p = NoProc
o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
return newCtx, true, nil
}
func (o *ordering) advanceGoStatus(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
gid := GoID(ev.args[0])
mid := ThreadID(ev.args[1])
status := go122.GoStatus(ev.args[2])
if int(status) >= len(go122GoStatus2GoState) {
return curCtx, false, fmt.Errorf("invalid status for goroutine %d: %d", gid, status)
}
oldState := go122GoStatus2GoState[status]
if s, ok := o.gStates[gid]; ok {
if s.status != status {
return curCtx, false, fmt.Errorf("inconsistent status for goroutine %d: old %v vs. new %v", gid, s.status, status)
}
s.seq = makeSeq(gen, 0) // Reset seq.
} else if gen == o.initialGen {
// Set the state.
o.gStates[gid] = &gState{id: gid, status: status, seq: makeSeq(gen, 0)}
oldState = GoUndetermined
} else {
return curCtx, false, fmt.Errorf("found goroutine status for new goroutine after the first generation: id=%v status=%v", gid, status)
}
ev.extra(version.Go122)[0] = uint64(oldState) // Smuggle in the old state for StateTransition.
newCtx := curCtx
switch status {
case go122.GoRunning:
// Bind the goroutine to the new context, since it's running.
newCtx.G = gid
case go122.GoSyscall:
if mid == NoThread {
return curCtx, false, fmt.Errorf("found goroutine %d in syscall without a thread", gid)
}
// Is the syscall on this thread? If so, bind it to the context.
// Otherwise, we're talking about a G sitting in a syscall on an M.
// Validate the named M.
if mid == curCtx.M {
if gen != o.initialGen && curCtx.G != gid {
// If this isn't the first generation, we *must* have seen this
// binding occur already. Even if the G was blocked in a syscall
// for multiple generations since trace start, we would have seen
// a previous GoStatus event that bound the goroutine to an M.
return curCtx, false, fmt.Errorf("inconsistent thread for syscalling goroutine %d: thread has goroutine %d", gid, curCtx.G)
}
newCtx.G = gid
break
}
// Now we're talking about a thread and goroutine that have been
// blocked on a syscall for the entire generation. This case must
// not have a P; the runtime makes sure that all Ps are traced at
// the beginning of a generation, which involves taking a P back
// from every thread.
ms, ok := o.mStates[mid]
if ok {
// This M has been seen. That means we must have seen this
// goroutine go into a syscall on this thread at some point.
if ms.g != gid {
// But the G on the M doesn't match. Something's wrong.
return curCtx, false, fmt.Errorf("inconsistent thread for syscalling goroutine %d: thread has goroutine %d", gid, ms.g)
}
// This case is just a Syscall->Syscall event, which needs to
// appear as having the G currently bound to this M.
curCtx.G = ms.g
} else if !ok {
// The M hasn't been seen yet. That means this goroutine
// has just been sitting in a syscall on this M. Create
// a state for it.
o.mStates[mid] = &mState{g: gid, p: NoProc}
// Don't set curCtx.G in this case because this event is the
// binding event (and curCtx represents the "before" state).
}
// Update the current context to the M we're talking about.
curCtx.M = mid
}
o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
return newCtx, true, nil
}
func (o *ordering) advanceGoCreate(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
// Goroutines must be created on a running P, but may or may not be created
// by a running goroutine.
reqs := event.SchedReqs{Thread: event.MustHave, Proc: event.MustHave, Goroutine: event.MayHave}
if err := validateCtx(curCtx, reqs); err != nil {
return curCtx, false, err
}
// If we have a goroutine, it must be running.
if state, ok := o.gStates[curCtx.G]; ok && state.status != go122.GoRunning {
return curCtx, false, fmt.Errorf("%s event for goroutine that's not %s", go122.EventString(ev.typ), GoRunning)
}
// This goroutine created another. Add a state for it.
newgid := GoID(ev.args[0])
if _, ok := o.gStates[newgid]; ok {
return curCtx, false, fmt.Errorf("tried to create goroutine (%v) that already exists", newgid)
}
status := go122.GoRunnable
if ev.typ == go122.EvGoCreateBlocked {
status = go122.GoWaiting
}
o.gStates[newgid] = &gState{id: newgid, status: status, seq: makeSeq(gen, 0)}
o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
return curCtx, true, nil
}
func (o *ordering) advanceGoStopExec(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
// These are goroutine events that all require an active running
// goroutine on some thread. They must *always* be advance-able,
// since running goroutines are bound to their M.
if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
return curCtx, false, err
}
state, ok := o.gStates[curCtx.G]
if !ok {
return curCtx, false, fmt.Errorf("event %s for goroutine (%v) that doesn't exist", go122.EventString(ev.typ), curCtx.G)
}
if state.status != go122.GoRunning {
return curCtx, false, fmt.Errorf("%s event for goroutine that's not %s", go122.EventString(ev.typ), GoRunning)
}
// Handle each case slightly differently; we just group them together
// because they have shared preconditions.
newCtx := curCtx
switch ev.typ {
case go122.EvGoDestroy:
// This goroutine is exiting itself.
delete(o.gStates, curCtx.G)
newCtx.G = NoGoroutine
case go122.EvGoStop:
// Goroutine stopped (yielded). It's runnable but not running on this M.
state.status = go122.GoRunnable
newCtx.G = NoGoroutine
case go122.EvGoBlock:
// Goroutine blocked. It's waiting now and not running on this M.
state.status = go122.GoWaiting
newCtx.G = NoGoroutine
}
o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
return newCtx, true, nil
}
func (o *ordering) advanceGoStart(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
gid := GoID(ev.args[0])
seq := makeSeq(gen, ev.args[1])
state, ok := o.gStates[gid]
if !ok || state.status != go122.GoRunnable || !seq.succeeds(state.seq) {
// We can't make an inference as to whether this is bad. We could just be seeing
// a GoStart on a different M before the goroutine was created, before it had its
// state emitted, or before we got to the right point in the trace yet.
return curCtx, false, nil
}
// We can advance this goroutine. Check some invariants.
reqs := event.SchedReqs{Thread: event.MustHave, Proc: event.MustHave, Goroutine: event.MustNotHave}
if err := validateCtx(curCtx, reqs); err != nil {
return curCtx, false, err
}
state.status = go122.GoRunning
state.seq = seq
newCtx := curCtx
newCtx.G = gid
o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
return newCtx, true, nil
}
func (o *ordering) advanceGoUnblock(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
// N.B. These both reference the goroutine to unblock, not the current goroutine.
gid := GoID(ev.args[0])
seq := makeSeq(gen, ev.args[1])
state, ok := o.gStates[gid]
if !ok || state.status != go122.GoWaiting || !seq.succeeds(state.seq) {
// We can't make an inference as to whether this is bad. We could just be seeing
// a GoUnblock on a different M before the goroutine was created and blocked itself,
// before it had its state emitted, or before we got to the right point in the trace yet.
return curCtx, false, nil
}
state.status = go122.GoRunnable
state.seq = seq
// N.B. No context to validate. Basically anything can unblock
// a goroutine (e.g. sysmon).
o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
return curCtx, true, nil
}
func (o *ordering) advanceGoSwitch(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
// GoSwitch and GoSwitchDestroy represent a trio of events:
// - Unblock of the goroutine to switch to.
// - Block or destroy of the current goroutine.
// - Start executing the next goroutine.
//
// Because it acts like a GoStart for the next goroutine, we can
// only advance it if the sequence numbers line up.
//
// The current goroutine on the thread must be actively running.
if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
return curCtx, false, err
}
curGState, ok := o.gStates[curCtx.G]
if !ok {
return curCtx, false, fmt.Errorf("event %s for goroutine (%v) that doesn't exist", go122.EventString(ev.typ), curCtx.G)
}
if curGState.status != go122.GoRunning {
return curCtx, false, fmt.Errorf("%s event for goroutine that's not %s", go122.EventString(ev.typ), GoRunning)
}
nextg := GoID(ev.args[0])
seq := makeSeq(gen, ev.args[1]) // seq is for nextg, not curCtx.G.
nextGState, ok := o.gStates[nextg]
if !ok || nextGState.status != go122.GoWaiting || !seq.succeeds(nextGState.seq) {
// We can't make an inference as to whether this is bad. We could just be seeing
// a GoSwitch on a different M before the goroutine was created, before it had its
// state emitted, or before we got to the right point in the trace yet.
return curCtx, false, nil
}
o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
// Update the state of the executing goroutine and emit an event for it
// (GoSwitch and GoSwitchDestroy will be interpreted as GoUnblock events
// for nextg).
switch ev.typ {
case go122.EvGoSwitch:
// Goroutine blocked. It's waiting now and not running on this M.
curGState.status = go122.GoWaiting
// Emit a GoBlock event.
// TODO(mknyszek): Emit a reason.
o.queue.push(makeEvent(evt, curCtx, go122.EvGoBlock, ev.time, 0 /* no reason */, 0 /* no stack */))
case go122.EvGoSwitchDestroy:
// This goroutine is exiting itself.
delete(o.gStates, curCtx.G)
// Emit a GoDestroy event.
o.queue.push(makeEvent(evt, curCtx, go122.EvGoDestroy, ev.time))
}
// Update the state of the next goroutine.
nextGState.status = go122.GoRunning
nextGState.seq = seq
newCtx := curCtx
newCtx.G = nextg
// Queue an event for the next goroutine starting to run.
startCtx := curCtx
startCtx.G = NoGoroutine
o.queue.push(makeEvent(evt, startCtx, go122.EvGoStart, ev.time, uint64(nextg), ev.args[1]))
return newCtx, true, nil
}
func (o *ordering) advanceGoSyscallBegin(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
// Entering a syscall requires an active running goroutine with a
// proc on some thread. It is always advancable.
if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
return curCtx, false, err
}
state, ok := o.gStates[curCtx.G]
if !ok {
return curCtx, false, fmt.Errorf("event %s for goroutine (%v) that doesn't exist", go122.EventString(ev.typ), curCtx.G)
}
if state.status != go122.GoRunning {
return curCtx, false, fmt.Errorf("%s event for goroutine that's not %s", go122.EventString(ev.typ), GoRunning)
}
// Goroutine entered a syscall. It's still running on this P and M.
state.status = go122.GoSyscall
pState, ok := o.pStates[curCtx.P]
if !ok {
return curCtx, false, fmt.Errorf("uninitialized proc %d found during %s", curCtx.P, go122.EventString(ev.typ))
}
pState.status = go122.ProcSyscall
// Validate the P sequence number on the event and advance it.
//
// We have a P sequence number for what is supposed to be a goroutine event
// so that we can correctly model P stealing. Without this sequence number here,
// the syscall from which a ProcSteal event is stealing can be ambiguous in the
// face of broken timestamps. See the go122-syscall-steal-proc-ambiguous test for
// more details.
//
// Note that because this sequence number only exists as a tool for disambiguation,
// we can enforce that we have the right sequence number at this point; we don't need
// to back off and see if any other events will advance. This is a running P.
pSeq := makeSeq(gen, ev.args[0])
if !pSeq.succeeds(pState.seq) {
return curCtx, false, fmt.Errorf("failed to advance %s: can't make sequence: %s -> %s", go122.EventString(ev.typ), pState.seq, pSeq)
}
pState.seq = pSeq
o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
return curCtx, true, nil
}
func (o *ordering) advanceGoSyscallEnd(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
// This event is always advance-able because it happens on the same
// thread that EvGoSyscallStart happened, and the goroutine can't leave
// that thread until its done.
if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
return curCtx, false, err
}
state, ok := o.gStates[curCtx.G]
if !ok {
return curCtx, false, fmt.Errorf("event %s for goroutine (%v) that doesn't exist", go122.EventString(ev.typ), curCtx.G)
}
if state.status != go122.GoSyscall {
return curCtx, false, fmt.Errorf("%s event for goroutine that's not %s", go122.EventString(ev.typ), GoRunning)
}
state.status = go122.GoRunning
// Transfer the P back to running from syscall.
pState, ok := o.pStates[curCtx.P]
if !ok {
return curCtx, false, fmt.Errorf("uninitialized proc %d found during %s", curCtx.P, go122.EventString(ev.typ))
}
if pState.status != go122.ProcSyscall {
return curCtx, false, fmt.Errorf("expected proc %d in state %v, but got %v instead", curCtx.P, go122.ProcSyscall, pState.status)
}
pState.status = go122.ProcRunning
o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
return curCtx, true, nil
}
func (o *ordering) advanceGoSyscallEndBlocked(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
// This event becomes advanceable when its P is not in a syscall state
// (lack of a P altogether is also acceptable for advancing).
// The transfer out of ProcSyscall can happen either voluntarily via
// ProcStop or involuntarily via ProcSteal. We may also acquire a new P
// before we get here (after the transfer out) but that's OK: that new
// P won't be in the ProcSyscall state anymore.
//
// Basically: while we have a preemptible P, don't advance, because we
// *know* from the event that we're going to lose it at some point during
// the syscall. We shouldn't advance until that happens.
if curCtx.P != NoProc {
pState, ok := o.pStates[curCtx.P]
if !ok {
return curCtx, false, fmt.Errorf("uninitialized proc %d found during %s", curCtx.P, go122.EventString(ev.typ))
}
if pState.status == go122.ProcSyscall {
return curCtx, false, nil
}
}
// As mentioned above, we may have a P here if we ProcStart
// before this event.
if err := validateCtx(curCtx, event.SchedReqs{Thread: event.MustHave, Proc: event.MayHave, Goroutine: event.MustHave}); err != nil {
return curCtx, false, err
}
state, ok := o.gStates[curCtx.G]
if !ok {
return curCtx, false, fmt.Errorf("event %s for goroutine (%v) that doesn't exist", go122.EventString(ev.typ), curCtx.G)
}
if state.status != go122.GoSyscall {
return curCtx, false, fmt.Errorf("%s event for goroutine that's not %s", go122.EventString(ev.typ), GoRunning)
}
newCtx := curCtx
newCtx.G = NoGoroutine
state.status = go122.GoRunnable
o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
return newCtx, true, nil
}
func (o *ordering) advanceGoCreateSyscall(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
// This event indicates that a goroutine is effectively
// being created out of a cgo callback. Such a goroutine
// is 'created' in the syscall state.
if err := validateCtx(curCtx, event.SchedReqs{Thread: event.MustHave, Proc: event.MayHave, Goroutine: event.MustNotHave}); err != nil {
return curCtx, false, err
}
// This goroutine is effectively being created. Add a state for it.
newgid := GoID(ev.args[0])
if _, ok := o.gStates[newgid]; ok {
return curCtx, false, fmt.Errorf("tried to create goroutine (%v) in syscall that already exists", newgid)
}
o.gStates[newgid] = &gState{id: newgid, status: go122.GoSyscall, seq: makeSeq(gen, 0)}
// Goroutine is executing. Bind it to the context.
newCtx := curCtx
newCtx.G = newgid
o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
return newCtx, true, nil
}
func (o *ordering) advanceGoDestroySyscall(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
// This event indicates that a goroutine created for a
// cgo callback is disappearing, either because the callback
// ending or the C thread that called it is being destroyed.
//
// Also, treat this as if we lost our P too.
// The thread ID may be reused by the platform and we'll get
// really confused if we try to steal the P is this is running
// with later. The new M with the same ID could even try to
// steal back this P from itself!
//
// The runtime is careful to make sure that any GoCreateSyscall
// event will enter the runtime emitting events for reacquiring a P.
//
// Note: we might have a P here. The P might not be released
// eagerly by the runtime, and it might get stolen back later
// (or never again, if the program is going to exit).
if err := validateCtx(curCtx, event.SchedReqs{Thread: event.MustHave, Proc: event.MayHave, Goroutine: event.MustHave}); err != nil {
return curCtx, false, err
}
// Check to make sure the goroutine exists in the right state.
state, ok := o.gStates[curCtx.G]
if !ok {
return curCtx, false, fmt.Errorf("event %s for goroutine (%v) that doesn't exist", go122.EventString(ev.typ), curCtx.G)
}
if state.status != go122.GoSyscall {
return curCtx, false, fmt.Errorf("%s event for goroutine that's not %v", go122.EventString(ev.typ), GoSyscall)
}
// This goroutine is exiting itself.
delete(o.gStates, curCtx.G)
newCtx := curCtx
newCtx.G = NoGoroutine
// If we have a proc, then we're dissociating from it now. See the comment at the top of the case.
if curCtx.P != NoProc {
pState, ok := o.pStates[curCtx.P]
if !ok {
return curCtx, false, fmt.Errorf("found invalid proc %d during %s", curCtx.P, go122.EventString(ev.typ))
}
if pState.status != go122.ProcSyscall {
return curCtx, false, fmt.Errorf("proc %d in unexpected state %s during %s", curCtx.P, pState.status, go122.EventString(ev.typ))
}
// See the go122-create-syscall-reuse-thread-id test case for more details.
pState.status = go122.ProcSyscallAbandoned
newCtx.P = NoProc
// Queue an extra self-ProcSteal event.
extra := makeEvent(evt, curCtx, go122.EvProcSteal, ev.time, uint64(curCtx.P))
extra.base.extra(version.Go122)[0] = uint64(go122.ProcSyscall)
o.queue.push(extra)
}
o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
return newCtx, true, nil
}
func (o *ordering) advanceUserTaskBegin(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
// Handle tasks. Tasks are interesting because:
// - There's no Begin event required to reference a task.
// - End for a particular task ID can appear multiple times.
// As a result, there's very little to validate. The only
// thing we have to be sure of is that a task didn't begin
// after it had already begun. Task IDs are allowed to be
// reused, so we don't care about a Begin after an End.
id := TaskID(ev.args[0])
if _, ok := o.activeTasks[id]; ok {
return curCtx, false, fmt.Errorf("task ID conflict: %d", id)
}
// Get the parent ID, but don't validate it. There's no guarantee
// we actually have information on whether it's active.
parentID := TaskID(ev.args[1])
if parentID == BackgroundTask {
// Note: a value of 0 here actually means no parent, *not* the
// background task. Automatic background task attachment only
// applies to regions.
parentID = NoTask
ev.args[1] = uint64(NoTask)
}
// Validate the name and record it. We'll need to pass it through to
// EvUserTaskEnd.
nameID := stringID(ev.args[2])
name, ok := evt.strings.get(nameID)
if !ok {
return curCtx, false, fmt.Errorf("invalid string ID %v for %v event", nameID, ev.typ)
}
o.activeTasks[id] = taskState{name: name, parentID: parentID}
if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
return curCtx, false, err
}
o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
return curCtx, true, nil
}
func (o *ordering) advanceUserTaskEnd(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
id := TaskID(ev.args[0])
if ts, ok := o.activeTasks[id]; ok {
// Smuggle the task info. This may happen in a different generation,
// which may not have the name in its string table. Add it to the extra
// strings table so we can look it up later.
ev.extra(version.Go122)[0] = uint64(ts.parentID)
ev.extra(version.Go122)[1] = uint64(evt.addExtraString(ts.name))
delete(o.activeTasks, id)
} else {
// Explicitly clear the task info.
ev.extra(version.Go122)[0] = uint64(NoTask)
ev.extra(version.Go122)[1] = uint64(evt.addExtraString(""))
}
if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
return curCtx, false, err
}
o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
return curCtx, true, nil
}
func (o *ordering) advanceUserRegionBegin(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
return curCtx, false, err
}
tid := TaskID(ev.args[0])
nameID := stringID(ev.args[1])
name, ok := evt.strings.get(nameID)
if !ok {
return curCtx, false, fmt.Errorf("invalid string ID %v for %v event", nameID, ev.typ)
}
gState, ok := o.gStates[curCtx.G]
if !ok {
return curCtx, false, fmt.Errorf("encountered EvUserRegionBegin without known state for current goroutine %d", curCtx.G)
}
if err := gState.beginRegion(userRegion{tid, name}); err != nil {
return curCtx, false, err
}
o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
return curCtx, true, nil
}
func (o *ordering) advanceUserRegionEnd(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
return curCtx, false, err
}
tid := TaskID(ev.args[0])
nameID := stringID(ev.args[1])
name, ok := evt.strings.get(nameID)
if !ok {
return curCtx, false, fmt.Errorf("invalid string ID %v for %v event", nameID, ev.typ)
}
gState, ok := o.gStates[curCtx.G]
if !ok {
return curCtx, false, fmt.Errorf("encountered EvUserRegionEnd without known state for current goroutine %d", curCtx.G)
}
if err := gState.endRegion(userRegion{tid, name}); err != nil {
return curCtx, false, err
}
o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
return curCtx, true, nil
}
// Handle the GC mark phase.
//
// We have sequence numbers for both start and end because they
// can happen on completely different threads. We want an explicit
// partial order edge between start and end here, otherwise we're
// relying entirely on timestamps to make sure we don't advance a
// GCEnd for a _different_ GC cycle if timestamps are wildly broken.
func (o *ordering) advanceGCActive(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
seq := ev.args[0]
if gen == o.initialGen {
if o.gcState != gcUndetermined {
return curCtx, false, fmt.Errorf("GCActive in the first generation isn't first GC event")
}
o.gcSeq = seq
o.gcState = gcRunning
o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
return curCtx, true, nil
}
if seq != o.gcSeq+1 {
// This is not the right GC cycle.
return curCtx, false, nil
}
if o.gcState != gcRunning {
return curCtx, false, fmt.Errorf("encountered GCActive while GC was not in progress")
}
o.gcSeq = seq
if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
return curCtx, false, err
}
o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
return curCtx, true, nil
}
func (o *ordering) advanceGCBegin(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
seq := ev.args[0]
if o.gcState == gcUndetermined {
o.gcSeq = seq
o.gcState = gcRunning
o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
return curCtx, true, nil
}
if seq != o.gcSeq+1 {
// This is not the right GC cycle.
return curCtx, false, nil
}
if o.gcState == gcRunning {
return curCtx, false, fmt.Errorf("encountered GCBegin while GC was already in progress")
}
o.gcSeq = seq
o.gcState = gcRunning
if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
return curCtx, false, err
}
o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
return curCtx, true, nil
}
func (o *ordering) advanceGCEnd(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
seq := ev.args[0]
if seq != o.gcSeq+1 {
// This is not the right GC cycle.
return curCtx, false, nil
}
if o.gcState == gcNotRunning {
return curCtx, false, fmt.Errorf("encountered GCEnd when GC was not in progress")
}
if o.gcState == gcUndetermined {
return curCtx, false, fmt.Errorf("encountered GCEnd when GC was in an undetermined state")
}
o.gcSeq = seq
o.gcState = gcNotRunning
if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
return curCtx, false, err
}
o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
return curCtx, true, nil
}
func (o *ordering) advanceAnnotation(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
// Handle simple instantaneous events that require a G.
if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
return curCtx, false, err
}
o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
return curCtx, true, nil
}
func (o *ordering) advanceHeapMetric(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
// Handle allocation metrics, which don't require a G.
if err := validateCtx(curCtx, event.SchedReqs{Thread: event.MustHave, Proc: event.MustHave, Goroutine: event.MayHave}); err != nil {
return curCtx, false, err
}
o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
return curCtx, true, nil
}
func (o *ordering) advanceGCSweepBegin(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
// Handle sweep, which is bound to a P and doesn't require a G.
if err := validateCtx(curCtx, event.SchedReqs{Thread: event.MustHave, Proc: event.MustHave, Goroutine: event.MayHave}); err != nil {
return curCtx, false, err
}
if err := o.pStates[curCtx.P].beginRange(makeRangeType(ev.typ, 0)); err != nil {
return curCtx, false, err
}
o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
return curCtx, true, nil
}
func (o *ordering) advanceGCSweepActive(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
pid := ProcID(ev.args[0])
// N.B. In practice Ps can't block while they're sweeping, so this can only
// ever reference curCtx.P. However, be lenient about this like we are with
// GCMarkAssistActive; there's no reason the runtime couldn't change to block
// in the middle of a sweep.
pState, ok := o.pStates[pid]
if !ok {
return curCtx, false, fmt.Errorf("encountered GCSweepActive for unknown proc %d", pid)
}
if err := pState.activeRange(makeRangeType(ev.typ, 0), gen == o.initialGen); err != nil {
return curCtx, false, err
}
o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
return curCtx, true, nil
}
func (o *ordering) advanceGCSweepEnd(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
if err := validateCtx(curCtx, event.SchedReqs{Thread: event.MustHave, Proc: event.MustHave, Goroutine: event.MayHave}); err != nil {
return curCtx, false, err
}
_, err := o.pStates[curCtx.P].endRange(ev.typ)
if err != nil {
return curCtx, false, err
}
o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
return curCtx, true, nil
}
func (o *ordering) advanceGoRangeBegin(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
// Handle special goroutine-bound event ranges.
if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
return curCtx, false, err
}
desc := stringID(0)
if ev.typ == go122.EvSTWBegin {
desc = stringID(ev.args[0])
}
gState, ok := o.gStates[curCtx.G]
if !ok {
return curCtx, false, fmt.Errorf("encountered event of type %d without known state for current goroutine %d", ev.typ, curCtx.G)
}
if err := gState.beginRange(makeRangeType(ev.typ, desc)); err != nil {
return curCtx, false, err
}
o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
return curCtx, true, nil
}
func (o *ordering) advanceGoRangeActive(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
gid := GoID(ev.args[0])
// N.B. Like GoStatus, this can happen at any time, because it can
// reference a non-running goroutine. Don't check anything about the
// current scheduler context.
gState, ok := o.gStates[gid]
if !ok {
return curCtx, false, fmt.Errorf("uninitialized goroutine %d found during %s", gid, go122.EventString(ev.typ))
}
if err := gState.activeRange(makeRangeType(ev.typ, 0), gen == o.initialGen); err != nil {
return curCtx, false, err
}
o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
return curCtx, true, nil
}
func (o *ordering) advanceGoRangeEnd(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
return curCtx, false, err
}
gState, ok := o.gStates[curCtx.G]
if !ok {
return curCtx, false, fmt.Errorf("encountered event of type %d without known state for current goroutine %d", ev.typ, curCtx.G)
}
desc, err := gState.endRange(ev.typ)
if err != nil {
return curCtx, false, err
}
if ev.typ == go122.EvSTWEnd {
// Smuggle the kind into the event.
// Don't use ev.extra here so we have symmetry with STWBegin.
ev.args[0] = uint64(desc)
}
o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
return curCtx, true, nil
}
// Next returns the next event in the ordering.
func (o *ordering) Next() (Event, bool) {
return o.queue.pop()
}
// schedCtx represents the scheduling resources associated with an event.
type schedCtx struct {
G GoID
P ProcID
M ThreadID
}
// validateCtx ensures that ctx conforms to some reqs, returning an error if
// it doesn't.
func validateCtx(ctx schedCtx, reqs event.SchedReqs) error {
// Check thread requirements.
if reqs.Thread == event.MustHave && ctx.M == NoThread {
return fmt.Errorf("expected a thread but didn't have one")
} else if reqs.Thread == event.MustNotHave && ctx.M != NoThread {
return fmt.Errorf("expected no thread but had one")
}
// Check proc requirements.
if reqs.Proc == event.MustHave && ctx.P == NoProc {
return fmt.Errorf("expected a proc but didn't have one")
} else if reqs.Proc == event.MustNotHave && ctx.P != NoProc {
return fmt.Errorf("expected no proc but had one")
}
// Check goroutine requirements.
if reqs.Goroutine == event.MustHave && ctx.G == NoGoroutine {
return fmt.Errorf("expected a goroutine but didn't have one")
} else if reqs.Goroutine == event.MustNotHave && ctx.G != NoGoroutine {
return fmt.Errorf("expected no goroutine but had one")
}
return nil
}
// gcState is a trinary variable for the current state of the GC.
//
// The third state besides "enabled" and "disabled" is "undetermined."
type gcState uint8
const (
gcUndetermined gcState = iota
gcNotRunning
gcRunning
)
// String returns a human-readable string for the GC state.
func (s gcState) String() string {
switch s {
case gcUndetermined:
return "Undetermined"
case gcNotRunning:
return "NotRunning"
case gcRunning:
return "Running"
}
return "Bad"
}
// userRegion represents a unique user region when attached to some gState.
type userRegion struct {
// name must be a resolved string because the string ID for the same
// string may change across generations, but we care about checking
// the value itself.
taskID TaskID
name string
}
// rangeType is a way to classify special ranges of time.
//
// These typically correspond 1:1 with "Begin" events, but
// they may have an optional subtype that describes the range
// in more detail.
type rangeType struct {
typ event.Type // "Begin" event.
desc stringID // Optional subtype.
}
// makeRangeType constructs a new rangeType.
func makeRangeType(typ event.Type, desc stringID) rangeType {
if styp := go122.Specs()[typ].StartEv; styp != go122.EvNone {
typ = styp
}
return rangeType{typ, desc}
}
// gState is the state of a goroutine at a point in the trace.
type gState struct {
id GoID
status go122.GoStatus
seq seqCounter
// regions are the active user regions for this goroutine.
regions []userRegion
// rangeState is the state of special time ranges bound to this goroutine.
rangeState
}
// beginRegion starts a user region on the goroutine.
func (s *gState) beginRegion(r userRegion) error {
s.regions = append(s.regions, r)
return nil
}
// endRegion ends a user region on the goroutine.
func (s *gState) endRegion(r userRegion) error {
if len(s.regions) == 0 {
// We do not know about regions that began before tracing started.
return nil
}
if next := s.regions[len(s.regions)-1]; next != r {
return fmt.Errorf("misuse of region in goroutine %v: region end %v when the inner-most active region start event is %v", s.id, r, next)
}
s.regions = s.regions[:len(s.regions)-1]
return nil
}
// pState is the state of a proc at a point in the trace.
type pState struct {
id ProcID
status go122.ProcStatus
seq seqCounter
// rangeState is the state of special time ranges bound to this proc.
rangeState
}
// mState is the state of a thread at a point in the trace.
type mState struct {
g GoID // Goroutine bound to this M. (The goroutine's state is Executing.)
p ProcID // Proc bound to this M. (The proc's state is Executing.)
}
// rangeState represents the state of special time ranges.
type rangeState struct {
// inFlight contains the rangeTypes of any ranges bound to a resource.
inFlight []rangeType
}
// beginRange begins a special range in time on the goroutine.
//
// Returns an error if the range is already in progress.
func (s *rangeState) beginRange(typ rangeType) error {
if s.hasRange(typ) {
return fmt.Errorf("discovered event already in-flight for when starting event %v", go122.Specs()[typ.typ].Name)
}
s.inFlight = append(s.inFlight, typ)
return nil
}
// activeRange marks special range in time on the goroutine as active in the
// initial generation, or confirms that it is indeed active in later generations.
func (s *rangeState) activeRange(typ rangeType, isInitialGen bool) error {
if isInitialGen {
if s.hasRange(typ) {
return fmt.Errorf("found named active range already in first gen: %v", typ)
}
s.inFlight = append(s.inFlight, typ)
} else if !s.hasRange(typ) {
return fmt.Errorf("resource is missing active range: %v %v", go122.Specs()[typ.typ].Name, s.inFlight)
}
return nil
}
// hasRange returns true if a special time range on the goroutine as in progress.
func (s *rangeState) hasRange(typ rangeType) bool {
for _, ftyp := range s.inFlight {
if ftyp == typ {
return true
}
}
return false
}
// endsRange ends a special range in time on the goroutine.
//
// This must line up with the start event type of the range the goroutine is currently in.
func (s *rangeState) endRange(typ event.Type) (stringID, error) {
st := go122.Specs()[typ].StartEv
idx := -1
for i, r := range s.inFlight {
if r.typ == st {
idx = i
break
}
}
if idx < 0 {
return 0, fmt.Errorf("tried to end event %v, but not in-flight", go122.Specs()[st].Name)
}
// Swap remove.
desc := s.inFlight[idx].desc
s.inFlight[idx], s.inFlight[len(s.inFlight)-1] = s.inFlight[len(s.inFlight)-1], s.inFlight[idx]
s.inFlight = s.inFlight[:len(s.inFlight)-1]
return desc, nil
}
// seqCounter represents a global sequence counter for a resource.
type seqCounter struct {
gen uint64 // The generation for the local sequence counter seq.
seq uint64 // The sequence number local to the generation.
}
// makeSeq creates a new seqCounter.
func makeSeq(gen, seq uint64) seqCounter {
return seqCounter{gen: gen, seq: seq}
}
// succeeds returns true if a is the immediate successor of b.
func (a seqCounter) succeeds(b seqCounter) bool {
return a.gen == b.gen && a.seq == b.seq+1
}
// String returns a debug string representation of the seqCounter.
func (c seqCounter) String() string {
return fmt.Sprintf("%d (gen=%d)", c.seq, c.gen)
}
func dumpOrdering(order *ordering) string {
var sb strings.Builder
for id, state := range order.gStates {
fmt.Fprintf(&sb, "G %d [status=%s seq=%s]\n", id, state.status, state.seq)
}
fmt.Fprintln(&sb)
for id, state := range order.pStates {
fmt.Fprintf(&sb, "P %d [status=%s seq=%s]\n", id, state.status, state.seq)
}
fmt.Fprintln(&sb)
for id, state := range order.mStates {
fmt.Fprintf(&sb, "M %d [g=%d p=%d]\n", id, state.g, state.p)
}
fmt.Fprintln(&sb)
fmt.Fprintf(&sb, "GC %d %s\n", order.gcSeq, order.gcState)
return sb.String()
}
// taskState represents an active task.
type taskState struct {
// name is the type of the active task.
name string
// parentID is the parent ID of the active task.
parentID TaskID
}
// queue implements a growable ring buffer with a queue API.
type queue[T any] struct {
start, end int
buf []T
}
// push adds a new event to the back of the queue.
func (q *queue[T]) push(value T) {
if q.end-q.start == len(q.buf) {
q.grow()
}
q.buf[q.end%len(q.buf)] = value
q.end++
}
// grow increases the size of the queue.
func (q *queue[T]) grow() {
if len(q.buf) == 0 {
q.buf = make([]T, 2)
return
}
// Create new buf and copy data over.
newBuf := make([]T, len(q.buf)*2)
pivot := q.start % len(q.buf)
first, last := q.buf[pivot:], q.buf[:pivot]
copy(newBuf[:len(first)], first)
copy(newBuf[len(first):], last)
// Update the queue state.
q.start = 0
q.end = len(q.buf)
q.buf = newBuf
}
// pop removes an event from the front of the queue. If the
// queue is empty, it returns an EventBad event.
func (q *queue[T]) pop() (T, bool) {
if q.end-q.start == 0 {
return *new(T), false
}
elem := &q.buf[q.start%len(q.buf)]
value := *elem
*elem = *new(T) // Clear the entry before returning, so we don't hold onto old tables.
q.start++
return value, true
}
// makeEvent creates an Event from the provided information.
//
// It's just a convenience function; it's always OK to construct
// an Event manually if this isn't quite the right way to express
// the contents of the event.
func makeEvent(table *evTable, ctx schedCtx, typ event.Type, time Time, args ...uint64) Event {
ev := Event{
table: table,
ctx: ctx,
base: baseEvent{
typ: typ,
time: time,
},
}
copy(ev.base.args[:], args)
return ev
}