Update sendalerts to log per-notification send times

To send notifications, sendalerts calls Flip.send_alerts().
I updated Flip.send_alerts() to be a generator, and to yield
a (channel, error, send_time_in_seconds) triple per sent
notification.
This commit is contained in:
Pēteris Caune 2021-01-15 15:15:00 +02:00
parent 3b6afae140
commit 9a0888aacd
No known key found for this signature in database
GPG Key ID: E28D7679E9A9EDE2
3 changed files with 81 additions and 12 deletions

View File

@ -29,13 +29,18 @@ def notify(flip_id, stdout):
# Send notifications
send_start = timezone.now()
errors = flip.send_alerts()
for ch, error in errors:
stdout.write("ERROR: %s %s %s\n" % (ch.kind, ch.value, error))
# If sending took more than 5s, log it
for ch, error, secs in flip.send_alerts():
label = "OK"
if error:
label = "ERROR"
elif secs > 5:
label = "SLOW"
s = " * %-5s %4.1fs %-10s %s %s\n" % (label, secs, ch.kind, ch.code, error)
stdout.write(s)
send_time = timezone.now() - send_start
if send_time.total_seconds() > 5:
stdout.write(SEND_TIME_TMPL % (send_time.total_seconds(), check.code))
statsd.timing("hc.sendalerts.dwellTime", send_start - flip.created)

View File

@ -2,6 +2,7 @@
import hashlib
import json
import time
import uuid
from datetime import datetime, timedelta as td
@ -822,20 +823,28 @@ class Flip(models.Model):
}
def send_alerts(self):
if self.new_status == "up" and self.old_status in ("new", "paused"):
"""Loop over the enabled channels, call notify() on each.
For each channel, yield a (channel, error, send_time) triple:
* channel is a Channel instance
* error is an empty string ("") on success, error message otherwise
* send_time is the send time in seconds (float)
"""
# Don't send alerts on new->up and paused->up transitions
return []
if self.new_status == "up" and self.old_status in ("new", "paused"):
return
if self.new_status not in ("up", "down"):
raise NotImplementedError("Unexpected status: %s" % self.status)
errors = []
for channel in self.owner.channel_set.all():
start = time.time()
error = channel.notify(self.owner)
if error not in ("", "no-op"):
errors.append((channel, error))
if error == "no-op":
continue
return errors
yield (channel, error, time.time() - start)
class TokenBucket(models.Model):

View File

@ -0,0 +1,55 @@
from unittest.mock import patch
from django.utils.timezone import now
from hc.api.models import Channel, Check, Flip
from hc.test import BaseTestCase
class FlipModelTestCase(BaseTestCase):
def setUp(self):
super().setUp()
self.check = Check.objects.create(project=self.project)
self.channel = Channel.objects.create(project=self.project, kind="email")
self.channel.checks.add(self.check)
self.flip = Flip(owner=self.check)
self.flip.created = now()
self.flip.old_status = "up"
self.flip.new_status = "down"
@patch("hc.api.models.Channel.notify")
def test_send_alerts_works(self, mock_notify):
mock_notify.return_value = ""
results = list(self.flip.send_alerts())
self.assertEqual(len(results), 1)
ch, error, send_time = results[0]
self.assertEqual(ch, self.channel)
self.assertEqual(error, "")
@patch("hc.api.models.Channel.notify")
def test_send_alerts_handles_error(self, mock_notify):
mock_notify.return_value = "something went wrong"
results = list(self.flip.send_alerts())
self.assertEqual(len(results), 1)
ch, error, send_time = results[0]
self.assertEqual(error, "something went wrong")
@patch("hc.api.models.Channel.notify")
def test_send_alerts_handles_noop(self, mock_notify):
mock_notify.return_value = "no-op"
results = list(self.flip.send_alerts())
self.assertEqual(results, [])
@patch("hc.api.models.Channel.notify")
def test_send_alerts_handles_new_up_transition(self, mock_notify):
self.flip.old_status = "new"
self.flip.new_status = "up"
results = list(self.flip.send_alerts())
self.assertEqual(results, [])