Make LruCache register its own metrics (#8561)
rather than have everything that instantiates an LruCache manage metrics separately, have LruCache do it itself.pull/8562/head
parent
da0090fdff
commit
3ee17585cd
|
@ -0,0 +1 @@
|
|||
Move metric registration code down into `LruCache`.
|
|
@ -34,7 +34,6 @@ from synapse.api.room_versions import KNOWN_ROOM_VERSIONS
|
|||
from synapse.events import EventBase
|
||||
from synapse.logging import opentracing as opentracing
|
||||
from synapse.types import StateMap, UserID
|
||||
from synapse.util.caches import register_cache
|
||||
from synapse.util.caches.lrucache import LruCache
|
||||
from synapse.util.metrics import Measure
|
||||
|
||||
|
@ -70,8 +69,7 @@ class Auth:
|
|||
self.store = hs.get_datastore()
|
||||
self.state = hs.get_state_handler()
|
||||
|
||||
self.token_cache = LruCache(10000)
|
||||
register_cache("cache", "token_cache", self.token_cache)
|
||||
self.token_cache = LruCache(10000, "token_cache")
|
||||
|
||||
self._auth_blocking = AuthBlocking(self.hs)
|
||||
|
||||
|
|
|
@ -20,7 +20,6 @@ from typing import Any, Dict, List, Optional, Pattern, Union
|
|||
|
||||
from synapse.events import EventBase
|
||||
from synapse.types import UserID
|
||||
from synapse.util.caches import register_cache
|
||||
from synapse.util.caches.lrucache import LruCache
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -186,8 +185,7 @@ class PushRuleEvaluatorForEvent:
|
|||
|
||||
|
||||
# Caches (string, is_glob, word_boundary) -> regex for push. See _glob_matches
|
||||
regex_cache = LruCache(50000)
|
||||
register_cache("cache", "regex_push_cache", regex_cache)
|
||||
regex_cache = LruCache(50000, "regex_push_cache")
|
||||
|
||||
|
||||
def _glob_matches(glob: str, value: str, word_boundary: bool = False) -> bool:
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
import logging
|
||||
from sys import intern
|
||||
from typing import Callable, Dict, Optional
|
||||
from typing import Callable, Dict, Optional, Sized
|
||||
|
||||
import attr
|
||||
from prometheus_client.core import Gauge
|
||||
|
@ -92,7 +92,7 @@ class CacheMetric:
|
|||
def register_cache(
|
||||
cache_type: str,
|
||||
cache_name: str,
|
||||
cache,
|
||||
cache: Sized,
|
||||
collect_callback: Optional[Callable] = None,
|
||||
resizable: bool = True,
|
||||
resize_callback: Optional[Callable] = None,
|
||||
|
@ -100,12 +100,15 @@ def register_cache(
|
|||
"""Register a cache object for metric collection and resizing.
|
||||
|
||||
Args:
|
||||
cache_type
|
||||
cache_type: a string indicating the "type" of the cache. This is used
|
||||
only for deduplication so isn't too important provided it's constant.
|
||||
cache_name: name of the cache
|
||||
cache: cache itself
|
||||
cache: cache itself, which must implement __len__(), and may optionally implement
|
||||
a max_size property
|
||||
collect_callback: If given, a function which is called during metric
|
||||
collection to update additional metrics.
|
||||
resizable: Whether this cache supports being resized.
|
||||
resizable: Whether this cache supports being resized, in which case either
|
||||
resize_callback must be provided, or the cache must support set_max_size().
|
||||
resize_callback: A function which can be called to resize the cache.
|
||||
|
||||
Returns:
|
||||
|
|
|
@ -24,7 +24,6 @@ from prometheus_client import Gauge
|
|||
from twisted.internet import defer
|
||||
|
||||
from synapse.util.async_helpers import ObservableDeferred
|
||||
from synapse.util.caches import register_cache
|
||||
from synapse.util.caches.lrucache import LruCache
|
||||
from synapse.util.caches.treecache import TreeCache, iterate_tree_cache_entry
|
||||
|
||||
|
@ -54,10 +53,7 @@ class DeferredCache(Generic[KT, VT]):
|
|||
|
||||
__slots__ = (
|
||||
"cache",
|
||||
"name",
|
||||
"keylen",
|
||||
"thread",
|
||||
"metrics",
|
||||
"_pending_deferred_cache",
|
||||
)
|
||||
|
||||
|
@ -89,37 +85,27 @@ class DeferredCache(Generic[KT, VT]):
|
|||
cache_type()
|
||||
) # type: MutableMapping[KT, CacheEntry]
|
||||
|
||||
def metrics_cb():
|
||||
cache_pending_metric.labels(name).set(len(self._pending_deferred_cache))
|
||||
|
||||
# cache is used for completed results and maps to the result itself, rather than
|
||||
# a Deferred.
|
||||
self.cache = LruCache(
|
||||
max_size=max_entries,
|
||||
keylen=keylen,
|
||||
cache_name=name,
|
||||
cache_type=cache_type,
|
||||
size_callback=(lambda d: len(d)) if iterable else None,
|
||||
evicted_callback=self._on_evicted,
|
||||
metrics_collection_callback=metrics_cb,
|
||||
apply_cache_factor_from_config=apply_cache_factor_from_config,
|
||||
)
|
||||
|
||||
self.name = name
|
||||
self.keylen = keylen
|
||||
self.thread = None # type: Optional[threading.Thread]
|
||||
self.metrics = register_cache(
|
||||
"cache",
|
||||
name,
|
||||
self.cache,
|
||||
collect_callback=self._metrics_collection_callback,
|
||||
)
|
||||
|
||||
@property
|
||||
def max_entries(self):
|
||||
return self.cache.max_size
|
||||
|
||||
def _on_evicted(self, evicted_count):
|
||||
self.metrics.inc_evictions(evicted_count)
|
||||
|
||||
def _metrics_collection_callback(self):
|
||||
cache_pending_metric.labels(self.name).set(len(self._pending_deferred_cache))
|
||||
|
||||
def check_thread(self):
|
||||
expected_thread = self.thread
|
||||
if expected_thread is None:
|
||||
|
@ -154,21 +140,18 @@ class DeferredCache(Generic[KT, VT]):
|
|||
if val is not _Sentinel.sentinel:
|
||||
val.callbacks.update(callbacks)
|
||||
if update_metrics:
|
||||
self.metrics.inc_hits()
|
||||
m = self.cache.metrics
|
||||
assert m # we always have a name, so should always have metrics
|
||||
m.inc_hits()
|
||||
return val.deferred
|
||||
|
||||
val = self.cache.get(key, _Sentinel.sentinel, callbacks=callbacks)
|
||||
if val is not _Sentinel.sentinel:
|
||||
self.metrics.inc_hits()
|
||||
return val
|
||||
|
||||
if update_metrics:
|
||||
self.metrics.inc_misses()
|
||||
|
||||
if default is _Sentinel.sentinel:
|
||||
val = self.cache.get(
|
||||
key, default, callbacks=callbacks, update_metrics=update_metrics
|
||||
)
|
||||
if val is _Sentinel.sentinel:
|
||||
raise KeyError()
|
||||
else:
|
||||
return default
|
||||
return val
|
||||
|
||||
def set(
|
||||
self,
|
||||
|
|
|
@ -19,8 +19,6 @@ from collections import namedtuple
|
|||
|
||||
from synapse.util.caches.lrucache import LruCache
|
||||
|
||||
from . import register_cache
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
@ -46,18 +44,16 @@ class DictionaryCache:
|
|||
"""
|
||||
|
||||
def __init__(self, name, max_entries=1000):
|
||||
self.cache = LruCache(max_size=max_entries, size_callback=len)
|
||||
self.cache = LruCache(max_size=max_entries, cache_name=name, size_callback=len)
|
||||
|
||||
self.name = name
|
||||
self.sequence = 0
|
||||
self.thread = None
|
||||
# caches_by_name[name] = self.cache
|
||||
|
||||
class Sentinel:
|
||||
__slots__ = []
|
||||
|
||||
self.sentinel = Sentinel()
|
||||
self.metrics = register_cache("dictionary", name, self.cache)
|
||||
|
||||
def check_thread(self):
|
||||
expected_thread = self.thread
|
||||
|
@ -82,8 +78,6 @@ class DictionaryCache:
|
|||
"""
|
||||
entry = self.cache.get(key, self.sentinel)
|
||||
if entry is not self.sentinel:
|
||||
self.metrics.inc_hits()
|
||||
|
||||
if dict_keys is None:
|
||||
return DictionaryEntry(
|
||||
entry.full, entry.known_absent, dict(entry.value)
|
||||
|
@ -95,7 +89,6 @@ class DictionaryCache:
|
|||
{k: entry.value[k] for k in dict_keys if k in entry.value},
|
||||
)
|
||||
|
||||
self.metrics.inc_misses()
|
||||
return DictionaryEntry(False, set(), {})
|
||||
|
||||
def invalidate(self, key):
|
||||
|
|
|
@ -18,6 +18,7 @@ from functools import wraps
|
|||
from typing import Callable, Optional, Type, Union
|
||||
|
||||
from synapse.config import cache as cache_config
|
||||
from synapse.util.caches import CacheMetric, register_cache
|
||||
from synapse.util.caches.treecache import TreeCache
|
||||
|
||||
|
||||
|
@ -43,27 +44,29 @@ class _Node:
|
|||
|
||||
class LruCache:
|
||||
"""
|
||||
Least-recently-used cache.
|
||||
Least-recently-used cache, supporting prometheus metrics and invalidation callbacks.
|
||||
|
||||
Supports del_multi only if cache_type=TreeCache
|
||||
If cache_type=TreeCache, all keys must be tuples.
|
||||
|
||||
Can also set callbacks on objects when getting/setting which are fired
|
||||
when that key gets invalidated/evicted.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
max_size: int,
|
||||
cache_name: Optional[str] = None,
|
||||
keylen: int = 1,
|
||||
cache_type: Type[Union[dict, TreeCache]] = dict,
|
||||
size_callback: Optional[Callable] = None,
|
||||
evicted_callback: Optional[Callable] = None,
|
||||
metrics_collection_callback: Optional[Callable[[], None]] = None,
|
||||
apply_cache_factor_from_config: bool = True,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
max_size: The maximum amount of entries the cache can hold
|
||||
|
||||
cache_name: The name of this cache, for the prometheus metrics. If unset,
|
||||
no metrics will be reported on this cache.
|
||||
|
||||
keylen: The length of the tuple used as the cache key. Ignored unless
|
||||
cache_type is `TreeCache`.
|
||||
|
||||
|
@ -73,9 +76,13 @@ class LruCache:
|
|||
|
||||
size_callback (func(V) -> int | None):
|
||||
|
||||
evicted_callback (func(int)|None):
|
||||
if not None, called on eviction with the size of the evicted
|
||||
entry
|
||||
metrics_collection_callback:
|
||||
metrics collection callback. This is called early in the metrics
|
||||
collection process, before any of the metrics registered with the
|
||||
prometheus Registry are collected, so can be used to update any dynamic
|
||||
metrics.
|
||||
|
||||
Ignored if cache_name is None.
|
||||
|
||||
apply_cache_factor_from_config (bool): If true, `max_size` will be
|
||||
multiplied by a cache factor derived from the homeserver config
|
||||
|
@ -94,6 +101,19 @@ class LruCache:
|
|||
else:
|
||||
self.max_size = int(max_size)
|
||||
|
||||
if cache_name is not None:
|
||||
metrics = register_cache(
|
||||
"lru_cache",
|
||||
cache_name,
|
||||
self,
|
||||
collect_callback=metrics_collection_callback,
|
||||
) # type: Optional[CacheMetric]
|
||||
else:
|
||||
metrics = None
|
||||
|
||||
# this is exposed for access from outside this class
|
||||
self.metrics = metrics
|
||||
|
||||
list_root = _Node(None, None, None, None)
|
||||
list_root.next_node = list_root
|
||||
list_root.prev_node = list_root
|
||||
|
@ -105,8 +125,8 @@ class LruCache:
|
|||
todelete = list_root.prev_node
|
||||
evicted_len = delete_node(todelete)
|
||||
cache.pop(todelete.key, None)
|
||||
if evicted_callback:
|
||||
evicted_callback(evicted_len)
|
||||
if metrics:
|
||||
metrics.inc_evictions(evicted_len)
|
||||
|
||||
def synchronized(f):
|
||||
@wraps(f)
|
||||
|
@ -169,13 +189,17 @@ class LruCache:
|
|||
return deleted_len
|
||||
|
||||
@synchronized
|
||||
def cache_get(key, default=None, callbacks=[]):
|
||||
def cache_get(key, default=None, callbacks=[], update_metrics=True):
|
||||
node = cache.get(key, None)
|
||||
if node is not None:
|
||||
move_node_to_front(node)
|
||||
node.callbacks.update(callbacks)
|
||||
if update_metrics and metrics:
|
||||
metrics.inc_hits()
|
||||
return node.value
|
||||
else:
|
||||
if update_metrics and metrics:
|
||||
metrics.inc_misses()
|
||||
return default
|
||||
|
||||
@synchronized
|
||||
|
|
|
@ -59,7 +59,7 @@ class LruCacheTestCase(unittest.HomeserverTestCase):
|
|||
self.assertEquals(cache.pop("key"), None)
|
||||
|
||||
def test_del_multi(self):
|
||||
cache = LruCache(4, 2, cache_type=TreeCache)
|
||||
cache = LruCache(4, keylen=2, cache_type=TreeCache)
|
||||
cache[("animal", "cat")] = "mew"
|
||||
cache[("animal", "dog")] = "woof"
|
||||
cache[("vehicles", "car")] = "vroom"
|
||||
|
@ -160,7 +160,7 @@ class LruCacheCallbacksTestCase(unittest.HomeserverTestCase):
|
|||
m2 = Mock()
|
||||
m3 = Mock()
|
||||
m4 = Mock()
|
||||
cache = LruCache(4, 2, cache_type=TreeCache)
|
||||
cache = LruCache(4, keylen=2, cache_type=TreeCache)
|
||||
|
||||
cache.set(("a", "1"), "value", callbacks=[m1])
|
||||
cache.set(("a", "2"), "value", callbacks=[m2])
|
||||
|
|
Loading…
Reference in New Issue