gitee.com/gricks/utils@v1.0.8/metrics.go (about) 1 package utils 2 3 import ( 4 "expvar" 5 "math" 6 "strconv" 7 "sync" 8 "sync/atomic" 9 ) 10 11 // Counter describes a metric that accumulates values monotonically. 12 type Counter struct { 13 v uint64 14 } 15 16 func (this *Counter) Set(v uint64) { 17 atomic.StoreUint64(&this.v, v) 18 } 19 20 func (this *Counter) Inc() { 21 this.Add(1) 22 } 23 24 func (this *Counter) Add(v uint64) { 25 atomic.AddUint64(&this.v, v) 26 } 27 28 func (this *Counter) Value() uint64 { 29 return atomic.LoadUint64(&this.v) 30 } 31 32 func (this *Counter) String() string { 33 return strconv.FormatUint(this.Value(), 10) 34 } 35 36 // Gauge describes a metric that takes specific values over time. 37 type Gauge struct { 38 v expvar.Float 39 } 40 41 func (this *Gauge) Set(v float64) { 42 this.v.Set(v) 43 } 44 45 func (this *Gauge) Add(v float64) { 46 this.v.Add(v) 47 } 48 49 func (this *Gauge) Value() float64 { 50 return this.v.Value() 51 } 52 53 func (this *Gauge) String() string { 54 return this.v.String() 55 } 56 57 // bucket store the count between value and next.value. [value, next.value) 58 type bucket struct { 59 next *bucket 60 prev *bucket 61 value float64 62 count float64 63 } 64 65 func (this *bucket) Next() *bucket { return this.next } 66 func (this *bucket) Prev() *bucket { return this.prev } 67 68 type bucketList struct { 69 head *bucket 70 tail *bucket 71 size int 72 } 73 74 func (this *bucketList) Reset() { this.head, this.tail, this.size = nil, nil, 0 } 75 func (this *bucketList) Empty() bool { return this.head == nil } 76 func (this *bucketList) Front() *bucket { return this.head } 77 func (this *bucketList) Back() *bucket { return this.tail } 78 func (this *bucketList) Size() int { return this.size } 79 80 func (this *bucketList) PushFront(e *bucket) { 81 e.next = this.head 82 e.prev = nil 83 84 if this.head != nil { 85 this.head.prev = e 86 } else { 87 this.tail = e 88 } 89 90 this.head = e 91 this.size++ 92 } 93 94 func (this *bucketList) PushBack(e *bucket) { 95 e.next = nil 96 e.prev = this.tail 97 98 if this.tail != nil { 99 this.tail.next = e 100 } else { 101 this.head = e 102 } 103 104 this.tail = e 105 this.size++ 106 } 107 108 func (this *bucketList) PushBackList(l *bucketList) { 109 if this.head == nil { 110 this.head = l.head 111 this.tail = l.tail 112 } else if l.head != nil { 113 this.tail.next = l.head 114 l.head.prev = this.tail 115 this.tail = l.tail 116 } 117 l.head = nil 118 l.tail = nil 119 this.size += l.Size() 120 } 121 122 func (this *bucketList) InsertAfter(b, e *bucket) { 123 a := b.next 124 e.next = a 125 e.prev = b 126 b.next = e 127 128 if a != nil { 129 a.prev = e 130 } else { 131 this.tail = e 132 } 133 this.size++ 134 } 135 136 func (this *bucketList) InsertBefore(a, e *bucket) { 137 b := a.prev 138 e.next = a 139 e.prev = b 140 a.prev = e 141 142 if b != nil { 143 b.next = e 144 } else { 145 this.head = e 146 } 147 this.size++ 148 } 149 150 func (this *bucketList) Remove(e *bucket) { 151 prev := e.prev 152 next := e.next 153 154 if prev != nil { 155 prev.next = next 156 } else { 157 this.head = next 158 } 159 160 if next != nil { 161 next.prev = prev 162 } else { 163 this.tail = prev 164 } 165 this.size-- 166 } 167 168 // Histogram implements the streaming approximate histogram metric 169 // See more: Apache Hive histogram_numeric & gohistogram 170 type Histogram struct { 171 mtx sync.Mutex 172 l bucketList 173 m int // max bucket 174 total int 175 } 176 177 // NewHistogram returns a Histogram object with the given name and number of 178 // buckets in the underlying histogram object. 179 // 50 is a good default number of buckets. 180 func NewHistogram(bucket int) *Histogram { 181 if bucket < 10 { 182 bucket = 10 183 } 184 return &Histogram{ 185 m: bucket, 186 } 187 } 188 189 func (this *Histogram) Add(v float64) { 190 this.mtx.Lock() 191 this.add(v) 192 this.merge() 193 this.mtx.Unlock() 194 } 195 196 func (this *Histogram) Addn(vs []float64) { 197 this.mtx.Lock() 198 for _, v := range vs { 199 this.add(v) 200 } 201 this.merge() 202 this.mtx.Unlock() 203 } 204 205 func (this *Histogram) add(v float64) { 206 this.total++ 207 for e := this.l.Front(); e != nil; e = e.Next() { 208 switch { 209 case e.value == v: 210 e.count++ 211 return 212 case e.value > v: 213 this.l.InsertBefore(e, &bucket{value: v, count: 1}) 214 return 215 } 216 } 217 this.l.PushBack(&bucket{value: v, count: 1}) 218 } 219 220 // merge adjacent bins to decrease the bin count to the maximum value 221 func (this *Histogram) merge() { 222 for this.l.Size() > this.m { 223 var ( 224 minDelta = math.MaxFloat64 225 minDeltaBucket *bucket 226 ) 227 228 // Find closest bucket in terms of value 229 for e := this.l.Front().Next(); e != nil; e = e.Next() { 230 if delta := e.value - e.Prev().value; delta < minDelta { 231 minDelta = delta 232 minDeltaBucket = e 233 } 234 } 235 236 // Merge minDeltaBucket and its Prev() 237 count := minDeltaBucket.count + minDeltaBucket.Prev().count 238 value := (minDeltaBucket.value*minDeltaBucket.count + 239 minDeltaBucket.Prev().value*minDeltaBucket.Prev().count) / count 240 minDeltaBucket.count = count 241 minDeltaBucket.value = value 242 243 // Remove 244 this.l.Remove(minDeltaBucket.Prev()) 245 } 246 } 247 248 // Quantile returns the value of the quantile q, 0.0 < q < 1.0. 249 func (this *Histogram) Quantile(q float64) float64 { 250 this.mtx.Lock() 251 defer this.mtx.Unlock() 252 count := q * float64(this.total) 253 for e := this.l.Front(); e != nil; e = e.Next() { 254 count -= e.count 255 if count <= 0 { 256 return e.value 257 } 258 } 259 return -1 260 } 261 262 // Mean returns the sample mean of the distribution 263 func (this *Histogram) Mean() float64 { 264 this.mtx.Lock() 265 defer this.mtx.Unlock() 266 if this.total == 0 { 267 return 0.0 268 } 269 sum := 0.0 270 for e := this.l.Front(); e != nil; e = e.Next() { 271 sum += e.value * e.count 272 } 273 return sum / float64(this.total) 274 } 275 276 func (this *Histogram) Count() int { 277 this.mtx.Lock() 278 defer this.mtx.Unlock() 279 return this.total 280 }