github.com/hasnat/dolt/go@v0.0.0-20210628190320-9eb5d843fbb7/store/metrics/histogram.go (about)

     1  // Copyright 2019 Dolthub, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  //
    15  // This file incorporates work covered by the following copyright and
    16  // permission notice:
    17  //
    18  // Copyright 2017 Attic Labs, Inc. All rights reserved.
    19  // Licensed under the Apache License, version 2.0:
    20  // http://www.apache.org/licenses/LICENSE-2.0
    21  
    22  package metrics
    23  
    24  import (
    25  	"fmt"
    26  	"strconv"
    27  	"sync/atomic"
    28  	"time"
    29  
    30  	"github.com/dustin/go-humanize"
    31  
    32  	"github.com/dolthub/dolt/go/store/d"
    33  )
    34  
    35  // Histogram is a shameless and low-rent knock of the chromium project's
    36  // histogram:
    37  //   https://chromium.googlesource.com/chromium/src/base/+/master/metrics/histogram.h
    38  //
    39  // It logically stores a running histogram of uint64 values and shares some
    40  // important features of its inspiration:
    41  //   * It acccepts a correctness deficit in return for not needing to lock.
    42  //     IOW, concurrent calls to Sample may clobber each other.
    43  //   * It trades compactness and ease of arithmatic across histograms for
    44  //     precision. Samples lose precision up to the range of the values which
    45  //     are stored in a bucket
    46  //
    47  // Only implemented: Log2-based histogram
    48  
    49  const bucketCount = 64
    50  
    51  type HistogramType uint64
    52  
    53  const (
    54  	UnspecifiedHistogram HistogramType = iota
    55  	TimeHistogram
    56  	ByteHistogram
    57  )
    58  
    59  type Histogram struct {
    60  	// this structure needs to be a multiple of 8 bytes in size. This is necessary for 32-bit architectures and
    61  	// guarantees 8 byte alignment for multiple Histograms laid out side by side in memory.
    62  	sum      uint64
    63  	buckets  [bucketCount]uint64
    64  	histType HistogramType
    65  }
    66  
    67  // Sample adds a uint64 data point to the histogram
    68  func (h *Histogram) Sample(v uint64) {
    69  	d.PanicIfTrue(v == 0)
    70  
    71  	atomic.AddUint64(&h.sum, v)
    72  
    73  	pot := 0
    74  	for v > 0 {
    75  		v = v >> 1
    76  		pot++
    77  	}
    78  
    79  	atomic.AddUint64(&h.buckets[pot-1], 1)
    80  }
    81  
    82  func (h *Histogram) Clone() *Histogram {
    83  	n := &Histogram{histType: h.histType}
    84  	n.Add(h)
    85  	return n
    86  }
    87  
    88  // SampleTimeSince is a convenience wrapper around Sample which takes the
    89  // duration since |t|, if 0, rounds to 1 and passes to Sample() as an uint64
    90  // number of nanoseconds.
    91  func (h *Histogram) SampleTimeSince(t time.Time) {
    92  	d := time.Since(t)
    93  	if d == 0 {
    94  		d = 1
    95  	}
    96  	h.Sample(uint64(d))
    97  }
    98  
    99  // SampleLen is a convenience wrapper around Sample which internally type
   100  // asserts the int to a uint64
   101  func (h *Histogram) SampleLen(l int) {
   102  	h.Sample(uint64(l))
   103  }
   104  
   105  func (h Histogram) bucketVal(bucket int) uint64 {
   106  	return 1 << (uint64(bucket))
   107  }
   108  
   109  // Sum return the sum of sampled values, note that Sum can be overflowed without
   110  // overflowing the histogram buckets.
   111  func (h Histogram) Sum() uint64 {
   112  	return atomic.LoadUint64(&h.sum)
   113  }
   114  
   115  // Add returns a new Histogram which is the result of adding this and other
   116  // bucket-wise.
   117  func (h *Histogram) Add(other *Histogram) {
   118  	atomic.AddUint64(&h.sum, atomic.LoadUint64(&other.sum))
   119  
   120  	for i := 0; i < bucketCount; i++ {
   121  		atomic.AddUint64(&h.buckets[i], atomic.LoadUint64(&other.buckets[i]))
   122  	}
   123  }
   124  
   125  // Mean returns 0 if there are no samples, and h.Sum()/h.Samples otherwise.
   126  func (h Histogram) Mean() uint64 {
   127  	samples := h.Samples()
   128  	if samples == 0 {
   129  		return 0
   130  	}
   131  
   132  	return h.Sum() / samples
   133  }
   134  
   135  // Samples returns the number of samples contained in the histogram
   136  func (h Histogram) Samples() uint64 {
   137  	s := uint64(0)
   138  	for i := 0; i < bucketCount; i++ {
   139  		s += atomic.LoadUint64(&h.buckets[i])
   140  	}
   141  	return s
   142  }
   143  
   144  func uintToString(v uint64) string {
   145  	return strconv.FormatUint(v, 10)
   146  }
   147  
   148  func timeToString(v uint64) string {
   149  	return time.Duration(v).String()
   150  }
   151  
   152  func (h Histogram) String() string {
   153  	var f func(uint64) string
   154  	switch h.histType {
   155  	case UnspecifiedHistogram:
   156  		f = uintToString
   157  	case ByteHistogram:
   158  		f = humanize.Bytes
   159  	case TimeHistogram:
   160  		f = timeToString
   161  	}
   162  
   163  	return fmt.Sprintf("Mean: %s, Sum: %s, Samples: %d", f(h.Mean()), f(h.Sum()), h.Samples())
   164  }
   165  
   166  func NewTimeHistogram() Histogram {
   167  	return Histogram{histType: TimeHistogram}
   168  }
   169  
   170  // NewByteHistogram stringifies values using humanize over byte values
   171  func NewByteHistogram() Histogram {
   172  	return Histogram{histType: ByteHistogram}
   173  }