mirror of
https://github.com/status-im/simulation.git
synced 2025-02-23 20:38:07 +00:00
197 lines
4.1 KiB
Go
197 lines
4.1 KiB
Go
package stats
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"math"
|
|
"strconv"
|
|
"sync"
|
|
)
|
|
|
|
// Histogram implements simple histogram for node counters.
|
|
type Histogram struct {
|
|
ranges []int
|
|
counts []int
|
|
|
|
totalCount int
|
|
|
|
TotalDataPoint int
|
|
MinDataPoint int
|
|
MaxDataPoint int
|
|
|
|
m sync.Mutex
|
|
}
|
|
|
|
// NewHistogram creates a new, ready to use Histogram. The numBins
|
|
// must be >= 2. The binFirst is the width of the first bin. The
|
|
// binGrowthFactor must be > 1.0 or 0.0.
|
|
//
|
|
// A special case of binGrowthFactor of 0.0 means the the allocated
|
|
// bins will have constant, non-growing size or "width".
|
|
func NewHistogram(
|
|
numBins int,
|
|
binFirst int,
|
|
binGrowthFactor float64) *Histogram {
|
|
gh := &Histogram{
|
|
ranges: make([]int, numBins),
|
|
counts: make([]int, numBins),
|
|
totalCount: 0,
|
|
MinDataPoint: math.MaxInt64,
|
|
MaxDataPoint: 0,
|
|
}
|
|
|
|
gh.ranges[0] = 0
|
|
gh.ranges[1] = binFirst
|
|
|
|
for i := 2; i < len(gh.ranges); i++ {
|
|
if binGrowthFactor == 0.0 {
|
|
gh.ranges[i] = gh.ranges[i-1] + binFirst
|
|
} else {
|
|
gh.ranges[i] =
|
|
int(math.Ceil(binGrowthFactor * float64(gh.ranges[i-1])))
|
|
}
|
|
}
|
|
|
|
return gh
|
|
}
|
|
|
|
// Add increases the count in the bin for the given dataPoint.
|
|
func (gh *Histogram) Add(dataPoint int, count int) {
|
|
gh.m.Lock()
|
|
|
|
idx := search(gh.ranges, dataPoint)
|
|
if idx >= 0 {
|
|
gh.counts[idx] += count
|
|
gh.totalCount += count
|
|
|
|
gh.TotalDataPoint += dataPoint
|
|
if gh.MinDataPoint > dataPoint {
|
|
gh.MinDataPoint = dataPoint
|
|
}
|
|
if gh.MaxDataPoint < dataPoint {
|
|
gh.MaxDataPoint = dataPoint
|
|
}
|
|
}
|
|
|
|
gh.m.Unlock()
|
|
}
|
|
|
|
// Finds the last arr index where the arr entry <= dataPoint.
|
|
func search(arr []int, dataPoint int) int {
|
|
i, j := 0, len(arr)
|
|
|
|
for i < j {
|
|
h := i + (j-i)/2 // Avoids h overflow, where i <= h < j.
|
|
if dataPoint >= arr[h] {
|
|
i = h + 1
|
|
} else {
|
|
j = h
|
|
}
|
|
}
|
|
|
|
return i - 1
|
|
}
|
|
|
|
// AddAll adds all the counts from the src histogram into this
|
|
// histogram. The src and this histogram must either have the same
|
|
// exact creation parameters.
|
|
func (gh *Histogram) AddAll(src *Histogram) {
|
|
src.m.Lock()
|
|
gh.m.Lock()
|
|
|
|
for i := 0; i < len(src.counts); i++ {
|
|
gh.counts[i] += src.counts[i]
|
|
}
|
|
gh.totalCount += src.totalCount
|
|
|
|
gh.TotalDataPoint += src.TotalDataPoint
|
|
if gh.MinDataPoint > src.MinDataPoint {
|
|
gh.MinDataPoint = src.MinDataPoint
|
|
}
|
|
if gh.MaxDataPoint < src.MaxDataPoint {
|
|
gh.MaxDataPoint = src.MaxDataPoint
|
|
}
|
|
|
|
gh.m.Unlock()
|
|
src.m.Unlock()
|
|
}
|
|
|
|
// Graph emits an ascii graph to the optional out buffer, allocating a
|
|
// out buffer if none was supplied. Returns the out buffer. Each
|
|
// line emitted may have an optional prefix.
|
|
//
|
|
// For example:
|
|
// 0+ 10=2 10.00% ********
|
|
// 10+ 10=1 10.00% ****
|
|
// 20+ 10=3 10.00% ************
|
|
func (gh *Histogram) EmitGraph(prefix []byte,
|
|
out *bytes.Buffer) *bytes.Buffer {
|
|
gh.m.Lock()
|
|
|
|
ranges := gh.ranges
|
|
rangesN := len(ranges)
|
|
counts := gh.counts
|
|
countsN := len(counts)
|
|
|
|
if out == nil {
|
|
out = bytes.NewBuffer(make([]byte, 0, 80*countsN))
|
|
}
|
|
|
|
var maxCount int
|
|
for _, c := range counts {
|
|
if maxCount < c {
|
|
maxCount = c
|
|
}
|
|
}
|
|
maxCountF := float64(maxCount)
|
|
totCountF := float64(gh.totalCount)
|
|
|
|
widthRange := len(strconv.Itoa(int(ranges[rangesN-1])))
|
|
widthWidth := len(strconv.Itoa(int(ranges[rangesN-1] - ranges[rangesN-2])))
|
|
widthCount := len(strconv.Itoa(int(maxCount)))
|
|
|
|
// Each line looks like: "[prefix]START+WIDTH=COUNT PCT% BAR\n"
|
|
f := fmt.Sprintf("%%%dd+%%%dd=%%%dd%% 7.2f%%%%",
|
|
widthRange, widthWidth, widthCount)
|
|
|
|
var runCount int // Running total while emitting lines.
|
|
|
|
barLen := float64(len(bar))
|
|
|
|
for i, c := range counts {
|
|
if prefix != nil {
|
|
out.Write(prefix)
|
|
}
|
|
|
|
var width int
|
|
if i < countsN-1 {
|
|
width = int(ranges[i+1] - ranges[i])
|
|
}
|
|
|
|
runCount += c
|
|
fmt.Fprintf(out, f, ranges[i], width, c,
|
|
100.0*(float64(runCount)/totCountF))
|
|
|
|
if c > 0 {
|
|
out.Write([]byte(" "))
|
|
barWant := int(math.Floor(barLen * (float64(c) / maxCountF)))
|
|
out.Write(bar[0:barWant])
|
|
}
|
|
|
|
out.Write([]byte("\n"))
|
|
}
|
|
|
|
gh.m.Unlock()
|
|
|
|
return out
|
|
}
|
|
|
|
var bar = []byte("******************************")
|
|
|
|
// CallSync invokes the callback func while the histogram is locked.
|
|
func (gh *Histogram) CallSync(f func()) {
|
|
gh.m.Lock()
|
|
f()
|
|
gh.m.Unlock()
|
|
}
|