From 59cd51af9891c7bcd66f30b722f168b30df65b95 Mon Sep 17 00:00:00 2001 From: Jeremiah Ness Date: Wed, 6 Jun 2012 16:31:46 -0400 Subject: added cache statitics - cache.lookups # number of calls to the get method - cache.hits # number of times a call to get found an object - cache.misses # number of times a call to get did not find an object - cahce.evictions # number of times a object was evicted from cache --- README.txt | 7 +++++++ repoze/lru/__init__.py | 31 +++++++++++++++++++++++++++++-- repoze/lru/tests.py | 31 +++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 2 deletions(-) diff --git a/README.txt b/README.txt index 72024fe..e37fab7 100644 --- a/README.txt +++ b/README.txt @@ -28,6 +28,13 @@ Clearing an LRUCache:: cache.clear() +Obtaining cache statistics: + + cache.lookups # number of calls to the get method + cache.hits # number of times a call to get found an object + cache.misses # number of times a call to get did not find an object + cahce.evictions # number of times a object was evicted from cache + Decorator --------- diff --git a/repoze/lru/__init__.py b/repoze/lru/__init__.py index 4092ce0..1d8ae94 100644 --- a/repoze/lru/__init__.py +++ b/repoze/lru/__init__.py @@ -31,6 +31,10 @@ class LRUCache(object): self.clock_keys = None self.clock_refs = None self.data = None + self.evictions = 0 + self.hits = 0 + self.misses = 0 + self.lookups = 0 self.clear() def clear(self): @@ -47,12 +51,19 @@ class LRUCache(object): self.clock_keys = [_MARKER] * size self.clock_refs = [False] * size self.hand = 0 + self.evictions = 0 + self.hits = 0 + self.misses = 0 + self.lookups = 0 def get(self, key, default=None): """Return value for key. If not in cache, return default""" + self.lookups += 1 try: pos, val = self.data[key] + self.hits += 1 except KeyError: + self.misses += 1 return default self.clock_refs[pos] = True return val @@ -98,7 +109,9 @@ class LRUCache(object): # Maybe oldkey was not in self.data to begin with. If it # was, self.invalidate() in another thread might have # already removed it. del() would raise KeyError, so pop(). - data.pop(oldkey, None) + oldentry = data.pop(oldkey, _MARKER) + if oldentry is not _MARKER: + self.evictions += 1 clock_keys[hand] = key clock_refs[hand] = True data[key] = (hand, val) @@ -137,6 +150,10 @@ class ExpiringLRUCache(object): self.clock_keys = None self.clock_refs = None self.data = None + self.evictions = 0 + self.hits = 0 + self.misses = 0 + self.lookups = 0 self.clear() def clear(self): @@ -154,20 +171,28 @@ class ExpiringLRUCache(object): self.clock_keys = [_MARKER] * size self.clock_refs = [False] * size self.hand = 0 + self.evictions = 0 + self.hits = 0 + self.misses = 0 + self.lookups = 0 def get(self, key, default=None): """Return value for key. If not in cache or expired, return default""" + self.lookups += 1 try: pos, val, expires = self.data[key] except KeyError: + self.misses += 1 return default if expires > time.time(): # cache entry still valid + self.hits += 1 self.clock_refs[pos] = True return val else: # cache entry has expired. Make sure the space in the cache can # be recycled soon. + self.misses += 1 self.clock_refs[pos] = False return default @@ -218,7 +243,9 @@ class ExpiringLRUCache(object): # Maybe oldkey was not in self.data to begin with. If it # was, self.invalidate() in another thread might have # already removed it. del() would raise KeyError, so pop(). - data.pop(oldkey, None) + oldentry = data.pop(oldkey, _MARKER) + if oldentry is not _MARKER: + self.evictions += 1 clock_keys[hand] = key clock_refs[hand] = True data[key] = (hand, val, time.time() + timeout) diff --git a/repoze/lru/tests.py b/repoze/lru/tests.py index 5f67385..eae1d47 100644 --- a/repoze/lru/tests.py +++ b/repoze/lru/tests.py @@ -181,6 +181,9 @@ class LRUCacheTests(unittest.TestCase): else: cache.put(item, "item%s" % item) + self.assertEqual(cache.misses, 0) + self.assertEqual(cache.evictions, 0) + self.check_cache_is_consistent(cache) def test_imperfect_hitrate(self): @@ -214,8 +217,36 @@ class LRUCacheTests(unittest.TestCase): self.assertTrue(hit_ratio > 45) self.assertTrue(hit_ratio < 55) + # The internal cache counters should have the same information + internal_hit_ratio = 100 * cache.hits / cache.lookups + self.assertTrue(internal_hit_ratio > 45) + self.assertTrue(internal_hit_ratio < 55) + + # The internal miss counters should also be around 50% + internal_miss_ratio = 100 * cache.misses / cache.lookups + self.assertTrue(internal_miss_ratio > 45) + self.assertTrue(internal_miss_ratio < 55) + self.check_cache_is_consistent(cache) + def test_eviction_counter(self): + cache = self._makeOne(2) + cache.put(1, 1) + cache.put(2, 1) + self.assertEqual(cache.evictions, 0) + + cache.put(3, 1) + cache.put(4, 1) + self.assertEqual(cache.evictions, 2) + + cache.put(3, 1) + cache.put(4, 1) + self.assertEqual(cache.evictions, 2) + + cache.clear() + self.assertEqual(cache.evictions, 0) + + def test_it(self): cache = self._makeOne(3) self.assertEqual(cache.get('a'), None) -- cgit v1.2.1