156 lines
3.6 KiB
Go
156 lines
3.6 KiB
Go
|
package memmetrics
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"time"
|
||
|
|
||
|
"github.com/mailgun/timetools"
|
||
|
)
|
||
|
|
||
|
type rcOptSetter func(*RollingCounter) error
|
||
|
|
||
|
func CounterClock(c timetools.TimeProvider) rcOptSetter {
|
||
|
return func(r *RollingCounter) error {
|
||
|
r.clock = c
|
||
|
return nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Calculates in memory failure rate of an endpoint using rolling window of a predefined size
|
||
|
type RollingCounter struct {
|
||
|
clock timetools.TimeProvider
|
||
|
resolution time.Duration
|
||
|
values []int
|
||
|
countedBuckets int // how many samples in different buckets have we collected so far
|
||
|
lastBucket int // last recorded bucket
|
||
|
lastUpdated time.Time
|
||
|
}
|
||
|
|
||
|
// NewCounter creates a counter with fixed amount of buckets that are rotated every resolution period.
|
||
|
// E.g. 10 buckets with 1 second means that every new second the bucket is refreshed, so it maintains 10 second rolling window.
|
||
|
// By default creates a bucket with 10 buckets and 1 second resolution
|
||
|
func NewCounter(buckets int, resolution time.Duration, options ...rcOptSetter) (*RollingCounter, error) {
|
||
|
if buckets <= 0 {
|
||
|
return nil, fmt.Errorf("Buckets should be >= 0")
|
||
|
}
|
||
|
if resolution < time.Second {
|
||
|
return nil, fmt.Errorf("Resolution should be larger than a second")
|
||
|
}
|
||
|
|
||
|
rc := &RollingCounter{
|
||
|
lastBucket: -1,
|
||
|
resolution: resolution,
|
||
|
|
||
|
values: make([]int, buckets),
|
||
|
}
|
||
|
|
||
|
for _, o := range options {
|
||
|
if err := o(rc); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if rc.clock == nil {
|
||
|
rc.clock = &timetools.RealTime{}
|
||
|
}
|
||
|
|
||
|
return rc, nil
|
||
|
}
|
||
|
|
||
|
func (c *RollingCounter) Append(o *RollingCounter) error {
|
||
|
c.Inc(int(o.Count()))
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (c *RollingCounter) Clone() *RollingCounter {
|
||
|
c.cleanup()
|
||
|
other := &RollingCounter{
|
||
|
resolution: c.resolution,
|
||
|
values: make([]int, len(c.values)),
|
||
|
clock: c.clock,
|
||
|
lastBucket: c.lastBucket,
|
||
|
lastUpdated: c.lastUpdated,
|
||
|
}
|
||
|
for i, v := range c.values {
|
||
|
other.values[i] = v
|
||
|
}
|
||
|
return other
|
||
|
}
|
||
|
|
||
|
func (c *RollingCounter) Reset() {
|
||
|
c.lastBucket = -1
|
||
|
c.countedBuckets = 0
|
||
|
c.lastUpdated = time.Time{}
|
||
|
for i := range c.values {
|
||
|
c.values[i] = 0
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (c *RollingCounter) CountedBuckets() int {
|
||
|
return c.countedBuckets
|
||
|
}
|
||
|
|
||
|
func (c *RollingCounter) Count() int64 {
|
||
|
c.cleanup()
|
||
|
return c.sum()
|
||
|
}
|
||
|
|
||
|
func (c *RollingCounter) Resolution() time.Duration {
|
||
|
return c.resolution
|
||
|
}
|
||
|
|
||
|
func (c *RollingCounter) Buckets() int {
|
||
|
return len(c.values)
|
||
|
}
|
||
|
|
||
|
func (c *RollingCounter) WindowSize() time.Duration {
|
||
|
return time.Duration(len(c.values)) * c.resolution
|
||
|
}
|
||
|
|
||
|
func (c *RollingCounter) Inc(v int) {
|
||
|
c.cleanup()
|
||
|
c.incBucketValue(v)
|
||
|
}
|
||
|
|
||
|
func (c *RollingCounter) incBucketValue(v int) {
|
||
|
now := c.clock.UtcNow()
|
||
|
bucket := c.getBucket(now)
|
||
|
c.values[bucket] += v
|
||
|
c.lastUpdated = now
|
||
|
// Update usage stats if we haven't collected enough data
|
||
|
if c.countedBuckets < len(c.values) {
|
||
|
// Only update if we have advanced to the next bucket and not incremented the value
|
||
|
// in the current bucket.
|
||
|
if c.lastBucket != bucket {
|
||
|
c.lastBucket = bucket
|
||
|
c.countedBuckets++
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Returns the number in the moving window bucket that this slot occupies
|
||
|
func (c *RollingCounter) getBucket(t time.Time) int {
|
||
|
return int(t.Truncate(c.resolution).Unix() % int64(len(c.values)))
|
||
|
}
|
||
|
|
||
|
// Reset buckets that were not updated
|
||
|
func (c *RollingCounter) cleanup() {
|
||
|
now := c.clock.UtcNow()
|
||
|
for i := 0; i < len(c.values); i++ {
|
||
|
now = now.Add(time.Duration(-1*i) * c.resolution)
|
||
|
if now.Truncate(c.resolution).After(c.lastUpdated.Truncate(c.resolution)) {
|
||
|
c.values[c.getBucket(now)] = 0
|
||
|
} else {
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (c *RollingCounter) sum() int64 {
|
||
|
out := int64(0)
|
||
|
for _, v := range c.values {
|
||
|
out += int64(v)
|
||
|
}
|
||
|
return out
|
||
|
}
|