fixed prometheus statistics to correctly use labels
This commit is contained in:
parent
9da37855c0
commit
d1ba39df1c
4 changed files with 57 additions and 3 deletions
|
@ -81,6 +81,8 @@ def api_etag(permissions=True, etag_func=AccessPermission.etag_func, base_mapdat
|
||||||
|
|
||||||
|
|
||||||
def api_stats(stat_name):
|
def api_stats(stat_name):
|
||||||
|
from c3nav.mapdata.metrics import APIStatsCollector
|
||||||
|
APIStatsCollector.add_stat(stat_name, ['by', 'query'])
|
||||||
def wrapper(func):
|
def wrapper(func):
|
||||||
@wraps(func)
|
@wraps(func)
|
||||||
def wrapped_func(request, *args, **kwargs):
|
def wrapped_func(request, *args, **kwargs):
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
import re
|
import re
|
||||||
|
from typing import Optional, Sequence
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
|
@ -18,15 +20,54 @@ if settings.METRCIS:
|
||||||
reports_open.set_function(lambda: Report.objects.filter(open=True).count()),
|
reports_open.set_function(lambda: Report.objects.filter(open=True).count()),
|
||||||
|
|
||||||
class APIStatsCollector(Collector):
|
class APIStatsCollector(Collector):
|
||||||
|
|
||||||
|
name_registry: dict[str, None | Sequence[str]] = dict()
|
||||||
|
|
||||||
def collect(self):
|
def collect(self):
|
||||||
|
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__*"):
|
for key in client.keys(f"*{settings.CACHES['default'].get('KEY_PREFIX', '')}apistats__*"):
|
||||||
key = key.decode('utf-8').split(':', 2)[2]
|
key: str = key.decode('utf-8').split(':', 2)[2]
|
||||||
key = re.sub(r'[^a-zA-Z0-9_]', '_', key)
|
value = cache.get(key)
|
||||||
yield CounterMetricFamily(f'c3nav_{key}', key, 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
|
||||||
|
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('__')
|
||||||
|
try:
|
||||||
|
label_names = self.name_registry[name]
|
||||||
|
except KeyError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if label_names is None:
|
||||||
|
label_names = list()
|
||||||
|
|
||||||
|
if len(label_names) != len(labels):
|
||||||
|
raise ValueError('configured labels and number of extracted labels doesn\'t match.')
|
||||||
|
|
||||||
|
try:
|
||||||
|
counter = metrics[name]
|
||||||
|
except KeyError:
|
||||||
|
counter = metrics[name] = CounterMetricFamily(f'c3nav_{name}', f'c3nav_{name}',
|
||||||
|
labels=label_names)
|
||||||
|
counter.add_metric(labels, value)
|
||||||
|
return metrics.values()
|
||||||
|
|
||||||
def describe(self):
|
def describe(self):
|
||||||
return list()
|
return list()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def add_stat(cls, name:str, label_names: Optional[str | Sequence[str]] = None):
|
||||||
|
if isinstance(label_names, str):
|
||||||
|
label_names = [label_names]
|
||||||
|
if name in cls.name_registry and label_names != cls.name_registry[name]:
|
||||||
|
raise KeyError(f'{name} already exists')
|
||||||
|
cls.name_registry[name] = label_names
|
||||||
|
|
||||||
|
|
||||||
REGISTRY.register(APIStatsCollector())
|
REGISTRY.register(APIStatsCollector())
|
||||||
|
|
|
@ -6,6 +6,7 @@ from ninja import Router as APIRouter
|
||||||
|
|
||||||
from c3nav.api.auth import auth_responses
|
from c3nav.api.auth import auth_responses
|
||||||
from c3nav.api.schema import BaseSchema
|
from c3nav.api.schema import BaseSchema
|
||||||
|
from c3nav.mapdata.metrics import APIStatsCollector
|
||||||
from c3nav.mapdata.models.access import AccessPermission
|
from c3nav.mapdata.models.access import AccessPermission
|
||||||
from c3nav.mapdata.schemas.models import CustomLocationSchema
|
from c3nav.mapdata.schemas.models import CustomLocationSchema
|
||||||
from c3nav.mapdata.utils.cache.stats import increment_cache_key
|
from c3nav.mapdata.utils.cache.stats import increment_cache_key
|
||||||
|
@ -51,6 +52,9 @@ def get_position(request, parameters: LocateRequestSchema):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
APIStatsCollector.add_stat('locate', 'location')
|
||||||
|
|
||||||
|
|
||||||
@positioning_api_router.get('/locate-test/', summary="debug position",
|
@positioning_api_router.get('/locate-test/', summary="debug position",
|
||||||
description="outputs a location for debugging purposes",
|
description="outputs a location for debugging purposes",
|
||||||
response={200: PositioningResult, **auth_responses})
|
response={200: PositioningResult, **auth_responses})
|
||||||
|
|
|
@ -14,6 +14,7 @@ from c3nav.api.exceptions import APIRequestValidationFailed
|
||||||
from c3nav.api.schema import BaseSchema
|
from c3nav.api.schema import BaseSchema
|
||||||
from c3nav.api.utils import NonEmptyStr
|
from c3nav.api.utils import NonEmptyStr
|
||||||
from c3nav.mapdata.api.base import api_stats_clean_location_value
|
from c3nav.mapdata.api.base import api_stats_clean_location_value
|
||||||
|
from c3nav.mapdata.metrics import APIStatsCollector
|
||||||
from c3nav.mapdata.models.access import AccessPermission
|
from c3nav.mapdata.models.access import AccessPermission
|
||||||
from c3nav.mapdata.models.locations import Position
|
from c3nav.mapdata.models.locations import Position
|
||||||
from c3nav.mapdata.schemas.model_base import AnyLocationID, Coordinates3D
|
from c3nav.mapdata.schemas.model_base import AnyLocationID, Coordinates3D
|
||||||
|
@ -251,6 +252,12 @@ def get_route(request, parameters: RouteParametersSchema):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
APIStatsCollector.add_stat('route')
|
||||||
|
APIStatsCollector.add_stat('route_tuple', ['origin', 'destination'])
|
||||||
|
APIStatsCollector.add_stat('route_origin', ['origin'])
|
||||||
|
APIStatsCollector.add_stat('route_destination', ['destination'])
|
||||||
|
|
||||||
|
|
||||||
def _new_serialize_route_options(options):
|
def _new_serialize_route_options(options):
|
||||||
# todo: RouteOptions should obviously be modernized
|
# todo: RouteOptions should obviously be modernized
|
||||||
main_options = {}
|
main_options = {}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue