1
18
19 package stats
20
21 import (
22 "bytes"
23 "fmt"
24 "io"
25 "log"
26 "math"
27 "strconv"
28 "strings"
29 )
30
31
32
33 type Histogram struct {
34
35 Count int64
36
37 Sum int64
38
39 SumOfSquares int64
40
41 Min int64
42
43 Max int64
44
45 Buckets []HistogramBucket
46
47 opts HistogramOptions
48 logBaseBucketSize float64
49 oneOverLogOnePlusGrowthFactor float64
50 }
51
52
53
54
55
56
57 type HistogramOptions struct {
58
59 NumBuckets int
60
61
62 GrowthFactor float64
63
64 BaseBucketSize float64
65
66 MinValue int64
67 }
68
69
70 type HistogramBucket struct {
71
72 LowBound float64
73
74 Count int64
75 }
76
77
78
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
106 func (h *Histogram) Print(w io.Writer) {
107 h.PrintWithUnit(w, 1)
108 }
109
110
111
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
143 func (h *Histogram) String() string {
144 var b bytes.Buffer
145 h.Print(&b)
146 return b.String()
147 }
148
149
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
162 func (h *Histogram) Opts() HistogramOptions {
163 return h.opts
164 }
165
166
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
193
194
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
204
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 }
222
View as plain text