diff --git a/kqueen/blueprints/metrics/helpers.py b/kqueen/blueprints/metrics/helpers.py new file mode 100644 index 00000000..e32e39ce --- /dev/null +++ b/kqueen/blueprints/metrics/helpers.py @@ -0,0 +1,91 @@ +from collections import defaultdict +from kqueen.models import Organization +from kqueen.models import User +from prometheus_client import Gauge + +import asyncio +import logging + +logger = logging.getLogger(__name__) + +metrics = { + 'users_by_namespace': Gauge('users_by_namespace', 'Number of users in namespace', ['namespace']), + 'users_by_role': Gauge('users_by_role', 'Number of users by role', ['role']), + 'users_active': Gauge('users_active', 'Number of users by role'), + 'organization_count': Gauge('organization_count', 'Number of organizations'), +} + + +class MetricUpdater: + def __init__(self): + self.data = {} + + self.get_data() + + def update_metrics(self): + loop = asyncio.get_event_loop() + futures = [] + + for metric_name, metric in metrics.items(): + + update_function_name = 'update_metric_{}'.format(metric_name) + + logger.debug('Updating metric {metric_name}, with function {function}'.format( + metric_name=metric_name, + function=update_function_name, + )) + + try: + fnc = getattr(self, update_function_name) + except AttributeError: + msg = 'Missing update function {function} for metric {metric_name}'.format( + metric_name=metric_name, + function=update_function_name + ) + + raise Exception(msg) + + # run update function + future = loop.run_in_executor(None, fnc, metric) + futures.append(future) + + # run all updates + asyncio.wait(futures) + + def get_data(self): + # users + cls = User + namespace = None + + sum = defaultdict(lambda: defaultdict(lambda: 0)) + + for obj_id, obj in cls.list(namespace, True).items(): + user_dict = obj.get_dict(True) + + user_namespace = user_dict['organization']['namespace'] + user_role = user_dict['role'] + user_active = user_dict['active'] + + sum['namespace'][user_namespace] += 1 + sum['roles'][user_role] += 1 + sum['active'][user_active] += 1 + + self.data['users'] = sum + + # organizations + objs = Organization.list(None, False) + self.data['organizations'] = len(objs) + + def update_metric_users_by_namespace(self, metric): + for namespace, count in self.data['users']['namespace'].items(): + metric.labels(namespace).set(count) + + def update_metric_users_by_role(self, metric): + for role, count in self.data['users']['roles'].items(): + metric.labels(role).set(count) + + def update_metric_users_active(self, metric): + metric.set(self.data['users']['active'][True]) + + def update_metric_organization_count(self, metric): + metric.set(self.data['organizations']) diff --git a/kqueen/blueprints/metrics/test_helpers.py b/kqueen/blueprints/metrics/test_helpers.py new file mode 100644 index 00000000..eed41014 --- /dev/null +++ b/kqueen/blueprints/metrics/test_helpers.py @@ -0,0 +1,27 @@ +from .helpers import MetricUpdater +from prometheus_client import generate_latest + +import pytest + + +class TestMetricUpdates: + @pytest.fixture(scope='class') + def latest(self, user): + user.save() + + m = MetricUpdater() + m.update_metrics() + + return generate_latest().decode('utf-8') + + @pytest.mark.parametrize('metric', [ + ('users_by_namespace{namespace="demoorg"}'), + ('users_by_role{role="superadmin"}'), + ('users_active'), + ('organization_count'), + ]) + def test_metrics_exist(user, latest, metric): + + req = "{metric} ".format(metric=metric) + + assert req in latest diff --git a/kqueen/middleware.py b/kqueen/middleware.py index f1985472..6671d0fd 100644 --- a/kqueen/middleware.py +++ b/kqueen/middleware.py @@ -8,7 +8,7 @@ # Prometheus metrics REQUEST_COUNT = Counter( 'request_count', - 'HTPP Request Count', + 'HTTP Request Count', ['method', 'endpoint', 'http_status'] ) REQUEST_LATENCY = Histogram(