github.com/swiftstack/ProxyFS@v0.0.0-20210203235616-4017c267d62f/bucketstats/api.go (about) 1 // Copyright (c) 2015-2021, NVIDIA CORPORATION. 2 // SPDX-License-Identifier: Apache-2.0 3 4 // Package bucketstats implements easy to use statistics collection and 5 // reporting, including bucketized statistics. Statistics start at zero and 6 // grow as they are added to. 7 // 8 // The statistics provided include totaler (with the Totaler interface), average 9 // (with the Averager interface), and distributions (with the Bucketer 10 // interface). 11 // 12 // Each statistic must have a unique name, "Name". One or more statistics is 13 // placed in a structure and registered, with a name, via a call to Register() 14 // before being used. The set of the statistics registered can be queried using 15 // the registered name or individually. 16 // 17 package bucketstats 18 19 import ( 20 "math/bits" 21 "sync/atomic" 22 ) 23 24 type StatStringFormat int 25 26 const ( 27 StatFormatParsable1 StatStringFormat = iota 28 ) 29 30 // A Totaler can be incremented, or added to, and tracks the total value of all 31 // values added. 32 // 33 // Adding a negative value is not supported. 34 // 35 type Totaler interface { 36 Increment() 37 Add(value uint64) 38 TotalGet() (total uint64) 39 Sprint(stringFmt StatStringFormat, pkgName string, statsGroupName string) (values string) 40 } 41 42 // An Averager is a Totaler with a average (mean) function added. 43 // 44 // This adds a CountGet() function that returns the number of values added as 45 // well as an AverageGet() method that returns the average. 46 // 47 type Averager interface { 48 Totaler 49 CountGet() (count uint64) 50 AverageGet() (avg uint64) 51 } 52 53 // Holds information for an individual statistics bucket, consisting of: 54 // 55 // Count the number of values added to the bucket 56 // RangeLow the smallest value mapped to the bucket 57 // RangeHigh the largest value mapped to the bucket 58 // NominalVal the nominal value of the bucket (sqrt(2)^n or 2^n) 59 // MeanVal the mean value of values added to the bucket, assuming 60 // a uniform distribution 61 // 62 // When performing math on these statistics be careful of overflowing a uint64. 63 // It may be a good idea to use the "math/big" package. 64 // 65 type BucketInfo struct { 66 Count uint64 67 NominalVal uint64 68 MeanVal uint64 69 RangeLow uint64 70 RangeHigh uint64 71 } 72 73 // A Bucketer is a Averager which also tracks the distribution of values. 74 // 75 // The number of buckets and the range of values mapped to a bucket depends on 76 // the bucket type used. 77 // 78 // DistGet() returns the distribution of values across the buckets as an array 79 // of BucketInfo. 80 // 81 type Bucketer interface { 82 Averager 83 DistGet() []BucketInfo 84 } 85 86 // Register and initialize a set of statistics. 87 // 88 // statsStruct is a pointer to a structure which has one or more fields holding 89 // statistics. It may also contain other fields that are not bucketstats types. 90 // 91 // The combination of pkgName and statsGroupName must be unique. pkgName is 92 // typically the name of a pacakge and statsGroupName is the name for the group 93 // of stats. One or the other, but not both, can be the empty string. 94 // Whitespace characters, '"' (double quote), '*' (asterik), and ':' (colon) are 95 // not allowed in either name. 96 // 97 func Register(pkgName string, statsGroupName string, statsStruct interface{}) { 98 register(pkgName, statsGroupName, statsStruct) 99 } 100 101 // UnRegister a set of statistics. 102 // 103 // Once unregistered, the same or a different set of statistics can be 104 // registered using the same name. 105 // 106 func UnRegister(pkgName string, statsGroupName string) { 107 unRegister(pkgName, statsGroupName) 108 } 109 110 // Print one or more groups of statistics. 111 // 112 // The value of all statistics associated with pkgName and statsGroupName are 113 // returned as a string, with one statistic per line, according to the specified 114 // format. 115 // 116 // Use "*" to select all package names with a given group name, all 117 // groups with a given package name, or all groups. 118 // 119 func SprintStats(stringFmt StatStringFormat, pkgName string, statsGroupName string) (values string) { 120 return sprintStats(stringFmt, pkgName, statsGroupName) 121 } 122 123 // Total is a simple totaler. It supports the Totaler interface. 124 // 125 // Name must be unique within statistics in the structure. If it is "" then 126 // Register() will assign a name based on the name of the field. 127 // 128 type Total struct { 129 total uint64 // Ensure 64-bit alignment 130 Name string 131 } 132 133 func (this *Total) Add(value uint64) { 134 atomicAddUint64(&this.total, value) 135 } 136 137 func (this *Total) Increment() { 138 atomicAddUint64(&this.total, 1) 139 } 140 141 func (this *Total) TotalGet() uint64 { 142 return this.total 143 } 144 145 // Return a string with the statistic's value in the specified format. 146 // 147 func (this *Total) Sprint(stringFmt StatStringFormat, pkgName string, statsGroupName string) string { 148 return this.sprint(stringFmt, pkgName, statsGroupName) 149 } 150 151 // Average counts a number of items and their average size. It supports the 152 // Averager interface. 153 // 154 // Name must be unique within statistics in the structure. If it is "" then 155 // Register() will assign a name based on the name of the field. 156 // 157 type Average struct { 158 count uint64 // Ensure 64-bit alignment 159 total uint64 // Ensure 64-bit alignment 160 Name string 161 } 162 163 // Add a value to the mean statistics. 164 // 165 func (this *Average) Add(value uint64) { 166 atomicAddUint64(&this.total, value) 167 atomicAddUint64(&this.count, 1) 168 } 169 170 // Add a value of 1 to the mean statistics. 171 // 172 func (this *Average) Increment() { 173 this.Add(1) 174 } 175 176 func (this *Average) CountGet() uint64 { 177 return atomicLoadUint64(&this.count) 178 } 179 180 func (this *Average) TotalGet() uint64 { 181 return atomicLoadUint64(&this.total) 182 } 183 184 func (this *Average) AverageGet() uint64 { 185 return atomicLoadUint64(&this.total) / atomicLoadUint64(&this.count) 186 } 187 188 // Return a string with the statistic's value in the specified format. 189 // 190 func (this *Average) Sprint(stringFmt StatStringFormat, pkgName string, statsGroupName string) string { 191 return this.sprint(stringFmt, pkgName, statsGroupName) 192 } 193 194 // BucketLog2Round holds bucketized statistics where the stats value is placed in 195 // bucket N, determined by round(log2(value) + 1), where round() rounds to the 196 // nearest integar and value 0 goes in bucket 0 instead of negative infinity. 197 // 198 // NBucket determines the number of buckets and has a maximum value of 65 and a 199 // minimum value that is implementation defined (currently 9). If it is less 200 // then the minimum it will be changed to the minimum. If NBucket is not set it 201 // defaults to 65. It must be set before the statistic is registered and cannot 202 // be changed afterward. 203 // 204 // Name must be unique within statistics in the structure. If it is "" then 205 // Register() will assign a name based on the name of the field. 206 // 207 // Example mappings of values to buckets: 208 // 209 // Values Bucket 210 // 0 0 211 // 1 1 212 // 2 2 213 // 3 - 5 3 214 // 6 - 11 4 215 // 12 - 22 5 216 // etc. 217 // 218 // Note that value 2^n increments the count in bucket n + 1, but the average of 219 // values in bucket n is very slightly larger than 2^n. 220 // 221 type BucketLog2Round struct { 222 Name string 223 NBucket uint 224 statBuckets [65]uint32 225 } 226 227 func (this *BucketLog2Round) Add(value uint64) { 228 if value < 256 { 229 idx := log2RoundIdxTable[value] 230 atomic.AddUint32(&this.statBuckets[idx], 1) 231 return 232 } 233 234 bits := uint(bits.Len64(value)) 235 baseIdx := uint(log2RoundIdxTable[value>>(bits-8)]) 236 idx := baseIdx + bits - 8 237 if idx > this.NBucket-1 { 238 idx = this.NBucket - 1 239 } 240 241 atomic.AddUint32(&this.statBuckets[idx], 1) 242 return 243 } 244 245 // Add a value of 1 to the bucketized statistics. 246 // 247 func (this *BucketLog2Round) Increment() { 248 this.Add(1) 249 } 250 251 func (this *BucketLog2Round) CountGet() uint64 { 252 _, _, count, _, _ := bucketCalcStat(this.DistGet()) 253 return count 254 } 255 256 func (this *BucketLog2Round) TotalGet() uint64 { 257 _, _, _, total, _ := bucketCalcStat(this.DistGet()) 258 return total 259 } 260 261 func (this *BucketLog2Round) AverageGet() uint64 { 262 _, _, _, _, mean := bucketCalcStat(this.DistGet()) 263 return mean 264 } 265 266 // Return BucketInfo information for all the buckets. 267 // 268 func (this *BucketLog2Round) DistGet() []BucketInfo { 269 return bucketDistMake(this.NBucket, this.statBuckets[:], log2RoundBucketTable[:]) 270 } 271 272 // Return a string with the statistic's value in the specified format. 273 // 274 func (this *BucketLog2Round) Sprint(stringFmt StatStringFormat, pkgName string, statsGroupName string) string { 275 return bucketSprint(stringFmt, pkgName, statsGroupName, this.Name, this.DistGet()) 276 } 277 278 // BucketLogRoot2Round holds bucketized statistics where the stats value is 279 // placed bucket N determined by round(logRoot(2)(value), except that: 280 // 281 // value 0 goes in bucket 0 (instead of negative infinity) 282 // value 1 goes in bucket 1 (instead of 0) 283 // value 2 goes in bucket 2 (instead of 1) 284 // 285 // NBucket determines the number of buckets and has a maximum value of 128 and a 286 // minimum value that is implementation defined (currently 9). If it is less 287 // then the minimum it will be changed to the minimum. If NBucket is not set it 288 // defaults to 128. It must be set before the statistic is registered and cannot 289 // be changed afterward. 290 // 291 // Name must be unique within statistics in the structure. If it is "" then 292 // Register() will assign a name based on the name of the field. 293 // 294 // Example mappings of values to buckets: 295 // 296 // Values Bucket 297 // 0 0 298 // 1 1 299 // 2 2 300 // 3 3 301 // 4 4 302 // 5 - 6 5 303 // 7 - 9 6 304 // 10 - 13 7 305 // etc. 306 // 307 // Note that a value sqrt(2)^n increments the count in bucket 2 * n, but the 308 // average of values in bucket n is slightly larger than sqrt(2)^n. 309 // 310 type BucketLogRoot2Round struct { 311 Name string 312 NBucket uint 313 statBuckets [128]uint32 314 } 315 316 func (this *BucketLogRoot2Round) Add(value uint64) { 317 if value < 256 { 318 idx := logRoot2RoundIdxTable[value] 319 atomic.AddUint32(&this.statBuckets[idx], 1) 320 return 321 } 322 323 bits := uint(bits.Len64(value)) 324 baseIdx := uint(logRoot2RoundIdxTable[value>>(bits-8)]) 325 idx := baseIdx + (bits-8)*2 326 if idx > this.NBucket-1 { 327 idx = this.NBucket - 1 328 } 329 330 atomic.AddUint32(&this.statBuckets[idx], 1) 331 return 332 } 333 334 // Add a value of 1 to the bucketized statistics. 335 // 336 func (this *BucketLogRoot2Round) Increment() { 337 this.Add(1) 338 } 339 340 func (this *BucketLogRoot2Round) CountGet() uint64 { 341 _, _, count, _, _ := bucketCalcStat(this.DistGet()) 342 return count 343 } 344 345 func (this *BucketLogRoot2Round) TotalGet() uint64 { 346 _, _, _, total, _ := bucketCalcStat(this.DistGet()) 347 return total 348 } 349 350 func (this *BucketLogRoot2Round) AverageGet() uint64 { 351 _, _, _, _, mean := bucketCalcStat(this.DistGet()) 352 return mean 353 } 354 355 // Return BucketInfo information for all the buckets. 356 // 357 func (this *BucketLogRoot2Round) DistGet() []BucketInfo { 358 return bucketDistMake(this.NBucket, this.statBuckets[:], logRoot2RoundBucketTable[:]) 359 } 360 361 // Return a string with the statistic's value in the specified format. 362 // 363 func (this *BucketLogRoot2Round) Sprint(stringFmt StatStringFormat, pkgName string, statsGroupName string) string { 364 return bucketSprint(stringFmt, pkgName, statsGroupName, this.Name, this.DistGet()) 365 }