From 6417726f10c712a8e669f67ffa7c185bbd35c355 Mon Sep 17 00:00:00 2001 From: ykuksenko <2882631+ykuksenko@users.noreply.github.com> Date: Wed, 29 Mar 2023 13:08:36 -0500 Subject: [PATCH] feat: add initial prometheus metrics support Prometheus environments want metrics to be in specific formats. So the metrics are slightly different from the monitoring endpoint. Some metrics were not transfered over from the monitoring endpoint because in a Prometheus environment one is likely to have other methods (node_exporter, blackbox_exporter) to monitor some aspects. --- api/tacticalrmm/core/decorators.py | 21 +++++++++++++ api/tacticalrmm/core/views.py | 46 +++++++++++++++++++++++++++-- api/tacticalrmm/tacticalrmm/urls.py | 2 ++ 3 files changed, 67 insertions(+), 2 deletions(-) diff --git a/api/tacticalrmm/core/decorators.py b/api/tacticalrmm/core/decorators.py index c77dde37fa..a022e29aaa 100644 --- a/api/tacticalrmm/core/decorators.py +++ b/api/tacticalrmm/core/decorators.py @@ -29,3 +29,24 @@ def wrap(request, *args, **kwargs): wrap.__doc__ = function.__doc__ wrap.__name__ = function.__name__ return wrap + +def metrics_view(function): + def wrap(request, *args, **kwargs): + if request.method != "GET": + return HttpResponse("Invalid request type\n", status=400) + + if "Authorization" not in request.headers: + return HttpResponse("Missing 'Authorization' header\n", status=400) + + token = getattr(settings, "MON_TOKEN", "") + if not token: + return HttpResponse("Missing token config\n", status=401) + + if request.headers["Authorization"] != 'Bearer ' + token: + return HttpResponse("Not authenticated\n", status=401) + + return function(request, *args, **kwargs) + + wrap.__doc__ = function.__doc__ + wrap.__name__ = function.__name__ + return wrap diff --git a/api/tacticalrmm/core/views.py b/api/tacticalrmm/core/views.py index b423067ec8..175c7a7b90 100644 --- a/api/tacticalrmm/core/views.py +++ b/api/tacticalrmm/core/views.py @@ -5,7 +5,7 @@ import pytz from cryptography import x509 from django.conf import settings -from django.http import JsonResponse +from django.http import JsonResponse, HttpResponse from django.shortcuts import get_object_or_404 from django.utils import timezone as djangotime from django.views.decorators.csrf import csrf_exempt @@ -15,7 +15,7 @@ from rest_framework.response import Response from rest_framework.views import APIView -from core.decorators import monitoring_view +from core.decorators import monitoring_view, metrics_view from core.utils import get_core_settings, sysd_svc_is_running, token_is_valid from logs.models import AuditLog from tacticalrmm.constants import AuditActionType, PAStatus @@ -449,3 +449,45 @@ def status(request): "nginx": sysd_svc_is_running("nginx.service"), } return JsonResponse(ret, json_dumps_params={"indent": 2}) + +@csrf_exempt +@metrics_view +def metrics(request): + from agents.models import Agent + from clients.models import Client, Site + cert_file, _ = get_certs() + cert_bytes = Path(cert_file).read_bytes() + cert = x509.load_pem_x509_certificate(cert_bytes) + cert_expiration = cert.not_valid_after.timestamp() + metrics = [ + 'trmm_buildinfo{{version="{}"}} 1'.format(settings.TRMM_VERSION), + 'trmm_meshinfo{{version="{}"}} 1'.format(settings.MESH_VER), + 'trmm_natsinfo{{version="{}"}} 1'.format(settings.NATS_SERVER_VER), + 'trmm_appinfo{{version="{}"}} 1'.format(settings.APP_VER), + 'trmm_agent{{latest_version="{}"}} 1'.format(settings.LATEST_AGENT_VER), + "trmm_agent_count {}".format(Agent.objects.count()), + "trmm_client_count {}".format(Client.objects.count()), + "trmm_site_count {}".format(Site.objects.count()), + "trmm_cert_expiration_time {}".format(cert_expiration), + ] + + if settings.DOCKER_BUILD: + metrics.append("trmm_docker_build 1") + else: + metrics.append("trmm_docker_build 0") + services = { + "django": "rmm.service", + "mesh": "meshcentral.service", + "daphne": "daphne.service", + "celery": "celery.service", + "celerybeat": "celerybeat.service", + "redis": "redis-server.service", + "postgres": "postgresql.service", + "mongo": "mongod.service", + "nats": "nats.service", + "nats-api": "nats-api.service", + "nginx": "nginx.service", + } + for k,v in services.items(): + metrics.append('trmm_service{{name="{}",unit="{}"}} {}'.format(k,v,int(sysd_svc_is_running(v)))) + return HttpResponse(("\n").join(metrics),content_type="text/plain") diff --git a/api/tacticalrmm/tacticalrmm/urls.py b/api/tacticalrmm/tacticalrmm/urls.py index 9d74598681..f7ffffe4b9 100644 --- a/api/tacticalrmm/tacticalrmm/urls.py +++ b/api/tacticalrmm/tacticalrmm/urls.py @@ -5,6 +5,7 @@ from accounts.views import CheckCreds, LoginView from agents.consumers import SendCMD from core.consumers import DashInfo +from core import views as core_views class AgentIDConverter: @@ -32,6 +33,7 @@ def to_url(self, value): path("winupdate/", include("winupdate.urls")), path("software/", include("software.urls")), path("core/", include("core.urls")), + path("metrics", core_views.metrics), path("automation/", include("automation.urls")), path("tasks/", include("autotasks.urls")), path("logs/", include("logs.urls")),