package trace
import (
tracev2 "internal/trace/v2"
"sort"
"time"
)
type Summary struct {
Goroutines map [tracev2 .GoID ]*GoroutineSummary
Tasks map [tracev2 .TaskID ]*UserTaskSummary
}
type GoroutineSummary struct {
ID tracev2 .GoID
Name string
PC uint64
CreationTime tracev2 .Time
StartTime tracev2 .Time
EndTime tracev2 .Time
Regions []*UserRegionSummary
GoroutineExecStats
*goroutineSummary
}
type UserTaskSummary struct {
ID tracev2 .TaskID
Name string
Parent *UserTaskSummary
Children []*UserTaskSummary
Start *tracev2 .Event
End *tracev2 .Event
Logs []*tracev2 .Event
Regions []*UserRegionSummary
Goroutines map [tracev2 .GoID ]*GoroutineSummary
}
func (s *UserTaskSummary ) Complete () bool {
return s .Start != nil && s .End != nil
}
func (s *UserTaskSummary ) Descendents () []*UserTaskSummary {
descendents := []*UserTaskSummary {s }
for _ , child := range s .Children {
descendents = append (descendents , child .Descendents ()...)
}
return descendents
}
type UserRegionSummary struct {
TaskID tracev2 .TaskID
Name string
Start *tracev2 .Event
End *tracev2 .Event
GoroutineExecStats
}
type GoroutineExecStats struct {
ExecTime time .Duration
SchedWaitTime time .Duration
BlockTimeByReason map [string ]time .Duration
SyscallTime time .Duration
SyscallBlockTime time .Duration
TotalTime time .Duration
RangeTime map [string ]time .Duration
}
func (s GoroutineExecStats ) NonOverlappingStats () map [string ]time .Duration {
stats := map [string ]time .Duration {
"Execution time" : s .ExecTime ,
"Sched wait time" : s .SchedWaitTime ,
"Syscall execution time" : s .SyscallTime ,
"Block time (syscall)" : s .SyscallBlockTime ,
"Unknown time" : s .UnknownTime (),
}
for reason , dt := range s .BlockTimeByReason {
stats ["Block time (" +reason +")" ] += dt
}
return stats
}
func (s GoroutineExecStats ) UnknownTime () time .Duration {
sum := s .ExecTime + s .SchedWaitTime + s .SyscallTime +
s .SyscallBlockTime
for _ , dt := range s .BlockTimeByReason {
sum += dt
}
if sum < s .TotalTime {
return s .TotalTime - sum
}
return 0
}
func (s GoroutineExecStats ) sub (v GoroutineExecStats ) (r GoroutineExecStats ) {
r = s .clone ()
r .ExecTime -= v .ExecTime
r .SchedWaitTime -= v .SchedWaitTime
for reason := range s .BlockTimeByReason {
r .BlockTimeByReason [reason ] -= v .BlockTimeByReason [reason ]
}
r .SyscallTime -= v .SyscallTime
r .SyscallBlockTime -= v .SyscallBlockTime
r .TotalTime -= v .TotalTime
for name := range s .RangeTime {
r .RangeTime [name ] -= v .RangeTime [name ]
}
return r
}
func (s GoroutineExecStats ) clone () (r GoroutineExecStats ) {
r = s
r .BlockTimeByReason = make (map [string ]time .Duration )
for reason , dt := range s .BlockTimeByReason {
r .BlockTimeByReason [reason ] = dt
}
r .RangeTime = make (map [string ]time .Duration )
for name , dt := range s .RangeTime {
r .RangeTime [name ] = dt
}
return r
}
func (g *GoroutineSummary ) snapshotStat (lastTs tracev2 .Time ) (ret GoroutineExecStats ) {
ret = g .GoroutineExecStats .clone ()
if g .goroutineSummary == nil {
return ret
}
if g .TotalTime == 0 {
ret .TotalTime = lastTs .Sub (g .CreationTime )
}
if g .lastStartTime != 0 {
ret .ExecTime += lastTs .Sub (g .lastStartTime )
}
if g .lastRunnableTime != 0 {
ret .SchedWaitTime += lastTs .Sub (g .lastRunnableTime )
}
if g .lastBlockTime != 0 {
ret .BlockTimeByReason [g .lastBlockReason ] += lastTs .Sub (g .lastBlockTime )
}
if g .lastSyscallTime != 0 {
ret .SyscallTime += lastTs .Sub (g .lastSyscallTime )
}
if g .lastSyscallBlockTime != 0 {
ret .SchedWaitTime += lastTs .Sub (g .lastSyscallBlockTime )
}
for name , ts := range g .lastRangeTime {
ret .RangeTime [name ] += lastTs .Sub (ts )
}
return ret
}
func (g *GoroutineSummary ) finalize (lastTs tracev2 .Time , trigger *tracev2 .Event ) {
if trigger != nil {
g .EndTime = trigger .Time ()
}
finalStat := g .snapshotStat (lastTs )
g .GoroutineExecStats = finalStat
if !IsSystemGoroutine (g .Name ) {
for _ , s := range g .activeRegions {
s .End = trigger
s .GoroutineExecStats = finalStat .sub (s .GoroutineExecStats )
g .Regions = append (g .Regions , s )
}
}
*(g .goroutineSummary ) = goroutineSummary {}
}
type goroutineSummary struct {
lastStartTime tracev2 .Time
lastRunnableTime tracev2 .Time
lastBlockTime tracev2 .Time
lastBlockReason string
lastSyscallTime tracev2 .Time
lastSyscallBlockTime tracev2 .Time
lastRangeTime map [string ]tracev2 .Time
activeRegions []*UserRegionSummary
}
type Summarizer struct {
gs map [tracev2 .GoID ]*GoroutineSummary
tasks map [tracev2 .TaskID ]*UserTaskSummary
syscallingP map [tracev2 .ProcID ]tracev2 .GoID
syscallingG map [tracev2 .GoID ]tracev2 .ProcID
rangesP map [rangeP ]tracev2 .GoID
lastTs tracev2 .Time
syncTs tracev2 .Time
}
func NewSummarizer () *Summarizer {
return &Summarizer {
gs : make (map [tracev2 .GoID ]*GoroutineSummary ),
tasks : make (map [tracev2 .TaskID ]*UserTaskSummary ),
syscallingP : make (map [tracev2 .ProcID ]tracev2 .GoID ),
syscallingG : make (map [tracev2 .GoID ]tracev2 .ProcID ),
rangesP : make (map [rangeP ]tracev2 .GoID ),
}
}
type rangeP struct {
id tracev2 .ProcID
name string
}
func (s *Summarizer ) Event (ev *tracev2 .Event ) {
if s .syncTs == 0 {
s .syncTs = ev .Time ()
}
s .lastTs = ev .Time ()
switch ev .Kind () {
case tracev2 .EventSync :
s .syncTs = ev .Time ()
case tracev2 .EventStateTransition :
st := ev .StateTransition ()
switch st .Resource .Kind {
case tracev2 .ResourceGoroutine :
id := st .Resource .Goroutine ()
old , new := st .Goroutine ()
if old == new {
break
}
g := s .gs [id ]
switch old {
case tracev2 .GoUndetermined , tracev2 .GoNotExist :
g = &GoroutineSummary {ID : id , goroutineSummary : &goroutineSummary {}}
if old == tracev2 .GoUndetermined {
g .CreationTime = s .syncTs
} else {
g .CreationTime = ev .Time ()
}
g .lastRangeTime = make (map [string ]tracev2 .Time )
g .BlockTimeByReason = make (map [string ]time .Duration )
g .RangeTime = make (map [string ]time .Duration )
if creatorG := s .gs [ev .Goroutine ()]; creatorG != nil && len (creatorG .activeRegions ) > 0 {
regions := creatorG .activeRegions
s := regions [len (regions )-1 ]
g .activeRegions = []*UserRegionSummary {{TaskID : s .TaskID , Start : ev }}
}
s .gs [g .ID ] = g
case tracev2 .GoRunning :
g .ExecTime += ev .Time ().Sub (g .lastStartTime )
g .lastStartTime = 0
case tracev2 .GoWaiting :
if g .lastBlockTime != 0 {
g .BlockTimeByReason [g .lastBlockReason ] += ev .Time ().Sub (g .lastBlockTime )
g .lastBlockTime = 0
}
case tracev2 .GoRunnable :
if g .lastRunnableTime != 0 {
g .SchedWaitTime += ev .Time ().Sub (g .lastRunnableTime )
g .lastRunnableTime = 0
}
case tracev2 .GoSyscall :
if g .lastSyscallTime != 0 {
if g .lastSyscallBlockTime != 0 {
g .SyscallBlockTime += ev .Time ().Sub (g .lastSyscallBlockTime )
g .SyscallTime += g .lastSyscallBlockTime .Sub (g .lastSyscallTime )
} else {
g .SyscallTime += ev .Time ().Sub (g .lastSyscallTime )
}
g .lastSyscallTime = 0
g .lastSyscallBlockTime = 0
delete (s .syscallingP , s .syscallingG [id ])
delete (s .syscallingG , id )
}
}
if g .Name == "" {
stk := st .Stack
if stk != tracev2 .NoStack {
var frame tracev2 .StackFrame
var ok bool
stk .Frames (func (f tracev2 .StackFrame ) bool {
frame = f
ok = true
return true
})
if ok {
g .PC = frame .PC
g .Name = frame .Func
}
}
}
switch new {
case tracev2 .GoRunning :
g .lastStartTime = ev .Time ()
if g .StartTime == 0 {
g .StartTime = ev .Time ()
}
case tracev2 .GoRunnable :
g .lastRunnableTime = ev .Time ()
case tracev2 .GoWaiting :
if st .Reason != "forever" {
g .lastBlockTime = ev .Time ()
g .lastBlockReason = st .Reason
break
}
fallthrough
case tracev2 .GoNotExist :
g .finalize (ev .Time (), ev )
case tracev2 .GoSyscall :
s .syscallingP [ev .Proc ()] = id
s .syscallingG [id ] = ev .Proc ()
g .lastSyscallTime = ev .Time ()
}
case tracev2 .ResourceProc :
id := st .Resource .Proc ()
old , new := st .Proc ()
if old != new && new == tracev2 .ProcIdle {
if goid , ok := s .syscallingP [id ]; ok {
g := s .gs [goid ]
g .lastSyscallBlockTime = ev .Time ()
delete (s .syscallingP , id )
}
}
}
case tracev2 .EventRangeBegin , tracev2 .EventRangeActive :
r := ev .Range ()
var g *GoroutineSummary
switch r .Scope .Kind {
case tracev2 .ResourceGoroutine :
g = s .gs [r .Scope .Goroutine ()]
case tracev2 .ResourceProc :
g = s .gs [ev .Goroutine ()]
if g != nil {
s .rangesP [rangeP {id : r .Scope .Proc (), name : r .Name }] = ev .Goroutine ()
}
}
if g == nil {
break
}
if ev .Kind () == tracev2 .EventRangeActive {
if ts := g .lastRangeTime [r .Name ]; ts != 0 {
g .RangeTime [r .Name ] += s .syncTs .Sub (ts )
}
g .lastRangeTime [r .Name ] = s .syncTs
} else {
g .lastRangeTime [r .Name ] = ev .Time ()
}
case tracev2 .EventRangeEnd :
r := ev .Range ()
var g *GoroutineSummary
switch r .Scope .Kind {
case tracev2 .ResourceGoroutine :
g = s .gs [r .Scope .Goroutine ()]
case tracev2 .ResourceProc :
rp := rangeP {id : r .Scope .Proc (), name : r .Name }
if goid , ok := s .rangesP [rp ]; ok {
if goid == ev .Goroutine () {
g = s .gs [goid ]
}
delete (s .rangesP , rp )
}
}
if g == nil {
break
}
ts := g .lastRangeTime [r .Name ]
if ts == 0 {
break
}
g .RangeTime [r .Name ] += ev .Time ().Sub (ts )
delete (g .lastRangeTime , r .Name )
case tracev2 .EventRegionBegin :
g := s .gs [ev .Goroutine ()]
r := ev .Region ()
region := &UserRegionSummary {
Name : r .Type ,
TaskID : r .Task ,
Start : ev ,
GoroutineExecStats : g .snapshotStat (ev .Time ()),
}
g .activeRegions = append (g .activeRegions , region )
task := s .getOrAddTask (r .Task )
task .Regions = append (task .Regions , region )
task .Goroutines [g .ID ] = g
case tracev2 .EventRegionEnd :
g := s .gs [ev .Goroutine ()]
r := ev .Region ()
var sd *UserRegionSummary
if regionStk := g .activeRegions ; len (regionStk ) > 0 {
n := len (regionStk )
sd = regionStk [n -1 ]
regionStk = regionStk [:n -1 ]
g .activeRegions = regionStk
} else {
sd = &UserRegionSummary {Name : r .Type , TaskID : r .Task }
task := s .getOrAddTask (r .Task )
task .Goroutines [g .ID ] = g
task .Regions = append (task .Regions , sd )
}
sd .GoroutineExecStats = g .snapshotStat (ev .Time ()).sub (sd .GoroutineExecStats )
sd .End = ev
g .Regions = append (g .Regions , sd )
case tracev2 .EventTaskBegin , tracev2 .EventTaskEnd :
t := ev .Task ()
task := s .getOrAddTask (t .ID )
task .Name = t .Type
task .Goroutines [ev .Goroutine ()] = s .gs [ev .Goroutine ()]
if ev .Kind () == tracev2 .EventTaskBegin {
task .Start = ev
} else {
task .End = ev
}
if t .Parent != tracev2 .NoTask && task .Parent == nil {
parent := s .getOrAddTask (t .Parent )
task .Parent = parent
parent .Children = append (parent .Children , task )
}
case tracev2 .EventLog :
log := ev .Log ()
task := s .getOrAddTask (log .Task )
task .Goroutines [ev .Goroutine ()] = s .gs [ev .Goroutine ()]
task .Logs = append (task .Logs , ev )
}
}
func (s *Summarizer ) getOrAddTask (id tracev2 .TaskID ) *UserTaskSummary {
task := s .tasks [id ]
if task == nil {
task = &UserTaskSummary {ID : id , Goroutines : make (map [tracev2 .GoID ]*GoroutineSummary )}
s .tasks [id ] = task
}
return task
}
func (s *Summarizer ) Finalize () *Summary {
for _ , g := range s .gs {
g .finalize (s .lastTs , nil )
sort .Slice (g .Regions , func (i , j int ) bool {
x := g .Regions [i ].Start
y := g .Regions [j ].Start
if x == nil {
return true
}
if y == nil {
return false
}
return x .Time () < y .Time ()
})
g .goroutineSummary = nil
}
return &Summary {
Goroutines : s .gs ,
Tasks : s .tasks ,
}
}
func RelatedGoroutinesV2 (events []tracev2 .Event , goid tracev2 .GoID ) map [tracev2 .GoID ]struct {} {
type unblockEdge struct {
operator tracev2 .GoID
operand tracev2 .GoID
}
var unblockEdges []unblockEdge
for _ , ev := range events {
if ev .Goroutine () == tracev2 .NoGoroutine {
continue
}
if ev .Kind () != tracev2 .EventStateTransition {
continue
}
st := ev .StateTransition ()
if st .Resource .Kind != tracev2 .ResourceGoroutine {
continue
}
id := st .Resource .Goroutine ()
old , new := st .Goroutine ()
if old == new || old != tracev2 .GoWaiting {
continue
}
unblockEdges = append (unblockEdges , unblockEdge {
operator : ev .Goroutine (),
operand : id ,
})
}
gmap := make (map [tracev2 .GoID ]struct {})
gmap [goid ] = struct {}{}
for i := 0 ; i < 2 ; i ++ {
gmap1 := make (map [tracev2 .GoID ]struct {})
for g := range gmap {
gmap1 [g ] = struct {}{}
}
for _ , edge := range unblockEdges {
if _ , ok := gmap [edge .operand ]; ok {
gmap1 [edge .operator ] = struct {}{}
}
}
gmap = gmap1
}
return gmap
}
The pages are generated with Golds v0.6.9-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 @Go100and1 (reachable from the left QR code) to get the latest news of Golds .