forked from Conflux-Chain/confura
/
percentage_time_window.go
164 lines (129 loc) · 3.81 KB
/
percentage_time_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
package metrics
import (
"container/list"
"sync"
"time"
"github.com/ethereum/go-ethereum/metrics"
)
// NewTimeWindowPercentage constructs a new time window Percentage.
func NewTimeWindowPercentage(slotInterval time.Duration, numSlots int) Percentage {
if slotInterval == 0 {
panic("slotInterval is zero")
}
if numSlots <= 1 {
panic("numSlots too small")
}
if !metrics.Enabled {
return &noopPercentage{}
}
return newTimeWindowPercentage(slotInterval, numSlots)
}
// GetOrRegisterTimeWindowPercentageDefault returns an existing Percentage or constructs and
// registers a new time window Percentage.
func GetOrRegisterTimeWindowPercentageDefault(name string, args ...interface{}) Percentage {
factory := func() Percentage {
return NewTimeWindowPercentage(time.Minute, 10)
}
return getOrRegisterPercentage(factory, name, args...)
}
// GetOrRegisterTimeWindowPercentage returns an existing Percentage or constructs and registers
// a new time window Percentage.
func GetOrRegisterTimeWindowPercentage(
slotInterval time.Duration, numSlots int, name string, args ...interface{},
) Percentage {
factory := func() Percentage {
return NewTimeWindowPercentage(slotInterval, numSlots)
}
return getOrRegisterPercentage(factory, name, args...)
}
type slot struct {
percentageData
endTime time.Time // end time to update percentage data
expireAt time.Time // time to remove the slot
}
// timeWindowPercentage implements Percentage interface to record recent percentage.
type timeWindowPercentage struct {
window percentageData
slots *list.List
slotInterval time.Duration
windowInterval time.Duration
mu sync.Mutex
}
func newTimeWindowPercentage(slotInterval time.Duration, numSlots int) *timeWindowPercentage {
return &timeWindowPercentage{
slots: list.New(),
slotInterval: slotInterval,
windowInterval: slotInterval * time.Duration(numSlots),
}
}
// expire removes expired slots.
func (p *timeWindowPercentage) expire(now time.Time) {
for {
// time window is empty
front := p.slots.Front()
if front == nil {
return
}
// not expired yet
slot := front.Value.(*slot)
if slot.expireAt.After(now) {
return
}
// updates when slot expired
p.slots.Remove(front)
p.window.total -= slot.total
p.window.marks -= slot.marks
}
}
// addNewSlot always appends a new slot to time window.
func (p *timeWindowPercentage) addNewSlot(now time.Time) *slot {
slotStartTime := now.Truncate(p.slotInterval)
newSlot := &slot{
endTime: slotStartTime.Add(p.slotInterval),
expireAt: slotStartTime.Add(p.windowInterval),
}
p.slots.PushBack(newSlot)
return newSlot
}
// getOrAddSlot gets the last slot or adds a new slot if the last one out of date.
func (p *timeWindowPercentage) getOrAddSlot(now time.Time) *slot {
// time window is empty
if p.slots.Len() == 0 {
return p.addNewSlot(now)
}
// last slot is not out of date
lastSlot := p.slots.Back().Value.(*slot)
if lastSlot.endTime.After(now) {
return lastSlot
}
// otherwise, add new slot
return p.addNewSlot(now)
}
func (p *timeWindowPercentage) Mark(marked bool) {
p.mu.Lock()
defer p.mu.Unlock()
// remove expired slots
now := time.Now()
p.expire(now)
// prepare slot to update
currentSlot := p.getOrAddSlot(now)
currentSlot.update(marked)
p.window.update(marked)
}
// Value implements the metrics.GaugeFloat64 interface.
func (p *timeWindowPercentage) Value() float64 {
p.mu.Lock()
defer p.mu.Unlock()
// remove expired slots
now := time.Now()
p.expire(now)
return p.window.value()
}
// Update implements the metrics.GaugeFloat64 interface.
func (p *timeWindowPercentage) Update(float64) {
panic("Update called on a timeWindowPercentage")
}
// Snapshot implements the metrics.GaugeFloat64 interface.
func (p *timeWindowPercentage) Snapshot() metrics.GaugeFloat64 {
return metrics.GaugeFloat64Snapshot(p.Value())
}