|  | // Copyright 2020 The Go Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style | 
|  | // license that can be found in the LICENSE file. | 
|  |  | 
|  | package runtime | 
|  |  | 
|  | import ( | 
|  | "runtime/internal/atomic" | 
|  | "runtime/internal/sys" | 
|  | "unsafe" | 
|  | ) | 
|  |  | 
|  | const ( | 
|  | // For the time histogram type, we use an HDR histogram. | 
|  | // Values are placed in super-buckets based solely on the most | 
|  | // significant set bit. Thus, super-buckets are power-of-2 sized. | 
|  | // Values are then placed into sub-buckets based on the value of | 
|  | // the next timeHistSubBucketBits most significant bits. Thus, | 
|  | // sub-buckets are linear within a super-bucket. | 
|  | // | 
|  | // Therefore, the number of sub-buckets (timeHistNumSubBuckets) | 
|  | // defines the error. This error may be computed as | 
|  | // 1/timeHistNumSubBuckets*100%. For example, for 16 sub-buckets | 
|  | // per super-bucket the error is approximately 6%. | 
|  | // | 
|  | // The number of super-buckets (timeHistNumSuperBuckets), on the | 
|  | // other hand, defines the range. To reserve room for sub-buckets, | 
|  | // bit timeHistSubBucketBits is the first bit considered for | 
|  | // super-buckets, so super-bucket indices are adjusted accordingly. | 
|  | // | 
|  | // As an example, consider 45 super-buckets with 16 sub-buckets. | 
|  | // | 
|  | //    00110 | 
|  | //    ^---- | 
|  | //    │  ^ | 
|  | //    │  └---- Lowest 4 bits -> sub-bucket 6 | 
|  | //    └------- Bit 4 unset -> super-bucket 0 | 
|  | // | 
|  | //    10110 | 
|  | //    ^---- | 
|  | //    │  ^ | 
|  | //    │  └---- Next 4 bits -> sub-bucket 6 | 
|  | //    └------- Bit 4 set -> super-bucket 1 | 
|  | //    100010 | 
|  | //    ^----^ | 
|  | //    │  ^ └-- Lower bits ignored | 
|  | //    │  └---- Next 4 bits -> sub-bucket 1 | 
|  | //    └------- Bit 5 set -> super-bucket 2 | 
|  | // | 
|  | // Following this pattern, super-bucket 44 will have the bit 47 set. We don't | 
|  | // have any buckets for higher values, so the highest sub-bucket will | 
|  | // contain values of 2^48-1 nanoseconds or approx. 3 days. This range is | 
|  | // more than enough to handle durations produced by the runtime. | 
|  | timeHistSubBucketBits   = 4 | 
|  | timeHistNumSubBuckets   = 1 << timeHistSubBucketBits | 
|  | timeHistNumSuperBuckets = 45 | 
|  | timeHistTotalBuckets    = timeHistNumSuperBuckets*timeHistNumSubBuckets + 1 | 
|  | ) | 
|  |  | 
|  | // timeHistogram represents a distribution of durations in | 
|  | // nanoseconds. | 
|  | // | 
|  | // The accuracy and range of the histogram is defined by the | 
|  | // timeHistSubBucketBits and timeHistNumSuperBuckets constants. | 
|  | // | 
|  | // It is an HDR histogram with exponentially-distributed | 
|  | // buckets and linearly distributed sub-buckets. | 
|  | // | 
|  | // Counts in the histogram are updated atomically, so it is safe | 
|  | // for concurrent use. It is also safe to read all the values | 
|  | // atomically. | 
|  | type timeHistogram struct { | 
|  | counts [timeHistNumSuperBuckets * timeHistNumSubBuckets]uint64 | 
|  |  | 
|  | // underflow counts all the times we got a negative duration | 
|  | // sample. Because of how time works on some platforms, it's | 
|  | // possible to measure negative durations. We could ignore them, | 
|  | // but we record them anyway because it's better to have some | 
|  | // signal that it's happening than just missing samples. | 
|  | underflow uint64 | 
|  | } | 
|  |  | 
|  | // record adds the given duration to the distribution. | 
|  | // | 
|  | // Disallow preemptions and stack growths because this function | 
|  | // may run in sensitive locations. | 
|  | //go:nosplit | 
|  | func (h *timeHistogram) record(duration int64) { | 
|  | if duration < 0 { | 
|  | atomic.Xadd64(&h.underflow, 1) | 
|  | return | 
|  | } | 
|  | // The index of the exponential bucket is just the index | 
|  | // of the highest set bit adjusted for how many bits we | 
|  | // use for the subbucket. Note that it's timeHistSubBucketsBits-1 | 
|  | // because we use the 0th bucket to hold values < timeHistNumSubBuckets. | 
|  | var superBucket, subBucket uint | 
|  | if duration >= timeHistNumSubBuckets { | 
|  | // At this point, we know the duration value will always be | 
|  | // at least timeHistSubBucketsBits long. | 
|  | superBucket = uint(sys.Len64(uint64(duration))) - timeHistSubBucketBits | 
|  | if superBucket*timeHistNumSubBuckets >= uint(len(h.counts)) { | 
|  | // The bucket index we got is larger than what we support, so | 
|  | // include this count in the highest bucket, which extends to | 
|  | // infinity. | 
|  | superBucket = timeHistNumSuperBuckets - 1 | 
|  | subBucket = timeHistNumSubBuckets - 1 | 
|  | } else { | 
|  | // The linear subbucket index is just the timeHistSubBucketsBits | 
|  | // bits after the top bit. To extract that value, shift down | 
|  | // the duration such that we leave the top bit and the next bits | 
|  | // intact, then extract the index. | 
|  | subBucket = uint((duration >> (superBucket - 1)) % timeHistNumSubBuckets) | 
|  | } | 
|  | } else { | 
|  | subBucket = uint(duration) | 
|  | } | 
|  | atomic.Xadd64(&h.counts[superBucket*timeHistNumSubBuckets+subBucket], 1) | 
|  | } | 
|  |  | 
|  | const ( | 
|  | fInf    = 0x7FF0000000000000 | 
|  | fNegInf = 0xFFF0000000000000 | 
|  | ) | 
|  |  | 
|  | func float64Inf() float64 { | 
|  | inf := uint64(fInf) | 
|  | return *(*float64)(unsafe.Pointer(&inf)) | 
|  | } | 
|  |  | 
|  | func float64NegInf() float64 { | 
|  | inf := uint64(fNegInf) | 
|  | return *(*float64)(unsafe.Pointer(&inf)) | 
|  | } | 
|  |  | 
|  | // timeHistogramMetricsBuckets generates a slice of boundaries for | 
|  | // the timeHistogram. These boundaries are represented in seconds, | 
|  | // not nanoseconds like the timeHistogram represents durations. | 
|  | func timeHistogramMetricsBuckets() []float64 { | 
|  | b := make([]float64, timeHistTotalBuckets+1) | 
|  | b[0] = float64NegInf() | 
|  | // Super-bucket 0 has no bits above timeHistSubBucketBits | 
|  | // set, so just iterate over each bucket and assign the | 
|  | // incrementing bucket. | 
|  | for i := 0; i < timeHistNumSubBuckets; i++ { | 
|  | bucketNanos := uint64(i) | 
|  | b[i+1] = float64(bucketNanos) / 1e9 | 
|  | } | 
|  | // Generate the rest of the super-buckets. It's easier to reason | 
|  | // about if we cut out the 0'th bucket, so subtract one since | 
|  | // we just handled that bucket. | 
|  | for i := 0; i < timeHistNumSuperBuckets-1; i++ { | 
|  | for j := 0; j < timeHistNumSubBuckets; j++ { | 
|  | // Set the super-bucket bit. | 
|  | bucketNanos := uint64(1) << (i + timeHistSubBucketBits) | 
|  | // Set the sub-bucket bits. | 
|  | bucketNanos |= uint64(j) << i | 
|  | // The index for this bucket is going to be the (i+1)'th super bucket | 
|  | // (note that we're starting from zero, but handled the first super-bucket | 
|  | // earlier, so we need to compensate), and the j'th sub bucket. | 
|  | // Add 1 because we left space for -Inf. | 
|  | bucketIndex := (i+1)*timeHistNumSubBuckets + j + 1 | 
|  | // Convert nanoseconds to seconds via a division. | 
|  | // These values will all be exactly representable by a float64. | 
|  | b[bucketIndex] = float64(bucketNanos) / 1e9 | 
|  | } | 
|  | } | 
|  | b[len(b)-1] = float64Inf() | 
|  | return b | 
|  | } |