package trace
import (
"errors"
"fmt"
"io"
"iter"
"math"
"regexp"
"slices"
"strconv"
"strings"
"time"
"internal/trace/tracev2"
"internal/trace/version"
)
type EventKind uint16
const (
EventBad EventKind = iota
EventSync
EventMetric
EventLabel
EventStackSample
EventRangeBegin
EventRangeActive
EventRangeEnd
EventTaskBegin
EventTaskEnd
EventRegionBegin
EventRegionEnd
EventLog
EventStateTransition
EventExperimental
)
func (e EventKind ) String () string {
if int (e ) >= len (eventKindStrings ) {
return eventKindStrings [0 ]
}
return eventKindStrings [e ]
}
var eventKindStrings = [...]string {
EventBad : "Bad" ,
EventSync : "Sync" ,
EventMetric : "Metric" ,
EventLabel : "Label" ,
EventStackSample : "StackSample" ,
EventRangeBegin : "RangeBegin" ,
EventRangeActive : "RangeActive" ,
EventRangeEnd : "RangeEnd" ,
EventTaskBegin : "TaskBegin" ,
EventTaskEnd : "TaskEnd" ,
EventRegionBegin : "RegionBegin" ,
EventRegionEnd : "RegionEnd" ,
EventLog : "Log" ,
EventStateTransition : "StateTransition" ,
EventExperimental : "Experimental" ,
}
const maxTime = Time (math .MaxInt64 )
type Time int64
func (t Time ) Sub (t0 Time ) time .Duration {
return time .Duration (int64 (t ) - int64 (t0 ))
}
type Metric struct {
Name string
Value Value
}
type Label struct {
Label string
Resource ResourceID
}
type Range struct {
Name string
Scope ResourceID
}
type RangeAttribute struct {
Name string
Value Value
}
type TaskID uint64
const (
NoTask = TaskID (^uint64 (0 ))
BackgroundTask = TaskID (0 )
)
type Task struct {
ID TaskID
Parent TaskID
Type string
}
type Region struct {
Task TaskID
Type string
}
type Log struct {
Task TaskID
Category string
Message string
}
type StackSample struct {}
func MakeStack (frames []StackFrame ) Stack {
tbl := &evTable {pcs : make (map [uint64 ]frame )}
tbl .strings .compactify ()
tbl .stacks .compactify ()
return Stack {table : tbl , id : addStack (tbl , frames )}
}
type Stack struct {
table *evTable
id stackID
}
func (s Stack ) Frames () iter .Seq [StackFrame ] {
return func (yield func (StackFrame ) bool ) {
if s .id == 0 {
return
}
stk := s .table .stacks .mustGet (s .id )
for _ , pc := range stk .pcs {
f := s .table .pcs [pc ]
sf := StackFrame {
PC : f .pc ,
Func : s .table .strings .mustGet (f .funcID ),
File : s .table .strings .mustGet (f .fileID ),
Line : f .line ,
}
if !yield (sf ) {
return
}
}
}
}
func (s Stack ) String () string {
var sb strings .Builder
printStack (&sb , "" , s .Frames ())
return sb .String ()
}
func printStack(w io .Writer , prefix string , frames iter .Seq [StackFrame ]) {
for f := range frames {
fmt .Fprintf (w , "%s%s @ 0x%x\n" , prefix , f .Func , f .PC )
fmt .Fprintf (w , "%s\t%s:%d\n" , prefix , f .File , f .Line )
}
}
var NoStack = Stack {}
type StackFrame struct {
PC uint64
Func string
File string
Line uint64
}
type ExperimentalEvent struct {
Name string
Experiment string
Args []string
table *evTable
argValues []uint64
}
func (e ExperimentalEvent ) ArgValue (i int ) Value {
if i < 0 || i >= len (e .Args ) {
panic (fmt .Sprintf ("experimental event argument index %d out of bounds [0, %d)" , i , len (e .Args )))
}
if strings .HasSuffix (e .Args [i ], "string" ) {
s := e .table .strings .mustGet (stringID (e .argValues [i ]))
return StringValue (s )
}
return Uint64Value (e .argValues [i ])
}
type ExperimentalBatch struct {
Thread ThreadID
Data []byte
}
type EventDetails interface {
Metric | Label | Range | StateTransition | Sync | Task | Region | Log | StackSample
}
type EventConfig [T EventDetails ] struct {
Time Time
Kind EventKind
Goroutine GoID
Proc ProcID
Thread ThreadID
Stack Stack
Details T
}
func MakeEvent [T EventDetails ](c EventConfig [T ]) (e Event , err error ) {
e = Event {
table : &evTable {pcs : make (map [uint64 ]frame ), sync : sync {freq : 1 }},
base : baseEvent {time : c .Time },
ctx : schedCtx {G : c .Goroutine , P : c .Proc , M : c .Thread },
}
defer func () {
if err != nil || e .base .typ == evSync {
return
}
spec := tracev2 .Specs ()[e .base .typ ]
if len (spec .StackIDs ) > 0 && c .Stack != NoStack {
e .base .args [spec .StackIDs [0 ]-1 ] = uint64 (addStack (e .table , slices .Collect (c .Stack .Frames ())))
}
e .table .strings .compactify ()
e .table .stacks .compactify ()
}()
var defaultKind EventKind
switch c .Kind {
case defaultKind :
return Event {}, fmt .Errorf ("the Kind field must be provided" )
case EventMetric :
if m , ok := any (c .Details ).(Metric ); ok {
return makeMetricEvent (e , m )
}
case EventLabel :
if l , ok := any (c .Details ).(Label ); ok {
return makeLabelEvent (e , l )
}
case EventRangeBegin , EventRangeActive , EventRangeEnd :
if r , ok := any (c .Details ).(Range ); ok {
return makeRangeEvent (e , c .Kind , r )
}
case EventStateTransition :
if t , ok := any (c .Details ).(StateTransition ); ok {
return makeStateTransitionEvent (e , t )
}
case EventSync :
if s , ok := any (c .Details ).(Sync ); ok {
return makeSyncEvent (e , s )
}
case EventTaskBegin , EventTaskEnd :
if t , ok := any (c .Details ).(Task ); ok {
return makeTaskEvent (e , c .Kind , t )
}
case EventRegionBegin , EventRegionEnd :
if r , ok := any (c .Details ).(Region ); ok {
return makeRegionEvent (e , c .Kind , r )
}
case EventLog :
if l , ok := any (c .Details ).(Log ); ok {
return makeLogEvent (e , l )
}
case EventStackSample :
if _ , ok := any (c .Details ).(StackSample ); ok {
return makeStackSampleEvent (e , c .Stack )
}
}
return Event {}, fmt .Errorf ("the Kind field %s is incompatible with Details type %T" , c .Kind , c .Details )
}
func makeMetricEvent(e Event , m Metric ) (Event , error ) {
if m .Value .Kind () != ValueUint64 {
return Event {}, fmt .Errorf ("metric value must be a uint64, got: %s" , m .Value .String ())
}
switch m .Name {
case "/sched/gomaxprocs:threads" :
e .base .typ = tracev2 .EvProcsChange
case "/memory/classes/heap/objects:bytes" :
e .base .typ = tracev2 .EvHeapAlloc
case "/gc/heap/goal:bytes" :
e .base .typ = tracev2 .EvHeapGoal
default :
return Event {}, fmt .Errorf ("unknown metric name: %s" , m .Name )
}
e .base .args [0 ] = uint64 (m .Value .Uint64 ())
return e , nil
}
func makeLabelEvent(e Event , l Label ) (Event , error ) {
if l .Resource .Kind != ResourceGoroutine {
return Event {}, fmt .Errorf ("resource must be a goroutine: %s" , l .Resource )
}
e .base .typ = tracev2 .EvGoLabel
e .base .args [0 ] = uint64 (e .table .strings .append (l .Label ))
e .ctx .G = l .Resource .Goroutine ()
return e , nil
}
var stwRangeRegexp = regexp .MustCompile (`^stop-the-world \((.*)\)$` )
func makeRangeEvent(e Event , kind EventKind , r Range ) (Event , error ) {
switch r .Name {
case "GC concurrent mark phase" :
if r .Scope .Kind != ResourceNone {
return Event {}, fmt .Errorf ("unexpected scope: %s" , r .Scope )
}
switch kind {
case EventRangeBegin :
e .base .typ = tracev2 .EvGCBegin
case EventRangeActive :
e .base .typ = tracev2 .EvGCActive
case EventRangeEnd :
e .base .typ = tracev2 .EvGCEnd
default :
return Event {}, fmt .Errorf ("unexpected range kind: %s" , kind )
}
case "GC incremental sweep" :
if r .Scope .Kind != ResourceProc {
return Event {}, fmt .Errorf ("unexpected scope: %s" , r .Scope )
}
switch kind {
case EventRangeBegin :
e .base .typ = tracev2 .EvGCSweepBegin
e .ctx .P = r .Scope .Proc ()
case EventRangeActive :
e .base .typ = tracev2 .EvGCSweepActive
e .base .args [0 ] = uint64 (r .Scope .Proc ())
case EventRangeEnd :
e .base .typ = tracev2 .EvGCSweepEnd
e .ctx .P = r .Scope .Proc ()
default :
return Event {}, fmt .Errorf ("unexpected range kind: %s" , kind )
}
case "GC mark assist" :
if r .Scope .Kind != ResourceGoroutine {
return Event {}, fmt .Errorf ("unexpected scope: %s" , r .Scope )
}
switch kind {
case EventRangeBegin :
e .base .typ = tracev2 .EvGCMarkAssistBegin
e .ctx .G = r .Scope .Goroutine ()
case EventRangeActive :
e .base .typ = tracev2 .EvGCMarkAssistActive
e .base .args [0 ] = uint64 (r .Scope .Goroutine ())
case EventRangeEnd :
e .base .typ = tracev2 .EvGCMarkAssistEnd
e .ctx .G = r .Scope .Goroutine ()
default :
return Event {}, fmt .Errorf ("unexpected range kind: %s" , kind )
}
default :
match := stwRangeRegexp .FindStringSubmatch (r .Name )
if len (match ) != 2 {
return Event {}, fmt .Errorf ("unexpected range name: %s" , r .Name )
}
if r .Scope .Kind != ResourceGoroutine {
return Event {}, fmt .Errorf ("unexpected scope: %s" , r .Scope )
}
switch kind {
case EventRangeBegin :
e .base .typ = tracev2 .EvSTWBegin
e .ctx .G = r .Scope .Goroutine ()
case EventRangeEnd :
e .base .typ = tracev2 .EvSTWEnd
e .ctx .G = r .Scope .Goroutine ()
default :
return Event {}, fmt .Errorf ("unexpected range kind: %s" , kind )
}
e .base .args [0 ] = uint64 (e .table .strings .append (match [1 ]))
}
return e , nil
}
func makeStateTransitionEvent(e Event , t StateTransition ) (Event , error ) {
switch t .Resource .Kind {
case ResourceProc :
from , to := ProcState (t .oldState ), ProcState (t .newState )
switch {
case from == ProcIdle && to == ProcIdle :
e .base .typ = tracev2 .EvProcSteal
e .base .args [0 ] = uint64 (t .Resource .Proc ())
e .base .extra (version .Go122 )[0 ] = uint64 (tracev2 .ProcSyscallAbandoned )
case from == ProcIdle && to == ProcRunning :
e .base .typ = tracev2 .EvProcStart
e .base .args [0 ] = uint64 (t .Resource .Proc ())
case from == ProcRunning && to == ProcIdle :
e .base .typ = tracev2 .EvProcStop
if t .Resource .Proc () != e .ctx .P {
e .base .typ = tracev2 .EvProcSteal
e .base .args [0 ] = uint64 (t .Resource .Proc ())
}
default :
e .base .typ = tracev2 .EvProcStatus
e .base .args [0 ] = uint64 (t .Resource .Proc ())
e .base .args [1 ] = uint64 (procState2Tracev2ProcStatus [to ])
e .base .extra (version .Go122 )[0 ] = uint64 (procState2Tracev2ProcStatus [from ])
return e , nil
}
case ResourceGoroutine :
from , to := GoState (t .oldState ), GoState (t .newState )
stack := slices .Collect (t .Stack .Frames ())
goroutine := t .Resource .Goroutine ()
if (from == GoUndetermined || from == to ) && from != GoNotExist {
e .base .typ = tracev2 .EvGoStatus
if len (stack ) > 0 {
e .base .typ = tracev2 .EvGoStatusStack
}
e .base .args [0 ] = uint64 (goroutine )
e .base .args [2 ] = uint64 (from )<<32 | uint64 (goState2Tracev2GoStatus [to ])
} else {
switch from {
case GoNotExist :
switch to {
case GoWaiting :
e .base .typ = tracev2 .EvGoCreateBlocked
e .base .args [0 ] = uint64 (goroutine )
e .base .args [1 ] = uint64 (addStack (e .table , stack ))
case GoRunnable :
e .base .typ = tracev2 .EvGoCreate
e .base .args [0 ] = uint64 (goroutine )
e .base .args [1 ] = uint64 (addStack (e .table , stack ))
case GoSyscall :
e .base .typ = tracev2 .EvGoCreateSyscall
e .base .args [0 ] = uint64 (goroutine )
default :
return Event {}, fmt .Errorf ("unexpected transition: %s -> %s" , from , to )
}
case GoRunnable :
e .base .typ = tracev2 .EvGoStart
e .base .args [0 ] = uint64 (goroutine )
case GoRunning :
switch to {
case GoNotExist :
e .base .typ = tracev2 .EvGoDestroy
e .ctx .G = goroutine
case GoRunnable :
e .base .typ = tracev2 .EvGoStop
e .ctx .G = goroutine
e .base .args [0 ] = uint64 (e .table .strings .append (t .Reason ))
case GoWaiting :
e .base .typ = tracev2 .EvGoBlock
e .ctx .G = goroutine
e .base .args [0 ] = uint64 (e .table .strings .append (t .Reason ))
case GoSyscall :
e .base .typ = tracev2 .EvGoSyscallBegin
e .ctx .G = goroutine
default :
return Event {}, fmt .Errorf ("unexpected transition: %s -> %s" , from , to )
}
case GoSyscall :
switch to {
case GoNotExist :
e .base .typ = tracev2 .EvGoDestroySyscall
e .ctx .G = goroutine
case GoRunning :
e .base .typ = tracev2 .EvGoSyscallEnd
e .ctx .G = goroutine
case GoRunnable :
e .base .typ = tracev2 .EvGoSyscallEndBlocked
e .ctx .G = goroutine
default :
return Event {}, fmt .Errorf ("unexpected transition: %s -> %s" , from , to )
}
case GoWaiting :
switch to {
case GoRunnable :
e .base .typ = tracev2 .EvGoUnblock
e .base .args [0 ] = uint64 (goroutine )
default :
return Event {}, fmt .Errorf ("unexpected transition: %s -> %s" , from , to )
}
default :
return Event {}, fmt .Errorf ("unexpected transition: %s -> %s" , from , to )
}
}
default :
return Event {}, fmt .Errorf ("unsupported state transition resource: %s" , t .Resource )
}
return e , nil
}
func makeSyncEvent(e Event , s Sync ) (Event , error ) {
e .base .typ = evSync
e .base .args [0 ] = uint64 (s .N )
if e .table .expBatches == nil {
e .table .expBatches = make (map [tracev2 .Experiment ][]ExperimentalBatch )
}
for name , batches := range s .ExperimentalBatches {
var found bool
for id , exp := range tracev2 .Experiments () {
if exp == name {
found = true
e .table .expBatches [tracev2 .Experiment (id )] = batches
break
}
}
if !found {
return Event {}, fmt .Errorf ("unknown experiment: %s" , name )
}
}
if s .ClockSnapshot != nil {
e .table .hasClockSnapshot = true
e .table .snapWall = s .ClockSnapshot .Wall
e .table .snapMono = s .ClockSnapshot .Mono
e .table .snapTime = timestamp (s .ClockSnapshot .Trace )
}
return e , nil
}
func makeTaskEvent(e Event , kind EventKind , t Task ) (Event , error ) {
if t .ID == NoTask {
return Event {}, errors .New ("task ID cannot be NoTask" )
}
e .base .args [0 ] = uint64 (t .ID )
switch kind {
case EventTaskBegin :
e .base .typ = tracev2 .EvUserTaskBegin
e .base .args [1 ] = uint64 (t .Parent )
e .base .args [2 ] = uint64 (e .table .strings .append (t .Type ))
case EventTaskEnd :
e .base .typ = tracev2 .EvUserTaskEnd
e .base .extra (version .Go122 )[0 ] = uint64 (t .Parent )
e .base .extra (version .Go122 )[1 ] = uint64 (e .table .addExtraString (t .Type ))
default :
panic ("unexpected task kind" )
}
return e , nil
}
func makeRegionEvent(e Event , kind EventKind , r Region ) (Event , error ) {
e .base .args [0 ] = uint64 (r .Task )
e .base .args [1 ] = uint64 (e .table .strings .append (r .Type ))
switch kind {
case EventRegionBegin :
e .base .typ = tracev2 .EvUserRegionBegin
case EventRegionEnd :
e .base .typ = tracev2 .EvUserRegionEnd
default :
panic ("unexpected region kind" )
}
return e , nil
}
func makeLogEvent(e Event , l Log ) (Event , error ) {
e .base .typ = tracev2 .EvUserLog
e .base .args [0 ] = uint64 (l .Task )
e .base .args [1 ] = uint64 (e .table .strings .append (l .Category ))
e .base .args [2 ] = uint64 (e .table .strings .append (l .Message ))
return e , nil
}
func makeStackSampleEvent(e Event , s Stack ) (Event , error ) {
e .base .typ = tracev2 .EvCPUSample
frames := slices .Collect (s .Frames ())
e .base .args [0 ] = uint64 (addStack (e .table , frames ))
return e , nil
}
func addStack(table *evTable , frames []StackFrame ) stackID {
var pcs []uint64
for _ , f := range frames {
table .pcs [f .PC ] = frame {
pc : f .PC ,
funcID : table .strings .append (f .Func ),
fileID : table .strings .append (f .File ),
line : f .Line ,
}
pcs = append (pcs , f .PC )
}
return table .stacks .append (stack {pcs : pcs })
}
type Event struct {
table *evTable
ctx schedCtx
base baseEvent
}
func (e Event ) Kind () EventKind {
return tracev2Type2Kind [e .base .typ ]
}
func (e Event ) Time () Time {
return e .base .time
}
func (e Event ) Goroutine () GoID {
return e .ctx .G
}
func (e Event ) Proc () ProcID {
return e .ctx .P
}
func (e Event ) Thread () ThreadID {
return e .ctx .M
}
func (e Event ) Stack () Stack {
if e .base .typ == evSync {
return NoStack
}
if e .base .typ == tracev2 .EvCPUSample {
return Stack {table : e .table , id : stackID (e .base .args [0 ])}
}
spec := tracev2 .Specs ()[e .base .typ ]
if len (spec .StackIDs ) == 0 {
return NoStack
}
id := stackID (e .base .args [spec .StackIDs [0 ]-1 ])
if id == 0 {
return NoStack
}
return Stack {table : e .table , id : id }
}
func (e Event ) Metric () Metric {
if e .Kind () != EventMetric {
panic ("Metric called on non-Metric event" )
}
var m Metric
switch e .base .typ {
case tracev2 .EvProcsChange :
m .Name = "/sched/gomaxprocs:threads"
m .Value = Uint64Value (e .base .args [0 ])
case tracev2 .EvHeapAlloc :
m .Name = "/memory/classes/heap/objects:bytes"
m .Value = Uint64Value (e .base .args [0 ])
case tracev2 .EvHeapGoal :
m .Name = "/gc/heap/goal:bytes"
m .Value = Uint64Value (e .base .args [0 ])
default :
panic (fmt .Sprintf ("internal error: unexpected wire-format event type for Metric kind: %d" , e .base .typ ))
}
return m
}
func (e Event ) Label () Label {
if e .Kind () != EventLabel {
panic ("Label called on non-Label event" )
}
if e .base .typ != tracev2 .EvGoLabel {
panic (fmt .Sprintf ("internal error: unexpected wire-format event type for Label kind: %d" , e .base .typ ))
}
return Label {
Label : e .table .strings .mustGet (stringID (e .base .args [0 ])),
Resource : ResourceID {Kind : ResourceGoroutine , id : int64 (e .ctx .G )},
}
}
func (e Event ) Range () Range {
if kind := e .Kind (); kind != EventRangeBegin && kind != EventRangeActive && kind != EventRangeEnd {
panic ("Range called on non-Range event" )
}
var r Range
switch e .base .typ {
case tracev2 .EvSTWBegin , tracev2 .EvSTWEnd :
r .Name = "stop-the-world (" + e .table .strings .mustGet (stringID (e .base .args [0 ])) + ")"
r .Scope = ResourceID {Kind : ResourceGoroutine , id : int64 (e .Goroutine ())}
case tracev2 .EvGCBegin , tracev2 .EvGCActive , tracev2 .EvGCEnd :
r .Name = "GC concurrent mark phase"
r .Scope = ResourceID {Kind : ResourceNone }
case tracev2 .EvGCSweepBegin , tracev2 .EvGCSweepActive , tracev2 .EvGCSweepEnd :
r .Name = "GC incremental sweep"
r .Scope = ResourceID {Kind : ResourceProc }
if e .base .typ == tracev2 .EvGCSweepActive {
r .Scope .id = int64 (e .base .args [0 ])
} else {
r .Scope .id = int64 (e .Proc ())
}
case tracev2 .EvGCMarkAssistBegin , tracev2 .EvGCMarkAssistActive , tracev2 .EvGCMarkAssistEnd :
r .Name = "GC mark assist"
r .Scope = ResourceID {Kind : ResourceGoroutine }
if e .base .typ == tracev2 .EvGCMarkAssistActive {
r .Scope .id = int64 (e .base .args [0 ])
} else {
r .Scope .id = int64 (e .Goroutine ())
}
default :
panic (fmt .Sprintf ("internal error: unexpected wire-event type for Range kind: %d" , e .base .typ ))
}
return r
}
func (e Event ) RangeAttributes () []RangeAttribute {
if e .Kind () != EventRangeEnd {
panic ("Range called on non-Range event" )
}
if e .base .typ != tracev2 .EvGCSweepEnd {
return nil
}
return []RangeAttribute {
{
Name : "bytes swept" ,
Value : Uint64Value (e .base .args [0 ]),
},
{
Name : "bytes reclaimed" ,
Value : Uint64Value (e .base .args [1 ]),
},
}
}
func (e Event ) Task () Task {
if kind := e .Kind (); kind != EventTaskBegin && kind != EventTaskEnd {
panic ("Task called on non-Task event" )
}
parentID := NoTask
var typ string
switch e .base .typ {
case tracev2 .EvUserTaskBegin :
parentID = TaskID (e .base .args [1 ])
typ = e .table .strings .mustGet (stringID (e .base .args [2 ]))
case tracev2 .EvUserTaskEnd :
parentID = TaskID (e .base .extra (version .Go122 )[0 ])
typ = e .table .getExtraString (extraStringID (e .base .extra (version .Go122 )[1 ]))
default :
panic (fmt .Sprintf ("internal error: unexpected wire-format event type for Task kind: %d" , e .base .typ ))
}
return Task {
ID : TaskID (e .base .args [0 ]),
Parent : parentID ,
Type : typ ,
}
}
func (e Event ) Region () Region {
if kind := e .Kind (); kind != EventRegionBegin && kind != EventRegionEnd {
panic ("Region called on non-Region event" )
}
if e .base .typ != tracev2 .EvUserRegionBegin && e .base .typ != tracev2 .EvUserRegionEnd {
panic (fmt .Sprintf ("internal error: unexpected wire-format event type for Region kind: %d" , e .base .typ ))
}
return Region {
Task : TaskID (e .base .args [0 ]),
Type : e .table .strings .mustGet (stringID (e .base .args [1 ])),
}
}
func (e Event ) Log () Log {
if e .Kind () != EventLog {
panic ("Log called on non-Log event" )
}
if e .base .typ != tracev2 .EvUserLog {
panic (fmt .Sprintf ("internal error: unexpected wire-format event type for Log kind: %d" , e .base .typ ))
}
return Log {
Task : TaskID (e .base .args [0 ]),
Category : e .table .strings .mustGet (stringID (e .base .args [1 ])),
Message : e .table .strings .mustGet (stringID (e .base .args [2 ])),
}
}
func (e Event ) StateTransition () StateTransition {
if e .Kind () != EventStateTransition {
panic ("StateTransition called on non-StateTransition event" )
}
var s StateTransition
switch e .base .typ {
case tracev2 .EvProcStart :
s = MakeProcStateTransition (ProcID (e .base .args [0 ]), ProcIdle , ProcRunning )
case tracev2 .EvProcStop :
s = MakeProcStateTransition (e .ctx .P , ProcRunning , ProcIdle )
case tracev2 .EvProcSteal :
beforeState := ProcRunning
if tracev2 .ProcStatus (e .base .extra (version .Go122 )[0 ]) == tracev2 .ProcSyscallAbandoned {
beforeState = ProcIdle
}
s = MakeProcStateTransition (ProcID (e .base .args [0 ]), beforeState , ProcIdle )
case tracev2 .EvProcStatus :
s = MakeProcStateTransition (ProcID (e .base .args [0 ]), ProcState (e .base .extra (version .Go122 )[0 ]), tracev2ProcStatus2ProcState [e .base .args [1 ]])
case tracev2 .EvGoCreate , tracev2 .EvGoCreateBlocked :
status := GoRunnable
if e .base .typ == tracev2 .EvGoCreateBlocked {
status = GoWaiting
}
s = MakeGoStateTransition (GoID (e .base .args [0 ]), GoNotExist , status )
s .Stack = Stack {table : e .table , id : stackID (e .base .args [1 ])}
case tracev2 .EvGoCreateSyscall :
s = MakeGoStateTransition (GoID (e .base .args [0 ]), GoNotExist , GoSyscall )
case tracev2 .EvGoStart :
s = MakeGoStateTransition (GoID (e .base .args [0 ]), GoRunnable , GoRunning )
case tracev2 .EvGoDestroy :
s = MakeGoStateTransition (e .ctx .G , GoRunning , GoNotExist )
case tracev2 .EvGoDestroySyscall :
s = MakeGoStateTransition (e .ctx .G , GoSyscall , GoNotExist )
case tracev2 .EvGoStop :
s = MakeGoStateTransition (e .ctx .G , GoRunning , GoRunnable )
s .Reason = e .table .strings .mustGet (stringID (e .base .args [0 ]))
s .Stack = e .Stack ()
case tracev2 .EvGoBlock :
s = MakeGoStateTransition (e .ctx .G , GoRunning , GoWaiting )
s .Reason = e .table .strings .mustGet (stringID (e .base .args [0 ]))
s .Stack = e .Stack ()
case tracev2 .EvGoUnblock , tracev2 .EvGoSwitch , tracev2 .EvGoSwitchDestroy :
s = MakeGoStateTransition (GoID (e .base .args [0 ]), GoWaiting , GoRunnable )
case tracev2 .EvGoSyscallBegin :
s = MakeGoStateTransition (e .ctx .G , GoRunning , GoSyscall )
s .Stack = e .Stack ()
case tracev2 .EvGoSyscallEnd :
s = MakeGoStateTransition (e .ctx .G , GoSyscall , GoRunning )
case tracev2 .EvGoSyscallEndBlocked :
s = MakeGoStateTransition (e .ctx .G , GoSyscall , GoRunnable )
case tracev2 .EvGoStatus , tracev2 .EvGoStatusStack :
packedStatus := e .base .args [2 ]
from , to := packedStatus >>32 , packedStatus &((1 <<32 )-1 )
s = MakeGoStateTransition (GoID (e .base .args [0 ]), GoState (from ), tracev2GoStatus2GoState [to ])
s .Stack = e .Stack ()
default :
panic (fmt .Sprintf ("internal error: unexpected wire-format event type for StateTransition kind: %d" , e .base .typ ))
}
return s
}
func (e Event ) Sync () Sync {
if e .Kind () != EventSync {
panic ("Sync called on non-Sync event" )
}
s := Sync {N : int (e .base .args [0 ])}
if e .table != nil {
expBatches := make (map [string ][]ExperimentalBatch )
for exp , batches := range e .table .expBatches {
expBatches [tracev2 .Experiments ()[exp ]] = batches
}
s .ExperimentalBatches = expBatches
if e .table .hasClockSnapshot {
s .ClockSnapshot = &ClockSnapshot {
Trace : e .table .freq .mul (e .table .snapTime ),
Wall : e .table .snapWall ,
Mono : e .table .snapMono ,
}
}
}
return s
}
type Sync struct {
N int
ClockSnapshot *ClockSnapshot
ExperimentalBatches map [string ][]ExperimentalBatch
}
type ClockSnapshot struct {
Trace Time
Wall time .Time
Mono uint64
}
func (e Event ) Experimental () ExperimentalEvent {
if e .Kind () != EventExperimental {
panic ("Experimental called on non-Experimental event" )
}
spec := tracev2 .Specs ()[e .base .typ ]
argNames := spec .Args [1 :]
return ExperimentalEvent {
Name : spec .Name ,
Experiment : tracev2 .Experiments ()[spec .Experiment ],
Args : argNames ,
table : e .table ,
argValues : e .base .args [:len (argNames )],
}
}
const evSync = ^tracev2 .EventType (0 )
var tracev2Type2Kind = [...]EventKind {
tracev2 .EvCPUSample : EventStackSample ,
tracev2 .EvProcsChange : EventMetric ,
tracev2 .EvProcStart : EventStateTransition ,
tracev2 .EvProcStop : EventStateTransition ,
tracev2 .EvProcSteal : EventStateTransition ,
tracev2 .EvProcStatus : EventStateTransition ,
tracev2 .EvGoCreate : EventStateTransition ,
tracev2 .EvGoCreateSyscall : EventStateTransition ,
tracev2 .EvGoStart : EventStateTransition ,
tracev2 .EvGoDestroy : EventStateTransition ,
tracev2 .EvGoDestroySyscall : EventStateTransition ,
tracev2 .EvGoStop : EventStateTransition ,
tracev2 .EvGoBlock : EventStateTransition ,
tracev2 .EvGoUnblock : EventStateTransition ,
tracev2 .EvGoSyscallBegin : EventStateTransition ,
tracev2 .EvGoSyscallEnd : EventStateTransition ,
tracev2 .EvGoSyscallEndBlocked : EventStateTransition ,
tracev2 .EvGoStatus : EventStateTransition ,
tracev2 .EvSTWBegin : EventRangeBegin ,
tracev2 .EvSTWEnd : EventRangeEnd ,
tracev2 .EvGCActive : EventRangeActive ,
tracev2 .EvGCBegin : EventRangeBegin ,
tracev2 .EvGCEnd : EventRangeEnd ,
tracev2 .EvGCSweepActive : EventRangeActive ,
tracev2 .EvGCSweepBegin : EventRangeBegin ,
tracev2 .EvGCSweepEnd : EventRangeEnd ,
tracev2 .EvGCMarkAssistActive : EventRangeActive ,
tracev2 .EvGCMarkAssistBegin : EventRangeBegin ,
tracev2 .EvGCMarkAssistEnd : EventRangeEnd ,
tracev2 .EvHeapAlloc : EventMetric ,
tracev2 .EvHeapGoal : EventMetric ,
tracev2 .EvGoLabel : EventLabel ,
tracev2 .EvUserTaskBegin : EventTaskBegin ,
tracev2 .EvUserTaskEnd : EventTaskEnd ,
tracev2 .EvUserRegionBegin : EventRegionBegin ,
tracev2 .EvUserRegionEnd : EventRegionEnd ,
tracev2 .EvUserLog : EventLog ,
tracev2 .EvGoSwitch : EventStateTransition ,
tracev2 .EvGoSwitchDestroy : EventStateTransition ,
tracev2 .EvGoCreateBlocked : EventStateTransition ,
tracev2 .EvGoStatusStack : EventStateTransition ,
tracev2 .EvSpan : EventExperimental ,
tracev2 .EvSpanAlloc : EventExperimental ,
tracev2 .EvSpanFree : EventExperimental ,
tracev2 .EvHeapObject : EventExperimental ,
tracev2 .EvHeapObjectAlloc : EventExperimental ,
tracev2 .EvHeapObjectFree : EventExperimental ,
tracev2 .EvGoroutineStack : EventExperimental ,
tracev2 .EvGoroutineStackAlloc : EventExperimental ,
tracev2 .EvGoroutineStackFree : EventExperimental ,
evSync : EventSync ,
}
var tracev2GoStatus2GoState = [...]GoState {
tracev2 .GoRunnable : GoRunnable ,
tracev2 .GoRunning : GoRunning ,
tracev2 .GoWaiting : GoWaiting ,
tracev2 .GoSyscall : GoSyscall ,
}
var goState2Tracev2GoStatus = [...]tracev2 .GoStatus {
GoRunnable : tracev2 .GoRunnable ,
GoRunning : tracev2 .GoRunning ,
GoWaiting : tracev2 .GoWaiting ,
GoSyscall : tracev2 .GoSyscall ,
}
var tracev2ProcStatus2ProcState = [...]ProcState {
tracev2 .ProcRunning : ProcRunning ,
tracev2 .ProcIdle : ProcIdle ,
tracev2 .ProcSyscall : ProcRunning ,
tracev2 .ProcSyscallAbandoned : ProcIdle ,
}
var procState2Tracev2ProcStatus = [...]tracev2 .ProcStatus {
ProcRunning : tracev2 .ProcRunning ,
ProcIdle : tracev2 .ProcIdle ,
}
func (e Event ) String () string {
var sb strings .Builder
fmt .Fprintf (&sb , "M=%d P=%d G=%d" , e .Thread (), e .Proc (), e .Goroutine ())
fmt .Fprintf (&sb , " %s Time=%d" , e .Kind (), e .Time ())
switch kind := e .Kind (); kind {
case EventMetric :
m := e .Metric ()
v := m .Value .String ()
if m .Value .Kind () == ValueString {
v = strconv .Quote (v )
}
fmt .Fprintf (&sb , " Name=%q Value=%s" , m .Name , m .Value )
case EventLabel :
l := e .Label ()
fmt .Fprintf (&sb , " Label=%q Resource=%s" , l .Label , l .Resource )
case EventRangeBegin , EventRangeActive , EventRangeEnd :
r := e .Range ()
fmt .Fprintf (&sb , " Name=%q Scope=%s" , r .Name , r .Scope )
if kind == EventRangeEnd {
fmt .Fprintf (&sb , " Attributes=[" )
for i , attr := range e .RangeAttributes () {
if i != 0 {
fmt .Fprintf (&sb , " " )
}
fmt .Fprintf (&sb , "%q=%s" , attr .Name , attr .Value )
}
fmt .Fprintf (&sb , "]" )
}
case EventTaskBegin , EventTaskEnd :
t := e .Task ()
fmt .Fprintf (&sb , " ID=%d Parent=%d Type=%q" , t .ID , t .Parent , t .Type )
case EventRegionBegin , EventRegionEnd :
r := e .Region ()
fmt .Fprintf (&sb , " Task=%d Type=%q" , r .Task , r .Type )
case EventLog :
l := e .Log ()
fmt .Fprintf (&sb , " Task=%d Category=%q Message=%q" , l .Task , l .Category , l .Message )
case EventStateTransition :
s := e .StateTransition ()
switch s .Resource .Kind {
case ResourceGoroutine :
id := s .Resource .Goroutine ()
old , new := s .Goroutine ()
fmt .Fprintf (&sb , " GoID=%d %s->%s" , id , old , new )
case ResourceProc :
id := s .Resource .Proc ()
old , new := s .Proc ()
fmt .Fprintf (&sb , " ProcID=%d %s->%s" , id , old , new )
}
fmt .Fprintf (&sb , " Reason=%q" , s .Reason )
if s .Stack != NoStack {
fmt .Fprintln (&sb )
fmt .Fprintln (&sb , "TransitionStack=" )
printStack (&sb , "\t" , s .Stack .Frames ())
}
case EventExperimental :
r := e .Experimental ()
fmt .Fprintf (&sb , " Name=%s Args=[" , r .Name )
for i , arg := range r .Args {
if i != 0 {
fmt .Fprintf (&sb , ", " )
}
fmt .Fprintf (&sb , "%s=%s" , arg , r .ArgValue (i ).String ())
}
fmt .Fprintf (&sb , "]" )
case EventSync :
s := e .Sync ()
fmt .Fprintf (&sb , " N=%d" , s .N )
if s .ClockSnapshot != nil {
fmt .Fprintf (&sb , " Trace=%d Mono=%d Wall=%s" ,
s .ClockSnapshot .Trace ,
s .ClockSnapshot .Mono ,
s .ClockSnapshot .Wall .Format (time .RFC3339Nano ),
)
}
}
if stk := e .Stack (); stk != NoStack {
fmt .Fprintln (&sb )
fmt .Fprintln (&sb , "Stack=" )
printStack (&sb , "\t" , stk .Frames ())
}
return sb .String ()
}
func (e Event ) validateTableIDs () error {
if e .base .typ == evSync {
return nil
}
spec := tracev2 .Specs ()[e .base .typ ]
for _ , i := range spec .StackIDs {
id := stackID (e .base .args [i -1 ])
_ , ok := e .table .stacks .get (id )
if !ok {
return fmt .Errorf ("found invalid stack ID %d for event %s" , id , spec .Name )
}
}
for _ , i := range spec .StringIDs {
id := stringID (e .base .args [i -1 ])
_ , ok := e .table .strings .get (id )
if !ok {
return fmt .Errorf ("found invalid string ID %d for event %s" , id , spec .Name )
}
}
return nil
}
func syncEvent(table *evTable , ts Time , n int ) Event {
ev := Event {
table : table ,
ctx : schedCtx {
G : NoGoroutine ,
P : NoProc ,
M : NoThread ,
},
base : baseEvent {
typ : evSync ,
time : ts ,
},
}
ev .base .args [0 ] = uint64 (n )
return ev
}
The pages are generated with Golds v0.8.3-preview . (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 .