package prof import ( "bytes" "strconv" "sync" "sync/atomic" "time" "github.com/olekukonko/tablewriter" "github.com/tal-tech/go-zero/core/logx" "github.com/tal-tech/go-zero/core/threading" ) type ( Slot struct { lifecount int64 lastcount int64 lifecycle int64 lastcycle int64 } ProfileCenter struct { lock sync.RWMutex slots map[string]*Slot } ) const flushInterval = 5 * time.Minute var ( profileCenter = &ProfileCenter{ slots: make(map[string]*Slot), } once sync.Once ) func report(name string, duration time.Duration) { updated := func() bool { profileCenter.lock.RLock() defer profileCenter.lock.RUnlock() slot, ok := profileCenter.slots[name] if ok { atomic.AddInt64(&slot.lifecount, 1) atomic.AddInt64(&slot.lastcount, 1) atomic.AddInt64(&slot.lifecycle, int64(duration)) atomic.AddInt64(&slot.lastcycle, int64(duration)) } return ok }() if !updated { func() { profileCenter.lock.Lock() defer profileCenter.lock.Unlock() profileCenter.slots[name] = &Slot{ lifecount: 1, lastcount: 1, lifecycle: int64(duration), lastcycle: int64(duration), } }() } once.Do(flushRepeatly) } func flushRepeatly() { threading.GoSafe(func() { for { time.Sleep(flushInterval) logx.Stat(generateReport()) } }) } func generateReport() string { var buffer bytes.Buffer buffer.WriteString("Profiling report\n") var data [][]string calcFn := func(total, count int64) string { if count == 0 { return "-" } return (time.Duration(total) / time.Duration(count)).String() } func() { profileCenter.lock.Lock() defer profileCenter.lock.Unlock() for key, slot := range profileCenter.slots { data = append(data, []string{ key, strconv.FormatInt(slot.lifecount, 10), calcFn(slot.lifecycle, slot.lifecount), strconv.FormatInt(slot.lastcount, 10), calcFn(slot.lastcycle, slot.lastcount), }) // reset the data for last cycle slot.lastcount = 0 slot.lastcycle = 0 } }() table := tablewriter.NewWriter(&buffer) table.SetHeader([]string{"QUEUE", "LIFECOUNT", "LIFECYCLE", "LASTCOUNT", "LASTCYCLE"}) table.SetBorder(false) table.AppendBulk(data) table.Render() return buffer.String() }