Skip to content

Commit 6417726

Browse files
committed
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.
1 parent 4f44671 commit 6417726

File tree

3 files changed

+67
-2
lines changed

3 files changed

+67
-2
lines changed

api/tacticalrmm/core/decorators.py

+21
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,24 @@ def wrap(request, *args, **kwargs):
2929
wrap.__doc__ = function.__doc__
3030
wrap.__name__ = function.__name__
3131
return wrap
32+
33+
def metrics_view(function):
34+
def wrap(request, *args, **kwargs):
35+
if request.method != "GET":
36+
return HttpResponse("Invalid request type\n", status=400)
37+
38+
if "Authorization" not in request.headers:
39+
return HttpResponse("Missing 'Authorization' header\n", status=400)
40+
41+
token = getattr(settings, "MON_TOKEN", "")
42+
if not token:
43+
return HttpResponse("Missing token config\n", status=401)
44+
45+
if request.headers["Authorization"] != 'Bearer ' + token:
46+
return HttpResponse("Not authenticated\n", status=401)
47+
48+
return function(request, *args, **kwargs)
49+
50+
wrap.__doc__ = function.__doc__
51+
wrap.__name__ = function.__name__
52+
return wrap

api/tacticalrmm/core/views.py

+44-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import pytz
66
from cryptography import x509
77
from django.conf import settings
8-
from django.http import JsonResponse
8+
from django.http import JsonResponse, HttpResponse
99
from django.shortcuts import get_object_or_404
1010
from django.utils import timezone as djangotime
1111
from django.views.decorators.csrf import csrf_exempt
@@ -15,7 +15,7 @@
1515
from rest_framework.response import Response
1616
from rest_framework.views import APIView
1717

18-
from core.decorators import monitoring_view
18+
from core.decorators import monitoring_view, metrics_view
1919
from core.utils import get_core_settings, sysd_svc_is_running, token_is_valid
2020
from logs.models import AuditLog
2121
from tacticalrmm.constants import AuditActionType, PAStatus
@@ -449,3 +449,45 @@ def status(request):
449449
"nginx": sysd_svc_is_running("nginx.service"),
450450
}
451451
return JsonResponse(ret, json_dumps_params={"indent": 2})
452+
453+
@csrf_exempt
454+
@metrics_view
455+
def metrics(request):
456+
from agents.models import Agent
457+
from clients.models import Client, Site
458+
cert_file, _ = get_certs()
459+
cert_bytes = Path(cert_file).read_bytes()
460+
cert = x509.load_pem_x509_certificate(cert_bytes)
461+
cert_expiration = cert.not_valid_after.timestamp()
462+
metrics = [
463+
'trmm_buildinfo{{version="{}"}} 1'.format(settings.TRMM_VERSION),
464+
'trmm_meshinfo{{version="{}"}} 1'.format(settings.MESH_VER),
465+
'trmm_natsinfo{{version="{}"}} 1'.format(settings.NATS_SERVER_VER),
466+
'trmm_appinfo{{version="{}"}} 1'.format(settings.APP_VER),
467+
'trmm_agent{{latest_version="{}"}} 1'.format(settings.LATEST_AGENT_VER),
468+
"trmm_agent_count {}".format(Agent.objects.count()),
469+
"trmm_client_count {}".format(Client.objects.count()),
470+
"trmm_site_count {}".format(Site.objects.count()),
471+
"trmm_cert_expiration_time {}".format(cert_expiration),
472+
]
473+
474+
if settings.DOCKER_BUILD:
475+
metrics.append("trmm_docker_build 1")
476+
else:
477+
metrics.append("trmm_docker_build 0")
478+
services = {
479+
"django": "rmm.service",
480+
"mesh": "meshcentral.service",
481+
"daphne": "daphne.service",
482+
"celery": "celery.service",
483+
"celerybeat": "celerybeat.service",
484+
"redis": "redis-server.service",
485+
"postgres": "postgresql.service",
486+
"mongo": "mongod.service",
487+
"nats": "nats.service",
488+
"nats-api": "nats-api.service",
489+
"nginx": "nginx.service",
490+
}
491+
for k,v in services.items():
492+
metrics.append('trmm_service{{name="{}",unit="{}"}} {}'.format(k,v,int(sysd_svc_is_running(v))))
493+
return HttpResponse(("\n").join(metrics),content_type="text/plain")

api/tacticalrmm/tacticalrmm/urls.py

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from accounts.views import CheckCreds, LoginView
66
from agents.consumers import SendCMD
77
from core.consumers import DashInfo
8+
from core import views as core_views
89

910

1011
class AgentIDConverter:
@@ -32,6 +33,7 @@ def to_url(self, value):
3233
path("winupdate/", include("winupdate.urls")),
3334
path("software/", include("software.urls")),
3435
path("core/", include("core.urls")),
36+
path("metrics", core_views.metrics),
3537
path("automation/", include("automation.urls")),
3638
path("tasks/", include("autotasks.urls")),
3739
path("logs/", include("logs.urls")),

0 commit comments

Comments
 (0)