Browse Source

Cleanup in `Check.outages_by_month()` and tests.

pull/272/head
Pēteris Caune 5 years ago
parent
commit
cb2e763e98
No known key found for this signature in database GPG Key ID: E28D7679E9A9EDE2
3 changed files with 89 additions and 19 deletions
  1. +24
    -17
      hc/api/models.py
  2. +64
    -1
      hc/api/tests/test_check_model.py
  3. +1
    -1
      templates/emails/summary-downtimes-html.html

+ 24
- 17
hc/api/models.py View File

@ -247,33 +247,40 @@ class Check(models.Model):
ping.save() ping.save()
def outages_by_month(self, months=2): def outages_by_month(self, months=2):
now = timezone.now()
""" Calculate the number of outages and downtime minutes per month.
Returns a list of (datetime, downtime_in_secs, number_of_outages) tuples.
"""
def monthkey(dt):
return dt.year, dt.month
# Will accumulate totals here.
# (year, month) -> [datetime, downtime_in_secs, number_of_outages]
totals = {} totals = {}
# Will collect flips and month boundaries here
events = [] events = []
for boundary in month_boundaries(months=months): for boundary in month_boundaries(months=months):
totals[(boundary.year, boundary.month)] = [boundary, 0, 0]
totals[monthkey(boundary)] = [boundary, 0, 0]
events.append((boundary, "---")) events.append((boundary, "---"))
flips = self.flip_set.filter(created__gt=now - td(days=32 * months))
for flip in flips:
for flip in self.flip_set.filter(created__gt=boundary):
events.append((flip.created, flip.old_status)) events.append((flip.created, flip.old_status))
events.sort(reverse=True)
needle, status = now, self.status
for dt, old_status in events:
# Iterate through flips and month boundaries in reverse order,
# and for each "down" event increase the counters in `totals`.
dt, status = timezone.now(), self.status
for prev_dt, prev_status in sorted(events, reverse=True):
if status == "down": if status == "down":
if (dt.year, dt.month) not in totals:
break
delta = needle - dt
totals[(dt.year, dt.month)][1] += int(delta.total_seconds())
totals[(dt.year, dt.month)][2] += 1
delta = dt - prev_dt
totals[monthkey(prev_dt)][1] += int(delta.total_seconds())
totals[monthkey(prev_dt)][2] += 1
needle = dt
if old_status != "---":
status = old_status
dt = prev_dt
if prev_status != "---":
status = prev_status
flattened = list(totals.values()) flattened = list(totals.values())
flattened.sort(reverse=True) flattened.sort(reverse=True)


+ 64
- 1
hc/api/tests/test_check_model.py View File

@ -1,8 +1,9 @@
from datetime import datetime, timedelta from datetime import datetime, timedelta
from django.utils import timezone from django.utils import timezone
from hc.api.models import Check
from hc.api.models import Check, Flip
from hc.test import BaseTestCase from hc.test import BaseTestCase
from mock import patch
class CheckModelTestCase(BaseTestCase): class CheckModelTestCase(BaseTestCase):
@ -164,3 +165,65 @@ class CheckModelTestCase(BaseTestCase):
d = check.to_dict() d = check.to_dict()
self.assertEqual(d["next_ping"], "2000-01-01T01:00:00+00:00") self.assertEqual(d["next_ping"], "2000-01-01T01:00:00+00:00")
def test_outages_by_month_handles_no_flips(self):
check = Check.objects.create(project=self.project)
r = check.outages_by_month(10)
self.assertEqual(len(r), 10)
for dt, secs, outages in r:
self.assertEqual(secs, 0)
self.assertEqual(outages, 0)
def test_outages_by_month_handles_currently_down_check(self):
check = Check.objects.create(project=self.project, status="down")
r = check.outages_by_month(10)
self.assertEqual(len(r), 10)
for dt, secs, outages in r:
self.assertEqual(outages, 1)
@patch("hc.api.models.timezone.now")
def test_outages_by_month_handles_flip_one_day_ago(self, mock_now):
mock_now.return_value = datetime(2019, 7, 19, tzinfo=timezone.utc)
check = Check.objects.create(project=self.project, status="down")
flip = Flip(owner=check)
flip.created = datetime(2019, 7, 18, tzinfo=timezone.utc)
flip.old_status = "up"
flip.new_status = "down"
flip.save()
r = check.outages_by_month(10)
self.assertEqual(len(r), 10)
for dt, secs, outages in r:
if dt.month == 7:
self.assertEqual(secs, 86400)
self.assertEqual(outages, 1)
else:
self.assertEqual(secs, 0)
self.assertEqual(outages, 0)
@patch("hc.api.models.timezone.now")
def test_outages_by_month_handles_flip_two_months_ago(self, mock_now):
mock_now.return_value = datetime(2019, 7, 19, tzinfo=timezone.utc)
check = Check.objects.create(project=self.project, status="down")
flip = Flip(owner=check)
flip.created = datetime(2019, 5, 19, tzinfo=timezone.utc)
flip.old_status = "up"
flip.new_status = "down"
flip.save()
r = check.outages_by_month(10)
self.assertEqual(len(r), 10)
for dt, secs, outages in r:
if dt.month == 7:
self.assertEqual(outages, 1)
elif dt.month == 6:
self.assertEqual(secs, 30 * 86400)
self.assertEqual(outages, 1)
elif dt.month == 5:
self.assertEqual(outages, 1)
else:
self.assertEqual(secs, 0)
self.assertEqual(outages, 0)

+ 1
- 1
templates/emails/summary-downtimes-html.html View File

@ -65,7 +65,7 @@
{% for boundary, seconds, count in check.outages_by_month %} {% for boundary, seconds, count in check.outages_by_month %}
{% if count %} {% if count %}
<td style="border-top: 1px solid #EDEFF2; padding: 16px 8px; font-family: Helvetica, Arial, sans-serif;"> <td style="border-top: 1px solid #EDEFF2; padding: 16px 8px; font-family: Helvetica, Arial, sans-serif;">
{{ count }} outage{{ count|pluralize }}
{{ count }} outage{{ count|pluralize }},
<br /> <br />
{{ seconds|hc_approx_duration }} total {{ seconds|hc_approx_duration }} total
</td> </td>


Loading…
Cancel
Save