diff --git a/reflex/app_module_for_backend.py b/reflex/app_module_for_backend.py index cae136354b..4149c3fd2c 100644 --- a/reflex/app_module_for_backend.py +++ b/reflex/app_module_for_backend.py @@ -2,6 +2,7 @@ Only the app attribute is explicitly exposed. """ +import time from concurrent.futures import ThreadPoolExecutor from reflex import constants @@ -12,17 +13,17 @@ if constants.CompileVars.APP != "app": raise AssertionError("unexpected variable name for 'app'") -telemetry.send("compile") app_module = get_app(reload=False) app = getattr(app_module, constants.CompileVars.APP) # For py3.8 and py3.9 compatibility when redis is used, we MUST add any decorator pages # before compiling the app in a thread to avoid event loop error (REF-2172). app._apply_decorated_pages() +start_time = time.perf_counter() + + compile_future = ThreadPoolExecutor(max_workers=1).submit(app._compile) -compile_future.add_done_callback( - # Force background compile errors to print eagerly - lambda f: f.result() -) +compile_future.add_done_callback(lambda f: telemetry.compile_callback(f, start_time)) + # Wait for the compile to finish in prod mode to ensure all optional endpoints are mounted. if is_prod_mode(): compile_future.result() @@ -32,6 +33,5 @@ del compile_future del get_app del is_prod_mode -del telemetry del constants del ThreadPoolExecutor diff --git a/reflex/components/el/elements/metadata.py b/reflex/components/el/elements/metadata.py index c19612abeb..82562a76a2 100644 --- a/reflex/components/el/elements/metadata.py +++ b/reflex/components/el/elements/metadata.py @@ -44,6 +44,7 @@ class Meta(BaseHTML): # Inherits common attributes from BaseHTML """Display the meta element.""" tag = "meta" + char_set: Var[Union[str, int, bool]] content: Var[Union[str, int, bool]] http_equiv: Var[Union[str, int, bool]] diff --git a/reflex/reflex.py b/reflex/reflex.py index 224bace471..000ee9df47 100644 --- a/reflex/reflex.py +++ b/reflex/reflex.py @@ -4,6 +4,7 @@ import atexit import os +import time import webbrowser from pathlib import Path from typing import List, Optional @@ -108,8 +109,13 @@ def _init( raise typer.Exit(2) template = constants.Templates.DEFAULT + start_time = time.perf_counter() + + # Check if the app is already initialized. + reinit = os.path.exists(constants.Config.FILE) + # Initialize the app. - prerequisites.initialize_app(app_name, template) + prerequisites.initialize_app(app_name, template, reinit=reinit) # If a reflex.build generation hash is available, download the code and apply it to the main module. if generation_hash: @@ -129,6 +135,10 @@ def _init( # Finish initializing the app. console.success(f"Initialized {app_name}") + # Post telemetry event + event_type = "reinit" if reinit else "init" + telemetry.send(event_type, duration=time.perf_counter() - start_time) + @cli.command() def init( diff --git a/reflex/utils/prerequisites.py b/reflex/utils/prerequisites.py index 40a2338d32..12bc49c167 100644 --- a/reflex/utils/prerequisites.py +++ b/reflex/utils/prerequisites.py @@ -1419,22 +1419,19 @@ def create_config_init_app_from_remote_template( shutil.rmtree(unzip_dir) -def initialize_app(app_name: str, template: str | None = None): +def initialize_app(app_name: str, template: str | None = None, reinit: bool = False): """Initialize the app either from a remote template or a blank app. If the config file exists, it is considered as reinit. Args: app_name: The name of the app. template: The name of the template to use. + reinit: Whether to reinitialize the app. Raises: Exit: If template is directly provided in the command flag and is invalid. """ - # Local imports to avoid circular imports. - from reflex.utils import telemetry - - # Check if the app is already initialized. - if os.path.exists(constants.Config.FILE): - telemetry.send("reinit") + # Check if the app is already initialized. If so, we don't need to init. + if reinit: return # Get the available templates @@ -1473,8 +1470,6 @@ def initialize_app(app_name: str, template: str | None = None): template_url=template_url, ) - telemetry.send("init", template=template) - def initialize_main_module_index_from_generation(app_name: str, generation_hash: str): """Overwrite the `index` function in the main module with reflex.build generated code. diff --git a/reflex/utils/telemetry.py b/reflex/utils/telemetry.py index e027ed81a7..8c78cf2011 100644 --- a/reflex/utils/telemetry.py +++ b/reflex/utils/telemetry.py @@ -4,7 +4,9 @@ import asyncio import multiprocessing +import os import platform +import time import warnings try: @@ -82,6 +84,21 @@ def get_memory() -> int: return 0 +def get_page_count(folder: str) -> int: + """Get the total number of files in a folder. + + Args: + folder: The path to the folder. + + Returns: + The total number of files in the folder. + """ + total_files = 0 + for _, _, filenames in os.walk(folder): + total_files += len(filenames) + return total_files + + def _raise_on_missing_project_hash() -> bool: """Check if an error should be raised when project hash is missing. @@ -98,6 +115,20 @@ def _raise_on_missing_project_hash() -> bool: return True +def compile_callback(f, start_time): + """Callback to send telemetry after compiling the app. + + Args: + f: The future object. + start_time: The start time of the compilation. + """ + try: + # Force background compile errors to print eagerly + f.result() + finally: + send("compile", duration=time.perf_counter() - start_time) + + def _prepare_event(event: str, **kwargs) -> dict: """Prepare the event to be sent to the PostHog server. @@ -128,12 +159,12 @@ def _prepare_event(event: str, **kwargs) -> dict: cpuinfo = get_cpu_info() - additional_keys = ["template", "context", "detail"] + additional_keys = ["template", "context", "detail", "duration"] additional_fields = { key: value for key in additional_keys if (value := kwargs.get(key)) is not None } return { - "api_key": "phc_JoMo0fOyi0GQAooY3UyO9k0hebGkMyFJrrCw1Gt5SGb", + "api_key": "phc_JoMo0fOyi0GQAooY3UyO9k0hebGkMyFJrrCw1Gt5SGb", # Public API key "event": event, "properties": { "distinct_id": installation_id, @@ -145,6 +176,9 @@ def _prepare_event(event: str, **kwargs) -> dict: "cpu_count": get_cpu_count(), "memory": get_memory(), "cpu_info": dict(cpuinfo) if cpuinfo else {}, + "pages_count": get_page_count(".web/pages") + if event == "compile" or event == "run-dev" + else None, **additional_fields, }, "timestamp": stamp,