package traceviewer
import (
"encoding/json"
"fmt"
"internal/trace"
"internal/trace/traceviewer/format"
"io"
"strconv"
"time"
)
type TraceConsumer struct {
ConsumeTimeUnit func (unit string )
ConsumeViewerEvent func (v *format .Event , required bool )
ConsumeViewerFrame func (key string , f format .Frame )
Flush func ()
}
func ViewerDataTraceConsumer (w io .Writer , startIdx , endIdx int64 ) TraceConsumer {
allFrames := make (map [string ]format .Frame )
requiredFrames := make (map [string ]format .Frame )
enc := json .NewEncoder (w )
written := 0
index := int64 (-1 )
io .WriteString (w , "{" )
return TraceConsumer {
ConsumeTimeUnit : func (unit string ) {
io .WriteString (w , `"displayTimeUnit":` )
enc .Encode (unit )
io .WriteString (w , "," )
},
ConsumeViewerEvent : func (v *format .Event , required bool ) {
index ++
if !required && (index < startIdx || index > endIdx ) {
return
}
WalkStackFrames (allFrames , v .Stack , func (id int ) {
s := strconv .Itoa (id )
requiredFrames [s ] = allFrames [s ]
})
WalkStackFrames (allFrames , v .EndStack , func (id int ) {
s := strconv .Itoa (id )
requiredFrames [s ] = allFrames [s ]
})
if written == 0 {
io .WriteString (w , `"traceEvents": [` )
}
if written > 0 {
io .WriteString (w , "," )
}
enc .Encode (v )
written ++
},
ConsumeViewerFrame : func (k string , v format .Frame ) {
allFrames [k ] = v
},
Flush : func () {
io .WriteString (w , `], "stackFrames":` )
enc .Encode (requiredFrames )
io .WriteString (w , `}` )
},
}
}
func SplittingTraceConsumer (max int ) (*splitter , TraceConsumer ) {
type eventSz struct {
Time float64
Sz int
Frames []int
}
var (
data = format .Data {Frames : make (map [string ]format .Frame )}
allFrames = make (map [string ]format .Frame )
sizes []eventSz
cw countingWriter
)
s := new (splitter )
return s , TraceConsumer {
ConsumeTimeUnit : func (unit string ) {
data .TimeUnit = unit
},
ConsumeViewerEvent : func (v *format .Event , required bool ) {
if required {
data .Events = append (data .Events , v )
WalkStackFrames (allFrames , v .Stack , func (id int ) {
s := strconv .Itoa (id )
data .Frames [s ] = allFrames [s ]
})
WalkStackFrames (allFrames , v .EndStack , func (id int ) {
s := strconv .Itoa (id )
data .Frames [s ] = allFrames [s ]
})
return
}
enc := json .NewEncoder (&cw )
enc .Encode (v )
size := eventSz {Time : v .Time , Sz : cw .size + 1 }
WalkStackFrames (allFrames , v .Stack , func (id int ) {
size .Frames = append (size .Frames , id )
})
WalkStackFrames (allFrames , v .EndStack , func (id int ) {
size .Frames = append (size .Frames , id )
})
sizes = append (sizes , size )
cw .size = 0
},
ConsumeViewerFrame : func (k string , v format .Frame ) {
allFrames [k ] = v
},
Flush : func () {
cw .size = 0
enc := json .NewEncoder (&cw )
enc .Encode (data )
requiredSize := cw .size
var (
start = 0
eventsSize = 0
frames = make (map [string ]format .Frame )
framesSize = 0
)
for i , ev := range sizes {
eventsSize += ev .Sz
for _ , id := range ev .Frames {
s := strconv .Itoa (id )
_ , ok := frames [s ]
if ok {
continue
}
f := allFrames [s ]
frames [s ] = f
framesSize += stackFrameEncodedSize (uint (id ), f )
}
total := requiredSize + framesSize + eventsSize
if total < max {
continue
}
startTime := time .Duration (sizes [start ].Time * 1000 )
endTime := time .Duration (ev .Time * 1000 )
s .Ranges = append (s .Ranges , Range {
Name : fmt .Sprintf ("%v-%v" , startTime , endTime ),
Start : start ,
End : i + 1 ,
StartTime : int64 (startTime ),
EndTime : int64 (endTime ),
})
start = i + 1
frames = make (map [string ]format .Frame )
framesSize = 0
eventsSize = 0
}
if len (s .Ranges ) <= 1 {
s .Ranges = nil
return
}
if end := len (sizes ) - 1 ; start < end {
s .Ranges = append (s .Ranges , Range {
Name : fmt .Sprintf ("%v-%v" , time .Duration (sizes [start ].Time *1000 ), time .Duration (sizes [end ].Time *1000 )),
Start : start ,
End : end ,
StartTime : int64 (sizes [start ].Time * 1000 ),
EndTime : int64 (sizes [end ].Time * 1000 ),
})
}
},
}
}
type splitter struct {
Ranges []Range
}
type countingWriter struct {
size int
}
func (cw *countingWriter ) Write (data []byte ) (int , error ) {
cw .size += len (data )
return len (data ), nil
}
func stackFrameEncodedSize(id uint , f format .Frame ) int {
const (
baseSize = len (`"` ) + len (`":{"name":"` ) + len (`"},` )
parentBaseSize = len (`,"parent":` )
)
size := baseSize
size += len (f .Name )
for id > 0 {
size += 1
id /= 10
}
if f .Parent > 0 {
size += parentBaseSize
for f .Parent > 0 {
size += 1
f .Parent /= 10
}
}
return size
}
func WalkStackFrames (allFrames map [string ]format .Frame , id int , fn func (id int )) {
for id != 0 {
f , ok := allFrames [strconv .Itoa (id )]
if !ok {
break
}
fn (id )
id = f .Parent
}
}
type Mode int
const (
ModeGoroutineOriented Mode = 1 << iota
ModeTaskOriented
ModeThreadOriented
)
func NewEmitter (c TraceConsumer , rangeStart , rangeEnd time .Duration ) *Emitter {
c .ConsumeTimeUnit ("ns" )
return &Emitter {
c : c ,
rangeStart : rangeStart ,
rangeEnd : rangeEnd ,
frameTree : frameNode {children : make (map [uint64 ]frameNode )},
resources : make (map [uint64 ]string ),
tasks : make (map [uint64 ]task ),
}
}
type Emitter struct {
c TraceConsumer
rangeStart time .Duration
rangeEnd time .Duration
heapStats, prevHeapStats heapStats
gstates, prevGstates [gStateCount ]int64
threadStats, prevThreadStats [threadStateCount ]int64
gomaxprocs uint64
frameTree frameNode
frameSeq int
arrowSeq uint64
filter func (uint64 ) bool
resourceType string
resources map [uint64 ]string
focusResource uint64
tasks map [uint64 ]task
asyncSliceSeq uint64
}
type task struct {
name string
sortIndex int
}
func (e *Emitter ) Gomaxprocs (v uint64 ) {
if v > e .gomaxprocs {
e .gomaxprocs = v
}
}
func (e *Emitter ) Resource (id uint64 , name string ) {
if e .filter != nil && !e .filter (id ) {
return
}
e .resources [id ] = name
}
func (e *Emitter ) SetResourceType (name string ) {
e .resourceType = name
}
func (e *Emitter ) SetResourceFilter (filter func (uint64 ) bool ) {
e .filter = filter
}
func (e *Emitter ) Task (id uint64 , name string , sortIndex int ) {
e .tasks [id ] = task {name , sortIndex }
}
func (e *Emitter ) Slice (s SliceEvent ) {
if e .filter != nil && !e .filter (s .Resource ) {
return
}
e .slice (s , format .ProcsSection , "" )
}
func (e *Emitter ) TaskSlice (s SliceEvent ) {
e .slice (s , format .TasksSection , pickTaskColor (s .Resource ))
}
func (e *Emitter ) slice (s SliceEvent , sectionID uint64 , cname string ) {
if !e .tsWithinRange (s .Ts ) && !e .tsWithinRange (s .Ts +s .Dur ) {
return
}
e .OptionalEvent (&format .Event {
Name : s .Name ,
Phase : "X" ,
Time : viewerTime (s .Ts ),
Dur : viewerTime (s .Dur ),
PID : sectionID ,
TID : s .Resource ,
Stack : s .Stack ,
EndStack : s .EndStack ,
Arg : s .Arg ,
Cname : cname ,
})
}
type SliceEvent struct {
Name string
Ts time .Duration
Dur time .Duration
Resource uint64
Stack int
EndStack int
Arg any
}
func (e *Emitter ) AsyncSlice (s AsyncSliceEvent ) {
if !e .tsWithinRange (s .Ts ) && !e .tsWithinRange (s .Ts +s .Dur ) {
return
}
if e .filter != nil && !e .filter (s .Resource ) {
return
}
cname := ""
if s .TaskColorIndex != 0 {
cname = pickTaskColor (s .TaskColorIndex )
}
e .asyncSliceSeq ++
e .OptionalEvent (&format .Event {
Category : s .Category ,
Name : s .Name ,
Phase : "b" ,
Time : viewerTime (s .Ts ),
TID : s .Resource ,
ID : e .asyncSliceSeq ,
Scope : s .Scope ,
Stack : s .Stack ,
Cname : cname ,
})
e .OptionalEvent (&format .Event {
Category : s .Category ,
Name : s .Name ,
Phase : "e" ,
Time : viewerTime (s .Ts + s .Dur ),
TID : s .Resource ,
ID : e .asyncSliceSeq ,
Scope : s .Scope ,
Stack : s .EndStack ,
Arg : s .Arg ,
Cname : cname ,
})
}
type AsyncSliceEvent struct {
SliceEvent
Category string
Scope string
TaskColorIndex uint64
}
func (e *Emitter ) Instant (i InstantEvent ) {
if !e .tsWithinRange (i .Ts ) {
return
}
if e .filter != nil && !e .filter (i .Resource ) {
return
}
cname := ""
e .OptionalEvent (&format .Event {
Name : i .Name ,
Category : i .Category ,
Phase : "I" ,
Scope : "t" ,
Time : viewerTime (i .Ts ),
PID : format .ProcsSection ,
TID : i .Resource ,
Stack : i .Stack ,
Cname : cname ,
Arg : i .Arg ,
})
}
type InstantEvent struct {
Ts time .Duration
Name string
Category string
Resource uint64
Stack int
Arg any
}
func (e *Emitter ) Arrow (a ArrowEvent ) {
if e .filter != nil && (!e .filter (a .FromResource ) || !e .filter (a .ToResource )) {
return
}
e .arrow (a , format .ProcsSection )
}
func (e *Emitter ) TaskArrow (a ArrowEvent ) {
e .arrow (a , format .TasksSection )
}
func (e *Emitter ) arrow (a ArrowEvent , sectionID uint64 ) {
if !e .tsWithinRange (a .Start ) || !e .tsWithinRange (a .End ) {
return
}
e .arrowSeq ++
e .OptionalEvent (&format .Event {
Name : a .Name ,
Phase : "s" ,
TID : a .FromResource ,
PID : sectionID ,
ID : e .arrowSeq ,
Time : viewerTime (a .Start ),
Stack : a .FromStack ,
})
e .OptionalEvent (&format .Event {
Name : a .Name ,
Phase : "t" ,
TID : a .ToResource ,
PID : sectionID ,
ID : e .arrowSeq ,
Time : viewerTime (a .End ),
})
}
type ArrowEvent struct {
Name string
Start time .Duration
End time .Duration
FromResource uint64
FromStack int
ToResource uint64
}
func (e *Emitter ) Event (ev *format .Event ) {
e .c .ConsumeViewerEvent (ev , true )
}
func (e *Emitter ) HeapAlloc (ts time .Duration , v uint64 ) {
e .heapStats .heapAlloc = v
e .emitHeapCounters (ts )
}
func (e *Emitter ) Focus (id uint64 ) {
e .focusResource = id
}
func (e *Emitter ) GoroutineTransition (ts time .Duration , from , to GState ) {
e .gstates [from ]--
e .gstates [to ]++
if e .prevGstates == e .gstates {
return
}
if e .tsWithinRange (ts ) {
e .OptionalEvent (&format .Event {
Name : "Goroutines" ,
Phase : "C" ,
Time : viewerTime (ts ),
PID : 1 ,
Arg : &format .GoroutineCountersArg {
Running : uint64 (e .gstates [GRunning ]),
Runnable : uint64 (e .gstates [GRunnable ]),
GCWaiting : uint64 (e .gstates [GWaitingGC ]),
},
})
}
e .prevGstates = e .gstates
}
func (e *Emitter ) IncThreadStateCount (ts time .Duration , state ThreadState , delta int64 ) {
e .threadStats [state ] += delta
if e .prevThreadStats == e .threadStats {
return
}
if e .tsWithinRange (ts ) {
e .OptionalEvent (&format .Event {
Name : "Threads" ,
Phase : "C" ,
Time : viewerTime (ts ),
PID : 1 ,
Arg : &format .ThreadCountersArg {
Running : int64 (e .threadStats [ThreadStateRunning ]),
InSyscall : int64 (e .threadStats [ThreadStateInSyscall ]),
},
})
}
e .prevThreadStats = e .threadStats
}
func (e *Emitter ) HeapGoal (ts time .Duration , v uint64 ) {
const PB = 1 << 50
if v > PB {
v = 0
}
e .heapStats .nextGC = v
e .emitHeapCounters (ts )
}
func (e *Emitter ) emitHeapCounters (ts time .Duration ) {
if e .prevHeapStats == e .heapStats {
return
}
diff := uint64 (0 )
if e .heapStats .nextGC > e .heapStats .heapAlloc {
diff = e .heapStats .nextGC - e .heapStats .heapAlloc
}
if e .tsWithinRange (ts ) {
e .OptionalEvent (&format .Event {
Name : "Heap" ,
Phase : "C" ,
Time : viewerTime (ts ),
PID : 1 ,
Arg : &format .HeapCountersArg {Allocated : e .heapStats .heapAlloc , NextGC : diff },
})
}
e .prevHeapStats = e .heapStats
}
func (e *Emitter ) Err () error {
if e .gstates [GRunnable ] < 0 || e .gstates [GRunning ] < 0 || e .threadStats [ThreadStateInSyscall ] < 0 || e .threadStats [ThreadStateInSyscallRuntime ] < 0 {
return fmt .Errorf (
"runnable=%d running=%d insyscall=%d insyscallRuntime=%d" ,
e .gstates [GRunnable ],
e .gstates [GRunning ],
e .threadStats [ThreadStateInSyscall ],
e .threadStats [ThreadStateInSyscallRuntime ],
)
}
return nil
}
func (e *Emitter ) tsWithinRange (ts time .Duration ) bool {
return e .rangeStart <= ts && ts <= e .rangeEnd
}
func (e *Emitter ) OptionalEvent (ev *format .Event ) {
e .c .ConsumeViewerEvent (ev , false )
}
func (e *Emitter ) Flush () {
e .processMeta (format .StatsSection , "STATS" , 0 )
if len (e .tasks ) != 0 {
e .processMeta (format .TasksSection , "TASKS" , 1 )
}
for id , task := range e .tasks {
e .threadMeta (format .TasksSection , id , task .name , task .sortIndex )
}
e .processMeta (format .ProcsSection , e .resourceType , 2 )
e .threadMeta (format .ProcsSection , trace .GCP , "GC" , -6 )
e .threadMeta (format .ProcsSection , trace .NetpollP , "Network" , -5 )
e .threadMeta (format .ProcsSection , trace .TimerP , "Timers" , -4 )
e .threadMeta (format .ProcsSection , trace .SyscallP , "Syscalls" , -3 )
for id , name := range e .resources {
priority := int (id )
if e .focusResource != 0 && id == e .focusResource {
priority = -2
}
e .threadMeta (format .ProcsSection , id , name , priority )
}
e .c .Flush ()
}
func (e *Emitter ) threadMeta (sectionID , tid uint64 , name string , priority int ) {
e .Event (&format .Event {
Name : "thread_name" ,
Phase : "M" ,
PID : sectionID ,
TID : tid ,
Arg : &format .NameArg {Name : name },
})
e .Event (&format .Event {
Name : "thread_sort_index" ,
Phase : "M" ,
PID : sectionID ,
TID : tid ,
Arg : &format .SortIndexArg {Index : priority },
})
}
func (e *Emitter ) processMeta (sectionID uint64 , name string , priority int ) {
e .Event (&format .Event {
Name : "process_name" ,
Phase : "M" ,
PID : sectionID ,
Arg : &format .NameArg {Name : name },
})
e .Event (&format .Event {
Name : "process_sort_index" ,
Phase : "M" ,
PID : sectionID ,
Arg : &format .SortIndexArg {Index : priority },
})
}
func (e *Emitter ) Stack (stk []*trace .Frame ) int {
return e .buildBranch (e .frameTree , stk )
}
func (e *Emitter ) buildBranch (parent frameNode , stk []*trace .Frame ) int {
if len (stk ) == 0 {
return parent .id
}
last := len (stk ) - 1
frame := stk [last ]
stk = stk [:last ]
node , ok := parent .children [frame .PC ]
if !ok {
e .frameSeq ++
node .id = e .frameSeq
node .children = make (map [uint64 ]frameNode )
parent .children [frame .PC ] = node
e .c .ConsumeViewerFrame (strconv .Itoa (node .id ), format .Frame {Name : fmt .Sprintf ("%v:%v" , frame .Fn , frame .Line ), Parent : parent .id })
}
return e .buildBranch (node , stk )
}
type heapStats struct {
heapAlloc uint64
nextGC uint64
}
func viewerTime(t time .Duration ) float64 {
return float64 (t ) / float64 (time .Microsecond )
}
type GState int
const (
GDead GState = iota
GRunnable
GRunning
GWaiting
GWaitingGC
gStateCount
)
type ThreadState int
const (
ThreadStateInSyscall ThreadState = iota
ThreadStateInSyscallRuntime
ThreadStateRunning
threadStateCount
)
type frameNode struct {
id int
children map [uint64 ]frameNode
}
const (
colorLightMauve = "thread_state_uninterruptible"
colorOrange = "thread_state_iowait"
colorSeafoamGreen = "thread_state_running"
colorVistaBlue = "thread_state_runnable"
colorTan = "thread_state_unknown"
colorIrisBlue = "background_memory_dump"
colorMidnightBlue = "light_memory_dump"
colorDeepMagenta = "detailed_memory_dump"
colorBlue = "vsync_highlight_color"
colorGrey = "generic_work"
colorGreen = "good"
colorDarkGoldenrod = "bad"
colorPeach = "terrible"
colorBlack = "black"
colorLightGrey = "grey"
colorWhite = "white"
colorYellow = "yellow"
colorOlive = "olive"
colorCornflowerBlue = "rail_response"
colorSunsetOrange = "rail_animation"
colorTangerine = "rail_idle"
colorShamrockGreen = "rail_load"
colorGreenishYellow = "startup"
colorDarkGrey = "heap_dump_stack_frame"
colorTawny = "heap_dump_child_node_arrow"
colorLemon = "cq_build_running"
colorLime = "cq_build_passed"
colorPink = "cq_build_failed"
colorSilver = "cq_build_abandoned"
colorManzGreen = "cq_build_attempt_runnig"
colorKellyGreen = "cq_build_attempt_passed"
colorAnotherGrey = "cq_build_attempt_failed"
)
var colorForTask = []string {
colorLightMauve ,
colorOrange ,
colorSeafoamGreen ,
colorVistaBlue ,
colorTan ,
colorMidnightBlue ,
colorIrisBlue ,
colorDeepMagenta ,
colorGreen ,
colorDarkGoldenrod ,
colorPeach ,
colorOlive ,
colorCornflowerBlue ,
colorSunsetOrange ,
colorTangerine ,
colorShamrockGreen ,
colorTawny ,
colorLemon ,
colorLime ,
colorPink ,
colorSilver ,
colorManzGreen ,
colorKellyGreen ,
}
func pickTaskColor(id uint64 ) string {
idx := id % uint64 (len (colorForTask ))
return colorForTask [idx ]
}
The pages are generated with Golds v0.7.0-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 .