forked from orijtech/groupcache
-
Notifications
You must be signed in to change notification settings - Fork 12
/
hotcache.go
144 lines (120 loc) · 3.43 KB
/
hotcache.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
/*
Copyright 2019 Vimeo Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package galaxycache
import (
"sync"
"time"
"github.com/vimeo/galaxycache/promoter"
)
// update the hotcache stats if at least one second has passed since
// last update
func (g *Galaxy) maybeUpdateHotCacheStats() {
g.mu.Lock()
defer g.mu.Unlock()
now := g.clock.Now()
if now.Sub(g.hcStatsWithTime.t) < time.Second {
return
}
nowRel := now.Sub(g.baseTime)
mruEleQPS := 0.0
lruEleQPS := 0.0
mruEle := g.hotCache.mostRecent()
lruEle := g.hotCache.leastRecent()
if mruEle != nil { // lru contains at least one element
_, mruEleQPS = mruEle.stats.val(nowRel)
_, lruEleQPS = lruEle.stats.val(nowRel)
}
newHCS := &promoter.HCStats{
MostRecentQPS: mruEleQPS,
LeastRecentQPS: lruEleQPS,
HCSize: (g.cacheBytes / g.opts.hcRatio) - g.hotCache.bytes(),
HCCapacity: g.cacheBytes / g.opts.hcRatio,
}
g.hcStatsWithTime.hcs = newHCS
g.hcStatsWithTime.t = now
}
// keyStats keeps track of the hotness of a key
type keyStats struct {
dQPS windowedAvgQPS
}
func (g *Galaxy) newValWithStat(data []byte, kStats *keyStats) valWithStat {
if kStats == nil {
kStats = &keyStats{
dQPS: windowedAvgQPS{
trackEpoch: g.now(),
lastRef: g.now(),
count: 0,
},
}
}
return valWithStat{
data: data,
stats: kStats,
}
}
func (k *keyStats) val(now time.Duration) (int64, float64) {
return k.dQPS.val(now)
}
func (k *keyStats) touch(resetIdleAge, now time.Duration) {
k.dQPS.touch(resetIdleAge, now)
}
type windowedAvgQPS struct {
// time used for the denominator of the calculation
// measured relative to the galaxy baseTime
trackEpoch time.Duration
// last time the count was updated
lastRef time.Duration
count int64
// protects all above
mu sync.Mutex
}
func (a *windowedAvgQPS) touch(resetIdleAge, now time.Duration) {
a.mu.Lock()
defer a.mu.Unlock()
// if the last touch was longer ago than resetIdleAge, pretend this is
// a new entry.
// This protects against the case where an entry manages to hang in the
// cache while idle but only gets hit hard periodically.
if now-a.lastRef > resetIdleAge {
a.trackEpoch = now
a.lastRef = now
a.count = 1
return
}
if a.lastRef < now {
// another goroutine may have grabbed a later "now" value
// before acquiring this lock before this one.
// Try not to let the timestamp go backwards due to races.
a.lastRef = now
}
a.count++
}
func (a *windowedAvgQPS) val(now time.Duration) (int64, float64) {
a.mu.Lock()
defer a.mu.Unlock()
age := now - a.trackEpoch
// Set a small minimum interval so one request that was super-recent
// doesn't give a huge rate.
const minInterval = 100 * time.Millisecond
if age < minInterval {
age = minInterval
}
return a.count, float64(a.count) / age.Seconds()
}
func (g *Galaxy) addNewToCandidateCache(key string) *keyStats {
kStats := &keyStats{
dQPS: windowedAvgQPS{trackEpoch: g.now()},
}
g.candidateCache.addToCandidateCache(key, kStats)
return kStats
}