models.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. import uuid
  2. from datetime import timedelta
  3. from django.core.validators import MaxValueValidator
  4. from django.db import models
  5. from django.db.models import OuterRef, Subquery
  6. from glitchtip.base_models import CreatedModel
  7. from .constants import MonitorCheckReason, MonitorType
  8. class MonitorManager(models.Manager):
  9. def with_check_annotations(self, on_update=False):
  10. """
  11. Adds MonitorCheck annotations:
  12. latest_is_up - Most recent check is_up result
  13. last_change - Most recent check where is_up state changed
  14. last_change_on_update: Same idea as last_change, but handled differently when monitor has not yet been updated (for alerts).
  15. Example: Monitor state: { latest_is_up } since { last_change }
  16. """
  17. initial_annotation = self.annotate(
  18. latest_is_up=Subquery(
  19. MonitorCheck.objects.filter(monitor_id=OuterRef("id"))
  20. .order_by("-start_check")
  21. .values("is_up")[:1]
  22. )
  23. )
  24. if on_update:
  25. return initial_annotation.annotate(
  26. last_change_on_update=Subquery(
  27. MonitorCheck.objects.filter(monitor_id=OuterRef("id"))
  28. .filter(is_up=OuterRef("latest_is_up"))
  29. .order_by("-start_check")
  30. .values("start_check")[:1]
  31. )
  32. )
  33. else:
  34. return initial_annotation.annotate(
  35. last_change=Subquery(
  36. MonitorCheck.objects.filter(monitor_id=OuterRef("id"))
  37. .exclude(is_up=OuterRef("latest_is_up"))
  38. .order_by("-start_check")
  39. .values("start_check")[:1]
  40. )
  41. )
  42. class Monitor(CreatedModel):
  43. monitor_type = models.CharField(
  44. max_length=12, choices=MonitorType.choices, default=MonitorType.PING
  45. )
  46. endpoint_id = models.UUIDField(
  47. blank=True,
  48. null=True,
  49. editable=False,
  50. help_text="Used for referencing heartbeat endpoint",
  51. )
  52. name = models.CharField(max_length=200)
  53. url = models.URLField(max_length=2000, blank=True)
  54. expected_status = models.PositiveSmallIntegerField(default=200)
  55. expected_body = models.CharField(max_length=2000, blank=True)
  56. environment = models.ForeignKey(
  57. "environments.Environment", on_delete=models.SET_NULL, null=True, blank=True,
  58. )
  59. project = models.ForeignKey(
  60. "projects.Project", on_delete=models.SET_NULL, null=True, blank=True,
  61. )
  62. organization = models.ForeignKey(
  63. "organizations_ext.Organization", on_delete=models.CASCADE
  64. )
  65. interval = models.DurationField(
  66. default=timedelta(minutes=1),
  67. validators=[MaxValueValidator(timedelta(hours=23, minutes=59, seconds=59))],
  68. )
  69. objects = MonitorManager()
  70. def __str__(self):
  71. return self.name
  72. def save(self, *args, **kwargs):
  73. if self.monitor_type == MonitorType.HEARTBEAT and not self.endpoint_id:
  74. self.endpoint_id = uuid.uuid4()
  75. super().save(*args, **kwargs)
  76. # pylint: disable=import-outside-toplevel
  77. from glitchtip.uptime.tasks import perform_checks
  78. if self.monitor_type != MonitorType.HEARTBEAT:
  79. perform_checks.apply_async(args=([self.pk],), countdown=1)
  80. class MonitorCheck(CreatedModel):
  81. monitor = models.ForeignKey(
  82. Monitor, on_delete=models.CASCADE, related_name="checks"
  83. )
  84. is_up = models.BooleanField()
  85. start_check = models.DateTimeField(
  86. help_text="Time when the start of this check was performed"
  87. )
  88. reason = models.PositiveSmallIntegerField(
  89. choices=MonitorCheckReason.choices, default=0, null=True, blank=True
  90. )
  91. response_time = models.DurationField(blank=True, null=True)
  92. data = models.JSONField(null=True, blank=True)
  93. class Meta:
  94. indexes = [
  95. models.Index(fields=["start_check", "monitor"]),
  96. ]
  97. ordering = ("-created",)
  98. def __str__(self):
  99. return self.up_or_down
  100. @property
  101. def up_or_down(self):
  102. if self.is_up:
  103. return "Up"
  104. return "Down"