utils.py 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. import asyncio
  2. import time
  3. from datetime import timedelta
  4. from ssl import SSLError
  5. import aiohttp
  6. from aiohttp import ClientTimeout
  7. from aiohttp.client_exceptions import ClientConnectorError
  8. from django.conf import settings
  9. from django.utils import timezone
  10. from .constants import MonitorCheckReason, MonitorType
  11. from .models import MonitorCheck
  12. DEFAULT_TIMEOUT = 20 # Seconds
  13. PAYLOAD_LIMIT = 2_000_000 # 2mb
  14. PAYLOAD_SAVE_LIMIT = 500_000 # pseudo 500kb
  15. async def process_response(monitor, response):
  16. if response.status == monitor["expected_status"]:
  17. if monitor["expected_body"]:
  18. # Limit size to 2MB
  19. body = await response.content.read(PAYLOAD_LIMIT)
  20. try:
  21. encoding = response.get_encoding()
  22. payload = body.decode(encoding, errors="ignore")
  23. except RuntimeError:
  24. payload = body.decode(errors="ignore")
  25. if monitor["expected_body"] in payload:
  26. monitor["is_up"] = True
  27. else:
  28. monitor["reason"] = MonitorCheckReason.BODY
  29. if monitor["latest_is_up"] != monitor["is_up"]:
  30. # Save only first 500k chars, to roughly reduce disk usage
  31. # Note that a unicode char is not always one byte
  32. # Only save on changes
  33. monitor["data"] = {"payload": payload[:PAYLOAD_SAVE_LIMIT]}
  34. else:
  35. monitor["is_up"] = True
  36. else:
  37. monitor["reason"] = MonitorCheckReason.STATUS
  38. async def fetch(session, monitor):
  39. monitor["is_up"] = False
  40. if monitor["monitor_type"] == MonitorType.HEARTBEAT:
  41. interval = timedelta(seconds=monitor["interval"])
  42. if await MonitorCheck.objects.filter(
  43. monitor_id=monitor["id"],
  44. start_check__gte=timezone.now() - interval,
  45. ).aexists():
  46. monitor["is_up"] = True
  47. return monitor
  48. url = monitor["url"]
  49. timeout = monitor["timeout"] or DEFAULT_TIMEOUT
  50. try:
  51. start = time.monotonic()
  52. if monitor["monitor_type"] == MonitorType.PORT:
  53. fut = asyncio.open_connection(*url.split(":"))
  54. await asyncio.wait_for(fut, timeout=timeout)
  55. monitor["is_up"] = True
  56. else:
  57. client_timeout = ClientTimeout(total=monitor["timeout"] or DEFAULT_TIMEOUT)
  58. if monitor["monitor_type"] == MonitorType.PING:
  59. async with session.head(url, timeout=client_timeout):
  60. monitor["is_up"] = True
  61. elif monitor["monitor_type"] == MonitorType.GET:
  62. async with session.get(url, timeout=client_timeout) as response:
  63. await process_response(monitor, response)
  64. elif monitor["monitor_type"] == MonitorType.POST:
  65. async with session.post(url, timeout=client_timeout) as response:
  66. await process_response(monitor, response)
  67. monitor["response_time"] = (
  68. timedelta(seconds=time.monotonic() - start).total_seconds() * 1000
  69. )
  70. except SSLError:
  71. monitor["reason"] = MonitorCheckReason.SSL
  72. except asyncio.TimeoutError:
  73. monitor["reason"] = MonitorCheckReason.TIMEOUT
  74. except ClientConnectorError:
  75. monitor["reason"] = MonitorCheckReason.NETWORK
  76. except OSError:
  77. monitor["reason"] = MonitorCheckReason.UNKNOWN
  78. return monitor
  79. async def fetch_all(monitors):
  80. async with aiohttp.ClientSession(
  81. headers={"User-Agent": "GlitchTip/" + settings.GLITCHTIP_VERSION}
  82. ) as session:
  83. results = await asyncio.gather(
  84. *[fetch(session, monitor) for monitor in monitors], return_exceptions=True
  85. )
  86. return results