diff --git a/reflex/app_module_for_backend.py b/reflex/app_module_for_backend.py index cae136354..508488e44 100644 --- a/reflex/app_module_for_backend.py +++ b/reflex/app_module_for_backend.py @@ -4,25 +4,34 @@ Only the app attribute is explicitly exposed. from concurrent.futures import ThreadPoolExecutor +import time from reflex import constants -from reflex.utils import telemetry + from reflex.utils.exec import is_prod_mode from reflex.utils.prerequisites import get_app 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() + +def compile_callback(f): + from reflex.utils import telemetry + try: + # Force background compile errors to print eagerly + f.result() + finally: + telemetry.send("test-compile", duration=time.perf_counter()- start_time) + del telemetry + 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(compile_callback) + # 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 +41,6 @@ del app_module 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 c19612abe..fa73d40cf 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 224bace47..28234ccf8 100644 --- a/reflex/reflex.py +++ b/reflex/reflex.py @@ -8,6 +8,7 @@ import webbrowser from pathlib import Path from typing import List, Optional +import time import typer import typer.core from reflex_cli.deployments import deployments_cli @@ -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,7 +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( name: str = typer.Option( diff --git a/reflex/utils/prerequisites.py b/reflex/utils/prerequisites.py index 40a2338d3..e3c1458a4 100644 --- a/reflex/utils/prerequisites.py +++ b/reflex/utils/prerequisites.py @@ -1419,7 +1419,7 @@ 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: @@ -1429,12 +1429,8 @@ def initialize_app(app_name: str, template: str | None = None): 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,9 +1469,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 e027ed81a..dc015df3f 100644 --- a/reflex/utils/telemetry.py +++ b/reflex/utils/telemetry.py @@ -6,7 +6,7 @@ import asyncio import multiprocessing import platform import warnings - +import os try: from datetime import UTC, datetime except ImportError: @@ -69,7 +69,7 @@ def get_cpu_count() -> int: """ return multiprocessing.cpu_count() - + def get_memory() -> int: """Get the total memory in MB. @@ -80,6 +80,21 @@ def get_memory() -> int: return psutil.virtual_memory().total >> 20 except ValueError: # needed to pass ubuntu test return 0 + +def get_folder_size(folder: str) -> int: + """Get the total size of a folder in bytes, ignoring 'node_modules' folder. + + Args: + folder: The path to the folder. + + Returns: + The total size of the folder in bytes. + """ + total_files = 0 + for dirpath, dirnames, filenames in os.walk(folder): + total_files += len(filenames) + return total_files + def _raise_on_missing_project_hash() -> bool: @@ -128,7 +143,7 @@ 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 } @@ -145,6 +160,7 @@ 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_folder_size(".web/pages") if event == "test-compile" or event == "run-dev" else None, **additional_fields, }, "timestamp": stamp,