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 }