gitee.com/gricks/utils@v1.0.8/metrics.go (about)

     1  package utils
     2  
     3  import (
     4  	"expvar"
     5  	"math"
     6  	"strconv"
     7  	"sync"
     8  	"sync/atomic"
     9  )
    10  
    11  // Counter describes a metric that accumulates values monotonically.
    12  type Counter struct {
    13  	v uint64
    14  }
    15  
    16  func (this *Counter) Set(v uint64) {
    17  	atomic.StoreUint64(&this.v, v)
    18  }
    19  
    20  func (this *Counter) Inc() {
    21  	this.Add(1)
    22  }
    23  
    24  func (this *Counter) Add(v uint64) {
    25  	atomic.AddUint64(&this.v, v)
    26  }
    27  
    28  func (this *Counter) Value() uint64 {
    29  	return atomic.LoadUint64(&this.v)
    30  }
    31  
    32  func (this *Counter) String() string {
    33  	return strconv.FormatUint(this.Value(), 10)
    34  }
    35  
    36  // Gauge describes a metric that takes specific values over time.
    37  type Gauge struct {
    38  	v expvar.Float
    39  }
    40  
    41  func (this *Gauge) Set(v float64) {
    42  	this.v.Set(v)
    43  }
    44  
    45  func (this *Gauge) Add(v float64) {
    46  	this.v.Add(v)
    47  }
    48  
    49  func (this *Gauge) Value() float64 {
    50  	return this.v.Value()
    51  }
    52  
    53  func (this *Gauge) String() string {
    54  	return this.v.String()
    55  }
    56  
    57  // bucket store the count between value and next.value. [value, next.value)
    58  type bucket struct {
    59  	next  *bucket
    60  	prev  *bucket
    61  	value float64
    62  	count float64
    63  }
    64  
    65  func (this *bucket) Next() *bucket { return this.next }
    66  func (this *bucket) Prev() *bucket { return this.prev }
    67  
    68  type bucketList struct {
    69  	head *bucket
    70  	tail *bucket
    71  	size int
    72  }
    73  
    74  func (this *bucketList) Reset()         { this.head, this.tail, this.size = nil, nil, 0 }
    75  func (this *bucketList) Empty() bool    { return this.head == nil }
    76  func (this *bucketList) Front() *bucket { return this.head }
    77  func (this *bucketList) Back() *bucket  { return this.tail }
    78  func (this *bucketList) Size() int      { return this.size }
    79  
    80  func (this *bucketList) PushFront(e *bucket) {
    81  	e.next = this.head
    82  	e.prev = nil
    83  
    84  	if this.head != nil {
    85  		this.head.prev = e
    86  	} else {
    87  		this.tail = e
    88  	}
    89  
    90  	this.head = e
    91  	this.size++
    92  }
    93  
    94  func (this *bucketList) PushBack(e *bucket) {
    95  	e.next = nil
    96  	e.prev = this.tail
    97  
    98  	if this.tail != nil {
    99  		this.tail.next = e
   100  	} else {
   101  		this.head = e
   102  	}
   103  
   104  	this.tail = e
   105  	this.size++
   106  }
   107  
   108  func (this *bucketList) PushBackList(l *bucketList) {
   109  	if this.head == nil {
   110  		this.head = l.head
   111  		this.tail = l.tail
   112  	} else if l.head != nil {
   113  		this.tail.next = l.head
   114  		l.head.prev = this.tail
   115  		this.tail = l.tail
   116  	}
   117  	l.head = nil
   118  	l.tail = nil
   119  	this.size += l.Size()
   120  }
   121  
   122  func (this *bucketList) InsertAfter(b, e *bucket) {
   123  	a := b.next
   124  	e.next = a
   125  	e.prev = b
   126  	b.next = e
   127  
   128  	if a != nil {
   129  		a.prev = e
   130  	} else {
   131  		this.tail = e
   132  	}
   133  	this.size++
   134  }
   135  
   136  func (this *bucketList) InsertBefore(a, e *bucket) {
   137  	b := a.prev
   138  	e.next = a
   139  	e.prev = b
   140  	a.prev = e
   141  
   142  	if b != nil {
   143  		b.next = e
   144  	} else {
   145  		this.head = e
   146  	}
   147  	this.size++
   148  }
   149  
   150  func (this *bucketList) Remove(e *bucket) {
   151  	prev := e.prev
   152  	next := e.next
   153  
   154  	if prev != nil {
   155  		prev.next = next
   156  	} else {
   157  		this.head = next
   158  	}
   159  
   160  	if next != nil {
   161  		next.prev = prev
   162  	} else {
   163  		this.tail = prev
   164  	}
   165  	this.size--
   166  }
   167  
   168  // Histogram implements the streaming approximate histogram metric
   169  // See more: Apache Hive histogram_numeric & gohistogram
   170  type Histogram struct {
   171  	mtx   sync.Mutex
   172  	l     bucketList
   173  	m     int // max bucket
   174  	total int
   175  }
   176  
   177  // NewHistogram returns a Histogram object with the given name and number of
   178  // buckets in the underlying histogram object.
   179  // 50 is a good default number of buckets.
   180  func NewHistogram(bucket int) *Histogram {
   181  	if bucket < 10 {
   182  		bucket = 10
   183  	}
   184  	return &Histogram{
   185  		m: bucket,
   186  	}
   187  }
   188  
   189  func (this *Histogram) Add(v float64) {
   190  	this.mtx.Lock()
   191  	this.add(v)
   192  	this.merge()
   193  	this.mtx.Unlock()
   194  }
   195  
   196  func (this *Histogram) Addn(vs []float64) {
   197  	this.mtx.Lock()
   198  	for _, v := range vs {
   199  		this.add(v)
   200  	}
   201  	this.merge()
   202  	this.mtx.Unlock()
   203  }
   204  
   205  func (this *Histogram) add(v float64) {
   206  	this.total++
   207  	for e := this.l.Front(); e != nil; e = e.Next() {
   208  		switch {
   209  		case e.value == v:
   210  			e.count++
   211  			return
   212  		case e.value > v:
   213  			this.l.InsertBefore(e, &bucket{value: v, count: 1})
   214  			return
   215  		}
   216  	}
   217  	this.l.PushBack(&bucket{value: v, count: 1})
   218  }
   219  
   220  // merge adjacent bins to decrease the bin count to the maximum value
   221  func (this *Histogram) merge() {
   222  	for this.l.Size() > this.m {
   223  		var (
   224  			minDelta       = math.MaxFloat64
   225  			minDeltaBucket *bucket
   226  		)
   227  
   228  		// Find closest bucket in terms of value
   229  		for e := this.l.Front().Next(); e != nil; e = e.Next() {
   230  			if delta := e.value - e.Prev().value; delta < minDelta {
   231  				minDelta = delta
   232  				minDeltaBucket = e
   233  			}
   234  		}
   235  
   236  		// Merge minDeltaBucket and its Prev()
   237  		count := minDeltaBucket.count + minDeltaBucket.Prev().count
   238  		value := (minDeltaBucket.value*minDeltaBucket.count +
   239  			minDeltaBucket.Prev().value*minDeltaBucket.Prev().count) / count
   240  		minDeltaBucket.count = count
   241  		minDeltaBucket.value = value
   242  
   243  		// Remove
   244  		this.l.Remove(minDeltaBucket.Prev())
   245  	}
   246  }
   247  
   248  // Quantile returns the value of the quantile q, 0.0 < q < 1.0.
   249  func (this *Histogram) Quantile(q float64) float64 {
   250  	this.mtx.Lock()
   251  	defer this.mtx.Unlock()
   252  	count := q * float64(this.total)
   253  	for e := this.l.Front(); e != nil; e = e.Next() {
   254  		count -= e.count
   255  		if count <= 0 {
   256  			return e.value
   257  		}
   258  	}
   259  	return -1
   260  }
   261  
   262  // Mean returns the sample mean of the distribution
   263  func (this *Histogram) Mean() float64 {
   264  	this.mtx.Lock()
   265  	defer this.mtx.Unlock()
   266  	if this.total == 0 {
   267  		return 0.0
   268  	}
   269  	sum := 0.0
   270  	for e := this.l.Front(); e != nil; e = e.Next() {
   271  		sum += e.value * e.count
   272  	}
   273  	return sum / float64(this.total)
   274  }
   275  
   276  func (this *Histogram) Count() int {
   277  	this.mtx.Lock()
   278  	defer this.mtx.Unlock()
   279  	return this.total
   280  }