150 lines
4.9 KiB
Python
150 lines
4.9 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
import threading
|
|
import time
|
|
from collections import deque
|
|
from typing import Any
|
|
|
|
|
|
_DEFAULT_WINDOWS = (60, 300, 900)
|
|
_MAX_SNAPSHOTS = 90
|
|
_MIN_INTERVAL = 10.0
|
|
|
|
|
|
class CacheRateTracker:
|
|
|
|
def __init__(
|
|
self,
|
|
max_snapshots: int = _MAX_SNAPSHOTS,
|
|
min_interval: float = _MIN_INTERVAL,
|
|
):
|
|
self._snapshots: deque[tuple[float, dict[str, int]]] = deque(
|
|
maxlen=max_snapshots
|
|
)
|
|
self._min_interval = min_interval
|
|
self._lock = threading.Lock()
|
|
|
|
def maybe_snapshot(self, counters: dict[str, int]) -> bool:
|
|
with self._lock:
|
|
now = time.monotonic()
|
|
if self._snapshots and (now - self._snapshots[-1][0]) < self._min_interval:
|
|
return False
|
|
self._snapshots.append((now, dict(counters)))
|
|
return True
|
|
|
|
def get_rates(
|
|
self, windows: tuple[int, ...] = _DEFAULT_WINDOWS
|
|
) -> dict[str, Any]:
|
|
with self._lock:
|
|
if not self._snapshots:
|
|
return {"windows": {}, "cumulative": {}}
|
|
|
|
now = self._snapshots[-1][0]
|
|
newest = self._snapshots[-1][1]
|
|
|
|
window_rates = {}
|
|
for w in windows:
|
|
label = _window_label(w)
|
|
baseline_ts = None
|
|
baseline_counters = None
|
|
for ts, counters in self._snapshots:
|
|
if (now - ts) <= w:
|
|
baseline_ts, baseline_counters = ts, counters
|
|
break
|
|
if baseline_ts is None:
|
|
baseline_ts, baseline_counters = self._snapshots[0]
|
|
elapsed = now - baseline_ts
|
|
if elapsed < 1.0:
|
|
window_rates[label] = {}
|
|
continue
|
|
window_rates[label] = _compute_window(
|
|
baseline_counters, newest, elapsed
|
|
)
|
|
|
|
cumulative = _compute_cumulative(newest)
|
|
return {"windows": window_rates, "cumulative": cumulative}
|
|
|
|
def snapshot_and_get_rates(
|
|
self,
|
|
counters: dict[str, int],
|
|
windows: tuple[int, ...] = _DEFAULT_WINDOWS,
|
|
) -> dict[str, Any]:
|
|
self.maybe_snapshot(counters)
|
|
return self.get_rates(windows)
|
|
|
|
def clear(self) -> None:
|
|
with self._lock:
|
|
self._snapshots.clear()
|
|
|
|
|
|
def _window_label(seconds: int) -> str:
|
|
if seconds < 60:
|
|
return f"{seconds}s"
|
|
return f"{seconds // 60}m"
|
|
|
|
|
|
def _safe_ratio(numerator: int, denominator: int) -> float:
|
|
if denominator == 0:
|
|
return 0.0
|
|
return numerator / denominator
|
|
|
|
|
|
def _compute_window(
|
|
old: dict[str, int], new: dict[str, int], elapsed: float
|
|
) -> dict[str, Any]:
|
|
def delta(key: str) -> int:
|
|
return max(0, new.get(key, 0) - old.get(key, 0))
|
|
|
|
d_prefix_hits = delta("prefix_hits")
|
|
d_prefix_misses = delta("prefix_misses")
|
|
d_evictions = delta("evictions")
|
|
d_ssd_hot = delta("ssd_hot_hits")
|
|
d_ssd_disk = delta("ssd_disk_loads")
|
|
d_tokens_matched = delta("prefix_tokens_matched")
|
|
d_tokens_requested = delta("prefix_tokens_requested")
|
|
|
|
minutes = elapsed / 60.0
|
|
|
|
return {
|
|
"prefix_hit_rate": round(
|
|
_safe_ratio(d_prefix_hits, d_prefix_hits + d_prefix_misses), 4
|
|
),
|
|
"prefix_hits": d_prefix_hits,
|
|
"prefix_misses": d_prefix_misses,
|
|
"prefix_match_efficiency": round(
|
|
_safe_ratio(d_tokens_matched, d_tokens_requested), 4
|
|
),
|
|
"evictions": d_evictions,
|
|
"eviction_rate_per_min": round(d_evictions / minutes, 2) if minutes > 0 else 0.0,
|
|
"ssd_hot_hits": d_ssd_hot,
|
|
"ssd_disk_loads": d_ssd_disk,
|
|
"ssd_hot_rate": round(
|
|
_safe_ratio(d_ssd_hot, d_ssd_hot + d_ssd_disk), 4
|
|
),
|
|
}
|
|
|
|
|
|
def _compute_cumulative(counters: dict[str, int]) -> dict[str, Any]:
|
|
prefix_hits = counters.get("prefix_hits", 0)
|
|
prefix_misses = counters.get("prefix_misses", 0)
|
|
ssd_hot = counters.get("ssd_hot_hits", 0)
|
|
ssd_disk = counters.get("ssd_disk_loads", 0)
|
|
tokens_matched = counters.get("prefix_tokens_matched", 0)
|
|
tokens_requested = counters.get("prefix_tokens_requested", 0)
|
|
|
|
return {
|
|
"prefix_hits": prefix_hits,
|
|
"prefix_misses": prefix_misses,
|
|
"prefix_hit_rate": round(_safe_ratio(prefix_hits, prefix_hits + prefix_misses), 4),
|
|
"prefix_tokens_saved": counters.get("prefix_tokens_saved", 0),
|
|
"prefix_match_efficiency": round(
|
|
_safe_ratio(tokens_matched, tokens_requested), 4
|
|
),
|
|
"evictions": counters.get("evictions", 0),
|
|
"ssd_hot_hits": ssd_hot,
|
|
"ssd_disk_loads": ssd_disk,
|
|
"ssd_saves": counters.get("ssd_saves", 0),
|
|
"hot_cache_evictions": counters.get("hot_cache_evictions", 0),
|
|
"hot_cache_promotions": counters.get("hot_cache_promotions", 0),
|
|
"ssd_hot_rate": round(_safe_ratio(ssd_hot, ssd_hot + ssd_disk), 4),
|
|
}
|