trpc.group/trpc-go/trpc-go@v1.0.3/metrics/histogram.go (about)

     1  //
     2  //
     3  // Tencent is pleased to support the open source community by making tRPC available.
     4  //
     5  // Copyright (C) 2023 THL A29 Limited, a Tencent company.
     6  // All rights reserved.
     7  //
     8  // If you have downloaded a copy of the tRPC source code from Tencent,
     9  // please note that tRPC source code is licensed under the  Apache 2.0 License,
    10  // A copy of the Apache 2.0 License is included in this file.
    11  //
    12  //
    13  
    14  package metrics
    15  
    16  import (
    17  	"math"
    18  	"sort"
    19  	"sync"
    20  	"time"
    21  )
    22  
    23  // IHistogram is the interface that emits histogram metrics.
    24  type IHistogram interface {
    25  	// AddSample records a sample into histogram.
    26  	AddSample(value float64)
    27  	// GetBuckets get histogram buckets.
    28  	GetBuckets() []*bucket
    29  }
    30  
    31  // RegisterHistogram registers all named Histogram configurations to all Sink(s) which implement
    32  // HistogramSink.
    33  func RegisterHistogram(name string, o HistogramOption) {
    34  	metricsSinksMutex.Lock()
    35  	defer metricsSinksMutex.Unlock()
    36  	for _, sink := range metricsSinks {
    37  		if histSink, ok := sink.(HistogramSink); ok {
    38  			histSink.Register(name, o)
    39  		}
    40  	}
    41  }
    42  
    43  // HistogramSink extends Sink in a way that allows to load a named Histogram configuration.
    44  // Those who do not implement HistogramSink must define their own default bucket configuration.
    45  type HistogramSink interface {
    46  	Register(name string, o HistogramOption)
    47  }
    48  
    49  // HistogramOption defines configurations when register a histogram.
    50  type HistogramOption struct {
    51  	BucketBounds BucketBounds
    52  }
    53  
    54  // histogram defines the histogram. Each sample is added to one of predefined buckets.
    55  type histogram struct {
    56  	Name          string
    57  	Meta          map[string]interface{}
    58  	Spec          BucketBounds
    59  	Buckets       []*bucket
    60  	LookupByValue []float64
    61  }
    62  
    63  // newHistogram creates a named histogram with buckets.
    64  func newHistogram(name string, buckets BucketBounds) *histogram {
    65  	ranges := newBucketRanges(buckets)
    66  	h := &histogram{
    67  		Name:          name,
    68  		Spec:          buckets,
    69  		Buckets:       make([]*bucket, 0, len(ranges)),
    70  		LookupByValue: make([]float64, 0, len(ranges)),
    71  	}
    72  	for _, r := range ranges {
    73  		h.LookupByValue = append(h.LookupByValue, r.upperBoundValue)
    74  		h.Buckets = append(h.Buckets, &bucket{
    75  			h:               h,
    76  			samples:         0.0,
    77  			ValueLowerBound: r.lowerBoundValue,
    78  			ValueUpperBound: r.upperBoundValue,
    79  		})
    80  	}
    81  	return h
    82  }
    83  
    84  // AddSample adds a new sample.
    85  func (h *histogram) AddSample(value float64) {
    86  	idx := sort.SearchFloat64s(h.LookupByValue, value)
    87  	h.Buckets[idx].mu.Lock()
    88  	h.Buckets[idx].samples += value
    89  	h.Buckets[idx].mu.Unlock()
    90  
    91  	if len(metricsSinks) == 0 {
    92  		return
    93  	}
    94  
    95  	r := NewSingleDimensionMetrics(h.Name, value, PolicyHistogram)
    96  	for _, sink := range metricsSinks {
    97  		sink.Report(r)
    98  	}
    99  }
   100  
   101  // GetBuckets gets the buckets.
   102  func (h *histogram) GetBuckets() []*bucket {
   103  	return h.Buckets
   104  }
   105  
   106  // BucketBounds allows developers to customize Buckets of histogram.
   107  type BucketBounds []float64
   108  
   109  // NewValueBounds creates a value bounds.
   110  func NewValueBounds(bounds ...float64) BucketBounds {
   111  	return bounds
   112  }
   113  
   114  // NewDurationBounds creates duration bounds.
   115  func NewDurationBounds(durations ...time.Duration) BucketBounds {
   116  	bounds := make([]float64, 0, len(durations))
   117  	for _, v := range durations {
   118  		bounds = append(bounds, float64(v))
   119  	}
   120  	return bounds
   121  }
   122  
   123  // Len implements sort.Interface.
   124  func (v BucketBounds) Len() int {
   125  	return len(v)
   126  }
   127  
   128  // Swap implements sort.Interface.
   129  func (v BucketBounds) Swap(i, j int) {
   130  	v[i], v[j] = v[j], v[i]
   131  }
   132  
   133  // Less implements sort.Interface.
   134  func (v BucketBounds) Less(i, j int) bool {
   135  	return v[i] < v[j]
   136  }
   137  
   138  func (v BucketBounds) sorted() []float64 {
   139  	valuesCopy := clone(v)
   140  	sort.Sort(BucketBounds(valuesCopy))
   141  	return valuesCopy
   142  }
   143  
   144  // bucket is used to assemble a histogram. Every bucket contains a counter.
   145  type bucket struct {
   146  	h *histogram
   147  
   148  	ValueLowerBound float64
   149  	ValueUpperBound float64
   150  
   151  	mu      sync.Mutex
   152  	samples float64
   153  }
   154  
   155  const inf = math.MaxFloat64
   156  
   157  // newBucketRanges creates a set of bucket pairs from a set of Buckets describing the lower and upper newBucketRanges
   158  // for each derived bucket.
   159  func newBucketRanges(buckets BucketBounds) []bucketRange {
   160  	if len(buckets) < 1 {
   161  		s := bucketRange{lowerBoundValue: -inf, upperBoundValue: inf}
   162  		return []bucketRange{s}
   163  	}
   164  
   165  	// if Buckets range is [A,B), don't forget (~,A) and [B,~)
   166  	ranges := make([]bucketRange, 0, buckets.Len()+2)
   167  	sortedBounds := buckets.sorted()
   168  	lowerBoundValue := -inf
   169  
   170  	for i := 0; i < buckets.Len(); i++ {
   171  		ranges = append(ranges, bucketRange{lowerBoundValue: lowerBoundValue, upperBoundValue: sortedBounds[i]})
   172  		lowerBoundValue = sortedBounds[i]
   173  	}
   174  	ranges = append(ranges, bucketRange{
   175  		lowerBoundValue: sortedBounds[len(sortedBounds)-1],
   176  		upperBoundValue: inf,
   177  	})
   178  	return ranges
   179  }
   180  
   181  // clone deeply copies a slice.
   182  func clone(values []float64) []float64 {
   183  	valuesCopy := make([]float64, len(values))
   184  	copy(valuesCopy, values)
   185  	return valuesCopy
   186  }
   187  
   188  // bucketRange is the bucket range.
   189  type bucketRange struct {
   190  	lowerBoundValue float64
   191  	upperBoundValue float64
   192  }