// 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" // ThreadID is the runtime-internal M structure's ID. This is unique // for each OS thread. type ThreadID int64 // NoThread indicates that the relevant events don't correspond to any // thread in particular. const NoThread = ThreadID(-1) // ProcID is the runtime-internal G structure's id field. This is unique // for each P. type ProcID int64 // NoProc indicates that the relevant events don't correspond to any // P in particular. const NoProc = ProcID(-1) // GoID is the runtime-internal G structure's goid field. This is unique // for each goroutine. type GoID int64 // NoGoroutine indicates that the relevant events don't correspond to any // goroutine in particular. const NoGoroutine = GoID(-1) // GoState represents the state of a goroutine. // // New GoStates may be added in the future. Users of this type must be robust // to that possibility. type GoState uint8 const ( GoUndetermined GoState = iota // No information is known about the goroutine. GoNotExist // Goroutine does not exist. GoRunnable // Goroutine is runnable but not running. GoRunning // Goroutine is running. GoWaiting // Goroutine is waiting on something to happen. GoSyscall // Goroutine is in a system call. ) // Executing returns true if the state indicates that the goroutine is executing // and bound to its thread. func (s GoState) Executing() bool { return s == GoRunning || s == GoSyscall } // String returns a human-readable representation of a GoState. // // The format of the returned string is for debugging purposes and is subject to change. func (s GoState) String() string { switch s { case GoUndetermined: return "Undetermined" case GoNotExist: return "NotExist" case GoRunnable: return "Runnable" case GoRunning: return "Running" case GoWaiting: return "Waiting" case GoSyscall: return "Syscall" } return "Bad" } // ProcState represents the state of a proc. // // New ProcStates may be added in the future. Users of this type must be robust // to that possibility. type ProcState uint8 const ( ProcUndetermined ProcState = iota // No information is known about the proc. ProcNotExist // Proc does not exist. ProcRunning // Proc is running. ProcIdle // Proc is idle. ) // Executing returns true if the state indicates that the proc is executing // and bound to its thread. func (s ProcState) Executing() bool { return s == ProcRunning } // String returns a human-readable representation of a ProcState. // // The format of the returned string is for debugging purposes and is subject to change. func (s ProcState) String() string { switch s { case ProcUndetermined: return "Undetermined" case ProcNotExist: return "NotExist" case ProcRunning: return "Running" case ProcIdle: return "Idle" } return "Bad" } // ResourceKind indicates a kind of resource that has a state machine. // // New ResourceKinds may be added in the future. Users of this type must be robust // to that possibility. type ResourceKind uint8 const ( ResourceNone ResourceKind = iota // No resource. ResourceGoroutine // Goroutine. ResourceProc // Proc. ResourceThread // Thread. ) // String returns a human-readable representation of a ResourceKind. // // The format of the returned string is for debugging purposes and is subject to change. func (r ResourceKind) String() string { switch r { case ResourceNone: return "None" case ResourceGoroutine: return "Goroutine" case ResourceProc: return "Proc" case ResourceThread: return "Thread" } return "Bad" } // ResourceID represents a generic resource ID. type ResourceID struct { // Kind is the kind of resource this ID is for. Kind ResourceKind id int64 } // MakeResourceID creates a general resource ID from a specific resource's ID. func MakeResourceID[T interface{ GoID | ProcID | ThreadID }](id T) ResourceID { var rd ResourceID var a any = id switch a.(type) { case GoID: rd.Kind = ResourceGoroutine case ProcID: rd.Kind = ResourceProc case ThreadID: rd.Kind = ResourceThread } rd.id = int64(id) return rd } // Goroutine obtains a GoID from the resource ID. // // r.Kind must be ResourceGoroutine or this function will panic. func (r ResourceID) Goroutine() GoID { if r.Kind != ResourceGoroutine { panic(fmt.Sprintf("attempted to get GoID from %s resource ID", r.Kind)) } return GoID(r.id) } // Proc obtains a ProcID from the resource ID. // // r.Kind must be ResourceProc or this function will panic. func (r ResourceID) Proc() ProcID { if r.Kind != ResourceProc { panic(fmt.Sprintf("attempted to get ProcID from %s resource ID", r.Kind)) } return ProcID(r.id) } // Thread obtains a ThreadID from the resource ID. // // r.Kind must be ResourceThread or this function will panic. func (r ResourceID) Thread() ThreadID { if r.Kind != ResourceThread { panic(fmt.Sprintf("attempted to get ThreadID from %s resource ID", r.Kind)) } return ThreadID(r.id) } // String returns a human-readable string representation of the ResourceID. // // This representation is subject to change and is intended primarily for debugging. func (r ResourceID) String() string { if r.Kind == ResourceNone { return r.Kind.String() } return fmt.Sprintf("%s(%d)", r.Kind, r.id) } // StateTransition provides details about a StateTransition event. type StateTransition struct { // Resource is the resource this state transition is for. Resource ResourceID // Reason is a human-readable reason for the state transition. Reason string // Stack is the stack trace of the resource making the state transition. // // This is distinct from the result (Event).Stack because it pertains to // the transitioning resource, not any of the ones executing the event // this StateTransition came from. // // An example of this difference is the NotExist -> Runnable transition for // goroutines, which indicates goroutine creation. In this particular case, // a Stack here would refer to the starting stack of the new goroutine, and // an (Event).Stack would refer to the stack trace of whoever created the // goroutine. Stack Stack // The actual transition data. Stored in a neutral form so that // we don't need fields for every kind of resource. id int64 oldState uint8 newState uint8 } func goStateTransition(id GoID, from, to GoState) StateTransition { return StateTransition{ Resource: ResourceID{Kind: ResourceGoroutine, id: int64(id)}, oldState: uint8(from), newState: uint8(to), } } func procStateTransition(id ProcID, from, to ProcState) StateTransition { return StateTransition{ Resource: ResourceID{Kind: ResourceProc, id: int64(id)}, oldState: uint8(from), newState: uint8(to), } } // Goroutine returns the state transition for a goroutine. // // Transitions to and from states that are Executing are special in that // they change the future execution context. In other words, future events // on the same thread will feature the same goroutine until it stops running. // // Panics if d.Resource.Kind is not ResourceGoroutine. func (d StateTransition) Goroutine() (from, to GoState) { if d.Resource.Kind != ResourceGoroutine { panic("Goroutine called on non-Goroutine state transition") } return GoState(d.oldState), GoState(d.newState) } // Proc returns the state transition for a proc. // // Transitions to and from states that are Executing are special in that // they change the future execution context. In other words, future events // on the same thread will feature the same goroutine until it stops running. // // Panics if d.Resource.Kind is not ResourceProc. func (d StateTransition) Proc() (from, to ProcState) { if d.Resource.Kind != ResourceProc { panic("Proc called on non-Proc state transition") } return ProcState(d.oldState), ProcState(d.newState) }