Files

264 lines
4.4 KiB
Go

package trace
import (
"math"
"runtime/metrics"
"slices"
"time"
)
type ZoneID uint16
const (
MaxDepth = 32
DefaultFrames = 240
DefaultEvents = 1024
gcMetric = "/gc/cycles/total:gc-cycles"
)
type Event struct {
Zone ZoneID
Depth uint8
Start uint32
Dur uint32
}
type Frame struct {
Index uint64
Start int64
Dur int64
GC uint32
Dropped uint32
World uint64
FrameID uint64
FirstTick uint64
LastTick uint64
Events []Event
}
func (f *Frame) Duration() time.Duration {
return time.Duration(f.Dur)
}
func (f *Frame) Depth() int {
d := 0
for _, e := range f.Events {
d = max(d, int(e.Depth)+1)
}
return d
}
type Options struct {
Frames int
Events int
}
type Tracer struct {
opt Options
names []string
index map[string]ZoneID
frames []Frame
head int
count int
cur *Frame
stack [MaxDepth]int32
depth int
on bool
seq uint64
base time.Time
gc []metrics.Sample
lastGC uint64
}
func New(o Options) *Tracer {
if o.Frames < 2 {
o.Frames = DefaultFrames
}
if o.Events <= 0 {
o.Events = DefaultEvents
}
return &Tracer{opt: o, names: []string{""}, index: map[string]ZoneID{}, base: time.Now()}
}
func (t *Tracer) now() int64 {
return int64(time.Since(t.base))
}
func (t *Tracer) rel(f *Frame) uint32 {
d := t.now() - f.Start
if d > math.MaxUint32 {
return math.MaxUint32
}
return uint32(d)
}
func (t *Tracer) gcCycles() uint64 {
metrics.Read(t.gc)
if t.gc[0].Value.Kind() != metrics.KindUint64 {
return 0
}
return t.gc[0].Value.Uint64()
}
func (t *Tracer) Zone(name string) ZoneID {
if t == nil {
return 0
}
if id, ok := t.index[name]; ok {
return id
}
if len(t.names) > math.MaxUint16 {
return 0
}
id := ZoneID(len(t.names))
t.names = append(t.names, name)
t.index[name] = id
return id
}
func (t *Tracer) Name(z ZoneID) string {
if t == nil || int(z) >= len(t.names) {
return ""
}
return t.names[z]
}
func (t *Tracer) Enabled() bool {
return t != nil && t.on
}
func (t *Tracer) SetEnabled(on bool) {
if t == nil {
return
}
if on == t.on {
return
}
if on && t.frames == nil {
t.frames = make([]Frame, t.opt.Frames)
for i := range t.frames {
t.frames[i].Events = make([]Event, 0, t.opt.Events)
}
t.gc = []metrics.Sample{{Name: gcMetric}}
}
if on {
t.lastGC = t.gcCycles()
}
t.cur, t.depth = nil, 0
t.on = on
}
func (t *Tracer) BeginFrame() {
if t == nil || !t.on {
return
}
if t.cur != nil {
t.EndFrame()
}
t.seq++
f := &t.frames[t.head]
f.Index, f.Start, f.Dur, f.GC, f.Dropped = t.seq, t.now(), 0, 0, 0
f.World, f.FrameID, f.FirstTick, f.LastTick = 0, 0, 0, 0
f.Events = f.Events[:0]
t.cur, t.depth = f, 0
}
func (t *Tracer) Mark(world, frame, first, last uint64) {
if t == nil || t.cur == nil {
return
}
f := t.cur
f.World, f.FrameID, f.FirstTick, f.LastTick = world, frame, first, last
}
func (t *Tracer) EndFrame() {
if t == nil || t.cur == nil {
return
}
for t.depth > 0 {
t.end()
}
f := t.cur
f.Dur = t.now() - f.Start
n := t.gcCycles()
f.GC = uint32(n - t.lastGC)
t.lastGC = n
t.cur = nil
t.head = (t.head + 1) % len(t.frames)
if t.count < len(t.frames)-1 {
t.count++
}
}
type Scope struct {
t *Tracer
frame uint64
}
func (t *Tracer) Begin(z ZoneID) Scope {
if t == nil || t.cur == nil {
return Scope{}
}
f := t.cur
i := int32(-1)
if t.depth < MaxDepth && len(f.Events) < cap(f.Events) {
i = int32(len(f.Events))
f.Events = append(f.Events, Event{Zone: z, Depth: uint8(t.depth), Start: t.rel(f)})
} else {
f.Dropped++
}
if t.depth < MaxDepth {
t.stack[t.depth] = i
}
t.depth++
return Scope{t: t, frame: f.Index}
}
func (s Scope) End() {
t := s.t
if t == nil || t.cur == nil || t.cur.Index != s.frame {
return
}
t.end()
}
func (t *Tracer) end() {
if t.depth == 0 {
return
}
t.depth--
if t.depth >= MaxDepth {
return
}
if i := t.stack[t.depth]; i >= 0 {
f := t.cur
f.Events[i].Dur = t.rel(f) - f.Events[i].Start
}
}
func (t *Tracer) Frames() int {
if t == nil {
return 0
}
return t.count
}
func (t *Tracer) Frame(back int) *Frame {
if t == nil || back < 0 || back >= t.count {
return nil
}
n := len(t.frames)
return &t.frames[(t.head-1-back+2*n)%n]
}
func (t *Tracer) Snapshot(n int) []Frame {
n = min(n, t.Frames())
out := make([]Frame, n)
for k := 0; k < n; k++ {
f := t.Frame(n - 1 - k)
out[k] = *f
out[k].Events = slices.Clone(f.Events)
}
return out
}