Browse Source

Experimental Prometheus metrics endpoint. cc: #300

pull/340/head
Pēteris Caune 5 years ago
parent
commit
12b946acf3
No known key found for this signature in database GPG Key ID: E28D7679E9A9EDE2
6 changed files with 103 additions and 3 deletions
  1. +2
    -1
      CHANGELOG.md
  2. +6
    -2
      hc/api/models.py
  3. +43
    -0
      hc/front/tests/test_metrics.py
  4. +1
    -0
      hc/front/urls.py
  5. +47
    -0
      hc/front/views.py
  6. +4
    -0
      templates/accounts/project.html

+ 2
- 1
CHANGELOG.md View File

@ -1,10 +1,11 @@
# Changelog # Changelog
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
## v1.14.0 - Unreleased
## v1.14.0-dev - Unreleased
### Improvements ### Improvements
- Improved UI to invite users from account's other projects (#258) - Improved UI to invite users from account's other projects (#258)
- Experimental Prometheus metrics endpoint (#300)
### Bug Fixes ### Bug Fixes
- The "render_docs" command checks if markdown and pygments is installed (#329) - The "render_docs" command checks if markdown and pygments is installed (#329)


+ 6
- 2
hc/api/models.py View File

@ -199,6 +199,11 @@ class Check(models.Model):
codes = self.channel_set.order_by("code").values_list("code", flat=True) codes = self.channel_set.order_by("code").values_list("code", flat=True)
return ",".join(map(str, codes)) return ",".join(map(str, codes))
@property
def unique_key(self):
code_half = self.code.hex[:16]
return hashlib.sha1(code_half.encode()).hexdigest()
def to_dict(self, readonly=False): def to_dict(self, readonly=False):
result = { result = {
@ -216,8 +221,7 @@ class Check(models.Model):
result["last_duration"] = int(self.last_duration.total_seconds()) result["last_duration"] = int(self.last_duration.total_seconds())
if readonly: if readonly:
code_half = self.code.hex[:16]
result["unique_key"] = hashlib.sha1(code_half.encode()).hexdigest()
result["unique_key"] = self.unique_key
else: else:
update_rel_url = reverse("hc-api-update", args=[self.code]) update_rel_url = reverse("hc-api-update", args=[self.code])
pause_rel_url = reverse("hc-api-pause", args=[self.code]) pause_rel_url = reverse("hc-api-pause", args=[self.code])


+ 43
- 0
hc/front/tests/test_metrics.py View File

@ -0,0 +1,43 @@
from hc.api.models import Check
from hc.test import BaseTestCase
class MetricsTestCase(BaseTestCase):
def setUp(self):
super(MetricsTestCase, self).setUp()
self.project.api_key_readonly = "R" * 32
self.project.save()
self.check = Check(project=self.project, name="Alice Was Here")
self.check.tags = "foo"
self.check.save()
key = "R" * 32
self.url = "/projects/%s/checks/metrics/?api_key=%s" % (self.project.code, key)
def test_it_works(self):
r = self.client.get(self.url)
self.assertEqual(r.status_code, 200)
self.assertContains(r, 'name="Alice Was Here"')
self.assertContains(r, 'tags="foo"')
self.assertContains(r, 'tag="foo"')
self.assertContains(r, "hc_checks_total 1")
def test_it_escapes_newline(self):
self.check.name = "Line 1\nLine2"
self.check.tags = "A\\C"
self.check.save()
r = self.client.get(self.url)
self.assertEqual(r.status_code, 200)
self.assertContains(r, "Line 1\\nLine2")
self.assertContains(r, "A\\\\C")
def test_it_checks_api_key_length(self):
r = self.client.get(self.url + "R")
self.assertEqual(r.status_code, 400)
def test_it_checks_api_key(self):
url = "/projects/%s/checks/metrics/?api_key=%s" % (self.project.code, "X" * 32)
r = self.client.get(url)
self.assertEqual(r.status_code, 403)

+ 1
- 0
hc/front/urls.py View File

@ -70,6 +70,7 @@ urlpatterns = [
path("projects/<uuid:code>/checks/add/", views.add_check, name="hc-add-check"), path("projects/<uuid:code>/checks/add/", views.add_check, name="hc-add-check"),
path("checks/cron_preview/", views.cron_preview), path("checks/cron_preview/", views.cron_preview),
path("projects/<uuid:code>/checks/status/", views.status, name="hc-status"), path("projects/<uuid:code>/checks/status/", views.status, name="hc-status"),
path("projects/<uuid:code>/checks/metrics/", views.metrics, name="hc-metrics"),
path("checks/<uuid:code>/", include(check_urls)), path("checks/<uuid:code>/", include(check_urls)),
path("integrations/", include(channel_urls)), path("integrations/", include(channel_urls)),
path("docs/", views.serve_doc, name="hc-docs"), path("docs/", views.serve_doc, name="hc-docs"),


+ 47
- 0
hc/front/views.py View File

@ -1540,3 +1540,50 @@ def add_msteams(request):
ctx = {"page": "channels", "project": request.project, "form": form} ctx = {"page": "channels", "project": request.project, "form": form}
return render(request, "integrations/add_msteams.html", ctx) return render(request, "integrations/add_msteams.html", ctx)
def metrics(request, code):
api_key = request.GET.get("api_key", "")
if len(api_key) != 32:
return HttpResponseBadRequest()
q = Project.objects.filter(code=code, api_key_readonly=api_key)
try:
project = q.get()
except Project.DoesNotExist:
return HttpResponseForbidden()
checks = Check.objects.filter(project_id=project.id).order_by("id")
def esc(s):
return s.replace("\\", "\\\\").replace('"', '\\"').replace("\n", "\\n")
def output(checks):
yield "# HELP hc_check_up Whether the check is currently up (1 for yes, 0 for no).\n"
yield "# TYPE hc_check_up gauge\n"
TMPL = """hc_check_up{name="%s", tags="%s", unique_key="%s"} %d\n"""
for check in checks:
value = 0 if check.get_status(with_started=False) == "down" else 1
yield TMPL % (esc(check.name), esc(check.tags), check.unique_key, value)
tags_statuses, num_down = _tags_statuses(checks)
yield "\n"
yield "# HELP hc_tag_up Whether all checks with this tag are up (1 for yes, 0 for no).\n"
yield "# TYPE hc_tag_up gauge\n"
TMPL = """hc_tag_up{tag="%s"} %d\n"""
for tag in sorted(tags_statuses):
value = 0 if tags_statuses[tag] == "down" else 1
yield TMPL % (esc(tag), value)
yield "\n"
yield "# HELP hc_checks_total The total number of checks.\n"
yield "# TYPE hc_checks_total gauge\n"
yield "hc_checks_total %d\n" % len(checks)
yield "\n"
yield "# HELP hc_checks_down_total The number of checks currently down.\n"
yield "# TYPE hc_checks_down_total gauge\n"
yield "hc_checks_down_total %d\n" % num_down
return HttpResponse(output(checks), content_type="text/plain")

+ 4
- 0
templates/accounts/project.html View File

@ -43,6 +43,10 @@
API key (read-only): <br /> API key (read-only): <br />
<code>{{ project.api_key_readonly }}</code> <code>{{ project.api_key_readonly }}</code>
</p> </p>
<p>
Prometheus metrics endpoint:
<a href="{% url 'hc-metrics' project.code %}?api_key={{ project.api_key_readonly }}">here</a>
</p>
{% endif %} {% endif %}
<button <button
data-toggle="modal" data-toggle="modal"


Loading…
Cancel
Save