telemetry.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. """Anonymous telemetry for Reflex."""
  2. from __future__ import annotations
  3. import multiprocessing
  4. import platform
  5. try:
  6. from datetime import UTC, datetime
  7. except ImportError:
  8. from datetime import datetime
  9. UTC = None
  10. import httpx
  11. import psutil
  12. from reflex import constants
  13. from reflex.utils import console
  14. from reflex.utils.exec import should_skip_compile
  15. from reflex.utils.prerequisites import ensure_reflex_installation_id, get_project_hash
  16. POSTHOG_API_URL: str = "https://app.posthog.com/capture/"
  17. def get_os() -> str:
  18. """Get the operating system.
  19. Returns:
  20. The operating system.
  21. """
  22. return platform.system()
  23. def get_python_version() -> str:
  24. """Get the Python version.
  25. Returns:
  26. The Python version.
  27. """
  28. return platform.python_version()
  29. def get_reflex_version() -> str:
  30. """Get the Reflex version.
  31. Returns:
  32. The Reflex version.
  33. """
  34. return constants.Reflex.VERSION
  35. def get_cpu_count() -> int:
  36. """Get the number of CPUs.
  37. Returns:
  38. The number of CPUs.
  39. """
  40. return multiprocessing.cpu_count()
  41. def get_memory() -> int:
  42. """Get the total memory in MB.
  43. Returns:
  44. The total memory in MB.
  45. """
  46. try:
  47. return psutil.virtual_memory().total >> 20
  48. except ValueError: # needed to pass ubuntu test
  49. return 0
  50. def _raise_on_missing_project_hash() -> bool:
  51. """Check if an error should be raised when project hash is missing.
  52. When running reflex with --backend-only, or doing database migration
  53. operations, there is no requirement for a .web directory, so the reflex.json
  54. file may not exist, and this should not be considered an error.
  55. Returns:
  56. False when compilation should be skipped (i.e. no .web directory is required).
  57. Otherwise return True.
  58. """
  59. if should_skip_compile():
  60. return False
  61. return True
  62. def _prepare_event(event: str, **kwargs) -> dict:
  63. """Prepare the event to be sent to the PostHog server.
  64. Args:
  65. event: The event name.
  66. kwargs: Additional data to send with the event.
  67. Returns:
  68. The event data.
  69. """
  70. installation_id = ensure_reflex_installation_id()
  71. project_hash = get_project_hash(raise_on_fail=_raise_on_missing_project_hash())
  72. if installation_id is None or project_hash is None:
  73. console.debug(
  74. f"Could not get installation_id or project_hash: {installation_id}, {project_hash}"
  75. )
  76. return {}
  77. if UTC is None:
  78. # for python 3.8, 3.9 & 3.10
  79. stamp = datetime.utcnow().isoformat()
  80. else:
  81. # for python 3.11 & 3.12
  82. stamp = datetime.now(UTC).isoformat()
  83. return {
  84. "api_key": "phc_JoMo0fOyi0GQAooY3UyO9k0hebGkMyFJrrCw1Gt5SGb",
  85. "event": event,
  86. "properties": {
  87. "distinct_id": installation_id,
  88. "distinct_app_id": project_hash,
  89. "user_os": get_os(),
  90. "reflex_version": get_reflex_version(),
  91. "python_version": get_python_version(),
  92. "cpu_count": get_cpu_count(),
  93. "memory": get_memory(),
  94. **(
  95. {"template": template}
  96. if (template := kwargs.get("template")) is not None
  97. else {}
  98. ),
  99. },
  100. "timestamp": stamp,
  101. }
  102. def _send_event(event_data: dict) -> bool:
  103. try:
  104. httpx.post(POSTHOG_API_URL, json=event_data)
  105. return True
  106. except Exception:
  107. return False
  108. def send(event: str, telemetry_enabled: bool | None = None, **kwargs) -> bool:
  109. """Send anonymous telemetry for Reflex.
  110. Args:
  111. event: The event name.
  112. telemetry_enabled: Whether to send the telemetry (If None, get from config).
  113. kwargs: Additional data to send with the event.
  114. Returns:
  115. Whether the telemetry was sent successfully.
  116. """
  117. from reflex.config import get_config
  118. # Get the telemetry_enabled from the config if it is not specified.
  119. if telemetry_enabled is None:
  120. telemetry_enabled = get_config().telemetry_enabled
  121. # Return if telemetry is disabled.
  122. if not telemetry_enabled:
  123. return False
  124. event_data = _prepare_event(event, **kwargs)
  125. if not event_data:
  126. return False
  127. return _send_event(event_data)