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