diff --git a/components/clp-py-utils/clp_py_utils/profiling_utils.py b/components/clp-py-utils/clp_py_utils/profiling_utils.py new file mode 100644 index 0000000000..8212693a7b --- /dev/null +++ b/components/clp-py-utils/clp_py_utils/profiling_utils.py @@ -0,0 +1,258 @@ +""" +Profiling utilities for CLP query execution performance analysis. + +This module provides lightweight profiling decorators using pyinstrument. + +Profile outputs include: +- HTML files with interactive flame graphs and call trees. +- Text summaries showing call hierarchy and timing. +""" + +import datetime +import functools +import inspect +import os +from collections.abc import Callable +from pathlib import Path +from typing import Any, TypeVar + +from pyinstrument import Profiler + +from clp_py_utils.clp_logging import get_logger + +logger = get_logger("profiler") + +F = TypeVar("F", bound=Callable[..., Any]) + +PROFILING_INTERVAL_SECONDS = 0.001 + + +def profile( + section_name: str | None = None, + job_id_param: str = "job_id", + task_id_param: str = "task_id", +) -> Callable[[F], F]: + """ + Profiles function execution as decorator with automatic context extraction. + + Output files are written to $CLP_LOGS_DIR/profiles/ (e.g., clp-package/var/log/query_worker/ + profiles/). + + :param section_name: Override for profile section name. If None, uses function name. + :param job_id_param: Parameter name to extract job_id from (default: "job_id"). + Can use dot notation for attributes, e.g., "job.id". + :param task_id_param: Parameter name to extract task_id from (default: "task_id"). + Can use dot notation for attributes, e.g., "task.id". + :return: Decorated function with profiling capabilities. + """ + + def decorator(func: F) -> F: + name = section_name or func.__name__ + is_async = inspect.iscoroutinefunction(func) + + if is_async: + + @functools.wraps(func) + async def async_wrapper(*args: Any, **kwargs: Any) -> Any: + if not _is_profiling_enabled(): + return await func(*args, **kwargs) + + # Profiling enabled: extract context and profile execution + job_id, task_id = _extract_context_from_args( + func, args, kwargs, job_id_param, task_id_param + ) + + profiler = Profiler(interval=PROFILING_INTERVAL_SECONDS) + try: + profiler.start() + except RuntimeError as e: + # Skip profiling this function to avoid conflicts + if "already a profiler running" in str(e): + logger.debug( + f"Skipping nested profiling for {name} " + f"(parent profiler already active)" + ) + return await func(*args, **kwargs) + raise + + try: + result = await func(*args, **kwargs) + return result + finally: + profiler.stop() + _save_profile(profiler, name, job_id, task_id) + + return async_wrapper # type: ignore + + @functools.wraps(func) + def sync_wrapper(*args: Any, **kwargs: Any) -> Any: + if not _is_profiling_enabled(): + return func(*args, **kwargs) + + # Profiling enabled: extract context and profile execution + job_id, task_id = _extract_context_from_args( + func, args, kwargs, job_id_param, task_id_param + ) + + profiler = Profiler(interval=PROFILING_INTERVAL_SECONDS) + try: + profiler.start() + except RuntimeError as e: + # Skip profiling this function to avoid conflicts + if "already a profiler running" in str(e): + logger.debug( + f"Skipping nested profiling for {name} (parent profiler already active)" + ) + return func(*args, **kwargs) + raise + + try: + result = func(*args, **kwargs) + return result + finally: + profiler.stop() + _save_profile(profiler, name, job_id, task_id) + + return sync_wrapper # type: ignore + + return decorator + + +def _extract_context_from_args( + func: Callable, + args: tuple, + kwargs: dict, + job_id_param: str = "job_id", + task_id_param: str = "task_id", +) -> tuple[str, str]: + """ + Extracts job_id and task_id from function arguments. + + :param func: The function being profiled. + :param args: Positional arguments passed to the function. + :param kwargs: Keyword arguments passed to the function. + :param job_id_param: Name/path of the parameter containing job_id (default: "job_id"). + :param task_id_param: Name/path of the parameter containing task_id (default: "task_id"). + :return: tuple of (job_id, task_id) as strings. Empty strings if not found. + """ + job_id = "" + task_id = "" + + try: + # Get function signature + sig = inspect.signature(func) + param_names = list(sig.parameters.keys()) + + def extract_value(param_spec: str) -> str: + """Extract value from parameter, supporting dot notation for attributes.""" + if not param_spec: + return "" + + # Split on '.' to handle attribute access + parts = param_spec.split(".") + param_name = parts[0] + + # Find the parameter value + value = None + if param_name in kwargs: + value = kwargs[param_name] + elif param_name in param_names: + idx = param_names.index(param_name) + if idx < len(args): + value = args[idx] + + if value is None: + return "" + + # Navigate through attributes if dot notation was used + for attr_name in parts[1:]: + if hasattr(value, attr_name): + value = getattr(value, attr_name) + else: + return "" + + return str(value) + + # Extract job_id and task_id + job_id = extract_value(job_id_param) + task_id = extract_value(task_id_param) + + except Exception as e: + logger.debug(f"Failed to extract context from {func.__name__}: {e}") + + return job_id, task_id + + +def _is_profiling_enabled() -> bool: + """ + Checks if profiling is enabled. + TODO: Add `CLPConfig` mechanism to enable/disable profiling for each component. + + :return: Whether the profiler is enabled. + """ + return False + + +def _save_profile( + profiler: Profiler, section_name: str, job_id: str = "", task_id: str = "" +) -> None: + """ + Saves profiler output to HTML and text formats. Generates .html and .txt files. + + :param profiler: The pyinstrument Profiler object containing profiling data. + :param section_name: Name identifying this profiling section. + :param job_id: Optional job identifier for filename. + :param task_id: Optional task identifier for filename. + """ + try: + # Get the session for logging + session = profiler.last_session + if not session: + logger.debug(f"No profiling session for {section_name}") + return + + duration = session.duration + sample_count = session.sample_count + + timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S_%f") + filename_parts = [section_name] + + if job_id: + filename_parts.append(f"job{job_id}") + if task_id: + filename_parts.append(f"task{task_id}") + + filename_parts.append(timestamp) + base_filename = "_".join(filename_parts) + + output_dir = Path(os.getenv("CLP_LOGS_DIR")) / "profiles" + output_dir.mkdir(exist_ok=True, parents=True) + + # Save HTML with interactive visualization + html_path = output_dir / f"{base_filename}.html" + with open(html_path, "w", encoding="utf-8") as f: + f.write(profiler.output_html()) + + # Save human-readable text summary with call hierarchy + txt_path = output_dir / f"{base_filename}.txt" + with open(txt_path, "w", encoding="utf-8") as f: + # Header + f.write("=" * 80 + "\n") + f.write(f"CLP Query Profiling Report (pyinstrument)\n") + f.write(f"Section: {section_name}\n") + if job_id: + f.write(f"Job ID: {job_id}\n") + if task_id: + f.write(f"Task ID: {task_id}\n") + f.write(f"Timestamp: {timestamp}\n") + f.write("=" * 80 + "\n\n") + f.write(profiler.output_text(unicode=True, color=False)) + + logger.info( + f"Profile saved: {section_name} " + f"(duration={duration:.6f}s, samples={sample_count}) " + f"HTML={html_path}, TXT={txt_path}" + ) + + except Exception as e: + logger.error(f"Failed to save profile for {section_name}: {e}", exc_info=True) diff --git a/components/clp-py-utils/pyproject.toml b/components/clp-py-utils/pyproject.toml index 0d3e759463..aa0b19e4a4 100644 --- a/components/clp-py-utils/pyproject.toml +++ b/components/clp-py-utils/pyproject.toml @@ -12,6 +12,7 @@ dependencies = [ "mariadb>=1.0.11,<1.1.dev0", "mysql-connector-python>=9.4.0", "pydantic>=2.12.3", + "pyinstrument>=5.1.1", "python-Levenshtein>=0.27.1", "PyYAML>=6.0.3", "result>=0.17.0", diff --git a/components/clp-py-utils/uv.lock b/components/clp-py-utils/uv.lock index 0e1b18c212..76febe6ea0 100644 --- a/components/clp-py-utils/uv.lock +++ b/components/clp-py-utils/uv.lock @@ -48,6 +48,7 @@ dependencies = [ { name = "mariadb" }, { name = "mysql-connector-python" }, { name = "pydantic" }, + { name = "pyinstrument" }, { name = "python-levenshtein" }, { name = "pyyaml" }, { name = "result" }, @@ -61,6 +62,7 @@ requires-dist = [ { name = "mariadb", specifier = ">=1.0.11,<1.1.dev0" }, { name = "mysql-connector-python", specifier = ">=9.4.0" }, { name = "pydantic", specifier = ">=2.12.3" }, + { name = "pyinstrument", specifier = ">=5.1.1" }, { name = "python-levenshtein", specifier = ">=0.27.1" }, { name = "pyyaml", specifier = ">=6.0.3" }, { name = "result", specifier = ">=0.17.0" }, @@ -379,6 +381,70 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/48/f7/925f65d930802e3ea2eb4d5afa4cb8730c8dc0d2cb89a59dc4ed2fcb2d74/pydantic_core-2.41.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c173ddcd86afd2535e2b695217e82191580663a1d1928239f877f5a1649ef39f", size = 2147775, upload-time = "2025-10-14T10:23:45.406Z" }, ] +[[package]] +name = "pyinstrument" +version = "5.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/80/ce/824ee634994e612156f7b84eaf50b8523c676ebfed8d8dd12939a82f4c15/pyinstrument-5.1.1.tar.gz", hash = "sha256:bc401cda990b3c1cfe8e0e0473cbd605df3c63b73478a89ac4ab108f2184baa8", size = 264730, upload-time = "2025-08-12T11:35:43.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/80/57448dba5ced8c45d753b4b8dc587f0e117712405d4235b305327fb90671/pyinstrument-5.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f4912edf01912792d2f44dfc43d3f041acc7ea634d0300b3394711963a431d1b", size = 130457, upload-time = "2025-08-12T11:34:20.354Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2a/af500f4352a2ca952eb6465e861964def199e30bfe6f3f5e7f0f05bd870e/pyinstrument-5.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:217ee5decbc7312b16092307a1bfbe1c04b175bb91ad9622388cd266f54fb260", size = 122967, upload-time = "2025-08-12T11:34:21.914Z" }, + { url = "https://files.pythonhosted.org/packages/83/e4/68952fce23590eb7cc47c852ad22037ef87148897af45194c6133689cc57/pyinstrument-5.1.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2fcad0f4211226e445cdbca1ee308567d3d7507cb18355463de64bea2dbe0b80", size = 147557, upload-time = "2025-08-12T11:34:23.386Z" }, + { url = "https://files.pythonhosted.org/packages/69/cd/d54450580569a075ee165e91a885586e0099424a7792ab4c3e1480fbbda5/pyinstrument-5.1.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:129dcdd57f0720ffb0c181517eddf5db6bfb2cdd1338c6fd5f4082d62c657ba0", size = 146253, upload-time = "2025-08-12T11:34:24.577Z" }, + { url = "https://files.pythonhosted.org/packages/3b/d6/8756e79c29e9ac6f21b437627012bfb3329d4af792a2f529a4a9b31f0c71/pyinstrument-5.1.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e1efb57a5b708f76b5fed08ecb9dc380a1949f4cb82794d14c3c40113c2f4a2d", size = 146483, upload-time = "2025-08-12T11:34:25.992Z" }, + { url = "https://files.pythonhosted.org/packages/73/48/8e0d264c5116325e38587aceda98b2d3da708149c320b6999b636a1735ad/pyinstrument-5.1.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9d1e89dcf9a88d328cc7eb7c67c43c8a4ad38c8162eda87d93166a998fc8580d", size = 146009, upload-time = "2025-08-12T11:34:27.109Z" }, + { url = "https://files.pythonhosted.org/packages/da/02/b492507090cf18f6d7a3ff419eecb53c1725c33f4ab6bbe32e9e611bacb5/pyinstrument-5.1.1-cp310-cp310-win32.whl", hash = "sha256:954bc886e11ddcf5789a8a953bec65e663f48d93cc634a8c77585f3e8762bcbb", size = 124155, upload-time = "2025-08-12T11:34:28.542Z" }, + { url = "https://files.pythonhosted.org/packages/86/2a/7e909fc1a3e8e9d0724b71829b1d9f4d398a9108b20ee1591bed45b014c5/pyinstrument-5.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:9b2f1d54c72661feeefd5ca81b6f487baf52add1d688ae349be599a258168bfc", size = 125038, upload-time = "2025-08-12T11:34:29.702Z" }, + { url = "https://files.pythonhosted.org/packages/52/65/dc0fcc6122e6f484ffa48260d1023e1fe90e53da8d69a7de656205af91ba/pyinstrument-5.1.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5272cff6eea21163b2105f6a80c907315e0f567720621e6d5672dc01bf71ee48", size = 130287, upload-time = "2025-08-12T11:34:32.436Z" }, + { url = "https://files.pythonhosted.org/packages/90/96/8a6cab312342f1ad7322b459dcbdb2e041251b7bbe4c1d0dd38ccbf2ae20/pyinstrument-5.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d4e7dc5a4aee37a44ff2e63db3127f2044dd95edcae240cb95915adbf223d4be", size = 122879, upload-time = "2025-08-12T11:34:33.506Z" }, + { url = "https://files.pythonhosted.org/packages/8a/89/1ad1ae703951832a34ec6b32354c6df1d0690b0333b7b8396c92afcdc23e/pyinstrument-5.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:602d55121df1d88aeb6d8ebc801597fdcb9718f78d602ae81458d65c56f25d24", size = 146412, upload-time = "2025-08-12T11:34:34.975Z" }, + { url = "https://files.pythonhosted.org/packages/a5/70/588ec07eeaa5d5d4aff2d47c8010379e9b39a7aee7e3fda625a0ce4f5838/pyinstrument-5.1.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63b5a788ff955e0597bc95463e64d5fa3747017524fdc02a0f5d12d5117cf2b9", size = 144885, upload-time = "2025-08-12T11:34:36.546Z" }, + { url = "https://files.pythonhosted.org/packages/0a/8f/119ad44d454edb927434bb53d8a69904ecaa47ec56f95c688c557a9590de/pyinstrument-5.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7f66038ec55a12e5510689240cdc745f8e98c90b93363f745106976e5cfb7397", size = 145530, upload-time = "2025-08-12T11:34:37.726Z" }, + { url = "https://files.pythonhosted.org/packages/8e/31/d53b20d1967ba78574807c5940251c0f238e8e3515ee9c4f20207eb09199/pyinstrument-5.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:110a08b6e7fa9542eb37c337e79467913d364a03bc2062f85566ba96bc82f54e", size = 144708, upload-time = "2025-08-12T11:34:38.855Z" }, + { url = "https://files.pythonhosted.org/packages/48/18/78ccb35dd265e83c0b7404a501004387b2e2390a0a44b6e4a004307931d1/pyinstrument-5.1.1-cp311-cp311-win32.whl", hash = "sha256:a223d5e9226ccede5bf2fbd4d13ce0aeb5120501b633ba85290ed94df37d3623", size = 124151, upload-time = "2025-08-12T11:34:40.392Z" }, + { url = "https://files.pythonhosted.org/packages/43/c0/3eb94aa2c2f0b8d49e290d4df662e21e707eb4a23f3b938239634fbfdc17/pyinstrument-5.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:16ce582a7b56287d338a8b59688458341aab5c6abda970ba50b2f7b3fd69f89d", size = 124942, upload-time = "2025-08-12T11:34:41.564Z" }, + { url = "https://files.pythonhosted.org/packages/76/3a/7824caf1fb419d0108f375a15b28cdd7ace8593f1ea56ef8276fddce9526/pyinstrument-5.1.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bcd6a03bdf180d73bc8dc7371e09dda089a48057095584e5f2818df1c820525b", size = 130306, upload-time = "2025-08-12T11:34:42.624Z" }, + { url = "https://files.pythonhosted.org/packages/3a/54/60ddd5eae617e29b58de774d178f4e4f7cdffd07ed1de36f976927ce69d3/pyinstrument-5.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5ffa0948c1e268356dcf930c128624f34037ce92ee865fa4c056dee067aee4c5", size = 122817, upload-time = "2025-08-12T11:34:44.182Z" }, + { url = "https://files.pythonhosted.org/packages/35/12/35b694bfa58050607eedc80a68f64e6195c738249101a0dcbed0657147e7/pyinstrument-5.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c95adf98a920f2039eb0065966f980452a7af794bab387e9bfe8af3c681affa0", size = 148053, upload-time = "2025-08-12T11:34:45.589Z" }, + { url = "https://files.pythonhosted.org/packages/5d/4a/338b891f9119cf747153301d5d095942f378032309cd385e53857d03c2d2/pyinstrument-5.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bcb46ca8596b375c27850d4d06a1ce94ed78074774d35cbed3ccd28b663c5ba6", size = 146817, upload-time = "2025-08-12T11:34:46.668Z" }, + { url = "https://files.pythonhosted.org/packages/3b/cc/186cdb048fee445bbf9bd18819287a61b57b66ec68cfc47bc3c1e38b1ae6/pyinstrument-5.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3fc16597d26b24a46bf3455686300c0b8a3eb565ebc82396f402c031dccc0145", size = 146914, upload-time = "2025-08-12T11:34:47.878Z" }, + { url = "https://files.pythonhosted.org/packages/b8/d1/533309830dd356d43e54d7feebaffab357f08568972285c609e98f7e6119/pyinstrument-5.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5aa135b4bd9667ddcb25fa582f4db77c5117ef207cb10ae901a8e4c5d5cde0e0", size = 146533, upload-time = "2025-08-12T11:34:49.015Z" }, + { url = "https://files.pythonhosted.org/packages/b8/df/2a656d6b1bd68ecfbb73c557906274a40ec7219dd92980fc1324997cf93e/pyinstrument-5.1.1-cp312-cp312-win32.whl", hash = "sha256:d15e37f8074b3043fca7aa985cb2079d2c221ccb0d27f059451ede800c801645", size = 124286, upload-time = "2025-08-12T11:34:50.285Z" }, + { url = "https://files.pythonhosted.org/packages/51/af/144d331cc9734e9141ac1a75f3ce904074ebc93dfe43cab44049ba8c8c28/pyinstrument-5.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:5c27d5cef0e809f213e5a94143c397d948650f5142c91dcce3611f584779183e", size = 125032, upload-time = "2025-08-12T11:34:51.369Z" }, + { url = "https://files.pythonhosted.org/packages/36/d4/b94f47aa7d301f6cdf5924bb75caacd0d0a1852bd4e876e3a64fc5798dad/pyinstrument-5.1.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:45af421c60c943a7f1619afabeba4951d4cc16b4206490d7d5b7ef5a4e2dfd42", size = 130315, upload-time = "2025-08-12T11:34:52.91Z" }, + { url = "https://files.pythonhosted.org/packages/1e/42/1bc2f28e139f69a0918d5d5dc1d59e65c640d4da9dd153fa48c2a8a87dd9/pyinstrument-5.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2603db3d745a65de66c96929ab9b0fcce050511eb24e32856ea2458785b8917f", size = 122805, upload-time = "2025-08-12T11:34:54.201Z" }, + { url = "https://files.pythonhosted.org/packages/a8/85/2f0c9115cd8a01e0a18d0650d9f3f20ff71e8ca17bd4af60dd3a0cb76f8a/pyinstrument-5.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2fe32492100efaa1b0a488c237fe420fdaf141646733a31a97f96c4e1fa6bbf8", size = 148210, upload-time = "2025-08-12T11:34:55.662Z" }, + { url = "https://files.pythonhosted.org/packages/86/62/3c73a63e6913378cc7e9ffb5af1e50836511eee83b7c7bf252fad7ec24e4/pyinstrument-5.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:999b5373f8b1e846357923063ae5c9275ad8a85ed4e0a42960a349288d1f5007", size = 146995, upload-time = "2025-08-12T11:34:57.133Z" }, + { url = "https://files.pythonhosted.org/packages/ab/8b/d21f4b6d8849881e9572967818e3e6d2dcb212e7dfa89e4e356d359db32b/pyinstrument-5.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:58a2f69052178ec624e4df0cf546eda48b3a381572ac1cb3272b4c163888af9d", size = 147029, upload-time = "2025-08-12T11:34:58.255Z" }, + { url = "https://files.pythonhosted.org/packages/8a/4d/1e43cecf2bcf4a3dd1100f4fc7a3da6438a65d0b95ca7b8ab5d094ea7c0b/pyinstrument-5.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4d9bbc00d2e258edbefeb39b61ad4636099b08acd1effdd40d76883a13e7bf5a", size = 146668, upload-time = "2025-08-12T11:34:59.401Z" }, + { url = "https://files.pythonhosted.org/packages/34/48/00322b48e7adb665d04303b487454eb0c13a76ec0af8da20f452098fcc12/pyinstrument-5.1.1-cp313-cp313-win32.whl", hash = "sha256:cf2d8933e2aeaa02d4cb6279d83ef11ee882fb243fff96e3378153a730aadd6e", size = 124288, upload-time = "2025-08-12T11:35:00.514Z" }, + { url = "https://files.pythonhosted.org/packages/f5/14/d56515a110f74799aefc7489c1578ce4d99a4d731309559a427f954e7abc/pyinstrument-5.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:2402683a92617066b13a6d48f904396dcd15938016875b392534df027660eed4", size = 125041, upload-time = "2025-08-12T11:35:01.913Z" }, + { url = "https://files.pythonhosted.org/packages/18/2b/e4bdcabb5ae67de2ec3fa1f6e4eb4ae707b0bf460f895d4594792cdc919b/pyinstrument-5.1.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:688acba1c00cad73e43254e610f8e384a53ced3b0dbb5268fb44636e2b99663e", size = 130358, upload-time = "2025-08-12T11:35:03.569Z" }, + { url = "https://files.pythonhosted.org/packages/20/36/616f8db63997c096d3fb65e657cdf5bd2a63b53ed24a14750770dc500979/pyinstrument-5.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:116f5ad8cec4d6f5626305d7c1a104f5845a084bfb4b192d231eb8c41ea81f9a", size = 122827, upload-time = "2025-08-12T11:35:04.661Z" }, + { url = "https://files.pythonhosted.org/packages/af/7a/4f5d2bbc7c2466d46eb5ff47c6e667464eead47140e01a64be45215a59d4/pyinstrument-5.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d139d12a637001d3884344330054ce8335b2c8165dc3dd239726e1b358576bd", size = 147947, upload-time = "2025-08-12T11:35:05.786Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8c/c9b0081c0e52789a910390ce44e54c1318999d74386f15d92d0deb522aff/pyinstrument-5.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bc5b87b1e27bec94457fed8d03c755a3c09edb4f35d975dbdffd77d863173254", size = 146702, upload-time = "2025-08-12T11:35:07.202Z" }, + { url = "https://files.pythonhosted.org/packages/1e/1b/745ed7997da22ae68ff21b8f28e5e3a97b220335dce4ee7cf46d5eb17b32/pyinstrument-5.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:15f4a2ed9562efab34b555e1208955cf9681b2272489d7a59cd0e289344ada2e", size = 146836, upload-time = "2025-08-12T11:35:08.297Z" }, + { url = "https://files.pythonhosted.org/packages/70/f0/05cefdcf79d1901f9d179e7f55f3acaadbc5fee7af955cebb3f555280638/pyinstrument-5.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1cb0c79bfa2b2b5734213429c9d7f455e5af664cfde785c69a5780f6c532c1fd", size = 146463, upload-time = "2025-08-12T11:35:09.483Z" }, + { url = "https://files.pythonhosted.org/packages/6c/cb/6a6f33316be3c7b8247f8ca0e418a2b6fb68d64c227169b7dbee50009366/pyinstrument-5.1.1-cp314-cp314-win32.whl", hash = "sha256:3b9f1216ae4848a8983dc405e1a42e46e75bd8ae96aaba328d4358b8fc80a7a0", size = 124950, upload-time = "2025-08-12T11:35:11.607Z" }, + { url = "https://files.pythonhosted.org/packages/d6/ea/99caeb29f446f57d077a83c7c5f2b7c27c1719984d425f679bf2ec1eb6b0/pyinstrument-5.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:26971d4a17e0d5d4f6737e71c9de7a7ce5c83ab7daf078c6bf330be41d65273b", size = 125720, upload-time = "2025-08-12T11:35:12.683Z" }, + { url = "https://files.pythonhosted.org/packages/f6/d0/953b75d634565ef34f8ed559f2e4af7cd1f2d5f5b578092e8f1d8199e4b1/pyinstrument-5.1.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:62362843884d654401ec4c25fed35f4b4ded077d96b3396f1e791c31e4203d3e", size = 131258, upload-time = "2025-08-12T11:35:13.805Z" }, + { url = "https://files.pythonhosted.org/packages/a6/a4/4ec87cfd0974d79b2fcd72b3e20336fc65b96a5b08f2eb2867bf71b27b82/pyinstrument-5.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f2d640230b71c6d9ac8f27a9c5cd07fc8a6acad9196d1e48d9c33658b176fb80", size = 123276, upload-time = "2025-08-12T11:35:14.933Z" }, + { url = "https://files.pythonhosted.org/packages/eb/f8/6a210989c8ede85f91b7e4ba5d9730492f1d081762570c06c750d787536c/pyinstrument-5.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3f54f7292c63461c75ddf193f5e733803e463ccbc54f2fb7c9591337ddea7d10", size = 155767, upload-time = "2025-08-12T11:35:16.124Z" }, + { url = "https://files.pythonhosted.org/packages/f4/a8/5ac81ffbfe36d2e5c3332a9452746a21540987da0d9491db751a905bba13/pyinstrument-5.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c156eb442f9f22960ae16bd195051863d5e8a68b877926e88bbaf8bbdc1456d1", size = 153423, upload-time = "2025-08-12T11:35:17.312Z" }, + { url = "https://files.pythonhosted.org/packages/3f/55/5620c2a61403cde044e81e33056c14fbf5793eea33f67f2223d61abec9ae/pyinstrument-5.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:caadaf67ad5926c46af784316024793c909b9e9ee550475855fd32171c4bd033", size = 153542, upload-time = "2025-08-12T11:35:18.729Z" }, + { url = "https://files.pythonhosted.org/packages/7a/83/a8f22466652250a847dfdf58f9a2717b470fdbbcb075c7f730bf608041a6/pyinstrument-5.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:88ef2e8f483a5e1501d79a7ebdab592a597467810ed24d8db09ab6f568e938d3", size = 152337, upload-time = "2025-08-12T11:35:19.849Z" }, + { url = "https://files.pythonhosted.org/packages/0d/a6/cd4590da14deaeda6315519c26064874bbb9648a1358b80e8a8ca5d4add0/pyinstrument-5.1.1-cp314-cp314t-win32.whl", hash = "sha256:265bc4389f82e6521777bfab426a62a15c4940955e86f75db79a44e7349f9757", size = 125621, upload-time = "2025-08-12T11:35:21.201Z" }, + { url = "https://files.pythonhosted.org/packages/b3/30/177102e798539368aef25688a6a171d66ec92e6f16b6b651a89045a2bd13/pyinstrument-5.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:fa254f269a72a007b5d02c18cd4b67081e0efabbd33e18acdbd5e3be905afa06", size = 126528, upload-time = "2025-08-12T11:35:22.578Z" }, + { url = "https://files.pythonhosted.org/packages/3b/d3/19fe0cd1a3025d8eff1179f4516382a94d7636a792cdcd7a7a6ac3055371/pyinstrument-5.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:88985f97a7656f1de4ee44abbc2d058720b493e465f1d159784caadc7e68dde4", size = 130468, upload-time = "2025-08-12T11:35:33.676Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f9/2d6acda6f0cd4493d29846171a3e57bf515e62b1dff1876be3aa19402571/pyinstrument-5.1.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b8725a94eb7a990c4e1937bf5f6f025e8e8d159e7e723f879f9e4e92678e0ea2", size = 122968, upload-time = "2025-08-12T11:35:34.758Z" }, + { url = "https://files.pythonhosted.org/packages/dc/37/8cf5df222645327121bd035a8d379c0e7150c6c17430a97d02ccfa2c4709/pyinstrument-5.1.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:18e0a6597eab69590312aa3f162f38e9bd1308a8d770d54d8dc510a51d2218d7", size = 147165, upload-time = "2025-08-12T11:35:35.933Z" }, + { url = "https://files.pythonhosted.org/packages/cd/c1/fe68f55753332c9de07d6b99ef8613fbe33f595bc214eef78193cd4f6c38/pyinstrument-5.1.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4adffa45d4338a7651ffc2eb9714fc5c8542caf8e7e4497f49695fb721bfb2e0", size = 145911, upload-time = "2025-08-12T11:35:37.527Z" }, + { url = "https://files.pythonhosted.org/packages/f1/6f/9de8c913d266ecfe27ce5eda88a151df7359e58396df50d8df63212663aa/pyinstrument-5.1.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f415f58f70734815b5e17606ce8474a6a0c27660266868d98824ec49a0f90377", size = 146155, upload-time = "2025-08-12T11:35:38.734Z" }, + { url = "https://files.pythonhosted.org/packages/02/be/8fc7d94115e5d404081d7313cd6148428d92975b62419c7ebc0cbaa1e638/pyinstrument-5.1.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:abfdcf2a1fa2fee2e3f84039ed5fd9914fe0b020a6211e959ae69f7a2c6daac7", size = 145686, upload-time = "2025-08-12T11:35:39.902Z" }, + { url = "https://files.pythonhosted.org/packages/ba/0b/30051674bd764cfb2134b9e62646b041dbfd1339dd8d7f86ac854321b051/pyinstrument-5.1.1-cp39-cp39-win32.whl", hash = "sha256:a8ebaf1d74b89f4216cde10968cbfeccd7038a6c8c62e36a80443159acadc6e0", size = 124162, upload-time = "2025-08-12T11:35:41.089Z" }, + { url = "https://files.pythonhosted.org/packages/5f/ef/3cd3ace3f64fa01f1cd2bdf9d3911c12c5de0ae1e971b778bf12b521bf8e/pyinstrument-5.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:420d3361e258e1d981b83aa1167201c35027895375a3377071d4efa8a95a7e66", size = 125037, upload-time = "2025-08-12T11:35:42.253Z" }, +] + [[package]] name = "python-dateutil" version = "2.9.0.post0" diff --git a/components/job-orchestration/job_orchestration/executor/query/extract_stream_task.py b/components/job-orchestration/job_orchestration/executor/query/extract_stream_task.py index fbae0b2c73..575206a851 100644 --- a/components/job-orchestration/job_orchestration/executor/query/extract_stream_task.py +++ b/components/job-orchestration/job_orchestration/executor/query/extract_stream_task.py @@ -14,6 +14,7 @@ WorkerConfig, ) from clp_py_utils.clp_logging import set_logging_level +from clp_py_utils.profiling_utils import profile from clp_py_utils.s3_utils import ( generate_s3_virtual_hosted_style_url, get_credential_env_vars, @@ -180,6 +181,7 @@ def _make_command_and_env_vars( @app.task(bind=True) +@profile(section_name="extract_stream_task") def extract_stream( self: Task, job_id: str, diff --git a/components/job-orchestration/job_orchestration/executor/query/fs_search_task.py b/components/job-orchestration/job_orchestration/executor/query/fs_search_task.py index b8710ef381..0fe61a28cc 100644 --- a/components/job-orchestration/job_orchestration/executor/query/fs_search_task.py +++ b/components/job-orchestration/job_orchestration/executor/query/fs_search_task.py @@ -12,6 +12,7 @@ WorkerConfig, ) from clp_py_utils.clp_logging import set_logging_level +from clp_py_utils.profiling_utils import profile from clp_py_utils.s3_utils import generate_s3_virtual_hosted_style_url, get_credential_env_vars from clp_py_utils.sql_adapter import SQL_Adapter @@ -166,6 +167,7 @@ def _make_command_and_env_vars( @app.task(bind=True) +@profile(section_name="search_task") def search( self: Task, job_id: str, diff --git a/components/job-orchestration/job_orchestration/scheduler/query/query_scheduler.py b/components/job-orchestration/job_orchestration/scheduler/query/query_scheduler.py index da5ce7cc04..caa59e4e87 100644 --- a/components/job-orchestration/job_orchestration/scheduler/query/query_scheduler.py +++ b/components/job-orchestration/job_orchestration/scheduler/query/query_scheduler.py @@ -46,6 +46,7 @@ ) from clp_py_utils.core import read_yaml_config_file from clp_py_utils.decorators import exception_default_value +from clp_py_utils.profiling_utils import profile from clp_py_utils.sql_adapter import SQL_Adapter from pydantic import ValidationError @@ -544,6 +545,7 @@ def get_task_group_for_job( raise NotImplementedError(error_msg) +@profile(section_name="scheduler_dispatch_job", job_id_param="job.id") def dispatch_query_job( db_conn, job: QueryJob, @@ -565,6 +567,7 @@ def dispatch_query_job( job.state = InternalJobState.RUNNING +@profile(section_name="scheduler_acquire_reducer", job_id_param="job.id") async def acquire_reducer_for_job(job: SearchJob): reducer_host: Optional[str] = None reducer_port: Optional[int] = None @@ -893,6 +896,7 @@ def found_max_num_latest_results( return max_timestamp_in_remaining_archives <= min_timestamp_in_top_results +@profile(section_name="scheduler_handle_finished_search", job_id_param="job.id") async def handle_finished_search_job( db_conn, job: SearchJob, task_results: Optional[Any], results_cache_uri: str ) -> None: