models.py 3.7 KB

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