0
0
Fork 0
mirror of https://github.com/healthchecks/healthchecks.git synced 2025-04-10 15:37:30 +00:00

Add DowntimeRecord.no_data field

This commit is contained in:
Pēteris Caune 2023-09-27 17:43:18 +03:00
parent 58d7c8cc55
commit 2a0ae809a7
No known key found for this signature in database
GPG key ID: E28D7679E9A9EDE2
4 changed files with 37 additions and 25 deletions

View file

@ -133,27 +133,34 @@ class CheckDict(TypedDict, total=False):
@dataclass @dataclass
class DowntimeRecord: class DowntimeRecord:
boundary: datetime boundary: datetime # The start of this time interval (timezone-aware)
tz: str tz: str # For calculating total seconds in a month
duration: td no_data: bool # True if the check did not yet exist in this time interval
count: int | None duration: td # Total downtime in this time interval
count: int # The number of downtime events in this time interval
def monthly_uptime(self) -> float: def monthly_uptime(self) -> float:
# NB: this method assumes monthly boundaries. # NB: this method assumes monthly boundaries.
# It will yield incorrect results for weekly boundaries # It will yield incorrect results for weekly boundaries
max_seconds = seconds_in_month(self.boundary.date(), self.tz) max_seconds = seconds_in_month(self.boundary.date(), self.tz)
up_seconds = max_seconds - self.duration.total_seconds() up_seconds = max_seconds - self.duration.total_seconds()
return up_seconds / max_seconds return up_seconds / max_seconds
class DowntimeSummary(object): class DowntimeSummary(object):
def __init__(self, boundaries: list[datetime], tz: str) -> None: def __init__(self, boundaries: list[datetime], tz: str, created: datetime) -> None:
""" """
`boundaries` are timezone-aware datetimes of the first days of time intervals `boundaries` is a list of timezone-aware datetimes of the starts of time
(months or weeks), and should be pre-sorted in descending order. intervals (months or weeks), and should be pre-sorted in descending order.
""" """
self.records = [DowntimeRecord(b, tz, td(), 0) for b in boundaries] self.records = []
prev_boundary = None
for b in boundaries:
# If the check was created *after* the start of the previous time
# interval then the check did not yet exist during this time interval:
no_data = prev_boundary and created > prev_boundary
self.records.append(DowntimeRecord(b, tz, no_data, td(), 0))
prev_boundary = b
def add(self, when: datetime, duration: td) -> None: def add(self, when: datetime, duration: td) -> None:
for record in self.records: for record in self.records:
@ -513,7 +520,7 @@ class Check(models.Model):
""" """
summary = DowntimeSummary(boundaries, tz) summary = DowntimeSummary(boundaries, tz, self.created)
# A list of flips and time interval boundaries # A list of flips and time interval boundaries
events = [(b, "---") for b in boundaries] events = [(b, "---") for b in boundaries]
@ -535,14 +542,6 @@ class Check(models.Model):
if prev_status != "---": if prev_status != "---":
status = prev_status status = prev_status
# Set count to None for intervals when the check didn't exist yet
prev_boundary = None
for record in summary.records:
if prev_boundary and self.created > prev_boundary:
record.count = None
prev_boundary = record.boundary
return summary.records return summary.records
def downtimes(self, months: int, tz: str) -> list[DowntimeRecord]: def downtimes(self, months: int, tz: str) -> list[DowntimeRecord]:

View file

