refactor(telemetry): sample OSS hot-path events at 10% to reduce PostHog volume (#4771)

This commit is contained in:
Gabriel Stein
2026-04-11 16:25:38 +05:30
committed by GitHub
parent 789cc9d607
commit 9d82e2329d
6 changed files with 501 additions and 4 deletions
+54 -3
View File
@@ -2,6 +2,7 @@ import atexit
import logging
import os
import platform
import random
import sys
import threading
@@ -22,16 +23,65 @@ if not isinstance(MEM0_TELEMETRY, bool):
logging.getLogger("posthog").setLevel(logging.CRITICAL + 1)
logging.getLogger("urllib3").setLevel(logging.CRITICAL + 1)
_logger = logging.getLogger(__name__)
# Default sampling rate for hot-path OSS events. Lifecycle events always fire at 100%.
# Override via MEM0_TELEMETRY_SAMPLE_RATE env var.
_DEFAULT_SAMPLE_RATE = 0.1
def _parse_sample_rate(raw):
"""Parse MEM0_TELEMETRY_SAMPLE_RATE env var. Never raises."""
try:
value = float(raw)
except (TypeError, ValueError):
_logger.debug("MEM0_TELEMETRY_SAMPLE_RATE %r is not a number, defaulting to %s", raw, _DEFAULT_SAMPLE_RATE)
return _DEFAULT_SAMPLE_RATE
if not 0.0 <= value <= 1.0:
_logger.debug("MEM0_TELEMETRY_SAMPLE_RATE %s out of [0.0, 1.0], defaulting to %s", value, _DEFAULT_SAMPLE_RATE)
return _DEFAULT_SAMPLE_RATE
return value
MEM0_TELEMETRY_SAMPLE_RATE = _parse_sample_rate(os.environ.get("MEM0_TELEMETRY_SAMPLE_RATE", str(_DEFAULT_SAMPLE_RATE)))
# Events that bypass sampling and always fire. Keep this set in sync with the
# event names passed to capture_event() in mem0/memory/main.py.
_LIFECYCLE_EVENTS = frozenset({"mem0.init", "mem0.reset", "mem0._create_procedural_memory"})
def _sampling_before_send(msg):
"""PostHog before_send hook: drop sampled hot-path events, annotate survivors with sample_rate."""
if not isinstance(msg, dict):
return None
event_name = msg.get("event", "")
is_lifecycle = event_name in _LIFECYCLE_EVENTS
# >= so that rate=0 drops everything and rate=1 keeps everything (random ∈ [0, 1)).
if not is_lifecycle and random.random() >= MEM0_TELEMETRY_SAMPLE_RATE:
return None
# Annotate so PostHog dashboards can extrapolate true counts via 1/sample_rate.
properties = msg.setdefault("properties", {})
properties["sample_rate"] = 1.0 if is_lifecycle else MEM0_TELEMETRY_SAMPLE_RATE
return msg
class AnonymousTelemetry:
def __init__(self, vector_store=None):
def __init__(self, vector_store=None, before_send=None):
if not MEM0_TELEMETRY:
self.posthog = None
self.user_id = None
return
self.posthog = Posthog(project_api_key=PROJECT_API_KEY, host=HOST)
try:
self.posthog = Posthog(project_api_key=PROJECT_API_KEY, host=HOST, before_send=before_send)
except TypeError:
# posthog <4.5.0 does not accept before_send; fall back without sampling.
_logger.debug("posthog.Posthog does not accept before_send; upgrade to >=4.5.0 for sampling")
self.posthog = Posthog(project_api_key=PROJECT_API_KEY, host=HOST)
self.user_id = get_or_create_user_id(vector_store)
def capture_event(self, event_name, properties=None, user_email=None):
@@ -87,7 +137,7 @@ def _get_oss_telemetry():
# Double-checked locking
if _oss_telemetry_instance is not None:
return _oss_telemetry_instance
_oss_telemetry_instance = AnonymousTelemetry()
_oss_telemetry_instance = AnonymousTelemetry(before_send=_sampling_before_send)
atexit.register(_shutdown_oss_telemetry)
return _oss_telemetry_instance
@@ -102,6 +152,7 @@ def _shutdown_oss_telemetry():
# Module-level client telemetry singleton (used by capture_client_event).
# No before_send — hosted MemoryClient traffic must never be sampled.
client_telemetry = AnonymousTelemetry()
atexit.register(client_telemetry.close)