0
0
Fork 0
mirror of https://github.com/healthchecks/healthchecks.git synced 2025-04-03 04:15:29 +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
class DowntimeRecord:
boundary: datetime
tz: str
duration: td
count: int | None
boundary: datetime # The start of this time interval (timezone-aware)
tz: str # For calculating total seconds in a month
no_data: bool # True if the check did not yet exist in this time interval
duration: td # Total downtime in this time interval
count: int # The number of downtime events in this time interval
def monthly_uptime(self) -> float:
# NB: this method assumes monthly boundaries.
# It will yield incorrect results for weekly boundaries
max_seconds = seconds_in_month(self.boundary.date(), self.tz)
up_seconds = max_seconds - self.duration.total_seconds()
return up_seconds / max_seconds
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
(months or weeks), and should be pre-sorted in descending order.
`boundaries` is a list of timezone-aware datetimes of the starts of time
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:
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
events = [(b, "---") for b in boundaries]
@ -535,14 +542,6 @@ class Check(models.Model):
if 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
def downtimes(self, months: int, tz: str) -> list[DowntimeRecord]:

View file

@ -188,16 +188,22 @@ class CheckModelTestCase(BaseTestCase):
# Jan. 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.count, 0)
# Dec. 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.count, 0)
# Nov. 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.count, 0)
@ -272,16 +278,19 @@ class CheckModelTestCase(BaseTestCase):
jan, dec, nov = r
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.monthly_uptime(), (31 - 14) / 31)
self.assertEqual(jan.count, 1)
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.monthly_uptime(), 0.0)
self.assertEqual(dec.count, 1)
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.monthly_uptime(), 14 / 30)
self.assertEqual(nov.count, 1)
@ -304,6 +313,8 @@ class CheckModelTestCase(BaseTestCase):
jan, dec = r
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))
total_hours = 31 * 24
up_hours = total_hours - 14 * 24 - 1
@ -311,6 +322,8 @@ class CheckModelTestCase(BaseTestCase):
self.assertEqual(jan.count, 1)
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.count, 0)
@ -324,13 +337,13 @@ class CheckModelTestCase(BaseTestCase):
jan, dec, nov = check.downtimes(3, "UTC")
# Jan. 2020
self.assertEqual(jan.count, 0)
self.assertFalse(jan.no_data)
# Dec. 2019
self.assertIsNone(dec.count)
self.assertTrue(dec.no_data)
# Nov. 2019
self.assertIsNone(nov.count)
self.assertTrue(nov.no_data)
@override_settings(S3_BUCKET=None)
def test_it_prunes(self) -> None:

View file

@ -73,7 +73,7 @@
</td>
{% else %}
<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 %}
{% else %}
All good!

View file

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