@ -188,16 +188,22 @@ class CheckModelTestCase(BaseTestCase):
# Jan. 2020 # Jan. 2020
self.assertEqual(jan.boundary.strftime("%m-%Y"), "01-2020") self.assertEqual(jan.boundary.strftime("%m-%Y"), "01-2020")
self.assertEqual(jan.tz, "UTC")
self.assertFalse(jan.no_data)
self.assertEqual(jan.duration, td()) self.assertEqual(jan.duration, td())
self.assertEqual(jan.count, 0) self.assertEqual(jan.count, 0)
# Dec. 2019 # Dec. 2019
self.assertEqual(dec.boundary.strftime("%m-%Y"), "12-2019") self.assertEqual(dec.boundary.strftime("%m-%Y"), "12-2019")
self.assertEqual(jan.tz, "UTC")
self.assertFalse(jan.no_data)
self.assertEqual(dec.duration, td()) self.assertEqual(dec.duration, td())
self.assertEqual(dec.count, 0) self.assertEqual(dec.count, 0)
# Nov. 2019 # Nov. 2019
self.assertEqual(nov.boundary.strftime("%m-%Y"), "11-2019") self.assertEqual(nov.boundary.strftime("%m-%Y"), "11-2019")
self.assertEqual(jan.tz, "UTC")
self.assertFalse(jan.no_data)
self.assertEqual(nov.duration, td()) self.assertEqual(nov.duration, td())
self.assertEqual(nov.count, 0) self.assertEqual(nov.count, 0)
@ -272,16 +278,19 @@ class CheckModelTestCase(BaseTestCase):
jan, dec, nov = r jan, dec, nov = r
self.assertEqual(jan.boundary.isoformat(), "2020-01-01T00:00:00+00:00") self.assertEqual(jan.boundary.isoformat(), "2020-01-01T00:00:00+00:00")
self.assertFalse(jan.no_data)
self.assertEqual(jan.duration, td(days=14)) self.assertEqual(jan.duration, td(days=14))
self.assertEqual(jan.monthly_uptime(), (31 - 14) / 31) self.assertEqual(jan.monthly_uptime(), (31 - 14) / 31)
self.assertEqual(jan.count, 1) self.assertEqual(jan.count, 1)
self.assertEqual(dec.boundary.isoformat(), "2019-12-01T00:00:00+00:00") self.assertEqual(dec.boundary.isoformat(), "2019-12-01T00:00:00+00:00")
self.assertFalse(dec.no_data)
self.assertEqual(dec.duration, td(days=31)) self.assertEqual(dec.duration, td(days=31))
self.assertEqual(dec.monthly_uptime(), 0.0) self.assertEqual(dec.monthly_uptime(), 0.0)
self.assertEqual(dec.count, 1) self.assertEqual(dec.count, 1)
self.assertEqual(nov.boundary.isoformat(), "2019-11-01T00:00:00+00:00") self.assertEqual(nov.boundary.isoformat(), "2019-11-01T00:00:00+00:00")
self.assertFalse(nov.no_data)
self.assertEqual(nov.duration, td(days=16)) self.assertEqual(nov.duration, td(days=16))
self.assertEqual(nov.monthly_uptime(), 14 / 30) self.assertEqual(nov.monthly_uptime(), 14 / 30)
self.assertEqual(nov.count, 1) self.assertEqual(nov.count, 1)
@ -304,6 +313,8 @@ class CheckModelTestCase(BaseTestCase):
jan, dec = r jan, dec = r
self.assertEqual(jan.boundary.isoformat(), "2020-01-01T00:00:00+02:00") self.assertEqual(jan.boundary.isoformat(), "2020-01-01T00:00:00+02:00")
self.assertEqual(jan.tz, "Europe/Riga")
self.assertFalse(jan.no_data)
self.assertEqual(jan.duration, td(days=14, hours=1)) self.assertEqual(jan.duration, td(days=14, hours=1))
total_hours = 31 * 24 total_hours = 31 * 24
up_hours = total_hours - 14 * 24 - 1 up_hours = total_hours - 14 * 24 - 1
@ -311,6 +322,8 @@ class CheckModelTestCase(BaseTestCase):
self.assertEqual(jan.count, 1) self.assertEqual(jan.count, 1)
self.assertEqual(dec.boundary.isoformat(), "2019-12-01T00:00:00+02:00") self.assertEqual(dec.boundary.isoformat(), "2019-12-01T00:00:00+02:00")
self.assertEqual(dec.tz, "Europe/Riga")
self.assertFalse(dec.no_data)
self.assertEqual(dec.duration, td()) self.assertEqual(dec.duration, td())
self.assertEqual(dec.count, 0) self.assertEqual(dec.count, 0)
@ -324,13 +337,13 @@ class CheckModelTestCase(BaseTestCase):
jan, dec, nov = check.downtimes(3, "UTC") jan, dec, nov = check.downtimes(3, "UTC")
# Jan. 2020 # Jan. 2020
self.assertEqual(jan.count, 0) self.assertFalse(jan.no_data)
# Dec. 2019 # Dec. 2019
self.assertIsNone(dec.count) self.assertTrue(dec.no_data)
# Nov. 2019 # Nov. 2019
self.assertIsNone(nov.count) self.assertTrue(nov.no_data)
@override_settings(S3_BUCKET=None) @override_settings(S3_BUCKET=None)
def test_it_prunes(self) -> None: def test_it_prunes(self) -> None:

View file

@ -73,7 +73,7 @@
</td> </td>
{% else %} {% else %}
<td style="border-top: 1px solid #EDEFF2; padding: 16px 8px; font-family: Helvetica, Arial, sans-serif; color: #9BA2AB;"> <td style="border-top: 1px solid #EDEFF2; padding: 16px 8px; font-family: Helvetica, Arial, sans-serif; color: #9BA2AB;">
{% if d.count is None %} {% if d.no_data %}
{% comment %} The check didn't exist yet {% endcomment %} {% comment %} The check didn't exist yet {% endcomment %}
{% else %} {% else %}
All good! All good!

View file

@ -5,11 +5,11 @@
<tr> <tr>
<th>{{ d.boundary|date:"N Y"}}</th> <th>{{ d.boundary|date:"N Y"}}</th>
<td> <td>
{% if d.count %} {% if d.no_data %}
{% elif d.count %}
{{ d.count }} downtime{{ d.count|pluralize }}, {{ d.duration|hc_approx_duration }} total {{ d.count }} downtime{{ d.count|pluralize }}, {{ d.duration|hc_approx_duration }} total
<span class="uptime">{{ d.monthly_uptime|pct }}% uptime</span> <span class="uptime">{{ d.monthly_uptime|pct }}% uptime</span>
{% elif d.count is None %}
{% else %} {% else %}
All good! All good!
{% endif %} {% endif %}