github.com/hasnat/dolt/go@v0.0.0-20210628190320-9eb5d843fbb7/store/metrics/histogram.go (about) 1 // Copyright 2019 Dolthub, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 // 15 // This file incorporates work covered by the following copyright and 16 // permission notice: 17 // 18 // Copyright 2017 Attic Labs, Inc. All rights reserved. 19 // Licensed under the Apache License, version 2.0: 20 // http://www.apache.org/licenses/LICENSE-2.0 21 22 package metrics 23 24 import ( 25 "fmt" 26 "strconv" 27 "sync/atomic" 28 "time" 29 30 "github.com/dustin/go-humanize" 31 32 "github.com/dolthub/dolt/go/store/d" 33 ) 34 35 // Histogram is a shameless and low-rent knock of the chromium project's 36 // histogram: 37 // https://chromium.googlesource.com/chromium/src/base/+/master/metrics/histogram.h 38 // 39 // It logically stores a running histogram of uint64 values and shares some 40 // important features of its inspiration: 41 // * It acccepts a correctness deficit in return for not needing to lock. 42 // IOW, concurrent calls to Sample may clobber each other. 43 // * It trades compactness and ease of arithmatic across histograms for 44 // precision. Samples lose precision up to the range of the values which 45 // are stored in a bucket 46 // 47 // Only implemented: Log2-based histogram 48 49 const bucketCount = 64 50 51 type HistogramType uint64 52 53 const ( 54 UnspecifiedHistogram HistogramType = iota 55 TimeHistogram 56 ByteHistogram 57 ) 58 59 type Histogram struct { 60 // this structure needs to be a multiple of 8 bytes in size. This is necessary for 32-bit architectures and 61 // guarantees 8 byte alignment for multiple Histograms laid out side by side in memory. 62 sum uint64 63 buckets [bucketCount]uint64 64 histType HistogramType 65 } 66 67 // Sample adds a uint64 data point to the histogram 68 func (h *Histogram) Sample(v uint64) { 69 d.PanicIfTrue(v == 0) 70 71 atomic.AddUint64(&h.sum, v) 72 73 pot := 0 74 for v > 0 { 75 v = v >> 1 76 pot++ 77 } 78 79 atomic.AddUint64(&h.buckets[pot-1], 1) 80 } 81 82 func (h *Histogram) Clone() *Histogram { 83 n := &Histogram{histType: h.histType} 84 n.Add(h) 85 return n 86 } 87 88 // SampleTimeSince is a convenience wrapper around Sample which takes the 89 // duration since |t|, if 0, rounds to 1 and passes to Sample() as an uint64 90 // number of nanoseconds. 91 func (h *Histogram) SampleTimeSince(t time.Time) { 92 d := time.Since(t) 93 if d == 0 { 94 d = 1 95 } 96 h.Sample(uint64(d)) 97 } 98 99 // SampleLen is a convenience wrapper around Sample which internally type 100 // asserts the int to a uint64 101 func (h *Histogram) SampleLen(l int) { 102 h.Sample(uint64(l)) 103 } 104 105 func (h Histogram) bucketVal(bucket int) uint64 { 106 return 1 << (uint64(bucket)) 107 } 108 109 // Sum return the sum of sampled values, note that Sum can be overflowed without 110 // overflowing the histogram buckets. 111 func (h Histogram) Sum() uint64 { 112 return atomic.LoadUint64(&h.sum) 113 } 114 115 // Add returns a new Histogram which is the result of adding this and other 116 // bucket-wise. 117 func (h *Histogram) Add(other *Histogram) { 118 atomic.AddUint64(&h.sum, atomic.LoadUint64(&other.sum)) 119 120 for i := 0; i < bucketCount; i++ { 121 atomic.AddUint64(&h.buckets[i], atomic.LoadUint64(&other.buckets[i])) 122 } 123 } 124 125 // Mean returns 0 if there are no samples, and h.Sum()/h.Samples otherwise. 126 func (h Histogram) Mean() uint64 { 127 samples := h.Samples() 128 if samples == 0 { 129 return 0 130 } 131 132 return h.Sum() / samples 133 } 134 135 // Samples returns the number of samples contained in the histogram 136 func (h Histogram) Samples() uint64 { 137 s := uint64(0) 138 for i := 0; i < bucketCount; i++ { 139 s += atomic.LoadUint64(&h.buckets[i]) 140 } 141 return s 142 } 143 144 func uintToString(v uint64) string { 145 return strconv.FormatUint(v, 10) 146 } 147 148 func timeToString(v uint64) string { 149 return time.Duration(v).String() 150 } 151 152 func (h Histogram) String() string { 153 var f func(uint64) string 154 switch h.histType { 155 case UnspecifiedHistogram: 156 f = uintToString 157 case ByteHistogram: 158 f = humanize.Bytes 159 case TimeHistogram: 160 f = timeToString 161 } 162 163 return fmt.Sprintf("Mean: %s, Sum: %s, Samples: %d", f(h.Mean()), f(h.Sum()), h.Samples()) 164 } 165 166 func NewTimeHistogram() Histogram { 167 return Histogram{histType: TimeHistogram} 168 } 169 170 // NewByteHistogram stringifies values using humanize over byte values 171 func NewByteHistogram() Histogram { 172 return Histogram{histType: ByteHistogram} 173 }