You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
129 lines
2.4 KiB
129 lines
2.4 KiB
package summary |
|
|
|
import ( |
|
"sync" |
|
"time" |
|
) |
|
|
|
type bucket struct { |
|
val int64 |
|
count int64 |
|
next *bucket |
|
} |
|
|
|
func (b *bucket) Add(val int64) { |
|
b.val += val |
|
b.count++ |
|
} |
|
|
|
func (b *bucket) Value() (int64, int64) { |
|
return b.val, b.count |
|
} |
|
|
|
func (b *bucket) Reset() { |
|
b.val = 0 |
|
b.count = 0 |
|
} |
|
|
|
// Summary is a summary interface. |
|
type Summary interface { |
|
Add(int64) |
|
Reset() |
|
Value() (val int64, cnt int64) |
|
} |
|
|
|
type summary struct { |
|
mu sync.RWMutex |
|
buckets []bucket |
|
bucketTime int64 |
|
lastAccess int64 |
|
cur *bucket |
|
} |
|
|
|
// New new a summary. |
|
// |
|
// use RollingCounter creates a new window. windowTime is the time covering the entire |
|
// window. windowBuckets is the number of buckets the window is divided into. |
|
// An example: a 10 second window with 10 buckets will have 10 buckets covering |
|
// 1 second each. |
|
func New(window time.Duration, winBucket int) Summary { |
|
buckets := make([]bucket, winBucket) |
|
bucket := &buckets[0] |
|
for i := 1; i < winBucket; i++ { |
|
bucket.next = &buckets[i] |
|
bucket = bucket.next |
|
} |
|
bucket.next = &buckets[0] |
|
bucketTime := time.Duration(window.Nanoseconds() / int64(winBucket)) |
|
return &summary{ |
|
cur: &buckets[0], |
|
buckets: buckets, |
|
bucketTime: int64(bucketTime), |
|
lastAccess: time.Now().UnixNano(), |
|
} |
|
} |
|
|
|
// Add increments the summary by value. |
|
func (s *summary) Add(val int64) { |
|
s.mu.Lock() |
|
s.lastBucket().Add(val) |
|
s.mu.Unlock() |
|
} |
|
|
|
// Value get the summary value and count. |
|
func (s *summary) Value() (val int64, cnt int64) { |
|
now := time.Now().UnixNano() |
|
s.mu.RLock() |
|
b := s.cur |
|
i := s.elapsed(now) |
|
for j := 0; j < len(s.buckets); j++ { |
|
// skip all future reset bucket. |
|
if i > 0 { |
|
i-- |
|
} else { |
|
v, c := b.Value() |
|
val += v |
|
cnt += c |
|
} |
|
b = b.next |
|
} |
|
s.mu.RUnlock() |
|
return |
|
} |
|
|
|
// Reset reset the counter. |
|
func (s *summary) Reset() { |
|
s.mu.Lock() |
|
for i := range s.buckets { |
|
s.buckets[i].Reset() |
|
} |
|
s.mu.Unlock() |
|
} |
|
|
|
func (s *summary) elapsed(now int64) (i int) { |
|
var e int64 |
|
if e = now - s.lastAccess; e <= s.bucketTime { |
|
return |
|
} |
|
if i = int(e / s.bucketTime); i > len(s.buckets) { |
|
i = len(s.buckets) |
|
} |
|
return |
|
} |
|
|
|
func (s *summary) lastBucket() (b *bucket) { |
|
now := time.Now().UnixNano() |
|
b = s.cur |
|
// reset the buckets between now and number of buckets ago. If |
|
// that is more that the existing buckets, reset all. |
|
if i := s.elapsed(now); i > 0 { |
|
s.lastAccess = now |
|
for ; i > 0; i-- { |
|
// replace the next used bucket. |
|
b = b.next |
|
b.Reset() |
|
} |
|
} |
|
s.cur = b |
|
return |
|
}
|
|
|