-
Notifications
You must be signed in to change notification settings - Fork 109
/
window.go
174 lines (147 loc) · 4.06 KB
/
window.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
package circuit
import (
"container/ring"
"sync"
"time"
"github.com/facebookgo/clock"
)
var (
// DefaultWindowTime is the default time the window covers, 10 seconds.
DefaultWindowTime = time.Millisecond * 10000
// DefaultWindowBuckets is the default number of buckets the window holds, 10.
DefaultWindowBuckets = 10
)
// bucket holds counts of failures and successes
type bucket struct {
failure int64
success int64
}
// Reset resets the counts to 0
func (b *bucket) Reset() {
b.failure = 0
b.success = 0
}
// Fail increments the failure count
func (b *bucket) Fail() {
b.failure++
}
// Sucecss increments the success count
func (b *bucket) Success() {
b.success++
}
// window maintains a ring of buckets and increments the failure and success
// counts of the current bucket. Once a specified time has elapsed, it will
// advance to the next bucket, reseting its counts. This allows the keeping of
// rolling statistics on the counts.
type window struct {
buckets *ring.Ring
bucketTime time.Duration
bucketLock sync.RWMutex
lastAccess time.Time
clock clock.Clock
}
// newWindow 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 newWindow(windowTime time.Duration, windowBuckets int) *window {
buckets := ring.New(windowBuckets)
for i := 0; i < buckets.Len(); i++ {
buckets.Value = &bucket{}
buckets = buckets.Next()
}
clock := clock.New()
bucketTime := time.Duration(windowTime.Nanoseconds() / int64(windowBuckets))
return &window{
buckets: buckets,
bucketTime: bucketTime,
clock: clock,
lastAccess: clock.Now(),
}
}
// Fail records a failure in the current bucket.
func (w *window) Fail() {
w.bucketLock.Lock()
b := w.getLatestBucket()
b.Fail()
w.bucketLock.Unlock()
}
// Success records a success in the current bucket.
func (w *window) Success() {
w.bucketLock.Lock()
b := w.getLatestBucket()
b.Success()
w.bucketLock.Unlock()
}
// Failures returns the total number of failures recorded in all buckets.
func (w *window) Failures() int64 {
w.bucketLock.RLock()
var failures int64
w.buckets.Do(func(x interface{}) {
b := x.(*bucket)
failures += b.failure
})
w.bucketLock.RUnlock()
return failures
}
// Successes returns the total number of successes recorded in all buckets.
func (w *window) Successes() int64 {
w.bucketLock.RLock()
var successes int64
w.buckets.Do(func(x interface{}) {
b := x.(*bucket)
successes += b.success
})
w.bucketLock.RUnlock()
return successes
}
// ErrorRate returns the error rate calculated over all buckets, expressed as
// a floating point number (e.g. 0.9 for 90%)
func (w *window) ErrorRate() float64 {
var total int64
var failures int64
w.bucketLock.RLock()
w.buckets.Do(func(x interface{}) {
b := x.(*bucket)
total += b.failure + b.success
failures += b.failure
})
w.bucketLock.RUnlock()
if total == 0 {
return 0.0
}
return float64(failures) / float64(total)
}
// Reset resets the count of all buckets.
func (w *window) Reset() {
w.bucketLock.Lock()
w.buckets.Do(func(x interface{}) {
x.(*bucket).Reset()
})
w.bucketLock.Unlock()
}
// getLatestBucket returns the current bucket. If the bucket time has elapsed
// it will move to the next bucket, resetting its counts and updating the last
// access time before returning it. getLatestBucket assumes that the caller has
// locked the bucketLock
func (w *window) getLatestBucket() *bucket {
var b *bucket
b = w.buckets.Value.(*bucket)
elapsed := w.clock.Now().Sub(w.lastAccess)
if elapsed > w.bucketTime {
// Reset the buckets between now and number of buckets ago. If
// that is more that the existing buckets, reset all.
for i := 0; i < w.buckets.Len(); i++ {
w.buckets = w.buckets.Next()
b = w.buckets.Value.(*bucket)
b.Reset()
elapsed = time.Duration(int64(elapsed) - int64(w.bucketTime))
if elapsed < w.bucketTime {
// Done resetting buckets.
break
}
}
w.lastAccess = w.clock.Now()
}
return b
}