| /* |
| * |
| * Copyright 2017 gRPC authors. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| */ |
| |
| 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 |
| } |
| |
| numBuckets := stats.numBuckets |
| if n := int(stats.max - stats.min + 1); n < numBuckets { |
| numBuckets = n |
| } |
| stats.histogram = NewHistogram(HistogramOptions{ |
| NumBuckets: numBuckets, |
| // max-min(lower bound of last bucket) = (1 + growthFactor)^(numBuckets-2) * baseBucketSize. |
| GrowthFactor: math.Pow(float64(stats.max-stats.min), 1/float64(numBuckets-2)) - 1, |
| BaseBucketSize: 1.0, |
| MinValue: stats.min}) |
| |
| for _, d := range stats.durations { |
| stats.histogram.Add(int64(d)) |
| } |
| |
| 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.PrintWithUnit(w, float64(stats.unit)) |
| } |
| } |
| |
| // 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() |
| } |