123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329 |
- /*
- *
- * 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.
- *
- */
- // Copyright (c) 2015 Arista Networks, Inc.
- // Use of this source code is governed by the Apache License 2.0
- // that can be found in the COPYING file.
- package stats
- import (
- "bytes"
- "encoding/json"
- "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
- }
- // PrintChart writes textual output of the histogram values.
- func (v HistogramValue) PrintChart(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))
- }
- }
- // MarshalJSON marshal the HistogramValue into JSON.
- func (v HistogramValue) MarshalJSON() ([]byte, error) {
- var b bytes.Buffer
- var min int64
- var max int64
- var avg float64
- var percentMulti float64
- if v.Count != 0 {
- min = v.Min
- max = v.Max
- avg = float64(v.Sum) / float64(v.Count)
- percentMulti = 100 / float64(v.Count)
- }
- fmt.Fprintf(&b,
- `{"stats":{"count":%d,"min":%d,"max":%d,"avg":%.2f}, "buckets": {`,
- v.Count, min, max, avg)
- for i, bucket := range v.Buckets {
- fmt.Fprintf(&b, `"[%d,`, bucket.LowBound)
- if i+1 < len(v.Buckets) {
- fmt.Fprintf(&b, `%d)":{`, v.Buckets[i+1].LowBound)
- } else {
- fmt.Fprintf(&b, `inf)":{`)
- }
- fmt.Fprintf(&b, `"count":%d,"percentage":%.1f}`,
- bucket.Count, float64(bucket.Count)*percentMulti)
- if i != len(v.Buckets)-1 {
- fmt.Fprintf(&b, ",")
- }
- }
- fmt.Fprint(&b, `}}`)
- return b.Bytes(), nil
- }
- // String returns the textual output of the histogram values as string.
- func (v HistogramValue) String() string {
- var b bytes.Buffer
- v.PrintChart(&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
- }
- // Print returns the histogram as a chart.
- func (h *Histogram) Print() string {
- return h.Value().String()
- }
- // String returns a JSON representation of the histogram for expvars.
- func (h *Histogram) String() string {
- j, err := json.Marshal(h.Value())
- if err != nil {
- return ""
- }
- return string(j)
- }
- // 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)
- }
|