models.py 3.6 KB

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