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  }