Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Profile compile times #3730

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions reflex/app_module_for_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Only the app attribute is explicitly exposed.
"""

import time
from concurrent.futures import ThreadPoolExecutor

from reflex import constants
Expand All @@ -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()
Expand All @@ -32,6 +33,5 @@
del compile_future
del get_app
del is_prod_mode
del telemetry
del constants
del ThreadPoolExecutor
1 change: 1 addition & 0 deletions reflex/components/el/elements/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]]
Expand Down
12 changes: 11 additions & 1 deletion reflex/reflex.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import atexit
import os
import time
import webbrowser
from pathlib import Path
from typing import List, Optional
Expand Down Expand Up @@ -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:
Expand All @@ -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(
Expand Down
13 changes: 4 additions & 9 deletions reflex/utils/prerequisites.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
38 changes: 36 additions & 2 deletions reflex/utils/telemetry.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

import asyncio
import multiprocessing
import os
import platform
import time
import warnings

try:
Expand Down Expand Up @@ -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.

Expand All @@ -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.

Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down
Loading