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  }