github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/pkg/storage/cache/lfu/lfu.go (about) 1 package lfu 2 3 import ( 4 "container/list" 5 "strings" 6 "sync" 7 "time" 8 ) 9 10 type Cache struct { 11 TTL int64 12 EvictionChannel chan<- Eviction 13 WriteBackChannel chan<- Eviction 14 15 lock sync.Mutex 16 values map[string]*cacheEntry 17 freqs *list.List 18 len int 19 } 20 21 type Eviction struct { 22 Key string 23 Value interface{} 24 } 25 26 type cacheEntry struct { 27 key string 28 value interface{} 29 freqNode *list.Element 30 persisted bool 31 lastAccessTime int64 32 } 33 34 type listEntry struct { 35 entries map[*cacheEntry]struct{} 36 freq int 37 } 38 39 func New() *Cache { 40 return &Cache{ 41 values: make(map[string]*cacheEntry), 42 freqs: list.New(), 43 } 44 } 45 46 func (c *Cache) Get(key string) interface{} { 47 c.lock.Lock() 48 defer c.lock.Unlock() 49 if e, ok := c.values[key]; ok { 50 c.increment(e) 51 return e.value 52 } 53 return nil 54 } 55 56 func (c *Cache) GetOrSet(key string, value func() (interface{}, error)) (interface{}, error) { 57 c.lock.Lock() 58 defer c.lock.Unlock() 59 e, ok := c.values[key] 60 if ok { 61 c.increment(e) 62 return e.value, nil 63 } 64 // value doesn't exist. 65 v, err := value() 66 if err != nil || v == nil { 67 return nil, err 68 } 69 e = new(cacheEntry) 70 e.key = key 71 e.value = v 72 // The item returned by value() is either newly allocated or was just 73 // read from the DB, therefore we mark it as persisted to avoid redundant 74 // writes or writing empty object. Once the item is invalidated, caller 75 // has to explicitly set it with Set call. 76 e.persisted = true 77 c.values[key] = e 78 c.increment(e) 79 c.len++ 80 return v, nil 81 } 82 83 func (c *Cache) Set(key string, value interface{}) { 84 c.lock.Lock() 85 defer c.lock.Unlock() 86 if e, ok := c.values[key]; ok { 87 // value already exists for key. overwrite 88 e.value = value 89 e.persisted = false 90 c.increment(e) 91 } else { 92 // value doesn't exist. insert 93 e = new(cacheEntry) 94 e.key = key 95 e.value = value 96 c.values[key] = e 97 c.increment(e) 98 c.len++ 99 } 100 } 101 102 func (c *Cache) Delete(key string) { 103 c.lock.Lock() 104 defer c.lock.Unlock() 105 if e, ok := c.values[key]; ok { 106 c.delete(e) 107 } 108 } 109 110 func (c *Cache) DeletePrefix(prefix string) { 111 c.lock.Lock() 112 defer c.lock.Unlock() 113 for k, e := range c.values { 114 if strings.HasPrefix(k, prefix) { 115 c.delete(e) 116 } 117 } 118 } 119 120 //revive:disable-next-line:confusing-naming methods are different 121 func (c *Cache) delete(entry *cacheEntry) { 122 delete(c.values, entry.key) 123 c.remEntry(entry.freqNode, entry) 124 c.len-- 125 } 126 127 func (c *Cache) Len() int { 128 c.lock.Lock() 129 defer c.lock.Unlock() 130 return c.len 131 } 132 133 func (c *Cache) Evict(count int) int { 134 c.lock.Lock() 135 defer c.lock.Unlock() 136 return c.evict(count) 137 } 138 139 // WriteBack persists modified items and evicts obsolete ones. 140 func (c *Cache) WriteBack() (persisted, evicted int) { 141 c.lock.Lock() 142 defer c.lock.Unlock() 143 return c.writeBack() 144 } 145 146 func (c *Cache) Iterate(fn func(k string, v interface{}) error) error { 147 c.lock.Lock() 148 defer c.lock.Unlock() 149 for k, entry := range c.values { 150 if err := fn(k, entry.value); err != nil { 151 return err 152 } 153 } 154 return nil 155 } 156 157 //revive:disable-next-line:confusing-naming methods are different 158 func (c *Cache) evict(count int) int { 159 // No lock here so it can be called 160 // from within the lock (during Set) 161 var evicted int 162 for i := 0; i < count; { 163 if place := c.freqs.Front(); place != nil { 164 for entry := range place.Value.(*listEntry).entries { 165 if i >= count { 166 return evicted 167 } 168 if c.EvictionChannel != nil && !entry.persisted { 169 c.EvictionChannel <- Eviction{ 170 Key: entry.key, 171 Value: entry.value, 172 } 173 } 174 c.delete(entry) 175 evicted++ 176 i++ 177 } 178 } 179 } 180 return evicted 181 } 182 183 //revive:disable-next-line:confusing-naming methods are different 184 func (c *Cache) writeBack() (persisted, evicted int) { 185 now := time.Now().Unix() 186 for k, entry := range c.values { 187 if c.WriteBackChannel != nil && !entry.persisted { 188 c.WriteBackChannel <- Eviction{ 189 Key: k, 190 Value: entry.value, 191 } 192 entry.persisted = true 193 persisted++ 194 } 195 if c.TTL > 0 && now-entry.lastAccessTime > c.TTL { 196 c.delete(entry) 197 evicted++ 198 } 199 } 200 return persisted, evicted 201 } 202 203 func (c *Cache) increment(e *cacheEntry) { 204 e.lastAccessTime = time.Now().Unix() 205 currentPlace := e.freqNode 206 var nextFreq int 207 var nextPlace *list.Element 208 if currentPlace == nil { 209 // new entry 210 nextFreq = 1 211 nextPlace = c.freqs.Front() 212 } else { 213 // move up 214 nextFreq = currentPlace.Value.(*listEntry).freq + 1 215 nextPlace = currentPlace.Next() 216 } 217 218 if nextPlace == nil || nextPlace.Value.(*listEntry).freq != nextFreq { 219 // create a new list entry 220 li := new(listEntry) 221 li.freq = nextFreq 222 li.entries = make(map[*cacheEntry]struct{}) 223 if currentPlace != nil { 224 nextPlace = c.freqs.InsertAfter(li, currentPlace) 225 } else { 226 nextPlace = c.freqs.PushFront(li) 227 } 228 } 229 e.freqNode = nextPlace 230 nextPlace.Value.(*listEntry).entries[e] = struct{}{} 231 if currentPlace != nil { 232 // remove from current position 233 c.remEntry(currentPlace, e) 234 } 235 } 236 237 func (c *Cache) remEntry(place *list.Element, entry *cacheEntry) { 238 entries := place.Value.(*listEntry).entries 239 delete(entries, entry) 240 if len(entries) == 0 { 241 c.freqs.Remove(place) 242 } 243 }