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  }