Merge pull request #2844 from matrix-org/rav/evicted_metrics
montoring metrics for number of cache evictions
This commit is contained in:
commit
db91e72ade
|
@ -193,7 +193,9 @@ class DistributionMetric(object):
|
||||||
|
|
||||||
|
|
||||||
class CacheMetric(object):
|
class CacheMetric(object):
|
||||||
__slots__ = ("name", "cache_name", "hits", "misses", "size_callback")
|
__slots__ = (
|
||||||
|
"name", "cache_name", "hits", "misses", "evicted_size", "size_callback",
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(self, name, size_callback, cache_name):
|
def __init__(self, name, size_callback, cache_name):
|
||||||
self.name = name
|
self.name = name
|
||||||
|
@ -201,6 +203,7 @@ class CacheMetric(object):
|
||||||
|
|
||||||
self.hits = 0
|
self.hits = 0
|
||||||
self.misses = 0
|
self.misses = 0
|
||||||
|
self.evicted_size = 0
|
||||||
|
|
||||||
self.size_callback = size_callback
|
self.size_callback = size_callback
|
||||||
|
|
||||||
|
@ -210,6 +213,9 @@ class CacheMetric(object):
|
||||||
def inc_misses(self):
|
def inc_misses(self):
|
||||||
self.misses += 1
|
self.misses += 1
|
||||||
|
|
||||||
|
def inc_evictions(self, size=1):
|
||||||
|
self.evicted_size += size
|
||||||
|
|
||||||
def render(self):
|
def render(self):
|
||||||
size = self.size_callback()
|
size = self.size_callback()
|
||||||
hits = self.hits
|
hits = self.hits
|
||||||
|
@ -219,6 +225,9 @@ class CacheMetric(object):
|
||||||
"""%s:hits{name="%s"} %d""" % (self.name, self.cache_name, hits),
|
"""%s:hits{name="%s"} %d""" % (self.name, self.cache_name, hits),
|
||||||
"""%s:total{name="%s"} %d""" % (self.name, self.cache_name, total),
|
"""%s:total{name="%s"} %d""" % (self.name, self.cache_name, total),
|
||||||
"""%s:size{name="%s"} %d""" % (self.name, self.cache_name, size),
|
"""%s:size{name="%s"} %d""" % (self.name, self.cache_name, size),
|
||||||
|
"""%s:evicted_size{name="%s"} %d""" % (
|
||||||
|
self.name, self.cache_name, self.evicted_size
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -75,6 +75,7 @@ class Cache(object):
|
||||||
self.cache = LruCache(
|
self.cache = LruCache(
|
||||||
max_size=max_entries, keylen=keylen, cache_type=cache_type,
|
max_size=max_entries, keylen=keylen, cache_type=cache_type,
|
||||||
size_callback=(lambda d: len(d)) if iterable else None,
|
size_callback=(lambda d: len(d)) if iterable else None,
|
||||||
|
evicted_callback=self._on_evicted,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.name = name
|
self.name = name
|
||||||
|
@ -83,6 +84,9 @@ class Cache(object):
|
||||||
self.thread = None
|
self.thread = None
|
||||||
self.metrics = register_cache(name, self.cache)
|
self.metrics = register_cache(name, self.cache)
|
||||||
|
|
||||||
|
def _on_evicted(self, evicted_count):
|
||||||
|
self.metrics.inc_evictions(evicted_count)
|
||||||
|
|
||||||
def check_thread(self):
|
def check_thread(self):
|
||||||
expected_thread = self.thread
|
expected_thread = self.thread
|
||||||
if expected_thread is None:
|
if expected_thread is None:
|
||||||
|
|
|
@ -79,7 +79,11 @@ class ExpiringCache(object):
|
||||||
while self._max_len and len(self) > self._max_len:
|
while self._max_len and len(self) > self._max_len:
|
||||||
_key, value = self._cache.popitem(last=False)
|
_key, value = self._cache.popitem(last=False)
|
||||||
if self.iterable:
|
if self.iterable:
|
||||||
self._size_estimate -= len(value.value)
|
removed_len = len(value.value)
|
||||||
|
self.metrics.inc_evictions(removed_len)
|
||||||
|
self._size_estimate -= removed_len
|
||||||
|
else:
|
||||||
|
self.metrics.inc_evictions()
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -49,7 +49,24 @@ class LruCache(object):
|
||||||
Can also set callbacks on objects when getting/setting which are fired
|
Can also set callbacks on objects when getting/setting which are fired
|
||||||
when that key gets invalidated/evicted.
|
when that key gets invalidated/evicted.
|
||||||
"""
|
"""
|
||||||
def __init__(self, max_size, keylen=1, cache_type=dict, size_callback=None):
|
def __init__(self, max_size, keylen=1, cache_type=dict, size_callback=None,
|
||||||
|
evicted_callback=None):
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
max_size (int):
|
||||||
|
|
||||||
|
keylen (int):
|
||||||
|
|
||||||
|
cache_type (type):
|
||||||
|
type of underlying cache to be used. Typically one of dict
|
||||||
|
or TreeCache.
|
||||||
|
|
||||||
|
size_callback (func(V) -> int | None):
|
||||||
|
|
||||||
|
evicted_callback (func(int)|None):
|
||||||
|
if not None, called on eviction with the size of the evicted
|
||||||
|
entry
|
||||||
|
"""
|
||||||
cache = cache_type()
|
cache = cache_type()
|
||||||
self.cache = cache # Used for introspection.
|
self.cache = cache # Used for introspection.
|
||||||
list_root = _Node(None, None, None, None)
|
list_root = _Node(None, None, None, None)
|
||||||
|
@ -61,8 +78,10 @@ class LruCache(object):
|
||||||
def evict():
|
def evict():
|
||||||
while cache_len() > max_size:
|
while cache_len() > max_size:
|
||||||
todelete = list_root.prev_node
|
todelete = list_root.prev_node
|
||||||
delete_node(todelete)
|
evicted_len = delete_node(todelete)
|
||||||
cache.pop(todelete.key, None)
|
cache.pop(todelete.key, None)
|
||||||
|
if evicted_callback:
|
||||||
|
evicted_callback(evicted_len)
|
||||||
|
|
||||||
def synchronized(f):
|
def synchronized(f):
|
||||||
@wraps(f)
|
@wraps(f)
|
||||||
|
@ -111,12 +130,15 @@ class LruCache(object):
|
||||||
prev_node.next_node = next_node
|
prev_node.next_node = next_node
|
||||||
next_node.prev_node = prev_node
|
next_node.prev_node = prev_node
|
||||||
|
|
||||||
|
deleted_len = 1
|
||||||
if size_callback:
|
if size_callback:
|
||||||
cached_cache_len[0] -= size_callback(node.value)
|
deleted_len = size_callback(node.value)
|
||||||
|
cached_cache_len[0] -= deleted_len
|
||||||
|
|
||||||
for cb in node.callbacks:
|
for cb in node.callbacks:
|
||||||
cb()
|
cb()
|
||||||
node.callbacks.clear()
|
node.callbacks.clear()
|
||||||
|
return deleted_len
|
||||||
|
|
||||||
@synchronized
|
@synchronized
|
||||||
def cache_get(key, default=None, callbacks=[]):
|
def cache_get(key, default=None, callbacks=[]):
|
||||||
|
|
|
@ -141,6 +141,7 @@ class CacheMetricTestCase(unittest.TestCase):
|
||||||
'cache:hits{name="cache_name"} 0',
|
'cache:hits{name="cache_name"} 0',
|
||||||
'cache:total{name="cache_name"} 0',
|
'cache:total{name="cache_name"} 0',
|
||||||
'cache:size{name="cache_name"} 0',
|
'cache:size{name="cache_name"} 0',
|
||||||
|
'cache:evicted_size{name="cache_name"} 0',
|
||||||
])
|
])
|
||||||
|
|
||||||
metric.inc_misses()
|
metric.inc_misses()
|
||||||
|
@ -150,6 +151,7 @@ class CacheMetricTestCase(unittest.TestCase):
|
||||||
'cache:hits{name="cache_name"} 0',
|
'cache:hits{name="cache_name"} 0',
|
||||||
'cache:total{name="cache_name"} 1',
|
'cache:total{name="cache_name"} 1',
|
||||||
'cache:size{name="cache_name"} 1',
|
'cache:size{name="cache_name"} 1',
|
||||||
|
'cache:evicted_size{name="cache_name"} 0',
|
||||||
])
|
])
|
||||||
|
|
||||||
metric.inc_hits()
|
metric.inc_hits()
|
||||||
|
@ -158,4 +160,14 @@ class CacheMetricTestCase(unittest.TestCase):
|
||||||
'cache:hits{name="cache_name"} 1',
|
'cache:hits{name="cache_name"} 1',
|
||||||
'cache:total{name="cache_name"} 2',
|
'cache:total{name="cache_name"} 2',
|
||||||
'cache:size{name="cache_name"} 1',
|
'cache:size{name="cache_name"} 1',
|
||||||
|
'cache:evicted_size{name="cache_name"} 0',
|
||||||
|
])
|
||||||
|
|
||||||
|
metric.inc_evictions(2)
|
||||||
|
|
||||||
|
self.assertEquals(metric.render(), [
|
||||||
|
'cache:hits{name="cache_name"} 1',
|
||||||
|
'cache:total{name="cache_name"} 2',
|
||||||
|
'cache:size{name="cache_name"} 1',
|
||||||
|
'cache:evicted_size{name="cache_name"} 2',
|
||||||
])
|
])
|
||||||
|
|
Loading…
Reference in New Issue