add LFUCache and LFUCacheWithMetrics
This commit is contained in:
parent
eb87474b48
commit
a46343c84f
1 changed files with 205 additions and 0 deletions
205
hub/common.py
205
hub/common.py
|
@ -279,6 +279,211 @@ class LRUCache:
|
|||
self.clear()
|
||||
|
||||
|
||||
class LinkedList:
|
||||
__slots__ = [
|
||||
'head',
|
||||
'tail',
|
||||
'size'
|
||||
]
|
||||
|
||||
def __init__(self):
|
||||
self.head = self.tail = None
|
||||
self.size = 0
|
||||
|
||||
def __len__(self):
|
||||
return self.size
|
||||
|
||||
def __iter__(self):
|
||||
node = self.head
|
||||
while node is not None:
|
||||
yield node
|
||||
node = node.next
|
||||
return
|
||||
|
||||
def clear(self):
|
||||
while self.size > 0:
|
||||
self.remove(self.tail)
|
||||
return
|
||||
|
||||
def append(self, node):
|
||||
if self.size == 0:
|
||||
self.head = self.tail = node
|
||||
else:
|
||||
self.tail.next, node.previous, self.tail = node, self.tail, node
|
||||
self.size += 1
|
||||
|
||||
def prepend(self, node):
|
||||
if self.size == 0:
|
||||
self.head = self.tail = node
|
||||
else:
|
||||
self.head.previous, node.next, self.head = node, self.head, node
|
||||
self.size += 1
|
||||
|
||||
def insert_after(self, ref_node, node):
|
||||
ref_node_next, node.next, ref_node.next, node.previous = ref_node.next, ref_node.next, node, ref_node
|
||||
if ref_node_next:
|
||||
ref_node_next.previous = node
|
||||
if self.tail is ref_node:
|
||||
self.tail = node
|
||||
self.size += 1
|
||||
|
||||
def remove(self, node):
|
||||
if self.tail is node:
|
||||
self.tail = node.previous
|
||||
if self.head is node:
|
||||
self.head = node.next
|
||||
if node.previous:
|
||||
node.previous.next = node.next
|
||||
if node.next:
|
||||
node.next.previous = node.previous
|
||||
node.next = node.previous = None
|
||||
|
||||
self.size -= 1
|
||||
|
||||
|
||||
class WeightNode:
|
||||
__slots__ = [
|
||||
'previous',
|
||||
'next',
|
||||
'weight',
|
||||
'cache_nodes'
|
||||
]
|
||||
|
||||
def __init__(self, weight):
|
||||
self.previous = self.next = None
|
||||
self.weight = weight
|
||||
self.cache_nodes = LinkedList()
|
||||
|
||||
|
||||
class CacheNode:
|
||||
__slots__ = [
|
||||
'previous',
|
||||
'next',
|
||||
'key',
|
||||
'value',
|
||||
'weight_node'
|
||||
]
|
||||
|
||||
def __init__(self, key, value, weight_node: WeightNode):
|
||||
self.previous = self.next = None
|
||||
self.key = key
|
||||
self.value = value
|
||||
self.weight_node = weight_node
|
||||
|
||||
|
||||
class LFUCache:
|
||||
def __init__(self, capacity: int):
|
||||
self.cache = {}
|
||||
self.weights = LinkedList()
|
||||
self.capacity = capacity
|
||||
|
||||
def increment_weight(self, cache_node: CacheNode):
|
||||
weight_node = cache_node.weight_node
|
||||
weight = weight_node.weight
|
||||
if not (weight_node.next and weight_node.next.weight == weight + 1):
|
||||
self.weights.insert_after(weight_node, WeightNode(weight=weight + 1))
|
||||
weight_node.cache_nodes.remove(cache_node)
|
||||
weight_node.next.cache_nodes.append(cache_node)
|
||||
cache_node.weight_node = weight_node.next
|
||||
if weight_node.cache_nodes.size == 0:
|
||||
self.weights.remove(weight_node)
|
||||
|
||||
def set(self, key, value):
|
||||
if key in self.cache:
|
||||
data_node = self.cache[key]
|
||||
self.increment_weight(data_node)
|
||||
data_node.value = value
|
||||
return
|
||||
if len(self.cache) == self.capacity:
|
||||
if self.capacity == 0:
|
||||
return
|
||||
lowest_data_list = self.weights.head.cache_nodes
|
||||
to_remove = lowest_data_list.head
|
||||
lowest_data_list.remove(to_remove)
|
||||
self.cache.pop(to_remove.key)
|
||||
if lowest_data_list.size == 0:
|
||||
self.weights.remove(self.weights.head)
|
||||
if not (self.weights.head and self.weights.head.weight == 1):
|
||||
self.weights.prepend(WeightNode(1))
|
||||
new_cache_node = CacheNode(key=key, value=value, weight_node=self.weights.head)
|
||||
self.weights.head.cache_nodes.append(new_cache_node)
|
||||
self.cache[key] = new_cache_node
|
||||
|
||||
def get(self, key):
|
||||
if key in self.cache:
|
||||
item = self.cache[key]
|
||||
self.increment_weight(item)
|
||||
return item.value
|
||||
|
||||
def __len__(self):
|
||||
return len(self.cache)
|
||||
|
||||
def __getitem__(self, item):
|
||||
return self.get(item)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
return self.set(key, value)
|
||||
|
||||
def items(self):
|
||||
return self.cache.items()
|
||||
|
||||
def clear(self):
|
||||
self.cache.clear()
|
||||
self.weights.clear()
|
||||
|
||||
def pop(self, key):
|
||||
cache_node = self.cache.pop(key)
|
||||
weight_node = cache_node.weight_node
|
||||
weight_node.cache_nodes.remove(cache_node)
|
||||
if weight_node.cache_nodes.size == 0:
|
||||
self.weights.remove(weight_node)
|
||||
|
||||
def __contains__(self, item) -> bool:
|
||||
return item in self.cache
|
||||
|
||||
def __delitem__(self, key):
|
||||
item = self.cache.pop(key)
|
||||
weight_node = item.weight_node
|
||||
weight_node.cache_nodes.remove(item)
|
||||
if weight_node.cache_nodes.size == 0:
|
||||
self.weights.remove(weight_node)
|
||||
|
||||
def __del__(self):
|
||||
self.clear()
|
||||
|
||||
|
||||
class LFUCacheWithMetrics(LFUCache):
|
||||
def __init__(self, capacity: int, metric_name: typing.Optional[str] = None, namespace: str = "daemon_cache"):
|
||||
super().__init__(capacity)
|
||||
if metric_name is None:
|
||||
self._track_metrics = False
|
||||
self.hits = self.misses = None
|
||||
else:
|
||||
self._track_metrics = True
|
||||
try:
|
||||
self.hits = Counter(
|
||||
f"{metric_name}_cache_hit_count", "Number of cache hits", namespace=namespace
|
||||
)
|
||||
self.misses = Counter(
|
||||
f"{metric_name}_cache_miss_count", "Number of cache misses", namespace=namespace
|
||||
)
|
||||
except ValueError as err:
|
||||
log.debug("failed to set up prometheus %s_cache_miss_count metric: %s", metric_name, err)
|
||||
self._track_metrics = False
|
||||
self.hits = self.misses = None
|
||||
|
||||
def get(self, key):
|
||||
# Key exists, assign it to a next freq_node
|
||||
if key in self.cache:
|
||||
item = self.cache[key]
|
||||
self.increment_weight(item)
|
||||
if self._track_metrics:
|
||||
self.hits.inc()
|
||||
return item.value
|
||||
if self._track_metrics:
|
||||
self.misses.inc()
|
||||
|
||||
|
||||
# the ipaddress module does not show these subnets as reserved
|
||||
CARRIER_GRADE_NAT_SUBNET = ipaddress.ip_network('100.64.0.0/10')
|
||||
IPV4_TO_6_RELAY_SUBNET = ipaddress.ip_network('192.88.99.0/24')
|
||||
|
|
Loading…
Reference in a new issue