use redis MGET for metrics instead of django's cache.get

should improve performance
This commit is contained in:
Gwendolyn 2024-12-27 01:44:20 +01:00
parent 0a10de795d
commit d95167350d
2 changed files with 39 additions and 24 deletions

View file

@ -1,3 +1,4 @@
import itertools
import re import re
from typing import Optional, Sequence from typing import Optional, Sequence
@ -7,6 +8,17 @@ from django.core.cache import cache
from c3nav.mapdata.models.report import Report from c3nav.mapdata.models.report import Report
def chunked_iterable(iterable, size):
it = iter(iterable)
while True:
chunk = tuple(itertools.islice(it, size))
if not chunk:
break
yield chunk
if settings.METRICS: if settings.METRICS:
from prometheus_client import Gauge from prometheus_client import Gauge
from prometheus_client.core import CounterMetricFamily from prometheus_client.core import CounterMetricFamily
@ -28,35 +40,37 @@ if settings.METRICS:
metrics: dict[str, CounterMetricFamily] = dict() metrics: dict[str, CounterMetricFamily] = dict()
if settings.CACHES['default']['BACKEND'] == 'django.core.cache.backends.redis.RedisCache': if settings.CACHES['default']['BACKEND'] == 'django.core.cache.backends.redis.RedisCache':
client = cache._cache.get_client() client = cache._cache.get_client()
for key in client.keys(f"*{settings.CACHES['default'].get('KEY_PREFIX', '')}apistats__*"): keys = client.keys(f"*{settings.CACHES['default'].get('KEY_PREFIX', '')}apistats__*")
key: str = key.decode('utf-8').split(':', 2)[2]
value = cache.get(key)
key = key[10:] # trim apistats__ from the beginning
# some routing stats don't use double underscores to separate fields, workaround for now for group in chunked_iterable(keys, settings.METRICS_REDIS_CHUNK_SIZE):
if key.startswith('route_tuple_'): values = client.mget(group)
key = re.sub(r'^route_tuple_(.*)_(.*)$', r'route_tuple__\1__\2', key) for key, value in zip(group, values):
if key.startswith('route_origin_') or key.startswith('route_destination_'): key: str = key.decode('utf-8').split(':', 2)[2]
key = re.sub(r'^route_(origin|destination)_(.*)$', r'route_\1__\2', key) key = key[10:] # trim apistats__ from the beginning
# some routing stats don't use double underscores to separate fields, workaround for now
if key.startswith('route_tuple_'):
key = re.sub(r'^route_tuple_(.*)_(.*)$', r'route_tuple__\1__\2', key)
if key.startswith('route_origin_') or key.startswith('route_destination_'):
key = re.sub(r'^route_(origin|destination)_(.*)$', r'route_\1__\2', key)
name, *labels = key.split('__') name, *labels = key.split('__')
try: try:
label_names = self.name_registry[name] label_names = self.name_registry[name]
except KeyError: except KeyError:
continue continue
if label_names is None: if label_names is None:
label_names = list() label_names = list()
if len(label_names) != len(labels): if len(label_names) != len(labels):
raise ValueError('configured labels and number of extracted labels doesn\'t match.') raise ValueError('configured labels and number of extracted labels doesn\'t match.')
try: try:
counter = metrics[name] counter = metrics[name]
except KeyError: except KeyError:
counter = metrics[name] = CounterMetricFamily(f'c3nav_{name}', f'c3nav_{name}', counter = metrics[name] = CounterMetricFamily(f'c3nav_{name}', f'c3nav_{name}',
labels=label_names) labels=label_names)
counter.add_metric(labels, value) counter.add_metric(labels, value)
return metrics.values() return metrics.values()
def describe(self): def describe(self):

View file

@ -413,6 +413,7 @@ with suppress(ImportError):
INSTALLED_APPS.append('django_extensions') INSTALLED_APPS.append('django_extensions')
METRICS = config.getboolean('c3nav', 'metrics', fallback=False) METRICS = config.getboolean('c3nav', 'metrics', fallback=False)
METRICS_REDIS_CHUNK_SIZE = config.getint('c3nav', 'metrics_redis_chunk_size', fallback=10)
if METRICS: if METRICS:
try: try:
import django_prometheus # noqa import django_prometheus # noqa