report metrics on number of cache evictions

This commit is contained in:
Richard van der Hoff 2018-02-01 17:57:51 +00:00
parent 77c0629ebc
commit bc496df192
5 changed files with 56 additions and 5 deletions

View file

@ -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
),
] ]

View file

@ -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:

View file

@ -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:

View file

@ -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=[]):

View file

@ -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',
]) ])