github.com/hxx258456/ccgo@v0.0.5-0.20230213014102-48b35f46f66f/grpc/benchmark/stats/histogram.go (about) 1 /* 2 * 3 * Copyright 2017 gRPC authors. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 19 package stats 20 21 import ( 22 "bytes" 23 "fmt" 24 "io" 25 "log" 26 "math" 27 "strconv" 28 "strings" 29 ) 30 31 // Histogram accumulates values in the form of a histogram with 32 // exponentially increased bucket sizes. 33 type Histogram struct { 34 // Count is the total number of values added to the histogram. 35 Count int64 36 // Sum is the sum of all the values added to the histogram. 37 Sum int64 38 // SumOfSquares is the sum of squares of all values. 39 SumOfSquares int64 40 // Min is the minimum of all the values added to the histogram. 41 Min int64 42 // Max is the maximum of all the values added to the histogram. 43 Max int64 44 // Buckets contains all the buckets of the histogram. 45 Buckets []HistogramBucket 46 47 opts HistogramOptions 48 logBaseBucketSize float64 49 oneOverLogOnePlusGrowthFactor float64 50 } 51 52 // HistogramOptions contains the parameters that define the histogram's buckets. 53 // The first bucket of the created histogram (with index 0) contains [min, min+n) 54 // where n = BaseBucketSize, min = MinValue. 55 // Bucket i (i>=1) contains [min + n * m^(i-1), min + n * m^i), where m = 1+GrowthFactor. 56 // The type of the values is int64. 57 type HistogramOptions struct { 58 // NumBuckets is the number of buckets. 59 NumBuckets int 60 // GrowthFactor is the growth factor of the buckets. A value of 0.1 61 // indicates that bucket N+1 will be 10% larger than bucket N. 62 GrowthFactor float64 63 // BaseBucketSize is the size of the first bucket. 64 BaseBucketSize float64 65 // MinValue is the lower bound of the first bucket. 66 MinValue int64 67 } 68 69 // HistogramBucket represents one histogram bucket. 70 type HistogramBucket struct { 71 // LowBound is the lower bound of the bucket. 72 LowBound float64 73 // Count is the number of values in the bucket. 74 Count int64 75 } 76 77 // NewHistogram returns a pointer to a new Histogram object that was created 78 // with the provided options. 79 func NewHistogram(opts HistogramOptions) *Histogram { 80 if opts.NumBuckets == 0 { 81 opts.NumBuckets = 32 82 } 83 if opts.BaseBucketSize == 0.0 { 84 opts.BaseBucketSize = 1.0 85 } 86 h := Histogram{ 87 Buckets: make([]HistogramBucket, opts.NumBuckets), 88 Min: math.MaxInt64, 89 Max: math.MinInt64, 90 91 opts: opts, 92 logBaseBucketSize: math.Log(opts.BaseBucketSize), 93 oneOverLogOnePlusGrowthFactor: 1 / math.Log(1+opts.GrowthFactor), 94 } 95 m := 1.0 + opts.GrowthFactor 96 delta := opts.BaseBucketSize 97 h.Buckets[0].LowBound = float64(opts.MinValue) 98 for i := 1; i < opts.NumBuckets; i++ { 99 h.Buckets[i].LowBound = float64(opts.MinValue) + delta 100 delta = delta * m 101 } 102 return &h 103 } 104 105 // Print writes textual output of the histogram values. 106 func (h *Histogram) Print(w io.Writer) { 107 h.PrintWithUnit(w, 1) 108 } 109 110 // PrintWithUnit writes textual output of the histogram values . 111 // Data in histogram is divided by a Unit before print. 112 func (h *Histogram) PrintWithUnit(w io.Writer, unit float64) { 113 avg := float64(h.Sum) / float64(h.Count) 114 fmt.Fprintf(w, "Count: %d Min: %5.1f Max: %5.1f Avg: %.2f\n", h.Count, float64(h.Min)/unit, float64(h.Max)/unit, avg/unit) 115 fmt.Fprintf(w, "%s\n", strings.Repeat("-", 60)) 116 if h.Count <= 0 { 117 return 118 } 119 120 maxBucketDigitLen := len(strconv.FormatFloat(h.Buckets[len(h.Buckets)-1].LowBound, 'f', 6, 64)) 121 maxCountDigitLen := len(strconv.FormatInt(h.Count, 10)) 122 percentMulti := 100 / float64(h.Count) 123 124 accCount := int64(0) 125 for i, b := range h.Buckets { 126 fmt.Fprintf(w, "[%*f, ", maxBucketDigitLen, b.LowBound/unit) 127 if i+1 < len(h.Buckets) { 128 fmt.Fprintf(w, "%*f)", maxBucketDigitLen, h.Buckets[i+1].LowBound/unit) 129 } else { 130 upperBound := float64(h.opts.MinValue) + (b.LowBound-float64(h.opts.MinValue))*(1.0+h.opts.GrowthFactor) 131 fmt.Fprintf(w, "%*f)", maxBucketDigitLen, upperBound/unit) 132 } 133 accCount += b.Count 134 fmt.Fprintf(w, " %*d %5.1f%% %5.1f%%", maxCountDigitLen, b.Count, float64(b.Count)*percentMulti, float64(accCount)*percentMulti) 135 136 const barScale = 0.1 137 barLength := int(float64(b.Count)*percentMulti*barScale + 0.5) 138 fmt.Fprintf(w, " %s\n", strings.Repeat("#", barLength)) 139 } 140 } 141 142 // String returns the textual output of the histogram values as string. 143 func (h *Histogram) String() string { 144 var b bytes.Buffer 145 h.Print(&b) 146 return b.String() 147 } 148 149 // Clear resets all the content of histogram. 150 func (h *Histogram) Clear() { 151 h.Count = 0 152 h.Sum = 0 153 h.SumOfSquares = 0 154 h.Min = math.MaxInt64 155 h.Max = math.MinInt64 156 for i := range h.Buckets { 157 h.Buckets[i].Count = 0 158 } 159 } 160 161 // Opts returns a copy of the options used to create the Histogram. 162 func (h *Histogram) Opts() HistogramOptions { 163 return h.opts 164 } 165 166 // Add adds a value to the histogram. 167 func (h *Histogram) Add(value int64) error { 168 bucket, err := h.findBucket(value) 169 if err != nil { 170 return err 171 } 172 h.Buckets[bucket].Count++ 173 h.Count++ 174 h.Sum += value 175 h.SumOfSquares += value * value 176 if value < h.Min { 177 h.Min = value 178 } 179 if value > h.Max { 180 h.Max = value 181 } 182 return nil 183 } 184 185 func (h *Histogram) findBucket(value int64) (int, error) { 186 delta := float64(value - h.opts.MinValue) 187 if delta < 0 { 188 return 0, fmt.Errorf("no bucket for value: %d", value) 189 } 190 var b int 191 if delta >= h.opts.BaseBucketSize { 192 // b = log_{1+growthFactor} (delta / baseBucketSize) + 1 193 // = log(delta / baseBucketSize) / log(1+growthFactor) + 1 194 // = (log(delta) - log(baseBucketSize)) * (1 / log(1+growthFactor)) + 1 195 b = int((math.Log(delta)-h.logBaseBucketSize)*h.oneOverLogOnePlusGrowthFactor + 1) 196 } 197 if b >= len(h.Buckets) { 198 return 0, fmt.Errorf("no bucket for value: %d", value) 199 } 200 return b, nil 201 } 202 203 // Merge takes another histogram h2, and merges its content into h. 204 // The two histograms must be created by equivalent HistogramOptions. 205 func (h *Histogram) Merge(h2 *Histogram) { 206 if h.opts != h2.opts { 207 log.Fatalf("failed to merge histograms, created by inequivalent options") 208 } 209 h.Count += h2.Count 210 h.Sum += h2.Sum 211 h.SumOfSquares += h2.SumOfSquares 212 if h2.Min < h.Min { 213 h.Min = h2.Min 214 } 215 if h2.Max > h.Max { 216 h.Max = h2.Max 217 } 218 for i, b := range h2.Buckets { 219 h.Buckets[i].Count += b.Count 220 } 221 }