Skip to content

Commit d0bd58f

Browse files
committed
refactor(telemetry): derive feature counts from compile-time walks
Move TelemetryContext into reflex_base and drop the scattered `increment_feature` call sites in Upload, lifespan, shared state, storage classes, and Model. Compile-event collection now walks the live app (states, pages, config, ModelRegistry) at compile end so the counters reflect actual final state rather than import-time markers that could double-count under reload or get out of sync.
1 parent d361da3 commit d0bd58f

13 files changed

Lines changed: 314 additions & 181 deletions

File tree

reflex/utils/telemetry_context.py renamed to packages/reflex-base/src/reflex_base/telemetry_context.py

Lines changed: 7 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
"""Compile-time telemetry context."""
1+
"""Compile-time telemetry context.
2+
3+
Lives in ``reflex_base`` (not ``reflex``) so deep packages like
4+
``reflex_components_core`` — which depend on ``reflex_base`` but not
5+
``reflex`` — can read the active context without inverting the
6+
dependency hierarchy.
7+
"""
28

39
from __future__ import annotations
410

@@ -31,33 +37,12 @@
3137

3238
_KNOWN_FEATURES: tuple[FeatureName, ...] = get_args(FeatureName)
3339

34-
# Counters bumped outside an active compile context (import-time class
35-
# definitions, decorators, registrations) accumulate here so they survive
36-
# into the next compile event.
37-
_recorded_features: dict[FeatureName, int] = {}
38-
39-
40-
def increment_feature(name: FeatureName, by: int = 1) -> None:
41-
"""Bump a feature invocation counter.
42-
43-
Writes to the active TelemetryContext if one is attached, else to the
44-
process-level counter so import-time signals survive into the next compile.
45-
46-
Args:
47-
name: The feature counter to bump.
48-
by: How much to add. Defaults to 1.
49-
"""
50-
target = TelemetryContext.get()
51-
target_dict = target.features_used if target is not None else _recorded_features
52-
target_dict[name] = target_dict.get(name, 0) + by
53-
5440

5541
@dataclasses.dataclass(frozen=True, kw_only=True, slots=True, eq=False)
5642
class TelemetryContext(BaseContext):
5743
"""Per-compile telemetry handle attached to the current contextvar."""
5844

5945
start_perf_counter: float = dataclasses.field(default_factory=time.perf_counter)
60-
features_used: dict[FeatureName, int] = dataclasses.field(default_factory=dict)
6146
trigger: CompileTrigger | None = None
6247
exception: BaseException | None = dataclasses.field(default=None, repr=False)
6348

packages/reflex-components-core/src/reflex_components_core/core/upload.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@
3838
from reflex_base.vars.sequence import ArrayVar, LiteralStringVar
3939
from reflex_components_sonner.toast import toast
4040

