Cleanup in Check.outages_by_month() and tests.

This commit is contained in:
Pēteris Caune 2019-07-19 19:42:37 +03:00
parent 1de0ef16f6
commit cb2e763e98
No known key found for this signature in database
GPG Key ID: E28D7679E9A9EDE2
3 changed files with 89 additions and 19 deletions

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 self.flip_set.filter(created__gt=boundary):
for flip in flips:
events.append((flip.created, flip.old_status)) events.append((flip.created, flip.old_status))
events.sort(reverse=True) # Iterate through flips and month boundaries in reverse order,
# and for each "down" event increase the counters in `totals`.
needle, status = now, self.status dt, status = timezone.now(), self.status
for dt, old_status in events: for prev_dt, prev_status in sorted(events, reverse=True):
if status == "down": if status == "down":
if (dt.year, dt.month) not in totals: delta = dt - prev_dt
break totals[monthkey(prev_dt)][1] += int(delta.total_seconds())
totals[monthkey(prev_dt)][2] += 1
delta = needle - dt dt = prev_dt
totals[(dt.year, dt.month)][1] += int(delta.total_seconds()) if prev_status != "---":
totals[(dt.year, dt.month)][2] += 1 status = prev_status
needle = dt
if old_status != "---":
status = old_status
flattened = list(totals.values()) flattened = list(totals.values())
flattened.sort(reverse=True) flattened.sort(reverse=True)

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)

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>