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  }