Skip to content

Commit

Permalink
chore: rename cache stats attrs/props to have "_count" suffix, rename…
Browse files Browse the repository at this point in the history
… CacheStats.asdict to to_dict, default hit_rate and eviction_rate to 0 when hits/evictions are 0, and minor docstring tweaks
  • Loading branch information
dgilland committed Nov 3, 2023
1 parent f80321b commit 1a67797
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 98 deletions.
6 changes: 3 additions & 3 deletions src/cacheout/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,9 +260,9 @@ def _get(self, key: t.Hashable, default: t.Any = None) -> t.Any:
if self.expired(key):
self._delete(key, RemovalCause.EXPIRED)
raise KeyError
self.stats.inc_hits(1)
self.stats.inc_hit_count()
except KeyError:
self.stats.inc_misses(1)
self.stats.inc_miss_count()
if default is None:
default = self.default

Expand Down Expand Up @@ -400,7 +400,7 @@ def _delete(self, key: t.Hashable, cause: RemovalCause) -> int:
self.on_delete(key, value, cause)
count = 1
if cause == RemovalCause.FULL:
self.stats.inc_evictions(1)
self.stats.inc_eviction_count()
except KeyError:
pass

Expand Down
99 changes: 46 additions & 53 deletions src/cacheout/stats.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,58 +13,45 @@ class CacheStats:
Cache statistics snapshot.
Attributes:
hits: The number of cache hits.
misses: The number of cache misses.
evictions: The number of cache entries have been evicted.
total_entries: The total number of cache entries.
hit_count: The number of cache hits.
miss_count: The number of cache misses.
eviction_count: The number of cache entries have been evicted.
entry_count: The total number of cache entries.
"""

hits: int = 0
misses: int = 0
evictions: int = 0
total_entries: int = 0
hit_count: int = 0
miss_count: int = 0
eviction_count: int = 0
entry_count: int = 0

@property
def accesses(self) -> int:
def access_count(self) -> int:
"""The number of times cache has been accessed."""
return self.hits + self.misses
return self.hit_count + self.miss_count

@property
def hit_rate(self) -> float:
"""
The cache hit rate.
Return 1.0 when ``accesses`` == 0.
"""
if self.accesses == 0:
return 1.0
return self.hits / self.accesses
"""The cache hit rate."""
if self.access_count == 0:
return 0.0
return self.hit_count / self.access_count

@property
def miss_rate(self) -> float:
"""
The cache miss rate.
Return 0.0 when ``accesses`` == 0.
"""
if self.accesses == 0:
"""The cache miss rate."""
if self.access_count == 0:
return 0.0
return self.misses / self.accesses
return self.miss_count / self.access_count

@property
def eviction_rate(self) -> float:
"""
The cache eviction rate.
Return 1.0 when ``accesses`` == 0.
"""
if self.accesses == 0:
return 1.0
return self.evictions / self.accesses
"""The cache eviction rate."""
if self.access_count == 0:
return 0.0
return self.eviction_count / self.access_count

def __repr__(self):
"""Return repr of object."""
data = self.asdict()
data = self.to_dict()
fields = []
for k, v in data.items():
if isinstance(v, float):
Expand All @@ -74,14 +61,17 @@ def __repr__(self):
fields.append(f"{k}={s}")
return f"{self.__class__.__name__}({', '.join(fields)})"

def asdict(self) -> t.Dict[str, t.Any]:
def __iter__(self):
return iter(self.to_dict().items())

def to_dict(self) -> t.Dict[str, t.Any]:
"""Return dictionary representation of object."""
return {
"hits": self.hits,
"misses": self.misses,
"evictions": self.evictions,
"total_entries": self.total_entries,
"accesses": self.accesses,
"hit_count": self.hit_count,
"miss_count": self.miss_count,
"eviction_count": self.eviction_count,
"entry_count": self.entry_count,
"access_count": self.access_count,
"hit_rate": self.hit_rate,
"miss_rate": self.miss_rate,
"eviction_rate": self.eviction_rate,
Expand All @@ -107,26 +97,29 @@ def __init__(self, cache: "Cache", *, enable: bool = True) -> None:
def __repr__(self):
return f"{self.__class__.__name__}(info={self.info()})"

def inc_hits(self, count: int) -> None:
def inc_hit_count(self, count: int = 1) -> None:
"""Increment the number of cache hits."""
if not self._enabled or self._paused:
return

with self._lock:
self._stats.hits += count
self._stats.hit_count += count

def inc_misses(self, count: int) -> None:
def inc_miss_count(self, count: int = 1) -> None:
"""Increment the number of cache misses."""
if not self._enabled or self._paused:
return

with self._lock:
self._stats.misses += count
self._stats.miss_count += count

def inc_evictions(self, count: int) -> None:
def inc_eviction_count(self, count: int = 1) -> None:
"""Increment the number of cache evictions."""
if not self._enabled or self._paused:
return

with self._lock:
self._stats.evictions += count
self._stats.eviction_count += count

def enable(self) -> None:
"""Enable statistics."""
Expand Down Expand Up @@ -154,24 +147,24 @@ def resume(self) -> None:
self._paused = False

def is_enabled(self) -> bool:
"""Whether statistics tracking is enabled."""
"""Return whether statistics tracking is enabled."""
return self._enabled

def is_paused(self) -> bool:
"""Whether statistics tracking is paused."""
"""Return whether statistics tracking is paused."""
return self._paused

def is_active(self) -> bool:
"""Whether statistics tracking is active (enabled and not paused)."""
"""Return whether statistics tracking is active (enabled and not paused)."""
return self._enabled and not self._paused

def reset(self) -> None:
"""Reset statistics."""
"""Reset statistics to zero values."""
with self._lock:
self._stats = CacheStats()

def info(self) -> CacheStats:
"""Get a snapshot of statistics."""
"""Return a snapshot of the cache statistics."""
with self._lock:
self._stats.total_entries = len(self._cache)
self._stats.entry_count = len(self._cache)
return self._stats.copy()
86 changes: 44 additions & 42 deletions tests/test_stats.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,42 +25,44 @@ def test_stats_tracker_info(cache: Cache):

info = cache.stats.info()
assert info is not None
assert info.hits == 1
assert info.misses == 4
assert info.hit_count == 1
assert info.miss_count == 4
assert info.hit_rate == 0.2
assert info.accesses == 5
assert info.access_count == 5
assert info.miss_rate == 0.8
assert info.eviction_rate == 0.2
assert info.total_entries == 2
assert info.entry_count == 2


def test_stats_tracker_info_asdict(cache: Cache):
def test_stats_tracker_info_to_dict(cache: Cache):
info = cache.stats.info()
assert info.asdict() == {
"hits": info.hits,
"misses": info.misses,
"evictions": info.evictions,
"total_entries": info.total_entries,
"accesses": info.accesses,
expected = {
"hit_count": info.hit_count,
"miss_count": info.miss_count,
"eviction_count": info.eviction_count,
"entry_count": info.entry_count,
"access_count": info.access_count,
"hit_rate": info.hit_rate,
"miss_rate": info.miss_rate,
"eviction_rate": info.eviction_rate,
}
assert info.to_dict() == expected
assert dict(info) == expected


def test_stats_tracker_reset(cache: Cache):
"""Test that cache.stats.reset() clears statistics."""
cache.stats.reset()

stats = cache.stats.info()
assert stats is not None
assert stats.hits == 0
assert stats.misses == 0
assert stats.accesses == 0
assert stats.hit_rate == 1.0
assert stats.miss_rate == 0.0
assert stats.eviction_rate == 1.0
assert stats.total_entries == 2
info = cache.stats.info()
assert info is not None
assert info.hit_count == 0
assert info.miss_count == 0
assert info.access_count == 0
assert info.hit_rate == 0.0
assert info.miss_rate == 0.0
assert info.eviction_rate == 0.0
assert info.entry_count == 2


def test_stats_tracker_pause(cache: Cache):
Expand All @@ -70,13 +72,13 @@ def test_stats_tracker_pause(cache: Cache):

info = cache.stats.info()
assert info is not None
assert info.hits == 1
assert info.misses == 4
assert info.hit_count == 1
assert info.miss_count == 4
assert info.hit_rate == 0.2
assert info.accesses == 5
assert info.access_count == 5
assert info.miss_rate == 0.8
assert info.eviction_rate == 0.2
assert info.total_entries == 2
assert info.entry_count == 2

cache.stats.pause()
assert cache.stats.is_paused() is True
Expand All @@ -88,13 +90,13 @@ def test_stats_tracker_pause(cache: Cache):

info = cache.stats.info()
assert info is not None
assert info.hits == 1
assert info.misses == 4
assert info.hit_count == 1
assert info.miss_count == 4
assert info.hit_rate == 0.2
assert info.accesses == 5
assert info.access_count == 5
assert info.miss_rate == 0.8
assert info.eviction_rate == 0.2
assert info.total_entries == 2
assert info.entry_count == 2


def test_stats_tracker_resume(cache: Cache):
Expand All @@ -112,13 +114,13 @@ def test_stats_tracker_resume(cache: Cache):

info = cache.stats.info()
assert info is not None
assert info.hits == 1
assert info.misses == 4
assert info.hit_count == 1
assert info.miss_count == 4
assert info.hit_rate == 0.2
assert info.accesses == 5
assert info.access_count == 5
assert info.miss_rate == 0.8
assert info.eviction_rate == 0.2
assert info.total_entries == 2
assert info.entry_count == 2


def test_stats_tracker_disable(cache: Cache):
Expand All @@ -129,13 +131,13 @@ def test_stats_tracker_disable(cache: Cache):

info = cache.stats.info()
assert info is not None
assert info.hits == 0
assert info.misses == 0
assert info.accesses == 0
assert info.hit_rate == 1.0
assert info.hit_count == 0
assert info.miss_count == 0
assert info.access_count == 0
assert info.hit_rate == 0.0
assert info.miss_rate == 0.0
assert info.eviction_rate == 1.0
assert info.total_entries == 2
assert info.eviction_rate == 0.0
assert info.entry_count == 2


def test_stats_tracker_enable(cache: Cache):
Expand All @@ -155,10 +157,10 @@ def test_stats_tracker_enable(cache: Cache):

info = cache.stats.info()
assert info is not None
assert info.hits == 1
assert info.misses == 3
assert info.accesses == 4
assert info.hit_count == 1
assert info.miss_count == 3
assert info.access_count == 4
assert info.hit_rate == 0.25
assert info.miss_rate == 0.75
assert info.eviction_rate == 0.5
assert info.total_entries == 2
assert info.entry_count == 2

0 comments on commit 1a67797

Please sign in to comment.