summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTres Seaver <tseaver@palladion.com>2012-06-11 17:15:19 -0400
committerTres Seaver <tseaver@palladion.com>2012-06-11 17:15:19 -0400
commit5243ae31252bd3f12bf1234cb3985a819aa42e3c (patch)
tree5aa0b7c48ca257706a4539241f84d6ed858e1821
parent0caf23cf4551751643420f8d7b6af2e05d26db0c (diff)
parent59cd51af9891c7bcd66f30b722f168b30df65b95 (diff)
downloadrepoze-lru-5243ae31252bd3f12bf1234cb3985a819aa42e3c.tar.gz
Pull mness-statistics branch.
-rw-r--r--docs/narr.rst7
-rw-r--r--repoze/lru/__init__.py31
-rw-r--r--repoze/lru/tests.py31
3 files changed, 67 insertions, 2 deletions
diff --git a/docs/narr.rst b/docs/narr.rst
index 7343a71..6c46325 100644
--- a/docs/narr.rst
+++ b/docs/narr.rst
@@ -39,6 +39,13 @@ Clearing an LRUCache:
>>> cache.clear()
+Each LRU cache tracks some basic statistics via attributes:
+
+ 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
+
Decorating an "expensive" function call
---------------------------------------
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)