github.com/uber-go/tally/v4@v4.1.17/histogram.go (about) 1 // Copyright (c) 2021 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package tally 22 23 import ( 24 "errors" 25 "fmt" 26 "math" 27 "sort" 28 "time" 29 ) 30 31 var ( 32 // DefaultBuckets can be passed to specify to default buckets. 33 DefaultBuckets Buckets 34 35 errBucketsCountNeedsGreaterThanZero = errors.New("n needs to be > 0") 36 errBucketsStartNeedsGreaterThanZero = errors.New("start needs to be > 0") 37 errBucketsFactorNeedsGreaterThanOne = errors.New("factor needs to be > 1") 38 39 _singleBucket = bucketPair{ 40 lowerBoundDuration: time.Duration(math.MinInt64), 41 upperBoundDuration: time.Duration(math.MaxInt64), 42 lowerBoundValue: -math.MaxFloat64, 43 upperBoundValue: math.MaxFloat64, 44 } 45 ) 46 47 // ValueBuckets is a set of float64 values that implements Buckets. 48 type ValueBuckets []float64 49 50 // Implements sort.Interface 51 func (v ValueBuckets) Len() int { 52 return len(v) 53 } 54 55 // Implements sort.Interface 56 func (v ValueBuckets) Swap(i, j int) { 57 v[i], v[j] = v[j], v[i] 58 } 59 60 // Implements sort.Interface 61 func (v ValueBuckets) Less(i, j int) bool { 62 return v[i] < v[j] 63 } 64 65 func (v ValueBuckets) String() string { 66 values := make([]string, len(v)) 67 for i := range values { 68 values[i] = fmt.Sprintf("%f", v[i]) 69 } 70 return fmt.Sprint(values) 71 } 72 73 // AsValues implements Buckets. 74 func (v ValueBuckets) AsValues() []float64 { 75 return v 76 } 77 78 // AsDurations implements Buckets and returns time.Duration 79 // representations of the float64 values divided by time.Second. 80 func (v ValueBuckets) AsDurations() []time.Duration { 81 values := make([]time.Duration, len(v)) 82 for i := range values { 83 values[i] = time.Duration(v[i] * float64(time.Second)) 84 } 85 return values 86 } 87 88 // DurationBuckets is a set of time.Duration values that implements Buckets. 89 type DurationBuckets []time.Duration 90 91 // Implements sort.Interface 92 func (v DurationBuckets) Len() int { 93 return len(v) 94 } 95 96 // Implements sort.Interface 97 func (v DurationBuckets) Swap(i, j int) { 98 v[i], v[j] = v[j], v[i] 99 } 100 101 // Implements sort.Interface 102 func (v DurationBuckets) Less(i, j int) bool { 103 return v[i] < v[j] 104 } 105 106 func (v DurationBuckets) String() string { 107 values := make([]string, len(v)) 108 for i := range values { 109 values[i] = v[i].String() 110 } 111 return fmt.Sprintf("%v", values) 112 } 113 114 // AsValues implements Buckets and returns float64 115 // representations of the time.Duration values divided by time.Second. 116 func (v DurationBuckets) AsValues() []float64 { 117 values := make([]float64, len(v)) 118 for i := range values { 119 values[i] = float64(v[i]) / float64(time.Second) 120 } 121 return values 122 } 123 124 // AsDurations implements Buckets. 125 func (v DurationBuckets) AsDurations() []time.Duration { 126 return v 127 } 128 129 func bucketsEqual(x Buckets, y Buckets) bool { 130 switch b1 := x.(type) { 131 case DurationBuckets: 132 b2, ok := y.(DurationBuckets) 133 if !ok { 134 return false 135 } 136 if len(b1) != len(b2) { 137 return false 138 } 139 for i := 0; i < len(b1); i++ { 140 if b1[i] != b2[i] { 141 return false 142 } 143 } 144 case ValueBuckets: 145 b2, ok := y.(ValueBuckets) 146 if !ok { 147 return false 148 } 149 if len(b1) != len(b2) { 150 return false 151 } 152 for i := 0; i < len(b1); i++ { 153 if b1[i] != b2[i] { 154 return false 155 } 156 } 157 } 158 159 return true 160 } 161 162 func newBucketPair( 163 htype histogramType, 164 durations []time.Duration, 165 values []float64, 166 upperBoundIndex int, 167 prev BucketPair, 168 ) bucketPair { 169 var pair bucketPair 170 171 switch htype { 172 case durationHistogramType: 173 pair = bucketPair{ 174 lowerBoundDuration: prev.UpperBoundDuration(), 175 upperBoundDuration: durations[upperBoundIndex], 176 } 177 case valueHistogramType: 178 pair = bucketPair{ 179 lowerBoundValue: prev.UpperBoundValue(), 180 upperBoundValue: values[upperBoundIndex], 181 } 182 default: 183 // nop 184 } 185 186 return pair 187 } 188 189 // BucketPairs creates a set of bucket pairs from a set 190 // of buckets describing the lower and upper bounds for 191 // each derived bucket. 192 func BucketPairs(buckets Buckets) []BucketPair { 193 htype := valueHistogramType 194 if _, ok := buckets.(DurationBuckets); ok { 195 htype = durationHistogramType 196 } 197 198 if buckets == nil || buckets.Len() < 1 { 199 return []BucketPair{_singleBucket} 200 } 201 202 var ( 203 values []float64 204 durations []time.Duration 205 pairs = make([]BucketPair, 0, buckets.Len()+2) 206 pair bucketPair 207 ) 208 209 switch htype { 210 case durationHistogramType: 211 durations = copyAndSortDurations(buckets.AsDurations()) 212 pair.lowerBoundDuration = _singleBucket.lowerBoundDuration 213 pair.upperBoundDuration = durations[0] 214 case valueHistogramType: 215 values = copyAndSortValues(buckets.AsValues()) 216 pair.lowerBoundValue = _singleBucket.lowerBoundValue 217 pair.upperBoundValue = values[0] 218 default: 219 // n.b. This branch will never be executed because htype is only ever 220 // one of two values. 221 panic("unsupported histogram type") 222 } 223 224 pairs = append(pairs, pair) 225 for i := 1; i < buckets.Len(); i++ { 226 pairs = append( 227 pairs, 228 newBucketPair(htype, durations, values, i, pairs[i-1]), 229 ) 230 } 231 232 switch htype { 233 case durationHistogramType: 234 pair.lowerBoundDuration = pairs[len(pairs)-1].UpperBoundDuration() 235 pair.upperBoundDuration = _singleBucket.upperBoundDuration 236 case valueHistogramType: 237 pair.lowerBoundValue = pairs[len(pairs)-1].UpperBoundValue() 238 pair.upperBoundValue = _singleBucket.upperBoundValue 239 } 240 pairs = append(pairs, pair) 241 242 return pairs 243 } 244 245 func copyAndSortValues(values []float64) []float64 { 246 valuesCopy := make([]float64, len(values)) 247 copy(valuesCopy, values) 248 sort.Sort(ValueBuckets(valuesCopy)) 249 return valuesCopy 250 } 251 252 func copyAndSortDurations(durations []time.Duration) []time.Duration { 253 durationsCopy := make([]time.Duration, len(durations)) 254 copy(durationsCopy, durations) 255 sort.Sort(DurationBuckets(durationsCopy)) 256 return durationsCopy 257 } 258 259 type bucketPair struct { 260 lowerBoundValue float64 261 upperBoundValue float64 262 lowerBoundDuration time.Duration 263 upperBoundDuration time.Duration 264 } 265 266 func (p bucketPair) LowerBoundValue() float64 { 267 return p.lowerBoundValue 268 } 269 270 func (p bucketPair) UpperBoundValue() float64 { 271 return p.upperBoundValue 272 } 273 274 func (p bucketPair) LowerBoundDuration() time.Duration { 275 return p.lowerBoundDuration 276 } 277 278 func (p bucketPair) UpperBoundDuration() time.Duration { 279 return p.upperBoundDuration 280 } 281 282 // LinearValueBuckets creates a set of linear value buckets. 283 func LinearValueBuckets(start, width float64, n int) (ValueBuckets, error) { 284 if n <= 0 { 285 return nil, errBucketsCountNeedsGreaterThanZero 286 } 287 buckets := make([]float64, n) 288 for i := range buckets { 289 buckets[i] = start + (float64(i) * width) 290 } 291 return buckets, nil 292 } 293 294 // MustMakeLinearValueBuckets creates a set of linear value buckets 295 // or panics. 296 func MustMakeLinearValueBuckets(start, width float64, n int) ValueBuckets { 297 buckets, err := LinearValueBuckets(start, width, n) 298 if err != nil { 299 panic(err) 300 } 301 return buckets 302 } 303 304 // LinearDurationBuckets creates a set of linear duration buckets. 305 func LinearDurationBuckets(start, width time.Duration, n int) (DurationBuckets, error) { 306 if n <= 0 { 307 return nil, errBucketsCountNeedsGreaterThanZero 308 } 309 buckets := make([]time.Duration, n) 310 for i := range buckets { 311 buckets[i] = start + (time.Duration(i) * width) 312 } 313 return buckets, nil 314 } 315 316 // MustMakeLinearDurationBuckets creates a set of linear duration buckets. 317 // or panics. 318 func MustMakeLinearDurationBuckets(start, width time.Duration, n int) DurationBuckets { 319 buckets, err := LinearDurationBuckets(start, width, n) 320 if err != nil { 321 panic(err) 322 } 323 return buckets 324 } 325 326 // ExponentialValueBuckets creates a set of exponential value buckets. 327 func ExponentialValueBuckets(start, factor float64, n int) (ValueBuckets, error) { 328 if n <= 0 { 329 return nil, errBucketsCountNeedsGreaterThanZero 330 } 331 if start <= 0 { 332 return nil, errBucketsStartNeedsGreaterThanZero 333 } 334 if factor <= 1 { 335 return nil, errBucketsFactorNeedsGreaterThanOne 336 } 337 buckets := make([]float64, n) 338 curr := start 339 for i := range buckets { 340 buckets[i] = curr 341 curr *= factor 342 } 343 return buckets, nil 344 } 345 346 // MustMakeExponentialValueBuckets creates a set of exponential value buckets 347 // or panics. 348 func MustMakeExponentialValueBuckets(start, factor float64, n int) ValueBuckets { 349 buckets, err := ExponentialValueBuckets(start, factor, n) 350 if err != nil { 351 panic(err) 352 } 353 return buckets 354 } 355 356 // ExponentialDurationBuckets creates a set of exponential duration buckets. 357 func ExponentialDurationBuckets(start time.Duration, factor float64, n int) (DurationBuckets, error) { 358 if n <= 0 { 359 return nil, errBucketsCountNeedsGreaterThanZero 360 } 361 if start <= 0 { 362 return nil, errBucketsStartNeedsGreaterThanZero 363 } 364 if factor <= 1 { 365 return nil, errBucketsFactorNeedsGreaterThanOne 366 } 367 buckets := make([]time.Duration, n) 368 curr := start 369 for i := range buckets { 370 buckets[i] = curr 371 curr = time.Duration(float64(curr) * factor) 372 } 373 return buckets, nil 374 } 375 376 // MustMakeExponentialDurationBuckets creates a set of exponential value buckets 377 // or panics. 378 func MustMakeExponentialDurationBuckets(start time.Duration, factor float64, n int) DurationBuckets { 379 buckets, err := ExponentialDurationBuckets(start, factor, n) 380 if err != nil { 381 panic(err) 382 } 383 return buckets 384 }