telemetry.py 3.7 KB

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