41-
from reflex.utils.telemetry_context import increment_feature
4241
from reflex_components_core.base.fragment import Fragment
4342
from reflex_components_core.core._upload import UploadChunkIterator, UploadFile
4443
from reflex_components_core.core.cond import cond
@@ -289,7 +288,6 @@ def create(cls, *children, **props) -> Component:
289288
"""
290289
# Mark the Upload component as used in the app.
291290
cls.is_used = True
292-
increment_feature("upload_count")
293291

294292
props.setdefault("multiple", True)
295293

pyi_hashes.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
"packages/reflex-components-core/src/reflex_components_core/core/helmet.pyi": "7fd81a99bde5b0ff94bb52523597fd5c",
2121
"packages/reflex-components-core/src/reflex_components_core/core/html.pyi": "753d6ae315369530dad450ed643f5be6",
2222
"packages/reflex-components-core/src/reflex_components_core/core/sticky.pyi": "ba60a7d9cba75b27a1133bd63a9fbd59",
23-
"packages/reflex-components-core/src/reflex_components_core/core/upload.pyi": "91d66d21b80fad4c0ebaa9c88274d2e2",
23+
"packages/reflex-components-core/src/reflex_components_core/core/upload.pyi": "2dd6ba6e3a4d61fc1d79eb582a7cc548",
2424
"packages/reflex-components-core/src/reflex_components_core/core/window_events.pyi": "5e1dcb1130bc8af282783fae329ae6a6",
2525
"packages/reflex-components-core/src/reflex_components_core/datadisplay/__init__.pyi": "c96fed4da42a13576d64f84e3c7cb25c",
2626
"packages/reflex-components-core/src/reflex_components_core/el/__init__.pyi": "f09129ddefb57ab4c7769c86dc9a3153",

reflex/app.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
from reflex_base.event.context import EventContext
4343
from reflex_base.event.processor import BaseStateEventProcessor, EventProcessor
4444
from reflex_base.registry import RegistrationContext
45+
from reflex_base.telemetry_context import CompileTrigger, TelemetryContext
4546
from reflex_base.utils import console
4647
from reflex_base.utils.imports import ImportVar
4748
from reflex_base.utils.types import ASGIApp, Message, Receive, Scope, Send
@@ -96,11 +97,6 @@
9697
should_prerender_routes,
9798
)
9899
from reflex.utils.misc import run_in_thread
99-
from reflex.utils.telemetry_context import (
100-
CompileTrigger,
101-
TelemetryContext,
102-
increment_feature,
103-
)
104100
from reflex.utils.token_manager import RedisTokenManager, TokenManager
105101

106102
if sys.version_info < (3, 13):
@@ -913,8 +909,6 @@ def add_page(
913909
state = self._state or State
914910
route_args = get_route_args(route)
915911
state.setup_dynamic_args(route_args)
916-
if route_args:
917-
increment_feature("dynamic_routes_count")
918912

919913
self._load_events[route] = (
920914
(on_load if isinstance(on_load, list) else [on_load])

reflex/app_mixins/lifespan.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@
1515
from reflex_base.utils.exceptions import InvalidLifespanTaskTypeError
1616
from starlette.applications import Starlette
1717

18-
from reflex.utils.telemetry_context import increment_feature
19-
2018
from .mixin import AppMixin
2119

2220
if TYPE_CHECKING:
@@ -200,7 +198,4 @@ def register_lifespan_task(
200198
functools.update_wrapper(registered_task, task)
201199
self._lifespan_tasks[registered_task] = None
202200
console.debug(f"Registered lifespan task: {task_name}")
203-
module = getattr(registered_task, "__module__", None) or ""
204-
if module != "reflex" and not module.startswith("reflex."):
205-
increment_feature("lifespan_tasks_count")
206201
return task

reflex/istate/shared.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313

1414
from reflex.istate.manager.token import BaseStateToken
1515
from reflex.state import BaseState, State, _override_base_method
16-
from reflex.utils.telemetry_context import increment_feature
1716

1817
UPDATE_OTHER_CLIENT_TASKS: set[asyncio.Task] = set()
1918
LINKED_STATE = TypeVar("LINKED_STATE", bound="SharedStateBaseInternal")
@@ -520,4 +519,3 @@ def __init_subclass__(cls, **kwargs):
520519
# pulls in all linked states and substates which may not actually be
521520
# accessed for this event.
522521
root_state._always_dirty_substates.add(SharedStateBaseInternal.get_name())
523-
increment_feature("shared_state_count")

reflex/istate/storage.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66

77
from reflex_base.utils import format
88

9-
from reflex.utils.telemetry_context import increment_feature
10-
119

1210
class ClientStorageBase:
1311
"""Base class for client-side storage."""
@@ -75,7 +73,6 @@ def __new__(
7573
inst.domain = domain
7674
inst.secure = secure
7775
inst.same_site = same_site
78-
increment_feature("cookie_count")
7976
return inst
8077

8178

@@ -112,7 +109,6 @@ def __new__(
112109
inst = super().__new__(cls, object)
113110
inst.name = name
114111
inst.sync = sync
115-
increment_feature("local_storage_count")
116112
return inst
117113

118114

@@ -145,5 +141,4 @@ def __new__(
145141
else:
146142
inst = super().__new__(cls, object)
147143
inst.name = name
148-
increment_feature("session_storage_count")
149144
return inst

reflex/model.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@
1313
from reflex_base.utils import console
1414
from reflex_base.utils.serializers import serializer
1515

16-
from reflex.utils.telemetry_context import increment_feature
17-
1816
if TYPE_CHECKING:
1917
from typing import TypeVar
2018

@@ -202,7 +200,6 @@ def register(cls, model: SQLModelOrSqlAlchemyT) -> SQLModelOrSqlAlchemyT:
202200
The model passed in as an argument (Allows decorator usage)
203201
"""
204202
cls.models.add(model)
205-
increment_feature("db_model_count")
206203
return model
207204

208205
@classmethod

reflex/utils/exec.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,13 @@
2020
from reflex_base.config import get_config
2121
from reflex_base.constants.base import LogLevel
2222
from reflex_base.environment import environment
23+
from reflex_base.telemetry_context import CompileTrigger
2324
from reflex_base.utils import console
2425
from reflex_base.utils.decorator import once
2526

2627
from reflex.utils import path_ops
2728
from reflex.utils.misc import get_module_path
2829
from reflex.utils.prerequisites import get_web_dir
29-
from reflex.utils.telemetry_context import CompileTrigger
3030

3131
# For uvicorn windows bug fix (#2335)
3232
frontend_process = None

reflex/utils/prerequisites.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@
3131
if typing.TYPE_CHECKING:
3232
from redis import Redis as RedisSync
3333
from redis.asyncio import Redis
34+
from reflex_base.telemetry_context import CompileTrigger
3435

3536
from reflex.app import App
36-
from reflex.utils.telemetry_context import CompileTrigger
3737

3838

3939
class AppInfo(NamedTuple):

0 commit comments

Comments
 (0)