github.com/twelsh-aw/go/src@v0.0.0-20230516233729-a56fe86a7c81/runtime/histogram.go (about) 1 // Copyright 2020 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package runtime 6 7 import ( 8 "runtime/internal/atomic" 9 "runtime/internal/sys" 10 "unsafe" 11 ) 12 13 const ( 14 // For the time histogram type, we use an HDR histogram. 15 // Values are placed in buckets based solely on the most 16 // significant set bit. Thus, buckets are power-of-2 sized. 17 // Values are then placed into sub-buckets based on the value of 18 // the next timeHistSubBucketBits most significant bits. Thus, 19 // sub-buckets are linear within a bucket. 20 // 21 // Therefore, the number of sub-buckets (timeHistNumSubBuckets) 22 // defines the error. This error may be computed as 23 // 1/timeHistNumSubBuckets*100%. For example, for 16 sub-buckets 24 // per bucket the error is approximately 6%. 25 // 26 // The number of buckets (timeHistNumBuckets), on the 27 // other hand, defines the range. To avoid producing a large number 28 // of buckets that are close together, especially for small numbers 29 // (e.g. 1, 2, 3, 4, 5 ns) that aren't very useful, timeHistNumBuckets 30 // is defined in terms of the least significant bit (timeHistMinBucketBits) 31 // that needs to be set before we start bucketing and the most 32 // significant bit (timeHistMaxBucketBits) that we bucket before we just 33 // dump it into a catch-all bucket. 34 // 35 // As an example, consider the configuration: 36 // 37 // timeHistMinBucketBits = 9 38 // timeHistMaxBucketBits = 48 39 // timeHistSubBucketBits = 2 40 // 41 // Then: 42 // 43 // 011000001 44 // ^-- 45 // │ ^ 46 // │ └---- Next 2 bits -> sub-bucket 3 47 // └------- Bit 9 unset -> bucket 0 48 // 49 // 110000001 50 // ^-- 51 // │ ^ 52 // │ └---- Next 2 bits -> sub-bucket 2 53 // └------- Bit 9 set -> bucket 1 54 // 55 // 1000000010 56 // ^-- ^ 57 // │ ^ └-- Lower bits ignored 58 // │ └---- Next 2 bits -> sub-bucket 0 59 // └------- Bit 10 set -> bucket 2 60 // 61 // Following this pattern, bucket 38 will have the bit 46 set. We don't 62 // have any buckets for higher values, so we spill the rest into an overflow 63 // bucket containing values of 2^47-1 nanoseconds or approx. 1 day or more. 64 // This range is more than enough to handle durations produced by the runtime. 65 timeHistMinBucketBits = 9 66 timeHistMaxBucketBits = 48 // Note that this is exclusive; 1 higher than the actual range. 67 timeHistSubBucketBits = 2 68 timeHistNumSubBuckets = 1 << timeHistSubBucketBits 69 timeHistNumBuckets = timeHistMaxBucketBits - timeHistMinBucketBits + 1 70 // Two extra buckets, one for underflow, one for overflow. 71 timeHistTotalBuckets = timeHistNumBuckets*timeHistNumSubBuckets + 2 72 ) 73 74 // timeHistogram represents a distribution of durations in 75 // nanoseconds. 76 // 77 // The accuracy and range of the histogram is defined by the 78 // timeHistSubBucketBits and timeHistNumBuckets constants. 79 // 80 // It is an HDR histogram with exponentially-distributed 81 // buckets and linearly distributed sub-buckets. 82 // 83 // The histogram is safe for concurrent reads and writes. 84 type timeHistogram struct { 85 counts [timeHistNumBuckets * timeHistNumSubBuckets]atomic.Uint64 86 87 // underflow counts all the times we got a negative duration 88 // sample. Because of how time works on some platforms, it's 89 // possible to measure negative durations. We could ignore them, 90 // but we record them anyway because it's better to have some 91 // signal that it's happening than just missing samples. 92 underflow atomic.Uint64 93 94 // overflow counts all the times we got a duration that exceeded 95 // the range counts represents. 96 overflow atomic.Uint64 97 } 98 99 // record adds the given duration to the distribution. 100 // 101 // Disallow preemptions and stack growths because this function 102 // may run in sensitive locations. 103 // 104 //go:nosplit 105 func (h *timeHistogram) record(duration int64) { 106 // If the duration is negative, capture that in underflow. 107 if duration < 0 { 108 h.underflow.Add(1) 109 return 110 } 111 // bucketBit is the target bit for the bucket which is usually the 112 // highest 1 bit, but if we're less than the minimum, is the highest 113 // 1 bit of the minimum (which will be zero in the duration). 114 // 115 // bucket is the bucket index, which is the bucketBit minus the 116 // highest bit of the minimum, plus one to leave room for the catch-all 117 // bucket for samples lower than the minimum. 118 var bucketBit, bucket uint 119 if l := sys.Len64(uint64(duration)); l < timeHistMinBucketBits { 120 bucketBit = timeHistMinBucketBits 121 bucket = 0 // bucketBit - timeHistMinBucketBits 122 } else { 123 bucketBit = uint(l) 124 bucket = bucketBit - timeHistMinBucketBits + 1 125 } 126 // If the bucket we computed is greater than the number of buckets, 127 // count that in overflow. 128 if bucket >= timeHistNumBuckets { 129 h.overflow.Add(1) 130 return 131 } 132 // The sub-bucket index is just next timeHistSubBucketBits after the bucketBit. 133 subBucket := uint(duration>>(bucketBit-1-timeHistSubBucketBits)) % timeHistNumSubBuckets 134 h.counts[bucket*timeHistNumSubBuckets+subBucket].Add(1) 135 } 136 137 const ( 138 fInf = 0x7FF0000000000000 139 fNegInf = 0xFFF0000000000000 140 ) 141 142 func float64Inf() float64 { 143 inf := uint64(fInf) 144 return *(*float64)(unsafe.Pointer(&inf)) 145 } 146 147 func float64NegInf() float64 { 148 inf := uint64(fNegInf) 149 return *(*float64)(unsafe.Pointer(&inf)) 150 } 151 152 // timeHistogramMetricsBuckets generates a slice of boundaries for 153 // the timeHistogram. These boundaries are represented in seconds, 154 // not nanoseconds like the timeHistogram represents durations. 155 func timeHistogramMetricsBuckets() []float64 { 156 b := make([]float64, timeHistTotalBuckets+1) 157 // Underflow bucket. 158 b[0] = float64NegInf() 159 160 for j := 0; j < timeHistNumSubBuckets; j++ { 161 // No bucket bit for the first few buckets. Just sub-bucket bits after the 162 // min bucket bit. 163 bucketNanos := uint64(j) << (timeHistMinBucketBits - 1 - timeHistSubBucketBits) 164 // Convert nanoseconds to seconds via a division. 165 // These values will all be exactly representable by a float64. 166 b[j+1] = float64(bucketNanos) / 1e9 167 } 168 // Generate the rest of the buckets. It's easier to reason 169 // about if we cut out the 0'th bucket. 170 for i := timeHistMinBucketBits; i < timeHistMaxBucketBits; i++ { 171 for j := 0; j < timeHistNumSubBuckets; j++ { 172 // Set the bucket bit. 173 bucketNanos := uint64(1) << (i - 1) 174 // Set the sub-bucket bits. 175 bucketNanos |= uint64(j) << (i - 1 - timeHistSubBucketBits) 176 // The index for this bucket is going to be the (i+1)'th bucket 177 // (note that we're starting from zero, but handled the first bucket 178 // earlier, so we need to compensate), and the j'th sub bucket. 179 // Add 1 because we left space for -Inf. 180 bucketIndex := (i-timeHistMinBucketBits+1)*timeHistNumSubBuckets + j + 1 181 // Convert nanoseconds to seconds via a division. 182 // These values will all be exactly representable by a float64. 183 b[bucketIndex] = float64(bucketNanos) / 1e9 184 } 185 } 186 // Overflow bucket. 187 b[len(b)-2] = float64(uint64(1)<<(timeHistMaxBucketBits-1)) / 1e9 188 b[len(b)-1] = float64Inf() 189 return b 190 }