featurebase/debugstats/stats.go
2023-03-11 21:19:05 -06:00

128 lines
2.3 KiB
Go

// Copyright 2022 Molecula Corp. (DBA FeatureBase).
// SPDX-License-Identifier: Apache-2.0
package debugstats
import (
"fmt"
"math"
"runtime"
"sort"
"sync"
"time"
)
type CallStats struct {
// protect elap
mu sync.Mutex
// track how much time each call took.
elap map[string]*elapsed
}
type elapsed struct {
dur []float64
}
func NewCallStats() *CallStats {
w := &CallStats{}
w.Reset()
return w
}
func (w *CallStats) Reset() {
w.mu.Lock()
defer w.mu.Unlock()
w.elap = make(map[string]*elapsed)
}
type LineSorter struct {
Line string
Tot float64
}
type SortByTot []*LineSorter
func (p SortByTot) Len() int {
return len(p)
}
func (p SortByTot) Less(i, j int) bool {
return p[i].Tot < p[j].Tot
}
func (p SortByTot) Swap(i, j int) {
p[i], p[j] = p[j], p[i]
}
func (c *CallStats) Report(title string) (r string) {
r = fmt.Sprintf("CallStats: (%v)\n", title)
c.mu.Lock()
defer c.mu.Unlock()
lines := make([]*LineSorter, 0, len(c.elap))
for id, elap := range c.elap {
slc := elap.dur
n := len(slc)
if n == 0 {
continue
}
mean, sd, totaltm := computeMeanSd(slc)
if n == 1 {
sd = 0
mean = slc[0]
totaltm = slc[0]
}
line := fmt.Sprintf(" %20v N=%8v avg/op: %12v sd: %12v total: %12v\n", id, n, time.Duration(mean), time.Duration(sd), time.Duration(totaltm))
lines = append(lines, &LineSorter{Line: line, Tot: totaltm})
}
sort.Sort(SortByTot(lines))
for i := range lines {
r += lines[i].Line
}
if false {
var m1 runtime.MemStats
runtime.ReadMemStats(&m1)
r += fmt.Sprintf("\n m1.TotalAlloc = %v\n", m1.TotalAlloc)
}
return
}
var NaN = math.NaN()
func computeMeanSd(slc []float64) (mean, sd, tot float64) {
if len(slc) < 2 {
return NaN, NaN, NaN
}
for _, v := range slc {
tot += v
}
n := float64(len(slc))
mean = tot / n
variance := 0.0
for _, v := range slc {
tmp := (v - mean)
variance += tmp * tmp
}
variance = variance / n // biased, but we don't care b/c we can have very small n
sd = math.Sqrt(variance)
if sd < 1e-8 {
// sd is super close to zero, NaN out the z-score rather than +/- Inf
sd = NaN
}
return
}
func (c *CallStats) Add(k string, dur time.Duration) {
if c == nil {
return
}
c.mu.Lock()
defer c.mu.Unlock()
e, ok := c.elap[k]
if !ok {
e = &elapsed{}
c.elap[k] = e
}
e.dur = append(e.dur, float64(dur))
}