mirror of
https://github.com/weaveworks/scope.git
synced 2026-03-05 11:11:13 +00:00
249 lines
4.7 KiB
Go
249 lines
4.7 KiB
Go
package gcache
|
|
|
|
import (
|
|
"container/list"
|
|
"time"
|
|
)
|
|
|
|
// Discards the least frequently used items first.
|
|
type LFUCache struct {
|
|
baseCache
|
|
items map[interface{}]*lfuItem
|
|
freqList *list.List // list for freqEntry
|
|
}
|
|
|
|
func newLFUCache(cb *CacheBuilder) *LFUCache {
|
|
c := &LFUCache{}
|
|
buildCache(&c.baseCache, cb)
|
|
|
|
c.freqList = list.New()
|
|
c.items = make(map[interface{}]*lfuItem, c.size+1)
|
|
c.freqList.PushFront(&freqEntry{
|
|
freq: 0,
|
|
items: make(map[*lfuItem]byte),
|
|
})
|
|
return c
|
|
}
|
|
|
|
// set a new key-value pair
|
|
func (c *LFUCache) Set(key, value interface{}) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
c.set(key, value)
|
|
}
|
|
|
|
func (c *LFUCache) set(key, value interface{}) (*lfuItem, error) {
|
|
// Check for existing item
|
|
item, ok := c.items[key]
|
|
if ok {
|
|
item.value = value
|
|
} else {
|
|
// Verify size not exceeded
|
|
if len(c.items) >= c.size {
|
|
c.evict(1)
|
|
}
|
|
item = &lfuItem{
|
|
key: key,
|
|
value: value,
|
|
freqElement: nil,
|
|
}
|
|
el := c.freqList.Front()
|
|
fe := el.Value.(*freqEntry)
|
|
fe.items[item] = 1
|
|
|
|
item.freqElement = el
|
|
c.items[key] = item
|
|
}
|
|
|
|
if c.expiration != nil {
|
|
t := time.Now().Add(*c.expiration)
|
|
item.expiration = &t
|
|
}
|
|
|
|
if c.addedFunc != nil {
|
|
go (*c.addedFunc)(key, value)
|
|
}
|
|
|
|
return item, nil
|
|
}
|
|
|
|
// Get a value from cache pool using key if it exists.
|
|
// If it dose not exists key and has LoaderFunc,
|
|
// generate a value using `LoaderFunc` method returns value.
|
|
func (c *LFUCache) Get(key interface{}) (interface{}, error) {
|
|
c.mu.RLock()
|
|
item, ok := c.items[key]
|
|
c.mu.RUnlock()
|
|
|
|
if ok {
|
|
if !item.IsExpired(nil) {
|
|
c.mu.Lock()
|
|
c.increment(item)
|
|
c.mu.Unlock()
|
|
return item.value, nil
|
|
}
|
|
c.mu.Lock()
|
|
c.removeItem(item)
|
|
c.mu.Unlock()
|
|
}
|
|
|
|
if c.loaderFunc == nil {
|
|
return nil, NotFoundKeyError
|
|
}
|
|
|
|
it, err := c.load(key, func(v interface{}, e error) (interface{}, error) {
|
|
if e == nil {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
return c.set(key, v)
|
|
}
|
|
return nil, e
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
li := it.(*lfuItem)
|
|
c.increment(li)
|
|
return li.value, nil
|
|
}
|
|
|
|
func (c *LFUCache) increment(item *lfuItem) {
|
|
currentFreqElement := item.freqElement
|
|
currentFreqEntry := currentFreqElement.Value.(*freqEntry)
|
|
nextFreq := currentFreqEntry.freq + 1
|
|
delete(currentFreqEntry.items, item)
|
|
|
|
nextFreqElement := currentFreqElement.Next()
|
|
if nextFreqElement == nil {
|
|
nextFreqElement = c.freqList.InsertAfter(&freqEntry{
|
|
freq: nextFreq,
|
|
items: make(map[*lfuItem]byte),
|
|
}, currentFreqElement)
|
|
}
|
|
nextFreqElement.Value.(*freqEntry).items[item] = 1
|
|
item.freqElement = nextFreqElement
|
|
}
|
|
|
|
// evict removes the least frequence item from the cache.
|
|
func (c *LFUCache) evict(count int) {
|
|
entry := c.freqList.Front()
|
|
for i := 0; i < count; {
|
|
if entry == nil {
|
|
return
|
|
} else {
|
|
for item, _ := range entry.Value.(*freqEntry).items {
|
|
if i >= count {
|
|
return
|
|
}
|
|
c.removeItem(item)
|
|
i++
|
|
}
|
|
entry = entry.Next()
|
|
}
|
|
}
|
|
}
|
|
|
|
// Removes the provided key from the cache.
|
|
func (c *LFUCache) Remove(key interface{}) bool {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
|
|
return c.remove(key)
|
|
}
|
|
|
|
func (c *LFUCache) remove(key interface{}) bool {
|
|
if item, ok := c.items[key]; ok {
|
|
c.removeItem(item)
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// removeElement is used to remove a given list element from the cache
|
|
func (c *LFUCache) removeItem(item *lfuItem) {
|
|
delete(c.items, item.key)
|
|
delete(item.freqElement.Value.(*freqEntry).items, item)
|
|
if c.evictedFunc != nil {
|
|
go (*c.evictedFunc)(item.key, item.value)
|
|
}
|
|
}
|
|
|
|
// Returns a slice of the keys in the cache.
|
|
func (c *LFUCache) Keys() []interface{} {
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
|
|
keys := make([]interface{}, len(c.items))
|
|
i := 0
|
|
for k := range c.items {
|
|
keys[i] = k
|
|
i++
|
|
}
|
|
|
|
return keys
|
|
}
|
|
|
|
// Returns the number of items in the cache.
|
|
func (c *LFUCache) Len() int {
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
|
|
return len(c.items)
|
|
}
|
|
|
|
// Completely clear the cache
|
|
func (c *LFUCache) Purge() {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
|
|
c.freqList = list.New()
|
|
c.items = make(map[interface{}]*lfuItem, c.size)
|
|
}
|
|
|
|
// evict all expired entry
|
|
func (c *LFUCache) gc() {
|
|
now := time.Now()
|
|
keys := []interface{}{}
|
|
c.mu.RLock()
|
|
for k, item := range c.items {
|
|
if item.IsExpired(&now) {
|
|
keys = append(keys, k)
|
|
}
|
|
}
|
|
c.mu.RUnlock()
|
|
if len(keys) == 0 {
|
|
return
|
|
}
|
|
c.mu.Lock()
|
|
for _, k := range keys {
|
|
c.remove(k)
|
|
}
|
|
c.mu.Unlock()
|
|
}
|
|
|
|
type freqEntry struct {
|
|
freq uint
|
|
items map[*lfuItem]byte
|
|
}
|
|
|
|
type lfuItem struct {
|
|
key interface{}
|
|
value interface{}
|
|
freqElement *list.Element
|
|
expiration *time.Time
|
|
}
|
|
|
|
// returns boolean value whether this item is expired or not.
|
|
func (it *lfuItem) IsExpired(now *time.Time) bool {
|
|
if it.expiration == nil {
|
|
return false
|
|
}
|
|
if now == nil {
|
|
t := time.Now()
|
|
now = &t
|
|
}
|
|
return it.expiration.Before(*now)
|
|
}
|