models.py 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  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",
  46. on_delete=models.SET_NULL,
  47. null=True,
  48. blank=True,
  49. )
  50. project = models.ForeignKey(
  51. "projects.Project",
  52. on_delete=models.SET_NULL,
  53. null=True,
  54. blank=True,
  55. )
  56. organization = models.ForeignKey(
  57. "organizations_ext.Organization", on_delete=models.CASCADE
  58. )
  59. interval = models.DurationField(
  60. default=timedelta(minutes=1),
  61. validators=[MaxValueValidator(timedelta(hours=23, minutes=59, seconds=59))],
  62. )
  63. objects = MonitorManager()
  64. def __str__(self):
  65. return self.name
  66. def save(self, *args, **kwargs):
  67. if self.monitor_type == MonitorType.HEARTBEAT and not self.endpoint_id:
  68. self.endpoint_id = uuid.uuid4()
  69. super().save(*args, **kwargs)
  70. # pylint: disable=import-outside-toplevel
  71. from glitchtip.uptime.tasks import perform_checks
  72. if self.monitor_type != MonitorType.HEARTBEAT:
  73. perform_checks.apply_async(args=([self.pk],), countdown=1)
  74. def get_detail_url(self):
  75. return f"{settings.GLITCHTIP_URL.geturl()}/{self.project.organization.slug}/uptime-monitors/{self.pk}"
  76. class MonitorCheck(CreatedModel):
  77. monitor = models.ForeignKey(
  78. Monitor, on_delete=models.CASCADE, related_name="checks"
  79. )
  80. is_up = models.BooleanField()
  81. start_check = models.DateTimeField(
  82. help_text="Time when the start of this check was performed"
  83. )
  84. reason = models.PositiveSmallIntegerField(
  85. choices=MonitorCheckReason.choices, default=0, null=True, blank=True
  86. )
  87. response_time = models.DurationField(blank=True, null=True)
  88. data = models.JSONField(null=True, blank=True)
  89. class Meta:
  90. indexes = [
  91. models.Index(fields=["start_check", "monitor"]),
  92. ]
  93. ordering = ("-created",)
  94. def __str__(self):
  95. return self.up_or_down
  96. @property
  97. def up_or_down(self):
  98. if self.is_up:
  99. return "Up"
  100. return "Down"