| package stats |
| |
| import ( |
| "bytes" |
| "fmt" |
| "io" |
| "strconv" |
| "strings" |
| "time" |
| ) |
| |
| // HistogramValue is the value of Histogram objects. |
| type HistogramValue struct { |
| // Count is the total number of values added to the histogram. |
| Count int64 |
| // Sum is the sum of all the values added to the histogram. |
| Sum int64 |
| // Min is the minimum of all the values added to the histogram. |
| Min int64 |
| // Max is the maximum of all the values added to the histogram. |
| Max int64 |
| // Buckets contains all the buckets of the histogram. |
| Buckets []HistogramBucket |
| } |
| |
| // HistogramBucket is one histogram bucket. |
| type HistogramBucket struct { |
| // LowBound is the lower bound of the bucket. |
| LowBound int64 |
| // Count is the number of values in the bucket. |
| Count int64 |
| } |
| |
| // Print writes textual output of the histogram values. |
| func (v HistogramValue) Print(w io.Writer) { |
| avg := float64(v.Sum) / float64(v.Count) |
| fmt.Fprintf(w, "Count: %d Min: %d Max: %d Avg: %.2f\n", v.Count, v.Min, v.Max, avg) |
| fmt.Fprintf(w, "%s\n", strings.Repeat("-", 60)) |
| if v.Count <= 0 { |
| return |
| } |
| |
| maxBucketDigitLen := len(strconv.FormatInt(v.Buckets[len(v.Buckets)-1].LowBound, 10)) |
| if maxBucketDigitLen < 3 { |
| // For "inf". |
| maxBucketDigitLen = 3 |
| } |
| maxCountDigitLen := len(strconv.FormatInt(v.Count, 10)) |
| percentMulti := 100 / float64(v.Count) |
| |
| accCount := int64(0) |
| for i, b := range v.Buckets { |
| fmt.Fprintf(w, "[%*d, ", maxBucketDigitLen, b.LowBound) |
| if i+1 < len(v.Buckets) { |
| fmt.Fprintf(w, "%*d)", maxBucketDigitLen, v.Buckets[i+1].LowBound) |
| } else { |
| fmt.Fprintf(w, "%*s)", maxBucketDigitLen, "inf") |
| } |
| |
| accCount += b.Count |
| fmt.Fprintf(w, " %*d %5.1f%% %5.1f%%", maxCountDigitLen, b.Count, float64(b.Count)*percentMulti, float64(accCount)*percentMulti) |
| |
| const barScale = 0.1 |
| barLength := int(float64(b.Count)*percentMulti*barScale + 0.5) |
| fmt.Fprintf(w, " %s\n", strings.Repeat("#", barLength)) |
| } |
| } |
| |
| // String returns the textual output of the histogram values as string. |
| func (v HistogramValue) String() string { |
| var b bytes.Buffer |
| v.Print(&b) |
| return b.String() |
| } |
| |
| // A Histogram accumulates values in the form of a histogram. The type of the |
| // values is int64, which is suitable for keeping track of things like RPC |
| // latency in milliseconds. New histogram objects should be obtained via the |
| // New() function. |
| type Histogram struct { |
| opts HistogramOptions |
| buckets []bucketInternal |
| count *Counter |
| sum *Counter |
| tracker *Tracker |
| } |
| |
| // HistogramOptions contains the parameters that define the histogram's buckets. |
| type HistogramOptions struct { |
| // NumBuckets is the number of buckets. |
| NumBuckets int |
| // GrowthFactor is the growth factor of the buckets. A value of 0.1 |
| // indicates that bucket N+1 will be 10% larger than bucket N. |
| GrowthFactor float64 |
| // SmallestBucketSize is the size of the first bucket. Bucket sizes are |
| // rounded down to the nearest integer. |
| SmallestBucketSize float64 |
| // MinValue is the lower bound of the first bucket. |
| MinValue int64 |
| } |
| |
| // bucketInternal is the internal representation of a bucket, which includes a |
| // rate counter. |
| type bucketInternal struct { |
| lowBound int64 |
| count *Counter |
| } |
| |
| // NewHistogram returns a pointer to a new Histogram object that was created |
| // with the provided options. |
| func NewHistogram(opts HistogramOptions) *Histogram { |
| if opts.NumBuckets == 0 { |
| opts.NumBuckets = 32 |
| } |
| if opts.SmallestBucketSize == 0.0 { |
| opts.SmallestBucketSize = 1.0 |
| } |
| h := Histogram{ |
| opts: opts, |
| buckets: make([]bucketInternal, opts.NumBuckets), |
| count: newCounter(), |
| sum: newCounter(), |
| tracker: newTracker(), |
| } |
| low := opts.MinValue |
| delta := opts.SmallestBucketSize |
| for i := 0; i < opts.NumBuckets; i++ { |
| h.buckets[i].lowBound = low |
| h.buckets[i].count = newCounter() |
| low = low + int64(delta) |
| delta = delta * (1.0 + opts.GrowthFactor) |
| } |
| return &h |
| } |
| |
| // Opts returns a copy of the options used to create the Histogram. |
| func (h *Histogram) Opts() HistogramOptions { |
| return h.opts |
| } |
| |
| // Add adds a value to the histogram. |
| func (h *Histogram) Add(value int64) error { |
| bucket, err := h.findBucket(value) |
| if err != nil { |
| return err |
| } |
| h.buckets[bucket].count.Incr(1) |
| h.count.Incr(1) |
| h.sum.Incr(value) |
| h.tracker.Push(value) |
| return nil |
| } |
| |
| // LastUpdate returns the time at which the object was last updated. |
| func (h *Histogram) LastUpdate() time.Time { |
| return h.count.LastUpdate() |
| } |
| |
| // Value returns the accumulated state of the histogram since it was created. |
| func (h *Histogram) Value() HistogramValue { |
| b := make([]HistogramBucket, len(h.buckets)) |
| for i, v := range h.buckets { |
| b[i] = HistogramBucket{ |
| LowBound: v.lowBound, |
| Count: v.count.Value(), |
| } |
| } |
| |
| v := HistogramValue{ |
| Count: h.count.Value(), |
| Sum: h.sum.Value(), |
| Min: h.tracker.Min(), |
| Max: h.tracker.Max(), |
| Buckets: b, |
| } |
| return v |
| } |
| |
| // Delta1h returns the change in the last hour. |
| func (h *Histogram) Delta1h() HistogramValue { |
| b := make([]HistogramBucket, len(h.buckets)) |
| for i, v := range h.buckets { |
| b[i] = HistogramBucket{ |
| LowBound: v.lowBound, |
| Count: v.count.Delta1h(), |
| } |
| } |
| |
| v := HistogramValue{ |
| Count: h.count.Delta1h(), |
| Sum: h.sum.Delta1h(), |
| Min: h.tracker.Min1h(), |
| Max: h.tracker.Max1h(), |
| Buckets: b, |
| } |
| return v |
| } |
| |
| // Delta10m returns the change in the last 10 minutes. |
| func (h *Histogram) Delta10m() HistogramValue { |
| b := make([]HistogramBucket, len(h.buckets)) |
| for i, v := range h.buckets { |
| b[i] = HistogramBucket{ |
| LowBound: v.lowBound, |
| Count: v.count.Delta10m(), |
| } |
| } |
| |
| v := HistogramValue{ |
| Count: h.count.Delta10m(), |
| Sum: h.sum.Delta10m(), |
| Min: h.tracker.Min10m(), |
| Max: h.tracker.Max10m(), |
| Buckets: b, |
| } |
| return v |
| } |
| |
| // Delta1m returns the change in the last 10 minutes. |
| func (h *Histogram) Delta1m() HistogramValue { |
| b := make([]HistogramBucket, len(h.buckets)) |
| for i, v := range h.buckets { |
| b[i] = HistogramBucket{ |
| LowBound: v.lowBound, |
| Count: v.count.Delta1m(), |
| } |
| } |
| |
| v := HistogramValue{ |
| Count: h.count.Delta1m(), |
| Sum: h.sum.Delta1m(), |
| Min: h.tracker.Min1m(), |
| Max: h.tracker.Max1m(), |
| Buckets: b, |
| } |
| return v |
| } |
| |
| // findBucket does a binary search to find in which bucket the value goes. |
| func (h *Histogram) findBucket(value int64) (int, error) { |
| lastBucket := len(h.buckets) - 1 |
| min, max := 0, lastBucket |
| for max >= min { |
| b := (min + max) / 2 |
| if value >= h.buckets[b].lowBound && (b == lastBucket || value < h.buckets[b+1].lowBound) { |
| return b, nil |
| } |
| if value < h.buckets[b].lowBound { |
| max = b - 1 |
| continue |
| } |
| min = b + 1 |
| } |
| return 0, fmt.Errorf("no bucket for value: %d", value) |
| } |