mirror of
https://github.com/element-hq/synapse
synced 2024-09-29 17:02:41 +00:00
Track memory usage of caches
This commit is contained in:
parent
a15c003e5b
commit
e9f5812eff
4 changed files with 63 additions and 1 deletions
3
mypy.ini
3
mypy.ini
|
@ -172,3 +172,6 @@ ignore_missing_imports = True
|
||||||
|
|
||||||
[mypy-txacme.*]
|
[mypy-txacme.*]
|
||||||
ignore_missing_imports = True
|
ignore_missing_imports = True
|
||||||
|
|
||||||
|
[mypy-pympler.*]
|
||||||
|
ignore_missing_imports = True
|
||||||
|
|
|
@ -121,6 +121,7 @@ CONDITIONAL_REQUIREMENTS = {
|
||||||
# hiredis is not a *strict* dependency, but it makes things much faster.
|
# hiredis is not a *strict* dependency, but it makes things much faster.
|
||||||
# (if it is not installed, we fall back to slow code.)
|
# (if it is not installed, we fall back to slow code.)
|
||||||
"redis": ["txredisapi>=1.4.7", "hiredis"],
|
"redis": ["txredisapi>=1.4.7", "hiredis"],
|
||||||
|
"cache_memroy": ["pympler"],
|
||||||
}
|
}
|
||||||
|
|
||||||
ALL_OPTIONAL_REQUIREMENTS = set() # type: Set[str]
|
ALL_OPTIONAL_REQUIREMENTS = set() # type: Set[str]
|
||||||
|
|
|
@ -32,6 +32,11 @@ cache_hits = Gauge("synapse_util_caches_cache:hits", "", ["name"])
|
||||||
cache_evicted = Gauge("synapse_util_caches_cache:evicted_size", "", ["name"])
|
cache_evicted = Gauge("synapse_util_caches_cache:evicted_size", "", ["name"])
|
||||||
cache_total = Gauge("synapse_util_caches_cache:total", "", ["name"])
|
cache_total = Gauge("synapse_util_caches_cache:total", "", ["name"])
|
||||||
cache_max_size = Gauge("synapse_util_caches_cache_max_size", "", ["name"])
|
cache_max_size = Gauge("synapse_util_caches_cache_max_size", "", ["name"])
|
||||||
|
cache_memory_usage = Gauge(
|
||||||
|
"synapse_util_caches_cache_memory_usage",
|
||||||
|
"Estimated size in bytes of the caches",
|
||||||
|
["name"],
|
||||||
|
)
|
||||||
|
|
||||||
response_cache_size = Gauge("synapse_util_caches_response_cache:size", "", ["name"])
|
response_cache_size = Gauge("synapse_util_caches_response_cache:size", "", ["name"])
|
||||||
response_cache_hits = Gauge("synapse_util_caches_response_cache:hits", "", ["name"])
|
response_cache_hits = Gauge("synapse_util_caches_response_cache:hits", "", ["name"])
|
||||||
|
@ -52,6 +57,7 @@ class CacheMetric:
|
||||||
hits = attr.ib(default=0)
|
hits = attr.ib(default=0)
|
||||||
misses = attr.ib(default=0)
|
misses = attr.ib(default=0)
|
||||||
evicted_size = attr.ib(default=0)
|
evicted_size = attr.ib(default=0)
|
||||||
|
memory_usage = attr.ib(default=None)
|
||||||
|
|
||||||
def inc_hits(self):
|
def inc_hits(self):
|
||||||
self.hits += 1
|
self.hits += 1
|
||||||
|
@ -81,6 +87,8 @@ class CacheMetric:
|
||||||
cache_total.labels(self._cache_name).set(self.hits + self.misses)
|
cache_total.labels(self._cache_name).set(self.hits + self.misses)
|
||||||
if getattr(self._cache, "max_size", None):
|
if getattr(self._cache, "max_size", None):
|
||||||
cache_max_size.labels(self._cache_name).set(self._cache.max_size)
|
cache_max_size.labels(self._cache_name).set(self._cache.max_size)
|
||||||
|
if self.memory_usage is not None:
|
||||||
|
cache_memory_usage.labels(self._cache_name).set(self.memory_usage)
|
||||||
if self._collect_callback:
|
if self._collect_callback:
|
||||||
self._collect_callback()
|
self._collect_callback()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
|
@ -33,6 +33,33 @@ from synapse.config import cache as cache_config
|
||||||
from synapse.util.caches import CacheMetric, register_cache
|
from synapse.util.caches import CacheMetric, register_cache
|
||||||
from synapse.util.caches.treecache import TreeCache
|
from synapse.util.caches.treecache import TreeCache
|
||||||
|
|
||||||
|
try:
|
||||||
|
from pympler.asizeof import Asizer
|
||||||
|
|
||||||
|
sizer = Asizer()
|
||||||
|
sizer.exclude_refs((), None, "")
|
||||||
|
|
||||||
|
def _get_size_of(val: Any, *, recurse=True) -> int:
|
||||||
|
"""Get an estimate of the size in bytes of the object.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
val: The object to size.
|
||||||
|
recurse: If true will include referenced values in the size,
|
||||||
|
otherwise only sizes the given object.
|
||||||
|
"""
|
||||||
|
return sizer.asizeof(val, limit=100 if recurse else 0)
|
||||||
|
|
||||||
|
|
||||||
|
except ImportError:
|
||||||
|
|
||||||
|
def _get_size_of(val: Any, recurse=True) -> int:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
# Whether to track estimated memory usage of the LruCaches.
|
||||||
|
TRACK_MEMORY_USAGE = True
|
||||||
|
|
||||||
|
|
||||||
# Function type: the type used for invalidation callbacks
|
# Function type: the type used for invalidation callbacks
|
||||||
FT = TypeVar("FT", bound=Callable[..., Any])
|
FT = TypeVar("FT", bound=Callable[..., Any])
|
||||||
|
|
||||||
|
@ -54,7 +81,7 @@ def enumerate_leaves(node, depth):
|
||||||
|
|
||||||
|
|
||||||
class _Node:
|
class _Node:
|
||||||
__slots__ = ["prev_node", "next_node", "key", "value", "callbacks"]
|
__slots__ = ["prev_node", "next_node", "key", "value", "callbacks", "memory"]
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, prev_node, next_node, key, value, callbacks: Optional[set] = None
|
self, prev_node, next_node, key, value, callbacks: Optional[set] = None
|
||||||
|
@ -65,6 +92,16 @@ class _Node:
|
||||||
self.value = value
|
self.value = value
|
||||||
self.callbacks = callbacks or set()
|
self.callbacks = callbacks or set()
|
||||||
|
|
||||||
|
self.memory = 0
|
||||||
|
if TRACK_MEMORY_USAGE:
|
||||||
|
self.memory = (
|
||||||
|
_get_size_of(key)
|
||||||
|
+ _get_size_of(value)
|
||||||
|
+ _get_size_of(self.callbacks, recurse=False)
|
||||||
|
+ _get_size_of(self, recurse=False)
|
||||||
|
)
|
||||||
|
self.memory += _get_size_of(self.memory, recurse=False)
|
||||||
|
|
||||||
|
|
||||||
class LruCache(Generic[KT, VT]):
|
class LruCache(Generic[KT, VT]):
|
||||||
"""
|
"""
|
||||||
|
@ -136,6 +173,9 @@ class LruCache(Generic[KT, VT]):
|
||||||
self,
|
self,
|
||||||
collect_callback=metrics_collection_callback,
|
collect_callback=metrics_collection_callback,
|
||||||
) # type: Optional[CacheMetric]
|
) # type: Optional[CacheMetric]
|
||||||
|
|
||||||
|
if TRACK_MEMORY_USAGE and metrics:
|
||||||
|
metrics.memory_usage = 0
|
||||||
else:
|
else:
|
||||||
metrics = None
|
metrics = None
|
||||||
|
|
||||||
|
@ -188,6 +228,9 @@ class LruCache(Generic[KT, VT]):
|
||||||
if size_callback:
|
if size_callback:
|
||||||
cached_cache_len[0] += size_callback(node.value)
|
cached_cache_len[0] += size_callback(node.value)
|
||||||
|
|
||||||
|
if TRACK_MEMORY_USAGE and metrics:
|
||||||
|
metrics.memory_usage += node.memory
|
||||||
|
|
||||||
def move_node_to_front(node):
|
def move_node_to_front(node):
|
||||||
prev_node = node.prev_node
|
prev_node = node.prev_node
|
||||||
next_node = node.next_node
|
next_node = node.next_node
|
||||||
|
@ -214,6 +257,10 @@ class LruCache(Generic[KT, VT]):
|
||||||
for cb in node.callbacks:
|
for cb in node.callbacks:
|
||||||
cb()
|
cb()
|
||||||
node.callbacks.clear()
|
node.callbacks.clear()
|
||||||
|
|
||||||
|
if TRACK_MEMORY_USAGE and metrics:
|
||||||
|
metrics.memory_usage -= node.memory
|
||||||
|
|
||||||
return deleted_len
|
return deleted_len
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
|
@ -332,6 +379,9 @@ class LruCache(Generic[KT, VT]):
|
||||||
if size_callback:
|
if size_callback:
|
||||||
cached_cache_len[0] = 0
|
cached_cache_len[0] = 0
|
||||||
|
|
||||||
|
if TRACK_MEMORY_USAGE and metrics:
|
||||||
|
metrics.memory_usage = 0
|
||||||
|
|
||||||
@synchronized
|
@synchronized
|
||||||
def cache_contains(key: KT) -> bool:
|
def cache_contains(key: KT) -> bool:
|
||||||
return key in cache
|
return key in cache
|
||||||
|
|
Loading…
Reference in a new issue