| package stats |
| |
| import ( |
| "bytes" |
| "fmt" |
| "io" |
| "math" |
| "time" |
| ) |
| |
| // Stats is a simple helper for gathering additional statistics like histogram |
| // during benchmarks. This is not thread safe. |
| type Stats struct { |
| numBuckets int |
| unit time.Duration |
| min, max int64 |
| histogram *Histogram |
| |
| durations durationSlice |
| dirty bool |
| } |
| |
| type durationSlice []time.Duration |
| |
| // NewStats creates a new Stats instance. If numBuckets is not positive, |
| // the default value (16) will be used. |
| func NewStats(numBuckets int) *Stats { |
| if numBuckets <= 0 { |
| numBuckets = 16 |
| } |
| return &Stats{ |
| // Use one more bucket for the last unbounded bucket. |
| numBuckets: numBuckets + 1, |
| durations: make(durationSlice, 0, 100000), |
| } |
| } |
| |
| // Add adds an elapsed time per operation to the stats. |
| func (stats *Stats) Add(d time.Duration) { |
| stats.durations = append(stats.durations, d) |
| stats.dirty = true |
| } |
| |
| // Clear resets the stats, removing all values. |
| func (stats *Stats) Clear() { |
| stats.durations = stats.durations[:0] |
| stats.histogram = nil |
| stats.dirty = false |
| } |
| |
| // maybeUpdate updates internal stat data if there was any newly added |
| // stats since this was updated. |
| func (stats *Stats) maybeUpdate() { |
| if !stats.dirty { |
| return |
| } |
| |
| stats.min = math.MaxInt64 |
| stats.max = 0 |
| for _, d := range stats.durations { |
| if stats.min > int64(d) { |
| stats.min = int64(d) |
| } |
| if stats.max < int64(d) { |
| stats.max = int64(d) |
| } |
| } |
| |
| // Use the largest unit that can represent the minimum time duration. |
| stats.unit = time.Nanosecond |
| for _, u := range []time.Duration{time.Microsecond, time.Millisecond, time.Second} { |
| if stats.min <= int64(u) { |
| break |
| } |
| stats.unit = u |
| } |
| |
| // Adjust the min/max according to the new unit. |
| stats.min /= int64(stats.unit) |
| stats.max /= int64(stats.unit) |
| numBuckets := stats.numBuckets |
| if n := int(stats.max - stats.min + 1); n < numBuckets { |
| numBuckets = n |
| } |
| stats.histogram = NewHistogram(HistogramOptions{ |
| NumBuckets: numBuckets, |
| // max(i.e., Nth lower bound) = min + (1 + growthFactor)^(numBuckets-2). |
| GrowthFactor: math.Pow(float64(stats.max-stats.min), 1/float64(stats.numBuckets-2)) - 1, |
| SmallestBucketSize: 1.0, |
| MinValue: stats.min}) |
| |
| for _, d := range stats.durations { |
| stats.histogram.Add(int64(d / stats.unit)) |
| } |
| |
| stats.dirty = false |
| } |
| |
| // Print writes textual output of the Stats. |
| func (stats *Stats) Print(w io.Writer) { |
| stats.maybeUpdate() |
| |
| if stats.histogram == nil { |
| fmt.Fprint(w, "Histogram (empty)\n") |
| } else { |
| fmt.Fprintf(w, "Histogram (unit: %s)\n", fmt.Sprintf("%v", stats.unit)[1:]) |
| stats.histogram.Value().Print(w) |
| } |
| } |
| |
| // String returns the textual output of the Stats as string. |
| func (stats *Stats) String() string { |
| var b bytes.Buffer |
| stats.Print(&b) |
| return b.String() |
| } |