From b92febf3b5a1fe1dd17328bb6175d37579179e72 Mon Sep 17 00:00:00 2001 From: johnslavik Date: Mon, 20 Apr 2026 04:52:16 +0200 Subject: [PATCH 1/9] Add typed stubs for the profiling stdlib package - Complete type annotations for profiling.tracing and profiling.sampling - Replace all Incomplete usages with proper types throughout - Add return types, parameter types, and attribute types to all stubs - Only exception: unwinder: Incomplete in sample.pyi (_remote_debugging C ext) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- stdlib/VERSIONS | 5 +- stdlib/profiling/__init__.pyi | 0 stdlib/profiling/sampling/__init__.pyi | 8 ++ stdlib/profiling/sampling/__main__.pyi | 13 ++ stdlib/profiling/sampling/_child_monitor.pyi | 19 +++ stdlib/profiling/sampling/_css_utils.pyi | 1 + stdlib/profiling/sampling/_format_utils.pyi | 1 + .../profiling/sampling/_sync_coordinator.pyi | 8 ++ .../profiling/sampling/binary_collector.pyi | 25 ++++ stdlib/profiling/sampling/binary_reader.pyi | 31 ++++ stdlib/profiling/sampling/cli.pyi | 52 +++++++ stdlib/profiling/sampling/collector.pyi | 24 ++++ stdlib/profiling/sampling/constants.pyi | 21 +++ stdlib/profiling/sampling/errors.pyi | 13 ++ stdlib/profiling/sampling/gecko_collector.pyi | 51 +++++++ .../profiling/sampling/heatmap_collector.pyi | 80 +++++++++++ .../sampling/live_collector/__init__.pyi | 93 ++++++++++++ .../sampling/live_collector/collector.pyi | 133 ++++++++++++++++++ .../sampling/live_collector/constants.pyi | 38 +++++ .../sampling/live_collector/display.pyi | 71 ++++++++++ .../sampling/live_collector/trend_tracker.pyi | 16 +++ .../sampling/live_collector/widgets.pyi | 82 +++++++++++ stdlib/profiling/sampling/opcode_utils.pyi | 6 + .../profiling/sampling/pstats_collector.pyi | 17 +++ stdlib/profiling/sampling/sample.pyi | 76 ++++++++++ stdlib/profiling/sampling/stack_collector.pyi | 45 ++++++ stdlib/profiling/sampling/string_table.pyi | 6 + stdlib/profiling/tracing/__init__.pyi | 30 ++++ stdlib/profiling/tracing/__main__.pyi | 1 + stdlib/profiling/tracing/_utils.pyi | 8 ++ 30 files changed, 972 insertions(+), 2 deletions(-) create mode 100644 stdlib/profiling/__init__.pyi create mode 100644 stdlib/profiling/sampling/__init__.pyi create mode 100644 stdlib/profiling/sampling/__main__.pyi create mode 100644 stdlib/profiling/sampling/_child_monitor.pyi create mode 100644 stdlib/profiling/sampling/_css_utils.pyi create mode 100644 stdlib/profiling/sampling/_format_utils.pyi create mode 100644 stdlib/profiling/sampling/_sync_coordinator.pyi create mode 100644 stdlib/profiling/sampling/binary_collector.pyi create mode 100644 stdlib/profiling/sampling/binary_reader.pyi create mode 100644 stdlib/profiling/sampling/cli.pyi create mode 100644 stdlib/profiling/sampling/collector.pyi create mode 100644 stdlib/profiling/sampling/constants.pyi create mode 100644 stdlib/profiling/sampling/errors.pyi create mode 100644 stdlib/profiling/sampling/gecko_collector.pyi create mode 100644 stdlib/profiling/sampling/heatmap_collector.pyi create mode 100644 stdlib/profiling/sampling/live_collector/__init__.pyi create mode 100644 stdlib/profiling/sampling/live_collector/collector.pyi create mode 100644 stdlib/profiling/sampling/live_collector/constants.pyi create mode 100644 stdlib/profiling/sampling/live_collector/display.pyi create mode 100644 stdlib/profiling/sampling/live_collector/trend_tracker.pyi create mode 100644 stdlib/profiling/sampling/live_collector/widgets.pyi create mode 100644 stdlib/profiling/sampling/opcode_utils.pyi create mode 100644 stdlib/profiling/sampling/pstats_collector.pyi create mode 100644 stdlib/profiling/sampling/sample.pyi create mode 100644 stdlib/profiling/sampling/stack_collector.pyi create mode 100644 stdlib/profiling/sampling/string_table.pyi create mode 100644 stdlib/profiling/tracing/__init__.pyi create mode 100644 stdlib/profiling/tracing/__main__.pyi create mode 100644 stdlib/profiling/tracing/_utils.pyi diff --git a/stdlib/VERSIONS b/stdlib/VERSIONS index 6fcf0161790d..7a4725a42d9c 100644 --- a/stdlib/VERSIONS +++ b/stdlib/VERSIONS @@ -107,7 +107,7 @@ binhex: 3.0-3.10 bisect: 3.0- builtins: 3.0- bz2: 3.0- -cProfile: 3.0- +cProfile: 3.0-3.17 calendar: 3.0- cgi: 3.0-3.12 cgitb: 3.0-3.12 @@ -245,7 +245,8 @@ poplib: 3.0- posix: 3.0- posixpath: 3.0- pprint: 3.0- -profile: 3.0- +profile: 3.0-3.17 +profiling: 3.15- pstats: 3.0- pty: 3.0- pwd: 3.0- diff --git a/stdlib/profiling/__init__.pyi b/stdlib/profiling/__init__.pyi new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/stdlib/profiling/sampling/__init__.pyi b/stdlib/profiling/sampling/__init__.pyi new file mode 100644 index 000000000000..537359e0086d --- /dev/null +++ b/stdlib/profiling/sampling/__init__.pyi @@ -0,0 +1,8 @@ +from .collector import Collector as Collector +from .gecko_collector import GeckoCollector as GeckoCollector +from .heatmap_collector import HeatmapCollector as HeatmapCollector +from .pstats_collector import PstatsCollector as PstatsCollector +from .stack_collector import CollapsedStackCollector as CollapsedStackCollector +from .string_table import StringTable as StringTable + +__all__ = ["Collector", "PstatsCollector", "CollapsedStackCollector", "HeatmapCollector", "GeckoCollector", "StringTable"] diff --git a/stdlib/profiling/sampling/__main__.pyi b/stdlib/profiling/sampling/__main__.pyi new file mode 100644 index 000000000000..61d0127a2f16 --- /dev/null +++ b/stdlib/profiling/sampling/__main__.pyi @@ -0,0 +1,13 @@ +from .cli import main as main +from .errors import ( + SamplingModuleNotFoundError as SamplingModuleNotFoundError, + SamplingScriptNotFoundError as SamplingScriptNotFoundError, + SamplingUnknownProcessError as SamplingUnknownProcessError, +) + +MACOS_PERMISSION_ERROR: str +LINUX_PERMISSION_ERROR: str +WINDOWS_PERMISSION_ERROR: str +GENERIC_PERMISSION_ERROR: str + +def handle_permission_error() -> None: ... diff --git a/stdlib/profiling/sampling/_child_monitor.pyi b/stdlib/profiling/sampling/_child_monitor.pyi new file mode 100644 index 000000000000..257efcc11451 --- /dev/null +++ b/stdlib/profiling/sampling/_child_monitor.pyi @@ -0,0 +1,19 @@ +import types +from typing import Any +from typing_extensions import Self + +def get_child_pids(pid: int, recursive: bool = True) -> list[int]: ... +def is_python_process(pid: int) -> bool: ... + +class ChildProcessMonitor: + parent_pid: int + cli_args: list[str] + output_pattern: str + def __init__(self, pid: int, cli_args: list[str], output_pattern: str) -> None: ... + def __enter__(self) -> Self: ... + def __exit__( + self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: types.TracebackType | None + ) -> None: ... + @property + def spawned_profilers(self) -> list[Any]: ... + def wait_for_profilers(self, timeout: float = ...) -> None: ... diff --git a/stdlib/profiling/sampling/_css_utils.pyi b/stdlib/profiling/sampling/_css_utils.pyi new file mode 100644 index 000000000000..04bcee2814e9 --- /dev/null +++ b/stdlib/profiling/sampling/_css_utils.pyi @@ -0,0 +1 @@ +def get_combined_css(component: str) -> str: ... diff --git a/stdlib/profiling/sampling/_format_utils.pyi b/stdlib/profiling/sampling/_format_utils.pyi new file mode 100644 index 000000000000..b56022840536 --- /dev/null +++ b/stdlib/profiling/sampling/_format_utils.pyi @@ -0,0 +1 @@ +def fmt(value: float, decimals: int = 1) -> str: ... diff --git a/stdlib/profiling/sampling/_sync_coordinator.pyi b/stdlib/profiling/sampling/_sync_coordinator.pyi new file mode 100644 index 000000000000..13950e26b0a1 --- /dev/null +++ b/stdlib/profiling/sampling/_sync_coordinator.pyi @@ -0,0 +1,8 @@ +from typing import Never + +class CoordinatorError(Exception): ... +class ArgumentError(CoordinatorError): ... +class SyncError(CoordinatorError): ... +class TargetError(CoordinatorError): ... + +def main() -> Never: ... diff --git a/stdlib/profiling/sampling/binary_collector.pyi b/stdlib/profiling/sampling/binary_collector.pyi new file mode 100644 index 000000000000..7fe230b7e276 --- /dev/null +++ b/stdlib/profiling/sampling/binary_collector.pyi @@ -0,0 +1,25 @@ +import types +from _typeshed import StrOrBytesPath +from typing import Any +from typing_extensions import Self + +from .collector import Collector as Collector + +COMPRESSION_NONE: int +COMPRESSION_ZSTD: int + +class BinaryCollector(Collector): + filename: str + sample_interval_usec: int + skip_idle: bool + def __init__(self, filename: str, sample_interval_usec: int, *, skip_idle: bool = False, compression: str = "auto") -> None: ... + def collect(self, stack_frames: Any, timestamp_us: int | None = None) -> None: ... + def collect_failed_sample(self) -> None: ... + def export(self, filename: StrOrBytesPath | None = None) -> None: ... + @property + def total_samples(self) -> int: ... + def get_stats(self) -> dict[str, Any]: ... + def __enter__(self) -> Self: ... + def __exit__( + self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: types.TracebackType | None + ) -> None: ... diff --git a/stdlib/profiling/sampling/binary_reader.pyi b/stdlib/profiling/sampling/binary_reader.pyi new file mode 100644 index 000000000000..09e011a268c2 --- /dev/null +++ b/stdlib/profiling/sampling/binary_reader.pyi @@ -0,0 +1,31 @@ +import types +from _typeshed import StrOrBytesPath +from collections.abc import Callable +from typing import Any +from typing_extensions import Self + +from .collector import Collector as Collector +from .gecko_collector import GeckoCollector as GeckoCollector +from .pstats_collector import PstatsCollector as PstatsCollector +from .stack_collector import CollapsedStackCollector as CollapsedStackCollector, FlamegraphCollector as FlamegraphCollector + +class BinaryReader: + filename: str + def __init__(self, filename: str) -> None: ... + def __enter__(self) -> Self: ... + def __exit__( + self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: types.TracebackType | None + ) -> None: ... + def get_info(self) -> dict[str, Any]: ... + def replay_samples(self, collector: Collector, progress_callback: Callable[[int, int], None] | None = None) -> int: ... + @property + def sample_count(self) -> int: ... + def get_stats(self) -> dict[str, Any]: ... + +def convert_binary_to_format( + input_file: StrOrBytesPath, + output_file: StrOrBytesPath, + output_format: str, + sample_interval_usec: int | None = None, + progress_callback: Callable[[int, int], None] | None = None, +) -> None: ... diff --git a/stdlib/profiling/sampling/cli.pyi b/stdlib/profiling/sampling/cli.pyi new file mode 100644 index 000000000000..e17def02d49d --- /dev/null +++ b/stdlib/profiling/sampling/cli.pyi @@ -0,0 +1,52 @@ +import argparse +from collections.abc import Sequence +from typing import Any + +from ._child_monitor import ChildProcessMonitor as ChildProcessMonitor +from .binary_collector import BinaryCollector as BinaryCollector +from .binary_reader import BinaryReader as BinaryReader +from .constants import ( + MICROSECONDS_PER_SECOND as MICROSECONDS_PER_SECOND, + PROFILING_MODE_ALL as PROFILING_MODE_ALL, + PROFILING_MODE_CPU as PROFILING_MODE_CPU, + PROFILING_MODE_EXCEPTION as PROFILING_MODE_EXCEPTION, + PROFILING_MODE_GIL as PROFILING_MODE_GIL, + PROFILING_MODE_WALL as PROFILING_MODE_WALL, + SORT_MODE_CUMTIME as SORT_MODE_CUMTIME, + SORT_MODE_CUMUL_PCT as SORT_MODE_CUMUL_PCT, + SORT_MODE_NSAMPLES as SORT_MODE_NSAMPLES, + SORT_MODE_NSAMPLES_CUMUL as SORT_MODE_NSAMPLES_CUMUL, + SORT_MODE_SAMPLE_PCT as SORT_MODE_SAMPLE_PCT, + SORT_MODE_TOTTIME as SORT_MODE_TOTTIME, +) +from .errors import ( + SamplingModuleNotFoundError as SamplingModuleNotFoundError, + SamplingScriptNotFoundError as SamplingScriptNotFoundError, + SamplingUnknownProcessError as SamplingUnknownProcessError, +) +from .gecko_collector import GeckoCollector as GeckoCollector +from .heatmap_collector import HeatmapCollector as HeatmapCollector +from .live_collector import LiveStatsCollector as LiveStatsCollector +from .pstats_collector import PstatsCollector as PstatsCollector +from .sample import sample as sample, sample_live as sample_live +from .stack_collector import ( + CollapsedStackCollector as CollapsedStackCollector, + DiffFlamegraphCollector as DiffFlamegraphCollector, + FlamegraphCollector as FlamegraphCollector, +) + +class CustomFormatter(argparse.ArgumentDefaultsHelpFormatter, argparse.RawDescriptionHelpFormatter): ... + +class DiffFlamegraphAction(argparse.Action): + def __call__( + self, + parser: argparse.ArgumentParser, + namespace: argparse.Namespace, + values: str | Sequence[Any] | None, + option_string: str | None = None, + ) -> None: ... + +FORMAT_EXTENSIONS: dict[str, str] +COLLECTOR_MAP: dict[str, type[Any]] + +def main() -> None: ... diff --git a/stdlib/profiling/sampling/collector.pyi b/stdlib/profiling/sampling/collector.pyi new file mode 100644 index 000000000000..47490c9feafa --- /dev/null +++ b/stdlib/profiling/sampling/collector.pyi @@ -0,0 +1,24 @@ +import abc +from _typeshed import StrOrBytesPath +from abc import ABC, abstractmethod +from typing import Any + +from .constants import ( + DEFAULT_LOCATION as DEFAULT_LOCATION, + THREAD_STATUS_GIL_REQUESTED as THREAD_STATUS_GIL_REQUESTED, + THREAD_STATUS_HAS_EXCEPTION as THREAD_STATUS_HAS_EXCEPTION, + THREAD_STATUS_HAS_GIL as THREAD_STATUS_HAS_GIL, + THREAD_STATUS_ON_CPU as THREAD_STATUS_ON_CPU, + THREAD_STATUS_UNKNOWN as THREAD_STATUS_UNKNOWN, +) + +def normalize_location(location: tuple[int, int, int, int] | None) -> tuple[int, int, int, int]: ... +def extract_lineno(location: tuple[int, int, int, int] | None) -> int: ... +def filter_internal_frames(frames: list[Any]) -> list[Any]: ... + +class Collector(ABC, metaclass=abc.ABCMeta): + @abstractmethod + def collect(self, stack_frames: Any, timestamps_us: list[int] | None = None) -> None: ... + def collect_failed_sample(self) -> None: ... + @abstractmethod + def export(self, filename: StrOrBytesPath) -> None: ... diff --git a/stdlib/profiling/sampling/constants.pyi b/stdlib/profiling/sampling/constants.pyi new file mode 100644 index 000000000000..a03d845e3add --- /dev/null +++ b/stdlib/profiling/sampling/constants.pyi @@ -0,0 +1,21 @@ +MICROSECONDS_PER_SECOND: int +MILLISECONDS_PER_SECOND: int +PROFILING_MODE_WALL: int +PROFILING_MODE_CPU: int +PROFILING_MODE_GIL: int +PROFILING_MODE_ALL: int +PROFILING_MODE_EXCEPTION: int +SORT_MODE_NSAMPLES: int +SORT_MODE_TOTTIME: int +SORT_MODE_CUMTIME: int +SORT_MODE_SAMPLE_PCT: int +SORT_MODE_CUMUL_PCT: int +SORT_MODE_NSAMPLES_CUMUL: int +DEFAULT_LOCATION: tuple[int, int, int, int] +_INTERNAL_FRAME_SUFFIXES: tuple[str, ...] +THREAD_STATUS_HAS_GIL: int +THREAD_STATUS_ON_CPU: int +THREAD_STATUS_UNKNOWN: int +THREAD_STATUS_GIL_REQUESTED: int +THREAD_STATUS_HAS_EXCEPTION: int +THREAD_STATUS_MAIN_THREAD: int diff --git a/stdlib/profiling/sampling/errors.pyi b/stdlib/profiling/sampling/errors.pyi new file mode 100644 index 000000000000..530fe7998539 --- /dev/null +++ b/stdlib/profiling/sampling/errors.pyi @@ -0,0 +1,13 @@ +class SamplingProfilerError(Exception): ... + +class SamplingUnknownProcessError(SamplingProfilerError): + pid: int + def __init__(self, pid: int) -> None: ... + +class SamplingScriptNotFoundError(SamplingProfilerError): + script_path: str + def __init__(self, script_path: str) -> None: ... + +class SamplingModuleNotFoundError(SamplingProfilerError): + module_name: str + def __init__(self, module_name: str) -> None: ... diff --git a/stdlib/profiling/sampling/gecko_collector.pyi b/stdlib/profiling/sampling/gecko_collector.pyi new file mode 100644 index 000000000000..ae763da96274 --- /dev/null +++ b/stdlib/profiling/sampling/gecko_collector.pyi @@ -0,0 +1,51 @@ +from typing import Any + +from .collector import Collector as Collector, filter_internal_frames as filter_internal_frames +from .opcode_utils import format_opcode as format_opcode, get_opcode_info as get_opcode_info + +GECKO_CATEGORIES: list[dict[str, Any]] +CATEGORY_OTHER: int +CATEGORY_PYTHON: int +CATEGORY_NATIVE: int +CATEGORY_GC: int +CATEGORY_GIL: int +CATEGORY_CPU: int +CATEGORY_CODE_TYPE: int +CATEGORY_OPCODES: int +CATEGORY_EXCEPTION: int +DEFAULT_SUBCATEGORY: int +GECKO_FORMAT_VERSION: int +GECKO_PREPROCESSED_VERSION: int +RESOURCE_TYPE_LIBRARY: int +FRAME_ADDRESS_NONE: int +FRAME_INLINE_DEPTH_ROOT: int +PROCESS_TYPE_MAIN: int +STACKWALK_DISABLED: int + +class GeckoCollector(Collector): + sample_interval_usec: int + skip_idle: bool + opcodes_enabled: bool + start_time: float + global_strings: list[str] + global_string_map: dict[str, int] + threads: dict[int, Any] + libs: list[Any] + sample_count: int + last_sample_time: int + interval: float + has_gil_start: dict[int, float] + no_gil_start: dict[int, float] + on_cpu_start: dict[int, float] + off_cpu_start: dict[int, float] + python_code_start: dict[int, float] + native_code_start: dict[int, float] + gil_wait_start: dict[int, float] + exception_start: dict[int, float] + no_exception_start: dict[int, float] + gc_start_per_thread: dict[int, float] + initialized_threads: set[int] + opcode_state: dict[Any, Any] + def __init__(self, sample_interval_usec: int, *, skip_idle: bool = False, opcodes: bool = False) -> None: ... + def collect(self, stack_frames: Any, timestamps_us: list[int] | None = None) -> None: ... + def export(self, filename: str) -> None: ... diff --git a/stdlib/profiling/sampling/heatmap_collector.pyi b/stdlib/profiling/sampling/heatmap_collector.pyi new file mode 100644 index 000000000000..dc914a0da4b8 --- /dev/null +++ b/stdlib/profiling/sampling/heatmap_collector.pyi @@ -0,0 +1,80 @@ +from _typeshed import StrOrBytesPath +from collections import Counter +from dataclasses import dataclass, field +from typing import Any + +from ._css_utils import get_combined_css as get_combined_css +from ._format_utils import fmt as fmt +from .collector import extract_lineno as extract_lineno, normalize_location as normalize_location +from .opcode_utils import format_opcode as format_opcode, get_opcode_info as get_opcode_info +from .stack_collector import StackTraceCollector as StackTraceCollector + +@dataclass +class FileStats: + filename: str + module_name: str + module_type: str + total_samples: int + total_self_samples: int + num_lines: int + max_samples: int + max_self_samples: int + percentage: float = ... + +@dataclass +class TreeNode: + files: list[FileStats] = field(default_factory=list) + samples: int = ... + count: int = ... + children: dict[str, "TreeNode"] = field(default_factory=dict) + +def get_python_path_info() -> dict[str, Any]: ... +def extract_module_name(filename: str, path_info: dict[str, Any]) -> str: ... + +class _TemplateLoader: + index_template: str | None + file_template: str | None + index_css: str | None + index_js: str | None + file_css: str | None + file_js: str | None + logo_html: str | None + def __init__(self) -> None: ... + +class _TreeBuilder: + @staticmethod + def build_file_tree(file_stats: list[FileStats]) -> dict[str, TreeNode]: ... + +class _HtmlRenderer: + file_index: dict[str, str] + heatmap_bar_height: int + def __init__(self, file_index: dict[str, str]) -> None: ... + def render_hierarchical_html(self, trees: dict[str, TreeNode]) -> str: ... + +class HeatmapCollector(StackTraceCollector): + FILE_INDEX_FORMAT: str + line_samples: Counter[Any] + file_samples: dict[str, Counter[int]] + line_self_samples: Counter[Any] + file_self_samples: dict[str, Counter[int]] + call_graph: dict[Any, set[Any]] + callers_graph: dict[Any, set[Any]] + function_definitions: dict[Any, Any] + line_to_function: dict[Any, Any] + edge_samples: Counter[Any] + line_opcodes: dict[Any, dict[Any, Any]] + stats: dict[str, Any] + opcodes_enabled: bool + file_index: dict[str, str] + def __init__(self, *args: Any, **kwargs: Any) -> None: ... + def set_stats( + self, + sample_interval_usec: int, + duration_sec: float, + sample_rate: float, + error_rate: float | None = None, + missed_samples: int | None = None, + **kwargs: Any, + ) -> None: ... + def process_frames(self, frames: list[Any], thread_id: int, weight: int = 1) -> None: ... + def export(self, output_path: StrOrBytesPath) -> None: ... diff --git a/stdlib/profiling/sampling/live_collector/__init__.pyi b/stdlib/profiling/sampling/live_collector/__init__.pyi new file mode 100644 index 000000000000..d8755f5d2a14 --- /dev/null +++ b/stdlib/profiling/sampling/live_collector/__init__.pyi @@ -0,0 +1,93 @@ +from .collector import LiveStatsCollector as LiveStatsCollector +from .constants import ( + COLOR_PAIR_CYAN as COLOR_PAIR_CYAN, + COLOR_PAIR_GREEN as COLOR_PAIR_GREEN, + COLOR_PAIR_HEADER_BG as COLOR_PAIR_HEADER_BG, + COLOR_PAIR_MAGENTA as COLOR_PAIR_MAGENTA, + COLOR_PAIR_RED as COLOR_PAIR_RED, + COLOR_PAIR_SORTED_HEADER as COLOR_PAIR_SORTED_HEADER, + COLOR_PAIR_YELLOW as COLOR_PAIR_YELLOW, + COL_SPACING as COL_SPACING, + COL_WIDTH_NSAMPLES as COL_WIDTH_NSAMPLES, + COL_WIDTH_SAMPLE_PCT as COL_WIDTH_SAMPLE_PCT, + COL_WIDTH_TIME as COL_WIDTH_TIME, + DEFAULT_DISPLAY_LIMIT as DEFAULT_DISPLAY_LIMIT, + DEFAULT_SORT_BY as DEFAULT_SORT_BY, + DISPLAY_UPDATE_HZ as DISPLAY_UPDATE_HZ, + DISPLAY_UPDATE_INTERVAL_SEC as DISPLAY_UPDATE_INTERVAL_SEC, + FINISHED_BANNER_EXTRA_LINES as FINISHED_BANNER_EXTRA_LINES, + FOOTER_LINES as FOOTER_LINES, + HEADER_LINES as HEADER_LINES, + MAX_EFFICIENCY_BAR_WIDTH as MAX_EFFICIENCY_BAR_WIDTH, + MAX_FUNC_NAME_WIDTH as MAX_FUNC_NAME_WIDTH, + MAX_SAMPLE_RATE_BAR_WIDTH as MAX_SAMPLE_RATE_BAR_WIDTH, + MICROSECONDS_PER_SECOND as MICROSECONDS_PER_SECOND, + MIN_AVAILABLE_SPACE as MIN_AVAILABLE_SPACE, + MIN_BAR_WIDTH as MIN_BAR_WIDTH, + MIN_FUNC_NAME_WIDTH as MIN_FUNC_NAME_WIDTH, + MIN_SAMPLE_RATE_FOR_SCALING as MIN_SAMPLE_RATE_FOR_SCALING, + MIN_TERMINAL_HEIGHT as MIN_TERMINAL_HEIGHT, + MIN_TERMINAL_WIDTH as MIN_TERMINAL_WIDTH, + SAFETY_MARGIN as SAFETY_MARGIN, + TOP_FUNCTIONS_DISPLAY_COUNT as TOP_FUNCTIONS_DISPLAY_COUNT, + WIDTH_THRESHOLD_CUMTIME as WIDTH_THRESHOLD_CUMTIME, + WIDTH_THRESHOLD_CUMUL_PCT as WIDTH_THRESHOLD_CUMUL_PCT, + WIDTH_THRESHOLD_SAMPLE_PCT as WIDTH_THRESHOLD_SAMPLE_PCT, + WIDTH_THRESHOLD_TOTTIME as WIDTH_THRESHOLD_TOTTIME, +) +from .display import CursesDisplay as CursesDisplay, DisplayInterface as DisplayInterface, MockDisplay as MockDisplay +from .widgets import ( + FooterWidget as FooterWidget, + HeaderWidget as HeaderWidget, + HelpWidget as HelpWidget, + ProgressBarWidget as ProgressBarWidget, + TableWidget as TableWidget, + Widget as Widget, +) + +__all__ = [ + "LiveStatsCollector", + "DisplayInterface", + "CursesDisplay", + "MockDisplay", + "Widget", + "ProgressBarWidget", + "HeaderWidget", + "TableWidget", + "FooterWidget", + "HelpWidget", + "MICROSECONDS_PER_SECOND", + "DISPLAY_UPDATE_HZ", + "DISPLAY_UPDATE_INTERVAL_SEC", + "MIN_TERMINAL_WIDTH", + "MIN_TERMINAL_HEIGHT", + "WIDTH_THRESHOLD_SAMPLE_PCT", + "WIDTH_THRESHOLD_TOTTIME", + "WIDTH_THRESHOLD_CUMUL_PCT", + "WIDTH_THRESHOLD_CUMTIME", + "HEADER_LINES", + "FOOTER_LINES", + "SAFETY_MARGIN", + "TOP_FUNCTIONS_DISPLAY_COUNT", + "COL_WIDTH_NSAMPLES", + "COL_SPACING", + "COL_WIDTH_SAMPLE_PCT", + "COL_WIDTH_TIME", + "MIN_FUNC_NAME_WIDTH", + "MAX_FUNC_NAME_WIDTH", + "MIN_AVAILABLE_SPACE", + "MIN_BAR_WIDTH", + "MAX_SAMPLE_RATE_BAR_WIDTH", + "MAX_EFFICIENCY_BAR_WIDTH", + "MIN_SAMPLE_RATE_FOR_SCALING", + "FINISHED_BANNER_EXTRA_LINES", + "COLOR_PAIR_HEADER_BG", + "COLOR_PAIR_CYAN", + "COLOR_PAIR_YELLOW", + "COLOR_PAIR_GREEN", + "COLOR_PAIR_MAGENTA", + "COLOR_PAIR_RED", + "COLOR_PAIR_SORTED_HEADER", + "DEFAULT_SORT_BY", + "DEFAULT_DISPLAY_LIMIT", +] diff --git a/stdlib/profiling/sampling/live_collector/collector.pyi b/stdlib/profiling/sampling/live_collector/collector.pyi new file mode 100644 index 000000000000..51b2df528c68 --- /dev/null +++ b/stdlib/profiling/sampling/live_collector/collector.pyi @@ -0,0 +1,133 @@ +import curses +from _typeshed import StrOrBytesPath +from dataclasses import dataclass, field +from typing import Any + +from ..collector import Collector as Collector, extract_lineno as extract_lineno +from ..constants import ( + PROFILING_MODE_CPU as PROFILING_MODE_CPU, + PROFILING_MODE_GIL as PROFILING_MODE_GIL, + PROFILING_MODE_WALL as PROFILING_MODE_WALL, + THREAD_STATUS_GIL_REQUESTED as THREAD_STATUS_GIL_REQUESTED, + THREAD_STATUS_HAS_EXCEPTION as THREAD_STATUS_HAS_EXCEPTION, + THREAD_STATUS_HAS_GIL as THREAD_STATUS_HAS_GIL, + THREAD_STATUS_ON_CPU as THREAD_STATUS_ON_CPU, + THREAD_STATUS_UNKNOWN as THREAD_STATUS_UNKNOWN, +) +from .constants import ( + COLOR_PAIR_CYAN as COLOR_PAIR_CYAN, + COLOR_PAIR_FILE as COLOR_PAIR_FILE, + COLOR_PAIR_FUNC as COLOR_PAIR_FUNC, + COLOR_PAIR_GREEN as COLOR_PAIR_GREEN, + COLOR_PAIR_HEADER_BG as COLOR_PAIR_HEADER_BG, + COLOR_PAIR_MAGENTA as COLOR_PAIR_MAGENTA, + COLOR_PAIR_RED as COLOR_PAIR_RED, + COLOR_PAIR_SAMPLES as COLOR_PAIR_SAMPLES, + COLOR_PAIR_SORTED_HEADER as COLOR_PAIR_SORTED_HEADER, + COLOR_PAIR_YELLOW as COLOR_PAIR_YELLOW, + DEFAULT_DISPLAY_LIMIT as DEFAULT_DISPLAY_LIMIT, + DEFAULT_SORT_BY as DEFAULT_SORT_BY, + DISPLAY_UPDATE_INTERVAL_SEC as DISPLAY_UPDATE_INTERVAL_SEC, + FINISHED_BANNER_EXTRA_LINES as FINISHED_BANNER_EXTRA_LINES, + FOOTER_LINES as FOOTER_LINES, + HEADER_LINES as HEADER_LINES, + MICROSECONDS_PER_SECOND as MICROSECONDS_PER_SECOND, + MIN_TERMINAL_HEIGHT as MIN_TERMINAL_HEIGHT, + MIN_TERMINAL_WIDTH as MIN_TERMINAL_WIDTH, + SAFETY_MARGIN as SAFETY_MARGIN, +) +from .display import CursesDisplay as CursesDisplay, DisplayInterface as DisplayInterface +from .trend_tracker import TrendTracker as TrendTracker +from .widgets import ( + FooterWidget as FooterWidget, + HeaderWidget as HeaderWidget, + HelpWidget as HelpWidget, + OpcodePanel as OpcodePanel, + TableWidget as TableWidget, +) + +@dataclass +class ThreadData: + thread_id: int + result: dict[Any, Any] = field(default_factory=dict) + has_gil: int = ... + on_cpu: int = ... + gil_requested: int = ... + unknown: int = ... + has_exception: int = ... + total: int = ... + sample_count: int = ... + gc_frame_samples: int = ... + opcode_stats: dict[Any, Any] = field(default_factory=dict) + def increment_status_flag(self, status_flags: int) -> None: ... + def as_status_dict(self) -> dict[str, int]: ... + +class LiveStatsCollector(Collector): + result: dict[Any, Any] + sample_interval_usec: int + sample_interval_sec: float + skip_idle: bool + sort_by: str + limit: int + total_samples: int + start_time: float | None + stdscr: curses.window | None + display: DisplayInterface | None + running: bool + pid: int | None + mode: int | None + async_aware: str | bool | None + max_sample_rate: int + successful_samples: int + failed_samples: int + display_update_interval_sec: float + thread_status_counts: dict[str, int] + gc_frame_samples: int + opcode_stats: dict[Any, Any] + show_opcodes: bool + selected_row: int + scroll_offset: int + paused: bool + show_help: bool + filter_pattern: str | None + filter_input_mode: bool + filter_input_buffer: str + finished: bool + finish_timestamp: float | None + finish_wall_time: float | None + thread_ids: list[int] + view_mode: str + current_thread_index: int + per_thread_data: dict[int, ThreadData] + header_widget: HeaderWidget | None + table_widget: TableWidget | None + footer_widget: FooterWidget | None + help_widget: HelpWidget | None + opcode_panel: OpcodePanel | None + def __init__( + self, + sample_interval_usec: int, + *, + skip_idle: bool = False, + sort_by: str = ..., + limit: int = ..., + pid: int | None = None, + display: DisplayInterface | None = None, + mode: int | None = None, + opcodes: bool = False, + async_aware: str | bool | None = None, + ) -> None: ... + @property + def elapsed_time(self) -> float: ... + @property + def current_time_display(self) -> str: ... + def simplify_path(self, filepath: str) -> str: ... + def process_frames(self, frames: Any, thread_id: int | None = None) -> None: ... + def collect_failed_sample(self) -> None: ... + def collect(self, stack_frames: Any, timestamp_us: int | None = None) -> None: ... + def build_stats_list(self) -> list[Any]: ... + def reset_stats(self) -> None: ... + def mark_finished(self) -> None: ... + def init_curses(self, stdscr: curses.window) -> None: ... + def cleanup_curses(self) -> None: ... + def export(self, filename: StrOrBytesPath) -> None: ... diff --git a/stdlib/profiling/sampling/live_collector/constants.pyi b/stdlib/profiling/sampling/live_collector/constants.pyi new file mode 100644 index 000000000000..863eb9dffebd --- /dev/null +++ b/stdlib/profiling/sampling/live_collector/constants.pyi @@ -0,0 +1,38 @@ +MICROSECONDS_PER_SECOND: int +DISPLAY_UPDATE_HZ: int +DISPLAY_UPDATE_INTERVAL_SEC: float +MIN_TERMINAL_WIDTH: int +MIN_TERMINAL_HEIGHT: int +WIDTH_THRESHOLD_SAMPLE_PCT: int +WIDTH_THRESHOLD_TOTTIME: int +WIDTH_THRESHOLD_CUMUL_PCT: int +WIDTH_THRESHOLD_CUMTIME: int +HEADER_LINES: int +FOOTER_LINES: int +SAFETY_MARGIN: int +TOP_FUNCTIONS_DISPLAY_COUNT: int +COL_WIDTH_NSAMPLES: int +COL_SPACING: int +COL_WIDTH_SAMPLE_PCT: int +COL_WIDTH_TIME: int +MIN_FUNC_NAME_WIDTH: int +MAX_FUNC_NAME_WIDTH: int +MIN_AVAILABLE_SPACE: int +MIN_BAR_WIDTH: int +MAX_SAMPLE_RATE_BAR_WIDTH: int +MAX_EFFICIENCY_BAR_WIDTH: int +MIN_SAMPLE_RATE_FOR_SCALING: int +FINISHED_BANNER_EXTRA_LINES: int +OPCODE_PANEL_HEIGHT: int +COLOR_PAIR_SAMPLES: int +COLOR_PAIR_FILE: int +COLOR_PAIR_FUNC: int +COLOR_PAIR_HEADER_BG: int +COLOR_PAIR_CYAN: int +COLOR_PAIR_YELLOW: int +COLOR_PAIR_GREEN: int +COLOR_PAIR_MAGENTA: int +COLOR_PAIR_RED: int +COLOR_PAIR_SORTED_HEADER: int +DEFAULT_SORT_BY: str +DEFAULT_DISPLAY_LIMIT: int diff --git a/stdlib/profiling/sampling/live_collector/display.pyi b/stdlib/profiling/sampling/live_collector/display.pyi new file mode 100644 index 000000000000..f8458b0082fd --- /dev/null +++ b/stdlib/profiling/sampling/live_collector/display.pyi @@ -0,0 +1,71 @@ +import abc +import curses +from abc import ABC, abstractmethod + +class DisplayInterface(ABC, metaclass=abc.ABCMeta): + @abstractmethod + def get_dimensions(self) -> tuple[int, int]: ... + @abstractmethod + def clear(self) -> None: ... + @abstractmethod + def refresh(self) -> None: ... + @abstractmethod + def redraw(self) -> None: ... + @abstractmethod + def add_str(self, line: int, col: int, text: str, attr: int = 0) -> None: ... + @abstractmethod + def get_input(self) -> int: ... + @abstractmethod + def set_nodelay(self, flag: bool) -> None: ... + @abstractmethod + def has_colors(self) -> bool: ... + @abstractmethod + def init_color_pair(self, pair_id: int, fg: int, bg: int) -> None: ... + @abstractmethod + def get_color_pair(self, pair_id: int) -> int: ... + @abstractmethod + def get_attr(self, name: str) -> int: ... + +class CursesDisplay(DisplayInterface): + stdscr: curses.window + def __init__(self, stdscr: curses.window) -> None: ... + def get_dimensions(self) -> tuple[int, int]: ... + def clear(self) -> None: ... + def refresh(self) -> None: ... + def redraw(self) -> None: ... + def add_str(self, line: int, col: int, text: str, attr: int = 0) -> None: ... + def get_input(self) -> int: ... + def set_nodelay(self, flag: bool) -> None: ... + def has_colors(self) -> bool: ... + def init_color_pair(self, pair_id: int, fg: int, bg: int) -> None: ... + def get_color_pair(self, pair_id: int) -> int: ... + def get_attr(self, name: str) -> int: ... + +class MockDisplay(DisplayInterface): + height: int + width: int + buffer: dict[tuple[int, int], tuple[str, int]] + cleared: bool + refreshed: bool + redrawn: bool + input_queue: list[int] + nodelay_flag: bool + colors_supported: bool + color_pairs: dict[int, int] + def __init__(self, height: int = 40, width: int = 160) -> None: ... + def get_dimensions(self) -> tuple[int, int]: ... + def clear(self) -> None: ... + def refresh(self) -> None: ... + def redraw(self) -> None: ... + def add_str(self, line: int, col: int, text: str, attr: int = 0) -> None: ... + def get_input(self) -> int: ... + def set_nodelay(self, flag: bool) -> None: ... + def has_colors(self) -> bool: ... + def init_color_pair(self, pair_id: int, fg: int, bg: int) -> None: ... + def get_color_pair(self, pair_id: int) -> int: ... + def get_attr(self, name: str) -> int: ... + def simulate_input(self, char: int) -> None: ... + def get_text_at(self, line: int, col: int) -> str | None: ... + def get_all_lines(self) -> list[str]: ... + def find_text(self, pattern: str) -> tuple[int, int] | None: ... + def contains_text(self, text: str) -> bool: ... diff --git a/stdlib/profiling/sampling/live_collector/trend_tracker.pyi b/stdlib/profiling/sampling/live_collector/trend_tracker.pyi new file mode 100644 index 000000000000..e158ffe7e8e4 --- /dev/null +++ b/stdlib/profiling/sampling/live_collector/trend_tracker.pyi @@ -0,0 +1,16 @@ +from typing import Any, Literal + +TrendDirection = Literal["up", "down", "stable"] + +class TrendTracker: + CHANGE_THRESHOLD: float + def __init__(self, colors: dict[str, int], enabled: bool = True) -> None: ... + @property + def enabled(self) -> bool: ... + def toggle(self) -> bool: ... + def set_enabled(self, enabled: bool) -> None: ... + def update(self, key: Any, metric: str, value: float) -> TrendDirection: ... + def get_trend(self, key: Any, metric: str) -> TrendDirection: ... + def get_color(self, trend: TrendDirection) -> int: ... + def update_metrics(self, key: Any, metrics: dict[str, float]) -> dict[str, TrendDirection]: ... + def clear(self) -> None: ... diff --git a/stdlib/profiling/sampling/live_collector/widgets.pyi b/stdlib/profiling/sampling/live_collector/widgets.pyi new file mode 100644 index 000000000000..5a330c6b6437 --- /dev/null +++ b/stdlib/profiling/sampling/live_collector/widgets.pyi @@ -0,0 +1,82 @@ +import abc +from abc import ABC, abstractmethod +from typing import Any + +from ..constants import ( + PROFILING_MODE_CPU as PROFILING_MODE_CPU, + PROFILING_MODE_GIL as PROFILING_MODE_GIL, + PROFILING_MODE_WALL as PROFILING_MODE_WALL, + THREAD_STATUS_GIL_REQUESTED as THREAD_STATUS_GIL_REQUESTED, + THREAD_STATUS_HAS_GIL as THREAD_STATUS_HAS_GIL, + THREAD_STATUS_ON_CPU as THREAD_STATUS_ON_CPU, + THREAD_STATUS_UNKNOWN as THREAD_STATUS_UNKNOWN, +) +from ..opcode_utils import format_opcode as format_opcode, get_opcode_info as get_opcode_info +from .collector import LiveStatsCollector as LiveStatsCollector +from .constants import ( + DISPLAY_UPDATE_INTERVAL_SEC as DISPLAY_UPDATE_INTERVAL_SEC, + FINISHED_BANNER_EXTRA_LINES as FINISHED_BANNER_EXTRA_LINES, + FOOTER_LINES as FOOTER_LINES, + MAX_EFFICIENCY_BAR_WIDTH as MAX_EFFICIENCY_BAR_WIDTH, + MAX_FUNC_NAME_WIDTH as MAX_FUNC_NAME_WIDTH, + MAX_SAMPLE_RATE_BAR_WIDTH as MAX_SAMPLE_RATE_BAR_WIDTH, + MICROSECONDS_PER_SECOND as MICROSECONDS_PER_SECOND, + MIN_BAR_WIDTH as MIN_BAR_WIDTH, + MIN_FUNC_NAME_WIDTH as MIN_FUNC_NAME_WIDTH, + MIN_SAMPLE_RATE_FOR_SCALING as MIN_SAMPLE_RATE_FOR_SCALING, + OPCODE_PANEL_HEIGHT as OPCODE_PANEL_HEIGHT, + TOP_FUNCTIONS_DISPLAY_COUNT as TOP_FUNCTIONS_DISPLAY_COUNT, + WIDTH_THRESHOLD_CUMTIME as WIDTH_THRESHOLD_CUMTIME, + WIDTH_THRESHOLD_CUMUL_PCT as WIDTH_THRESHOLD_CUMUL_PCT, + WIDTH_THRESHOLD_SAMPLE_PCT as WIDTH_THRESHOLD_SAMPLE_PCT, + WIDTH_THRESHOLD_TOTTIME as WIDTH_THRESHOLD_TOTTIME, +) +from .display import DisplayInterface as DisplayInterface + +class Widget(ABC, metaclass=abc.ABCMeta): + display: DisplayInterface + colors: dict[str, int] + def __init__(self, display: DisplayInterface, colors: dict[str, int]) -> None: ... + @abstractmethod + def render(self, line: int, width: int, **kwargs: Any) -> int: ... + def add_str(self, line: int, col: int, text: str, attr: int = 0) -> None: ... + +class ProgressBarWidget(Widget): + def render(self, line: int, width: int, **kwargs: Any) -> None: ... + def render_bar(self, filled: float, total: float, max_width: int, fill_char: str = "█", empty_char: str = "░") -> tuple[str, int]: ... + +class HeaderWidget(Widget): + collector: LiveStatsCollector + progress_bar: ProgressBarWidget + def __init__(self, display: DisplayInterface, colors: dict[str, int], collector: LiveStatsCollector) -> None: ... + def render(self, line: int, width: int, **kwargs: Any) -> int: ... + def format_uptime(self, elapsed: float) -> str: ... + def draw_header_info(self, line: int, width: int, elapsed: float) -> int: ... + def format_rate_with_units(self, rate_hz: float) -> str: ... + def draw_sample_stats(self, line: int, width: int, elapsed: float) -> int: ... + def draw_efficiency_bar(self, line: int, width: int) -> int: ... + def draw_thread_status(self, line: int, width: int) -> int: ... + def draw_function_stats(self, line: int, width: int, stats_list: list[Any]) -> int: ... + def draw_top_functions(self, line: int, width: int, stats_list: list[Any]) -> int: ... + def draw_finished_banner(self, line: int, width: int) -> int: ... + +class TableWidget(Widget): + collector: LiveStatsCollector + def __init__(self, display: DisplayInterface, colors: dict[str, int], collector: LiveStatsCollector) -> None: ... + def render(self, line: int, width: int, **kwargs: Any) -> int: ... + def draw_column_headers(self, line: int, width: int) -> int: ... + def draw_stats_rows(self, line: int, height: int, width: int, stats_list: list[Any], column_flags: Any) -> int: ... + +class FooterWidget(Widget): + collector: LiveStatsCollector + def __init__(self, display: DisplayInterface, colors: dict[str, int], collector: LiveStatsCollector) -> None: ... + def render(self, line: int, width: int, **kwargs: Any) -> int: ... + def render_filter_input_prompt(self, line: int, width: int) -> None: ... + +class HelpWidget(Widget): + def render(self, line: int, width: int, **kwargs: Any) -> int: ... + +class OpcodePanel(Widget): + collector: LiveStatsCollector + def __init__(self, display: DisplayInterface, colors: dict[str, int], collector: LiveStatsCollector) -> None: ... + def render(self, line: int, width: int, **kwargs: Any) -> int: ... diff --git a/stdlib/profiling/sampling/opcode_utils.pyi b/stdlib/profiling/sampling/opcode_utils.pyi new file mode 100644 index 000000000000..4676c7232482 --- /dev/null +++ b/stdlib/profiling/sampling/opcode_utils.pyi @@ -0,0 +1,6 @@ +base_opcode: int | None +variant_opcode: int | None + +def get_opcode_info(opcode_num: int) -> dict[str, str | bool]: ... +def format_opcode(opcode_num: int) -> str: ... +def get_opcode_mapping() -> dict[str, dict[int, str] | dict[int, int]]: ... diff --git a/stdlib/profiling/sampling/pstats_collector.pyi b/stdlib/profiling/sampling/pstats_collector.pyi new file mode 100644 index 000000000000..028f1a5f0a78 --- /dev/null +++ b/stdlib/profiling/sampling/pstats_collector.pyi @@ -0,0 +1,17 @@ +from _typeshed import StrOrBytesPath +from typing import Any + +from .collector import Collector as Collector, extract_lineno as extract_lineno +from .constants import MICROSECONDS_PER_SECOND as MICROSECONDS_PER_SECOND, PROFILING_MODE_CPU as PROFILING_MODE_CPU + +class PstatsCollector(Collector): + result: dict[Any, Any] + stats: dict[Any, Any] + sample_interval_usec: int + callers: dict[Any, Any] + skip_idle: bool + def __init__(self, sample_interval_usec: int, *, skip_idle: bool = False) -> None: ... + def collect(self, stack_frames: Any, timestamps_us: list[int] | None = None) -> None: ... + def export(self, filename: StrOrBytesPath) -> None: ... + def create_stats(self) -> None: ... + def print_stats(self, sort: str | int = -1, limit: int | None = None, show_summary: bool = True, mode: int | None = None) -> None: ... diff --git a/stdlib/profiling/sampling/sample.pyi b/stdlib/profiling/sampling/sample.pyi new file mode 100644 index 000000000000..1db92f0bce9e --- /dev/null +++ b/stdlib/profiling/sampling/sample.pyi @@ -0,0 +1,76 @@ +from _typeshed import Incomplete +from collections import deque + +from ._format_utils import fmt as fmt +from .binary_collector import BinaryCollector as BinaryCollector +from .collector import Collector as Collector +from .constants import ( + PROFILING_MODE_ALL as PROFILING_MODE_ALL, + PROFILING_MODE_CPU as PROFILING_MODE_CPU, + PROFILING_MODE_EXCEPTION as PROFILING_MODE_EXCEPTION, + PROFILING_MODE_GIL as PROFILING_MODE_GIL, + PROFILING_MODE_WALL as PROFILING_MODE_WALL, +) +from .gecko_collector import GeckoCollector as GeckoCollector +from .heatmap_collector import HeatmapCollector as HeatmapCollector +from .live_collector import LiveStatsCollector as LiveStatsCollector +from .pstats_collector import PstatsCollector as PstatsCollector +from .stack_collector import CollapsedStackCollector as CollapsedStackCollector, FlamegraphCollector as FlamegraphCollector + +MIN_SAMPLES_FOR_TUI: int + +class SampleProfiler: + pid: int + sample_interval_usec: int + all_threads: bool + mode: int + collect_stats: bool + blocking: bool + unwinder: Incomplete + sample_intervals: deque[float] + total_samples: int + realtime_stats: bool + def __init__( + self, + pid: int, + sample_interval_usec: int, + all_threads: bool, + *, + mode: int = ..., + native: bool = False, + gc: bool = True, + opcodes: bool = False, + skip_non_matching_threads: bool = True, + collect_stats: bool = False, + blocking: bool = False, + ) -> None: ... + def sample(self, collector: Collector, duration_sec: float | None = None, *, async_aware: bool | str = False) -> None: ... + +def sample( + pid: int, + collector: Collector, + *, + duration_sec: float | None = None, + all_threads: bool = False, + realtime_stats: bool = False, + mode: int = ..., + async_aware: str | bool | None = None, + native: bool = False, + gc: bool = True, + opcodes: bool = False, + blocking: bool = False, +) -> None: ... +def sample_live( + pid: int, + collector: Collector, + *, + duration_sec: float | None = None, + all_threads: bool = False, + realtime_stats: bool = False, + mode: int = ..., + async_aware: str | bool | None = None, + native: bool = False, + gc: bool = True, + opcodes: bool = False, + blocking: bool = False, +) -> None: ... diff --git a/stdlib/profiling/sampling/stack_collector.pyi b/stdlib/profiling/sampling/stack_collector.pyi new file mode 100644 index 000000000000..14b4c0912659 --- /dev/null +++ b/stdlib/profiling/sampling/stack_collector.pyi @@ -0,0 +1,45 @@ +import abc +from _typeshed import StrOrBytesPath +from collections import Counter +from typing import Any + +from ._css_utils import get_combined_css as get_combined_css +from .collector import Collector as Collector, extract_lineno as extract_lineno +from .opcode_utils import get_opcode_mapping as get_opcode_mapping +from .string_table import StringTable as StringTable + +class StackTraceCollector(Collector, metaclass=abc.ABCMeta): + sample_interval_usec: int + skip_idle: bool + def __init__(self, sample_interval_usec: int, *, skip_idle: bool = False) -> None: ... + def collect(self, stack_frames: Any, timestamps_us: list[int] | None = None) -> None: ... + def process_frames(self, frames: list[Any], thread_id: int, weight: int = 1) -> None: ... + +class CollapsedStackCollector(StackTraceCollector): + stack_counter: Counter[str] + def __init__(self, *args: Any, **kwargs: Any) -> None: ... + def process_frames(self, frames: list[Any], thread_id: int, weight: int = 1) -> None: ... + def export(self, filename: StrOrBytesPath) -> None: ... + +class FlamegraphCollector(StackTraceCollector): + stats: dict[str, Any] + thread_status_counts: dict[str, int] + samples_with_gc_frames: int + per_thread_stats: dict[int, Any] + def __init__(self, *args: Any, **kwargs: Any) -> None: ... + def collect(self, stack_frames: Any, timestamps_us: list[int] | None = None) -> None: ... + def set_stats( + self, + sample_interval_usec: int, + duration_sec: float, + sample_rate: float, + error_rate: float | None = None, + missed_samples: int | None = None, + mode: int | None = None, + ) -> None: ... + def export(self, filename: StrOrBytesPath) -> None: ... + def process_frames(self, frames: list[Any], thread_id: int, weight: int = 1) -> None: ... + +class DiffFlamegraphCollector(FlamegraphCollector): + baseline_binary_path: str + def __init__(self, sample_interval_usec: int, *, baseline_binary_path: str, skip_idle: bool = False) -> None: ... diff --git a/stdlib/profiling/sampling/string_table.pyi b/stdlib/profiling/sampling/string_table.pyi new file mode 100644 index 000000000000..b951371b96b6 --- /dev/null +++ b/stdlib/profiling/sampling/string_table.pyi @@ -0,0 +1,6 @@ +class StringTable: + def __init__(self) -> None: ... + def intern(self, string: str | object) -> int: ... + def get_string(self, index: int) -> str: ... + def get_strings(self) -> list[str]: ... + def __len__(self) -> int: ... diff --git a/stdlib/profiling/tracing/__init__.pyi b/stdlib/profiling/tracing/__init__.pyi new file mode 100644 index 000000000000..e2c4d6e302c9 --- /dev/null +++ b/stdlib/profiling/tracing/__init__.pyi @@ -0,0 +1,30 @@ +import _lsprof +from _typeshed import StrOrBytesPath, Unused +from collections.abc import Callable, Mapping +from typing import Any, TypeVar +from typing_extensions import ParamSpec, Self, TypeAlias + +__all__ = ["run", "runctx", "Profile"] + +_T = TypeVar("_T") +_P = ParamSpec("_P") +_Label: TypeAlias = tuple[str, int, str] + +def run(statement: str, filename: str | None = None, sort: str | int = -1) -> None: ... +def runctx( + statement: str, globals: dict[str, Any], locals: Mapping[str, Any], filename: str | None = None, sort: str | int = -1 +) -> None: ... + +class Profile(_lsprof.Profiler): + stats: dict[_Label, tuple[int, int, int, int, dict[_Label, tuple[int, int, int, int]]]] + def print_stats(self, sort: str | int = -1) -> None: ... + def dump_stats(self, file: StrOrBytesPath) -> None: ... + def create_stats(self) -> None: ... + def snapshot_stats(self) -> None: ... + def run(self, cmd: str) -> Self: ... + def runctx(self, cmd: str, globals: dict[str, Any], locals: Mapping[str, Any]) -> Self: ... + def runcall(self, func: Callable[_P, _T], /, *args: _P.args, **kw: _P.kwargs) -> _T: ... + def __enter__(self) -> Self: ... + def __exit__(self, *exc_info: Unused) -> None: ... + +def main() -> None: ... diff --git a/stdlib/profiling/tracing/__main__.pyi b/stdlib/profiling/tracing/__main__.pyi new file mode 100644 index 000000000000..6bc611fe8d76 --- /dev/null +++ b/stdlib/profiling/tracing/__main__.pyi @@ -0,0 +1 @@ +from profiling.tracing import main as main diff --git a/stdlib/profiling/tracing/_utils.pyi b/stdlib/profiling/tracing/_utils.pyi new file mode 100644 index 000000000000..b01b9941febb --- /dev/null +++ b/stdlib/profiling/tracing/_utils.pyi @@ -0,0 +1,8 @@ +from collections.abc import Mapping +from typing import Any + +class _Utils: + profiler: type[Any] + def __init__(self, profiler: type[Any]) -> None: ... + def run(self, statement: str, filename: str | None, sort: str | int) -> None: ... + def runctx(self, statement: str, globals: dict[str, Any], locals: Mapping[str, Any], filename: str | None, sort: str | int) -> None: ... From 4836e71f87b19d0c25b8dd51abe5352795f25172 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 20 Apr 2026 02:57:59 +0000 Subject: [PATCH 2/9] [pre-commit.ci] auto fixes from pre-commit.com hooks --- stdlib/profiling/sampling/binary_collector.pyi | 4 +++- stdlib/profiling/sampling/heatmap_collector.pyi | 6 +++--- stdlib/profiling/sampling/live_collector/__init__.pyi | 8 ++++---- stdlib/profiling/sampling/live_collector/collector.pyi | 6 +++--- stdlib/profiling/sampling/live_collector/widgets.pyi | 4 +++- stdlib/profiling/sampling/pstats_collector.pyi | 4 +++- stdlib/profiling/tracing/_utils.pyi | 4 +++- 7 files changed, 22 insertions(+), 14 deletions(-) diff --git a/stdlib/profiling/sampling/binary_collector.pyi b/stdlib/profiling/sampling/binary_collector.pyi index 7fe230b7e276..8c2be531f713 100644 --- a/stdlib/profiling/sampling/binary_collector.pyi +++ b/stdlib/profiling/sampling/binary_collector.pyi @@ -12,7 +12,9 @@ class BinaryCollector(Collector): filename: str sample_interval_usec: int skip_idle: bool - def __init__(self, filename: str, sample_interval_usec: int, *, skip_idle: bool = False, compression: str = "auto") -> None: ... + def __init__( + self, filename: str, sample_interval_usec: int, *, skip_idle: bool = False, compression: str = "auto" + ) -> None: ... def collect(self, stack_frames: Any, timestamp_us: int | None = None) -> None: ... def collect_failed_sample(self) -> None: ... def export(self, filename: StrOrBytesPath | None = None) -> None: ... diff --git a/stdlib/profiling/sampling/heatmap_collector.pyi b/stdlib/profiling/sampling/heatmap_collector.pyi index dc914a0da4b8..596d6555f86e 100644 --- a/stdlib/profiling/sampling/heatmap_collector.pyi +++ b/stdlib/profiling/sampling/heatmap_collector.pyi @@ -1,6 +1,6 @@ from _typeshed import StrOrBytesPath from collections import Counter -from dataclasses import dataclass, field +from dataclasses import dataclass from typing import Any from ._css_utils import get_combined_css as get_combined_css @@ -23,10 +23,10 @@ class FileStats: @dataclass class TreeNode: - files: list[FileStats] = field(default_factory=list) + files: list[FileStats] = ... samples: int = ... count: int = ... - children: dict[str, "TreeNode"] = field(default_factory=dict) + children: dict[str, TreeNode] = ... def get_python_path_info() -> dict[str, Any]: ... def extract_module_name(filename: str, path_info: dict[str, Any]) -> str: ... diff --git a/stdlib/profiling/sampling/live_collector/__init__.pyi b/stdlib/profiling/sampling/live_collector/__init__.pyi index d8755f5d2a14..da337679da2f 100644 --- a/stdlib/profiling/sampling/live_collector/__init__.pyi +++ b/stdlib/profiling/sampling/live_collector/__init__.pyi @@ -1,5 +1,9 @@ from .collector import LiveStatsCollector as LiveStatsCollector from .constants import ( + COL_SPACING as COL_SPACING, + COL_WIDTH_NSAMPLES as COL_WIDTH_NSAMPLES, + COL_WIDTH_SAMPLE_PCT as COL_WIDTH_SAMPLE_PCT, + COL_WIDTH_TIME as COL_WIDTH_TIME, COLOR_PAIR_CYAN as COLOR_PAIR_CYAN, COLOR_PAIR_GREEN as COLOR_PAIR_GREEN, COLOR_PAIR_HEADER_BG as COLOR_PAIR_HEADER_BG, @@ -7,10 +11,6 @@ from .constants import ( COLOR_PAIR_RED as COLOR_PAIR_RED, COLOR_PAIR_SORTED_HEADER as COLOR_PAIR_SORTED_HEADER, COLOR_PAIR_YELLOW as COLOR_PAIR_YELLOW, - COL_SPACING as COL_SPACING, - COL_WIDTH_NSAMPLES as COL_WIDTH_NSAMPLES, - COL_WIDTH_SAMPLE_PCT as COL_WIDTH_SAMPLE_PCT, - COL_WIDTH_TIME as COL_WIDTH_TIME, DEFAULT_DISPLAY_LIMIT as DEFAULT_DISPLAY_LIMIT, DEFAULT_SORT_BY as DEFAULT_SORT_BY, DISPLAY_UPDATE_HZ as DISPLAY_UPDATE_HZ, diff --git a/stdlib/profiling/sampling/live_collector/collector.pyi b/stdlib/profiling/sampling/live_collector/collector.pyi index 51b2df528c68..f6c331478199 100644 --- a/stdlib/profiling/sampling/live_collector/collector.pyi +++ b/stdlib/profiling/sampling/live_collector/collector.pyi @@ -1,6 +1,6 @@ import curses from _typeshed import StrOrBytesPath -from dataclasses import dataclass, field +from dataclasses import dataclass from typing import Any from ..collector import Collector as Collector, extract_lineno as extract_lineno @@ -49,7 +49,7 @@ from .widgets import ( @dataclass class ThreadData: thread_id: int - result: dict[Any, Any] = field(default_factory=dict) + result: dict[Any, Any] = ... has_gil: int = ... on_cpu: int = ... gil_requested: int = ... @@ -58,7 +58,7 @@ class ThreadData: total: int = ... sample_count: int = ... gc_frame_samples: int = ... - opcode_stats: dict[Any, Any] = field(default_factory=dict) + opcode_stats: dict[Any, Any] = ... def increment_status_flag(self, status_flags: int) -> None: ... def as_status_dict(self) -> dict[str, int]: ... diff --git a/stdlib/profiling/sampling/live_collector/widgets.pyi b/stdlib/profiling/sampling/live_collector/widgets.pyi index 5a330c6b6437..c0160d7ca734 100644 --- a/stdlib/profiling/sampling/live_collector/widgets.pyi +++ b/stdlib/profiling/sampling/live_collector/widgets.pyi @@ -43,7 +43,9 @@ class Widget(ABC, metaclass=abc.ABCMeta): class ProgressBarWidget(Widget): def render(self, line: int, width: int, **kwargs: Any) -> None: ... - def render_bar(self, filled: float, total: float, max_width: int, fill_char: str = "█", empty_char: str = "░") -> tuple[str, int]: ... + def render_bar( + self, filled: float, total: float, max_width: int, fill_char: str = "█", empty_char: str = "░" + ) -> tuple[str, int]: ... class HeaderWidget(Widget): collector: LiveStatsCollector diff --git a/stdlib/profiling/sampling/pstats_collector.pyi b/stdlib/profiling/sampling/pstats_collector.pyi index 028f1a5f0a78..0f853d505d79 100644 --- a/stdlib/profiling/sampling/pstats_collector.pyi +++ b/stdlib/profiling/sampling/pstats_collector.pyi @@ -14,4 +14,6 @@ class PstatsCollector(Collector): def collect(self, stack_frames: Any, timestamps_us: list[int] | None = None) -> None: ... def export(self, filename: StrOrBytesPath) -> None: ... def create_stats(self) -> None: ... - def print_stats(self, sort: str | int = -1, limit: int | None = None, show_summary: bool = True, mode: int | None = None) -> None: ... + def print_stats( + self, sort: str | int = -1, limit: int | None = None, show_summary: bool = True, mode: int | None = None + ) -> None: ... diff --git a/stdlib/profiling/tracing/_utils.pyi b/stdlib/profiling/tracing/_utils.pyi index b01b9941febb..0038c8439825 100644 --- a/stdlib/profiling/tracing/_utils.pyi +++ b/stdlib/profiling/tracing/_utils.pyi @@ -5,4 +5,6 @@ class _Utils: profiler: type[Any] def __init__(self, profiler: type[Any]) -> None: ... def run(self, statement: str, filename: str | None, sort: str | int) -> None: ... - def runctx(self, statement: str, globals: dict[str, Any], locals: Mapping[str, Any], filename: str | None, sort: str | int) -> None: ... + def runctx( + self, statement: str, globals: dict[str, Any], locals: Mapping[str, Any], filename: str | None, sort: str | int + ) -> None: ... From 5afdd540cfce82f943889711da32621d1f767d57 Mon Sep 17 00:00:00 2001 From: johnslavik Date: Mon, 20 Apr 2026 05:04:59 +0200 Subject: [PATCH 3/9] Use a `TypeAlias` --- stdlib/profiling/sampling/live_collector/trend_tracker.pyi | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/stdlib/profiling/sampling/live_collector/trend_tracker.pyi b/stdlib/profiling/sampling/live_collector/trend_tracker.pyi index e158ffe7e8e4..a087db69df7a 100644 --- a/stdlib/profiling/sampling/live_collector/trend_tracker.pyi +++ b/stdlib/profiling/sampling/live_collector/trend_tracker.pyi @@ -1,6 +1,7 @@ from typing import Any, Literal +from typing_extensions import TypeAlias -TrendDirection = Literal["up", "down", "stable"] +TrendDirection: TypeAlias = Literal["up", "down", "stable"] class TrendTracker: CHANGE_THRESHOLD: float From 56a19240f0ead180567aac7efc76fb287a5d8663 Mon Sep 17 00:00:00 2001 From: johnslavik Date: Mon, 20 Apr 2026 06:10:04 +0200 Subject: [PATCH 4/9] Remove unnecessary annotations from `profiling` stubs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removed: - tracing/__main__.pyi: deleted — __main__ stubs are executable entry points, not importable API surface - tracing/_utils.pyi: deleted — _Utils is a private helper used only inside the tracing module itself - sampling/__main__.pyi: deleted — PERMISSION_ERROR constants and handle_permission_error are CLI implementation details, not importable API - sampling/binary_collector.pyi: removed COMPRESSION_NONE, COMPRESSION_ZSTD — internal constants not part of user-facing API - sampling/cli.pyi: removed CustomFormatter, DiffFlamegraphAction, FORMAT_EXTENSIONS, COLLECTOR_MAP, and associated imports — internal CLI scaffolding and dispatch dicts used only inside cli.py; also removed re-exports of errors/collectors/sample that duplicate their own modules - sampling/constants.pyi: removed MICROSECONDS_PER_SECOND (re-exported only internally), MILLISECONDS_PER_SECOND and _INTERNAL_FRAME_SUFFIXES (private implementation details) - sampling/gecko_collector.pyi: removed GECKO_CATEGORIES, CATEGORY_*, DEFAULT_SUBCATEGORY, GECKO_FORMAT_VERSION, GECKO_PREPROCESSED_VERSION, RESOURCE_TYPE_LIBRARY, FRAME_ADDRESS_NONE, FRAME_INLINE_DEPTH_ROOT, PROCESS_TYPE_MAIN, STACKWALK_DISABLED — integer constants that encode the internal Gecko profile format; not part of user-facing API - sampling/heatmap_collector.pyi: removed _TemplateLoader, _TreeBuilder, _HtmlRenderer — private classes (underscore-prefixed) used only internally for HTML rendering - sampling/opcode_utils.pyi: removed base_opcode, variant_opcode — these are local variables inside get_opcode_info, not module-level attributes; the stubs were incorrect - sampling/sample.pyi: removed MIN_SAMPLES_FOR_TUI — internal threshold constant for TUI display logic - sampling/_sync_coordinator.pyi: removed CoordinatorError, ArgumentError, SyncError, TargetError — all exceptions live in a private module (_sync_coordinator) and are only raised and caught internally; never exposed through any public module Kept despite `dead` flagging: - tracing/__init__.pyi: dump_stats, snapshot_stats, runcall — public methods on the exported Profile class; standard profiling API - sampling/binary_collector.pyi: get_stats, collect_failed_sample — public methods; collect_failed_sample is part of the Collector interface called in sample.py - sampling/binary_reader.pyi: get_stats, convert_binary_to_format — public method and public utility function - sampling/string_table.pyi: intern, get_string, get_strings — core API of StringTable - sampling/live_collector/display.pyi: get_dimensions, refresh, redraw, get_input, set_nodelay, has_colors, init_color_pair, get_color_pair, get_attr, find_text — abstract methods on DisplayInterface define the ABC contract; concrete implementations on CursesDisplay and MockDisplay must also be stubbed - sampling/live_collector/trend_tracker.pyi: set_enabled, get_trend — public methods on public class - sampling/live_collector/widgets.pyi: format_rate_with_units, draw_thread_status, draw_finished_banner, draw_stats_rows, render_filter_input_prompt — public methods on public widget classes - sampling/live_collector/collector.pyi: increment_status_flag, as_status_dict, collect_failed_sample — public methods on ThreadData dataclass and LiveStatsCollector Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- stdlib/profiling/sampling/__main__.pyi | 13 ------- .../profiling/sampling/_sync_coordinator.pyi | 5 --- .../profiling/sampling/binary_collector.pyi | 3 -- stdlib/profiling/sampling/cli.pyi | 34 ------------------- stdlib/profiling/sampling/constants.pyi | 3 -- stdlib/profiling/sampling/gecko_collector.pyi | 19 ----------- .../profiling/sampling/heatmap_collector.pyi | 20 ----------- stdlib/profiling/sampling/opcode_utils.pyi | 3 -- stdlib/profiling/sampling/sample.pyi | 2 -- stdlib/profiling/tracing/__main__.pyi | 1 - stdlib/profiling/tracing/_utils.pyi | 10 ------ 11 files changed, 113 deletions(-) delete mode 100644 stdlib/profiling/sampling/__main__.pyi delete mode 100644 stdlib/profiling/tracing/__main__.pyi delete mode 100644 stdlib/profiling/tracing/_utils.pyi diff --git a/stdlib/profiling/sampling/__main__.pyi b/stdlib/profiling/sampling/__main__.pyi deleted file mode 100644 index 61d0127a2f16..000000000000 --- a/stdlib/profiling/sampling/__main__.pyi +++ /dev/null @@ -1,13 +0,0 @@ -from .cli import main as main -from .errors import ( - SamplingModuleNotFoundError as SamplingModuleNotFoundError, - SamplingScriptNotFoundError as SamplingScriptNotFoundError, - SamplingUnknownProcessError as SamplingUnknownProcessError, -) - -MACOS_PERMISSION_ERROR: str -LINUX_PERMISSION_ERROR: str -WINDOWS_PERMISSION_ERROR: str -GENERIC_PERMISSION_ERROR: str - -def handle_permission_error() -> None: ... diff --git a/stdlib/profiling/sampling/_sync_coordinator.pyi b/stdlib/profiling/sampling/_sync_coordinator.pyi index 13950e26b0a1..c6472df775a4 100644 --- a/stdlib/profiling/sampling/_sync_coordinator.pyi +++ b/stdlib/profiling/sampling/_sync_coordinator.pyi @@ -1,8 +1,3 @@ from typing import Never -class CoordinatorError(Exception): ... -class ArgumentError(CoordinatorError): ... -class SyncError(CoordinatorError): ... -class TargetError(CoordinatorError): ... - def main() -> Never: ... diff --git a/stdlib/profiling/sampling/binary_collector.pyi b/stdlib/profiling/sampling/binary_collector.pyi index 8c2be531f713..7ca65916d2c5 100644 --- a/stdlib/profiling/sampling/binary_collector.pyi +++ b/stdlib/profiling/sampling/binary_collector.pyi @@ -5,9 +5,6 @@ from typing_extensions import Self from .collector import Collector as Collector -COMPRESSION_NONE: int -COMPRESSION_ZSTD: int - class BinaryCollector(Collector): filename: str sample_interval_usec: int diff --git a/stdlib/profiling/sampling/cli.pyi b/stdlib/profiling/sampling/cli.pyi index e17def02d49d..9d5fd0747d6a 100644 --- a/stdlib/profiling/sampling/cli.pyi +++ b/stdlib/profiling/sampling/cli.pyi @@ -1,12 +1,7 @@ -import argparse -from collections.abc import Sequence -from typing import Any - from ._child_monitor import ChildProcessMonitor as ChildProcessMonitor from .binary_collector import BinaryCollector as BinaryCollector from .binary_reader import BinaryReader as BinaryReader from .constants import ( - MICROSECONDS_PER_SECOND as MICROSECONDS_PER_SECOND, PROFILING_MODE_ALL as PROFILING_MODE_ALL, PROFILING_MODE_CPU as PROFILING_MODE_CPU, PROFILING_MODE_EXCEPTION as PROFILING_MODE_EXCEPTION, @@ -19,34 +14,5 @@ from .constants import ( SORT_MODE_SAMPLE_PCT as SORT_MODE_SAMPLE_PCT, SORT_MODE_TOTTIME as SORT_MODE_TOTTIME, ) -from .errors import ( - SamplingModuleNotFoundError as SamplingModuleNotFoundError, - SamplingScriptNotFoundError as SamplingScriptNotFoundError, - SamplingUnknownProcessError as SamplingUnknownProcessError, -) -from .gecko_collector import GeckoCollector as GeckoCollector -from .heatmap_collector import HeatmapCollector as HeatmapCollector -from .live_collector import LiveStatsCollector as LiveStatsCollector -from .pstats_collector import PstatsCollector as PstatsCollector -from .sample import sample as sample, sample_live as sample_live -from .stack_collector import ( - CollapsedStackCollector as CollapsedStackCollector, - DiffFlamegraphCollector as DiffFlamegraphCollector, - FlamegraphCollector as FlamegraphCollector, -) - -class CustomFormatter(argparse.ArgumentDefaultsHelpFormatter, argparse.RawDescriptionHelpFormatter): ... - -class DiffFlamegraphAction(argparse.Action): - def __call__( - self, - parser: argparse.ArgumentParser, - namespace: argparse.Namespace, - values: str | Sequence[Any] | None, - option_string: str | None = None, - ) -> None: ... - -FORMAT_EXTENSIONS: dict[str, str] -COLLECTOR_MAP: dict[str, type[Any]] def main() -> None: ... diff --git a/stdlib/profiling/sampling/constants.pyi b/stdlib/profiling/sampling/constants.pyi index a03d845e3add..92408b59e85f 100644 --- a/stdlib/profiling/sampling/constants.pyi +++ b/stdlib/profiling/sampling/constants.pyi @@ -1,5 +1,3 @@ -MICROSECONDS_PER_SECOND: int -MILLISECONDS_PER_SECOND: int PROFILING_MODE_WALL: int PROFILING_MODE_CPU: int PROFILING_MODE_GIL: int @@ -12,7 +10,6 @@ SORT_MODE_SAMPLE_PCT: int SORT_MODE_CUMUL_PCT: int SORT_MODE_NSAMPLES_CUMUL: int DEFAULT_LOCATION: tuple[int, int, int, int] -_INTERNAL_FRAME_SUFFIXES: tuple[str, ...] THREAD_STATUS_HAS_GIL: int THREAD_STATUS_ON_CPU: int THREAD_STATUS_UNKNOWN: int diff --git a/stdlib/profiling/sampling/gecko_collector.pyi b/stdlib/profiling/sampling/gecko_collector.pyi index ae763da96274..4b1db27f05b3 100644 --- a/stdlib/profiling/sampling/gecko_collector.pyi +++ b/stdlib/profiling/sampling/gecko_collector.pyi @@ -3,25 +3,6 @@ from typing import Any from .collector import Collector as Collector, filter_internal_frames as filter_internal_frames from .opcode_utils import format_opcode as format_opcode, get_opcode_info as get_opcode_info -GECKO_CATEGORIES: list[dict[str, Any]] -CATEGORY_OTHER: int -CATEGORY_PYTHON: int -CATEGORY_NATIVE: int -CATEGORY_GC: int -CATEGORY_GIL: int -CATEGORY_CPU: int -CATEGORY_CODE_TYPE: int -CATEGORY_OPCODES: int -CATEGORY_EXCEPTION: int -DEFAULT_SUBCATEGORY: int -GECKO_FORMAT_VERSION: int -GECKO_PREPROCESSED_VERSION: int -RESOURCE_TYPE_LIBRARY: int -FRAME_ADDRESS_NONE: int -FRAME_INLINE_DEPTH_ROOT: int -PROCESS_TYPE_MAIN: int -STACKWALK_DISABLED: int - class GeckoCollector(Collector): sample_interval_usec: int skip_idle: bool diff --git a/stdlib/profiling/sampling/heatmap_collector.pyi b/stdlib/profiling/sampling/heatmap_collector.pyi index 596d6555f86e..ce32ccf957ae 100644 --- a/stdlib/profiling/sampling/heatmap_collector.pyi +++ b/stdlib/profiling/sampling/heatmap_collector.pyi @@ -31,26 +31,6 @@ class TreeNode: def get_python_path_info() -> dict[str, Any]: ... def extract_module_name(filename: str, path_info: dict[str, Any]) -> str: ... -class _TemplateLoader: - index_template: str | None - file_template: str | None - index_css: str | None - index_js: str | None - file_css: str | None - file_js: str | None - logo_html: str | None - def __init__(self) -> None: ... - -class _TreeBuilder: - @staticmethod - def build_file_tree(file_stats: list[FileStats]) -> dict[str, TreeNode]: ... - -class _HtmlRenderer: - file_index: dict[str, str] - heatmap_bar_height: int - def __init__(self, file_index: dict[str, str]) -> None: ... - def render_hierarchical_html(self, trees: dict[str, TreeNode]) -> str: ... - class HeatmapCollector(StackTraceCollector): FILE_INDEX_FORMAT: str line_samples: Counter[Any] diff --git a/stdlib/profiling/sampling/opcode_utils.pyi b/stdlib/profiling/sampling/opcode_utils.pyi index 4676c7232482..7190ae2e706f 100644 --- a/stdlib/profiling/sampling/opcode_utils.pyi +++ b/stdlib/profiling/sampling/opcode_utils.pyi @@ -1,6 +1,3 @@ -base_opcode: int | None -variant_opcode: int | None - def get_opcode_info(opcode_num: int) -> dict[str, str | bool]: ... def format_opcode(opcode_num: int) -> str: ... def get_opcode_mapping() -> dict[str, dict[int, str] | dict[int, int]]: ... diff --git a/stdlib/profiling/sampling/sample.pyi b/stdlib/profiling/sampling/sample.pyi index 1db92f0bce9e..cf7748d35a73 100644 --- a/stdlib/profiling/sampling/sample.pyi +++ b/stdlib/profiling/sampling/sample.pyi @@ -17,8 +17,6 @@ from .live_collector import LiveStatsCollector as LiveStatsCollector from .pstats_collector import PstatsCollector as PstatsCollector from .stack_collector import CollapsedStackCollector as CollapsedStackCollector, FlamegraphCollector as FlamegraphCollector -MIN_SAMPLES_FOR_TUI: int - class SampleProfiler: pid: int sample_interval_usec: int diff --git a/stdlib/profiling/tracing/__main__.pyi b/stdlib/profiling/tracing/__main__.pyi deleted file mode 100644 index 6bc611fe8d76..000000000000 --- a/stdlib/profiling/tracing/__main__.pyi +++ /dev/null @@ -1 +0,0 @@ -from profiling.tracing import main as main diff --git a/stdlib/profiling/tracing/_utils.pyi b/stdlib/profiling/tracing/_utils.pyi deleted file mode 100644 index 0038c8439825..000000000000 --- a/stdlib/profiling/tracing/_utils.pyi +++ /dev/null @@ -1,10 +0,0 @@ -from collections.abc import Mapping -from typing import Any - -class _Utils: - profiler: type[Any] - def __init__(self, profiler: type[Any]) -> None: ... - def run(self, statement: str, filename: str | None, sort: str | int) -> None: ... - def runctx( - self, statement: str, globals: dict[str, Any], locals: Mapping[str, Any], filename: str | None, sort: str | int - ) -> None: ... From 56b03c7b214993c095c69f8906707b2ce1c20b31 Mon Sep 17 00:00:00 2001 From: johnslavik Date: Mon, 20 Apr 2026 06:16:23 +0200 Subject: [PATCH 5/9] Remove private module stubs and their re-exports MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Judgement calls — removed: - sampling/_css_utils.pyi: deleted — private module (_css_utils) used only internally by stack_collector and heatmap_collector; get_combined_css is not part of any public API - sampling/_format_utils.pyi: deleted — private module (_format_utils) used only internally; fmt is a simple formatting helper not intended for external use - sampling/_sync_coordinator.pyi: deleted — private module only containing main() -> Never, which is a subprocess entry point never called externally - sampling/_child_monitor.pyi: removed get_child_pids and is_python_process — private utility functions used only inside _child_monitor itself; ChildProcessMonitor kept because it is re-exported from cli.pyi - sampling/heatmap_collector.pyi: removed re-exports of get_combined_css and fmt — these are private implementation imports, not part of HeatmapCollector's public API - sampling/stack_collector.pyi: removed re-export of get_combined_css — same reason as above - sampling/sample.pyi: removed re-export of fmt — private formatting helper leaked into a public module's stub Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- stdlib/profiling/sampling/_child_monitor.pyi | 3 --- stdlib/profiling/sampling/_css_utils.pyi | 1 - stdlib/profiling/sampling/_format_utils.pyi | 1 - stdlib/profiling/sampling/_sync_coordinator.pyi | 3 --- stdlib/profiling/sampling/heatmap_collector.pyi | 2 -- stdlib/profiling/sampling/sample.pyi | 1 - stdlib/profiling/sampling/stack_collector.pyi | 1 - 7 files changed, 12 deletions(-) delete mode 100644 stdlib/profiling/sampling/_css_utils.pyi delete mode 100644 stdlib/profiling/sampling/_format_utils.pyi delete mode 100644 stdlib/profiling/sampling/_sync_coordinator.pyi diff --git a/stdlib/profiling/sampling/_child_monitor.pyi b/stdlib/profiling/sampling/_child_monitor.pyi index 257efcc11451..bdfb44b92498 100644 --- a/stdlib/profiling/sampling/_child_monitor.pyi +++ b/stdlib/profiling/sampling/_child_monitor.pyi @@ -2,9 +2,6 @@ import types from typing import Any from typing_extensions import Self -def get_child_pids(pid: int, recursive: bool = True) -> list[int]: ... -def is_python_process(pid: int) -> bool: ... - class ChildProcessMonitor: parent_pid: int cli_args: list[str] diff --git a/stdlib/profiling/sampling/_css_utils.pyi b/stdlib/profiling/sampling/_css_utils.pyi deleted file mode 100644 index 04bcee2814e9..000000000000 --- a/stdlib/profiling/sampling/_css_utils.pyi +++ /dev/null @@ -1 +0,0 @@ -def get_combined_css(component: str) -> str: ... diff --git a/stdlib/profiling/sampling/_format_utils.pyi b/stdlib/profiling/sampling/_format_utils.pyi deleted file mode 100644 index b56022840536..000000000000 --- a/stdlib/profiling/sampling/_format_utils.pyi +++ /dev/null @@ -1 +0,0 @@ -def fmt(value: float, decimals: int = 1) -> str: ... diff --git a/stdlib/profiling/sampling/_sync_coordinator.pyi b/stdlib/profiling/sampling/_sync_coordinator.pyi deleted file mode 100644 index c6472df775a4..000000000000 --- a/stdlib/profiling/sampling/_sync_coordinator.pyi +++ /dev/null @@ -1,3 +0,0 @@ -from typing import Never - -def main() -> Never: ... diff --git a/stdlib/profiling/sampling/heatmap_collector.pyi b/stdlib/profiling/sampling/heatmap_collector.pyi index ce32ccf957ae..51e1591d1503 100644 --- a/stdlib/profiling/sampling/heatmap_collector.pyi +++ b/stdlib/profiling/sampling/heatmap_collector.pyi @@ -3,8 +3,6 @@ from collections import Counter from dataclasses import dataclass from typing import Any -from ._css_utils import get_combined_css as get_combined_css -from ._format_utils import fmt as fmt from .collector import extract_lineno as extract_lineno, normalize_location as normalize_location from .opcode_utils import format_opcode as format_opcode, get_opcode_info as get_opcode_info from .stack_collector import StackTraceCollector as StackTraceCollector diff --git a/stdlib/profiling/sampling/sample.pyi b/stdlib/profiling/sampling/sample.pyi index cf7748d35a73..aa9dfeec4993 100644 --- a/stdlib/profiling/sampling/sample.pyi +++ b/stdlib/profiling/sampling/sample.pyi @@ -1,7 +1,6 @@ from _typeshed import Incomplete from collections import deque -from ._format_utils import fmt as fmt from .binary_collector import BinaryCollector as BinaryCollector from .collector import Collector as Collector from .constants import ( diff --git a/stdlib/profiling/sampling/stack_collector.pyi b/stdlib/profiling/sampling/stack_collector.pyi index 14b4c0912659..4fde66f47fe6 100644 --- a/stdlib/profiling/sampling/stack_collector.pyi +++ b/stdlib/profiling/sampling/stack_collector.pyi @@ -3,7 +3,6 @@ from _typeshed import StrOrBytesPath from collections import Counter from typing import Any -from ._css_utils import get_combined_css as get_combined_css from .collector import Collector as Collector, extract_lineno as extract_lineno from .opcode_utils import get_opcode_mapping as get_opcode_mapping from .string_table import StringTable as StringTable From 5816e2d69509720d57763e11fbd60dd666b0f0f6 Mon Sep 17 00:00:00 2001 From: johnslavik Date: Mon, 20 Apr 2026 06:18:55 +0200 Subject: [PATCH 6/9] Remove live_collector constant re-exports from public API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit live_collector/__init__.pyi re-exported ~30 TUI layout/color constants (MICROSECONDS_PER_SECOND, MIN_TERMINAL_WIDTH, WIDTH_THRESHOLD_*, COLOR_PAIR_*, COL_WIDTH_*, etc.) in __all__. Nothing outside the live_collector package ever imports them — only LiveStatsCollector and MockDisplay are actually used by callers in the source and tests. Judgement calls — removed: - live_collector/__init__.pyi: stripped all constant imports and __all__ entries down to the 10 public classes (LiveStatsCollector, DisplayInterface, CursesDisplay, MockDisplay, and the Widget subclasses) — the constants are internal TUI dimensions and curses color pair IDs, not user-facing API - live_collector/constants.pyi: removed DISPLAY_UPDATE_HZ, COL_WIDTH_NSAMPLES, COL_SPACING, COL_WIDTH_SAMPLE_PCT, COL_WIDTH_TIME, MIN_AVAILABLE_SPACE — these became unreferenced once the __init__ re-exports were dropped; they are not imported by any other stub in the package Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../sampling/live_collector/__init__.pyi | 70 ------------------- .../sampling/live_collector/constants.pyi | 6 -- 2 files changed, 76 deletions(-) diff --git a/stdlib/profiling/sampling/live_collector/__init__.pyi b/stdlib/profiling/sampling/live_collector/__init__.pyi index da337679da2f..d9b41bc2c7cb 100644 --- a/stdlib/profiling/sampling/live_collector/__init__.pyi +++ b/stdlib/profiling/sampling/live_collector/__init__.pyi @@ -1,40 +1,4 @@ from .collector import LiveStatsCollector as LiveStatsCollector -from .constants import ( - COL_SPACING as COL_SPACING, - COL_WIDTH_NSAMPLES as COL_WIDTH_NSAMPLES, - COL_WIDTH_SAMPLE_PCT as COL_WIDTH_SAMPLE_PCT, - COL_WIDTH_TIME as COL_WIDTH_TIME, - COLOR_PAIR_CYAN as COLOR_PAIR_CYAN, - COLOR_PAIR_GREEN as COLOR_PAIR_GREEN, - COLOR_PAIR_HEADER_BG as COLOR_PAIR_HEADER_BG, - COLOR_PAIR_MAGENTA as COLOR_PAIR_MAGENTA, - COLOR_PAIR_RED as COLOR_PAIR_RED, - COLOR_PAIR_SORTED_HEADER as COLOR_PAIR_SORTED_HEADER, - COLOR_PAIR_YELLOW as COLOR_PAIR_YELLOW, - DEFAULT_DISPLAY_LIMIT as DEFAULT_DISPLAY_LIMIT, - DEFAULT_SORT_BY as DEFAULT_SORT_BY, - DISPLAY_UPDATE_HZ as DISPLAY_UPDATE_HZ, - DISPLAY_UPDATE_INTERVAL_SEC as DISPLAY_UPDATE_INTERVAL_SEC, - FINISHED_BANNER_EXTRA_LINES as FINISHED_BANNER_EXTRA_LINES, - FOOTER_LINES as FOOTER_LINES, - HEADER_LINES as HEADER_LINES, - MAX_EFFICIENCY_BAR_WIDTH as MAX_EFFICIENCY_BAR_WIDTH, - MAX_FUNC_NAME_WIDTH as MAX_FUNC_NAME_WIDTH, - MAX_SAMPLE_RATE_BAR_WIDTH as MAX_SAMPLE_RATE_BAR_WIDTH, - MICROSECONDS_PER_SECOND as MICROSECONDS_PER_SECOND, - MIN_AVAILABLE_SPACE as MIN_AVAILABLE_SPACE, - MIN_BAR_WIDTH as MIN_BAR_WIDTH, - MIN_FUNC_NAME_WIDTH as MIN_FUNC_NAME_WIDTH, - MIN_SAMPLE_RATE_FOR_SCALING as MIN_SAMPLE_RATE_FOR_SCALING, - MIN_TERMINAL_HEIGHT as MIN_TERMINAL_HEIGHT, - MIN_TERMINAL_WIDTH as MIN_TERMINAL_WIDTH, - SAFETY_MARGIN as SAFETY_MARGIN, - TOP_FUNCTIONS_DISPLAY_COUNT as TOP_FUNCTIONS_DISPLAY_COUNT, - WIDTH_THRESHOLD_CUMTIME as WIDTH_THRESHOLD_CUMTIME, - WIDTH_THRESHOLD_CUMUL_PCT as WIDTH_THRESHOLD_CUMUL_PCT, - WIDTH_THRESHOLD_SAMPLE_PCT as WIDTH_THRESHOLD_SAMPLE_PCT, - WIDTH_THRESHOLD_TOTTIME as WIDTH_THRESHOLD_TOTTIME, -) from .display import CursesDisplay as CursesDisplay, DisplayInterface as DisplayInterface, MockDisplay as MockDisplay from .widgets import ( FooterWidget as FooterWidget, @@ -56,38 +20,4 @@ __all__ = [ "TableWidget", "FooterWidget", "HelpWidget", - "MICROSECONDS_PER_SECOND", - "DISPLAY_UPDATE_HZ", - "DISPLAY_UPDATE_INTERVAL_SEC", - "MIN_TERMINAL_WIDTH", - "MIN_TERMINAL_HEIGHT", - "WIDTH_THRESHOLD_SAMPLE_PCT", - "WIDTH_THRESHOLD_TOTTIME", - "WIDTH_THRESHOLD_CUMUL_PCT", - "WIDTH_THRESHOLD_CUMTIME", - "HEADER_LINES", - "FOOTER_LINES", - "SAFETY_MARGIN", - "TOP_FUNCTIONS_DISPLAY_COUNT", - "COL_WIDTH_NSAMPLES", - "COL_SPACING", - "COL_WIDTH_SAMPLE_PCT", - "COL_WIDTH_TIME", - "MIN_FUNC_NAME_WIDTH", - "MAX_FUNC_NAME_WIDTH", - "MIN_AVAILABLE_SPACE", - "MIN_BAR_WIDTH", - "MAX_SAMPLE_RATE_BAR_WIDTH", - "MAX_EFFICIENCY_BAR_WIDTH", - "MIN_SAMPLE_RATE_FOR_SCALING", - "FINISHED_BANNER_EXTRA_LINES", - "COLOR_PAIR_HEADER_BG", - "COLOR_PAIR_CYAN", - "COLOR_PAIR_YELLOW", - "COLOR_PAIR_GREEN", - "COLOR_PAIR_MAGENTA", - "COLOR_PAIR_RED", - "COLOR_PAIR_SORTED_HEADER", - "DEFAULT_SORT_BY", - "DEFAULT_DISPLAY_LIMIT", ] diff --git a/stdlib/profiling/sampling/live_collector/constants.pyi b/stdlib/profiling/sampling/live_collector/constants.pyi index 863eb9dffebd..0ee78f2e4e14 100644 --- a/stdlib/profiling/sampling/live_collector/constants.pyi +++ b/stdlib/profiling/sampling/live_collector/constants.pyi @@ -1,5 +1,4 @@ MICROSECONDS_PER_SECOND: int -DISPLAY_UPDATE_HZ: int DISPLAY_UPDATE_INTERVAL_SEC: float MIN_TERMINAL_WIDTH: int MIN_TERMINAL_HEIGHT: int @@ -11,13 +10,8 @@ HEADER_LINES: int FOOTER_LINES: int SAFETY_MARGIN: int TOP_FUNCTIONS_DISPLAY_COUNT: int -COL_WIDTH_NSAMPLES: int -COL_SPACING: int -COL_WIDTH_SAMPLE_PCT: int -COL_WIDTH_TIME: int MIN_FUNC_NAME_WIDTH: int MAX_FUNC_NAME_WIDTH: int -MIN_AVAILABLE_SPACE: int MIN_BAR_WIDTH: int MAX_SAMPLE_RATE_BAR_WIDTH: int MAX_EFFICIENCY_BAR_WIDTH: int From 978d61a6a9d3e732e45f1d779075cb04499232c8 Mon Sep 17 00:00:00 2001 From: johnslavik Date: Mon, 20 Apr 2026 06:24:49 +0200 Subject: [PATCH 7/9] Strip live_collector to its user-facing API surface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Guiding principle: stubs should only contain what users of the profiling API would actually import or tab-complete. The live_collector subpackage is primarily a TUI display component; its internal widget/display architecture is not part of any user-facing API. Judgement calls — removed: - live_collector/widgets.pyi: deleted — Widget, ProgressBarWidget, HeaderWidget, TableWidget, FooterWidget, HelpWidget, OpcodePanel are internal TUI rendering components; nobody writing code against the profiling API would import them - live_collector/trend_tracker.pyi: deleted — TrendTracker is an internal widget state helper used only inside the TUI rendering loop - live_collector/constants.pyi: deleted — TUI layout dimensions and curses color pair IDs (COLOR_PAIR_*, WIDTH_THRESHOLD_*, MIN_TERMINAL_*, etc.) have no meaning outside the live_collector implementation - live_collector/display.pyi: removed CursesDisplay (internal curses wrapper) and MockDisplay (test helper only referenced in tests, not production API); kept DisplayInterface because it is the type of the display= parameter on LiveStatsCollector.__init__ - live_collector/collector.pyi: removed all constant imports (COLOR_PAIR_*, MICROSECONDS_PER_SECOND, MIN_TERMINAL_*, etc.) that were leaked implementation imports not used in any type annotations; removed widget imports (HeaderWidget, TableWidget, etc.) and TrendTracker; removed ThreadData dataclass (internal per-sample accumulator); stripped LiveStatsCollector of TUI-state attributes (stdscr, selected_row, scroll_offset, paused, show_help, filter_*, view_mode, current_thread_index, per_thread_data, *_widget, display_update_interval_sec, thread_status_counts, gc_frame_samples, opcode_stats) and internal methods (simplify_path, process_frames, init_curses, cleanup_curses) - live_collector/__init__.pyi: stripped __all__ and re-exports to just LiveStatsCollector and DisplayInterface Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../sampling/live_collector/__init__.pyi | 23 +---- .../sampling/live_collector/collector.pyi | 99 ++----------------- .../sampling/live_collector/constants.pyi | 32 ------ .../sampling/live_collector/display.pyi | 45 --------- .../sampling/live_collector/trend_tracker.pyi | 17 ---- .../sampling/live_collector/widgets.pyi | 84 ---------------- 6 files changed, 10 insertions(+), 290 deletions(-) delete mode 100644 stdlib/profiling/sampling/live_collector/constants.pyi delete mode 100644 stdlib/profiling/sampling/live_collector/trend_tracker.pyi delete mode 100644 stdlib/profiling/sampling/live_collector/widgets.pyi diff --git a/stdlib/profiling/sampling/live_collector/__init__.pyi b/stdlib/profiling/sampling/live_collector/__init__.pyi index d9b41bc2c7cb..75d83a73e182 100644 --- a/stdlib/profiling/sampling/live_collector/__init__.pyi +++ b/stdlib/profiling/sampling/live_collector/__init__.pyi @@ -1,23 +1,4 @@ from .collector import LiveStatsCollector as LiveStatsCollector -from .display import CursesDisplay as CursesDisplay, DisplayInterface as DisplayInterface, MockDisplay as MockDisplay -from .widgets import ( - FooterWidget as FooterWidget, - HeaderWidget as HeaderWidget, - HelpWidget as HelpWidget, - ProgressBarWidget as ProgressBarWidget, - TableWidget as TableWidget, - Widget as Widget, -) +from .display import DisplayInterface as DisplayInterface -__all__ = [ - "LiveStatsCollector", - "DisplayInterface", - "CursesDisplay", - "MockDisplay", - "Widget", - "ProgressBarWidget", - "HeaderWidget", - "TableWidget", - "FooterWidget", - "HelpWidget", -] +__all__ = ["LiveStatsCollector", "DisplayInterface"] diff --git a/stdlib/profiling/sampling/live_collector/collector.pyi b/stdlib/profiling/sampling/live_collector/collector.pyi index f6c331478199..effb140f55e0 100644 --- a/stdlib/profiling/sampling/live_collector/collector.pyi +++ b/stdlib/profiling/sampling/live_collector/collector.pyi @@ -1,66 +1,8 @@ -import curses from _typeshed import StrOrBytesPath -from dataclasses import dataclass from typing import Any -from ..collector import Collector as Collector, extract_lineno as extract_lineno -from ..constants import ( - PROFILING_MODE_CPU as PROFILING_MODE_CPU, - PROFILING_MODE_GIL as PROFILING_MODE_GIL, - PROFILING_MODE_WALL as PROFILING_MODE_WALL, - THREAD_STATUS_GIL_REQUESTED as THREAD_STATUS_GIL_REQUESTED, - THREAD_STATUS_HAS_EXCEPTION as THREAD_STATUS_HAS_EXCEPTION, - THREAD_STATUS_HAS_GIL as THREAD_STATUS_HAS_GIL, - THREAD_STATUS_ON_CPU as THREAD_STATUS_ON_CPU, - THREAD_STATUS_UNKNOWN as THREAD_STATUS_UNKNOWN, -) -from .constants import ( - COLOR_PAIR_CYAN as COLOR_PAIR_CYAN, - COLOR_PAIR_FILE as COLOR_PAIR_FILE, - COLOR_PAIR_FUNC as COLOR_PAIR_FUNC, - COLOR_PAIR_GREEN as COLOR_PAIR_GREEN, - COLOR_PAIR_HEADER_BG as COLOR_PAIR_HEADER_BG, - COLOR_PAIR_MAGENTA as COLOR_PAIR_MAGENTA, - COLOR_PAIR_RED as COLOR_PAIR_RED, - COLOR_PAIR_SAMPLES as COLOR_PAIR_SAMPLES, - COLOR_PAIR_SORTED_HEADER as COLOR_PAIR_SORTED_HEADER, - COLOR_PAIR_YELLOW as COLOR_PAIR_YELLOW, - DEFAULT_DISPLAY_LIMIT as DEFAULT_DISPLAY_LIMIT, - DEFAULT_SORT_BY as DEFAULT_SORT_BY, - DISPLAY_UPDATE_INTERVAL_SEC as DISPLAY_UPDATE_INTERVAL_SEC, - FINISHED_BANNER_EXTRA_LINES as FINISHED_BANNER_EXTRA_LINES, - FOOTER_LINES as FOOTER_LINES, - HEADER_LINES as HEADER_LINES, - MICROSECONDS_PER_SECOND as MICROSECONDS_PER_SECOND, - MIN_TERMINAL_HEIGHT as MIN_TERMINAL_HEIGHT, - MIN_TERMINAL_WIDTH as MIN_TERMINAL_WIDTH, - SAFETY_MARGIN as SAFETY_MARGIN, -) -from .display import CursesDisplay as CursesDisplay, DisplayInterface as DisplayInterface -from .trend_tracker import TrendTracker as TrendTracker -from .widgets import ( - FooterWidget as FooterWidget, - HeaderWidget as HeaderWidget, - HelpWidget as HelpWidget, - OpcodePanel as OpcodePanel, - TableWidget as TableWidget, -) - -@dataclass -class ThreadData: - thread_id: int - result: dict[Any, Any] = ... - has_gil: int = ... - on_cpu: int = ... - gil_requested: int = ... - unknown: int = ... - has_exception: int = ... - total: int = ... - sample_count: int = ... - gc_frame_samples: int = ... - opcode_stats: dict[Any, Any] = ... - def increment_status_flag(self, status_flags: int) -> None: ... - def as_status_dict(self) -> dict[str, int]: ... +from ..collector import Collector as Collector +from .display import DisplayInterface as DisplayInterface class LiveStatsCollector(Collector): result: dict[Any, Any] @@ -70,40 +12,19 @@ class LiveStatsCollector(Collector): sort_by: str limit: int total_samples: int + successful_samples: int + failed_samples: int start_time: float | None - stdscr: curses.window | None - display: DisplayInterface | None running: bool + finished: bool + finish_timestamp: float | None + finish_wall_time: float | None pid: int | None mode: int | None async_aware: str | bool | None max_sample_rate: int - successful_samples: int - failed_samples: int - display_update_interval_sec: float - thread_status_counts: dict[str, int] - gc_frame_samples: int - opcode_stats: dict[Any, Any] - show_opcodes: bool - selected_row: int - scroll_offset: int - paused: bool - show_help: bool - filter_pattern: str | None - filter_input_mode: bool - filter_input_buffer: str - finished: bool - finish_timestamp: float | None - finish_wall_time: float | None + display: DisplayInterface | None thread_ids: list[int] - view_mode: str - current_thread_index: int - per_thread_data: dict[int, ThreadData] - header_widget: HeaderWidget | None - table_widget: TableWidget | None - footer_widget: FooterWidget | None - help_widget: HelpWidget | None - opcode_panel: OpcodePanel | None def __init__( self, sample_interval_usec: int, @@ -121,13 +42,9 @@ class LiveStatsCollector(Collector): def elapsed_time(self) -> float: ... @property def current_time_display(self) -> str: ... - def simplify_path(self, filepath: str) -> str: ... - def process_frames(self, frames: Any, thread_id: int | None = None) -> None: ... def collect_failed_sample(self) -> None: ... def collect(self, stack_frames: Any, timestamp_us: int | None = None) -> None: ... def build_stats_list(self) -> list[Any]: ... def reset_stats(self) -> None: ... def mark_finished(self) -> None: ... - def init_curses(self, stdscr: curses.window) -> None: ... - def cleanup_curses(self) -> None: ... def export(self, filename: StrOrBytesPath) -> None: ... diff --git a/stdlib/profiling/sampling/live_collector/constants.pyi b/stdlib/profiling/sampling/live_collector/constants.pyi deleted file mode 100644 index 0ee78f2e4e14..000000000000 --- a/stdlib/profiling/sampling/live_collector/constants.pyi +++ /dev/null @@ -1,32 +0,0 @@ -MICROSECONDS_PER_SECOND: int -DISPLAY_UPDATE_INTERVAL_SEC: float -MIN_TERMINAL_WIDTH: int -MIN_TERMINAL_HEIGHT: int -WIDTH_THRESHOLD_SAMPLE_PCT: int -WIDTH_THRESHOLD_TOTTIME: int -WIDTH_THRESHOLD_CUMUL_PCT: int -WIDTH_THRESHOLD_CUMTIME: int -HEADER_LINES: int -FOOTER_LINES: int -SAFETY_MARGIN: int -TOP_FUNCTIONS_DISPLAY_COUNT: int -MIN_FUNC_NAME_WIDTH: int -MAX_FUNC_NAME_WIDTH: int -MIN_BAR_WIDTH: int -MAX_SAMPLE_RATE_BAR_WIDTH: int -MAX_EFFICIENCY_BAR_WIDTH: int -MIN_SAMPLE_RATE_FOR_SCALING: int -FINISHED_BANNER_EXTRA_LINES: int -OPCODE_PANEL_HEIGHT: int -COLOR_PAIR_SAMPLES: int -COLOR_PAIR_FILE: int -COLOR_PAIR_FUNC: int -COLOR_PAIR_HEADER_BG: int -COLOR_PAIR_CYAN: int -COLOR_PAIR_YELLOW: int -COLOR_PAIR_GREEN: int -COLOR_PAIR_MAGENTA: int -COLOR_PAIR_RED: int -COLOR_PAIR_SORTED_HEADER: int -DEFAULT_SORT_BY: str -DEFAULT_DISPLAY_LIMIT: int diff --git a/stdlib/profiling/sampling/live_collector/display.pyi b/stdlib/profiling/sampling/live_collector/display.pyi index f8458b0082fd..f99a418b1975 100644 --- a/stdlib/profiling/sampling/live_collector/display.pyi +++ b/stdlib/profiling/sampling/live_collector/display.pyi @@ -1,5 +1,4 @@ import abc -import curses from abc import ABC, abstractmethod class DisplayInterface(ABC, metaclass=abc.ABCMeta): @@ -25,47 +24,3 @@ class DisplayInterface(ABC, metaclass=abc.ABCMeta): def get_color_pair(self, pair_id: int) -> int: ... @abstractmethod def get_attr(self, name: str) -> int: ... - -class CursesDisplay(DisplayInterface): - stdscr: curses.window - def __init__(self, stdscr: curses.window) -> None: ... - def get_dimensions(self) -> tuple[int, int]: ... - def clear(self) -> None: ... - def refresh(self) -> None: ... - def redraw(self) -> None: ... - def add_str(self, line: int, col: int, text: str, attr: int = 0) -> None: ... - def get_input(self) -> int: ... - def set_nodelay(self, flag: bool) -> None: ... - def has_colors(self) -> bool: ... - def init_color_pair(self, pair_id: int, fg: int, bg: int) -> None: ... - def get_color_pair(self, pair_id: int) -> int: ... - def get_attr(self, name: str) -> int: ... - -class MockDisplay(DisplayInterface): - height: int - width: int - buffer: dict[tuple[int, int], tuple[str, int]] - cleared: bool - refreshed: bool - redrawn: bool - input_queue: list[int] - nodelay_flag: bool - colors_supported: bool - color_pairs: dict[int, int] - def __init__(self, height: int = 40, width: int = 160) -> None: ... - def get_dimensions(self) -> tuple[int, int]: ... - def clear(self) -> None: ... - def refresh(self) -> None: ... - def redraw(self) -> None: ... - def add_str(self, line: int, col: int, text: str, attr: int = 0) -> None: ... - def get_input(self) -> int: ... - def set_nodelay(self, flag: bool) -> None: ... - def has_colors(self) -> bool: ... - def init_color_pair(self, pair_id: int, fg: int, bg: int) -> None: ... - def get_color_pair(self, pair_id: int) -> int: ... - def get_attr(self, name: str) -> int: ... - def simulate_input(self, char: int) -> None: ... - def get_text_at(self, line: int, col: int) -> str | None: ... - def get_all_lines(self) -> list[str]: ... - def find_text(self, pattern: str) -> tuple[int, int] | None: ... - def contains_text(self, text: str) -> bool: ... diff --git a/stdlib/profiling/sampling/live_collector/trend_tracker.pyi b/stdlib/profiling/sampling/live_collector/trend_tracker.pyi deleted file mode 100644 index a087db69df7a..000000000000 --- a/stdlib/profiling/sampling/live_collector/trend_tracker.pyi +++ /dev/null @@ -1,17 +0,0 @@ -from typing import Any, Literal -from typing_extensions import TypeAlias - -TrendDirection: TypeAlias = Literal["up", "down", "stable"] - -class TrendTracker: - CHANGE_THRESHOLD: float - def __init__(self, colors: dict[str, int], enabled: bool = True) -> None: ... - @property - def enabled(self) -> bool: ... - def toggle(self) -> bool: ... - def set_enabled(self, enabled: bool) -> None: ... - def update(self, key: Any, metric: str, value: float) -> TrendDirection: ... - def get_trend(self, key: Any, metric: str) -> TrendDirection: ... - def get_color(self, trend: TrendDirection) -> int: ... - def update_metrics(self, key: Any, metrics: dict[str, float]) -> dict[str, TrendDirection]: ... - def clear(self) -> None: ... diff --git a/stdlib/profiling/sampling/live_collector/widgets.pyi b/stdlib/profiling/sampling/live_collector/widgets.pyi deleted file mode 100644 index c0160d7ca734..000000000000 --- a/stdlib/profiling/sampling/live_collector/widgets.pyi +++ /dev/null @@ -1,84 +0,0 @@ -import abc -from abc import ABC, abstractmethod -from typing import Any - -from ..constants import ( - PROFILING_MODE_CPU as PROFILING_MODE_CPU, - PROFILING_MODE_GIL as PROFILING_MODE_GIL, - PROFILING_MODE_WALL as PROFILING_MODE_WALL, - THREAD_STATUS_GIL_REQUESTED as THREAD_STATUS_GIL_REQUESTED, - THREAD_STATUS_HAS_GIL as THREAD_STATUS_HAS_GIL, - THREAD_STATUS_ON_CPU as THREAD_STATUS_ON_CPU, - THREAD_STATUS_UNKNOWN as THREAD_STATUS_UNKNOWN, -) -from ..opcode_utils import format_opcode as format_opcode, get_opcode_info as get_opcode_info -from .collector import LiveStatsCollector as LiveStatsCollector -from .constants import ( - DISPLAY_UPDATE_INTERVAL_SEC as DISPLAY_UPDATE_INTERVAL_SEC, - FINISHED_BANNER_EXTRA_LINES as FINISHED_BANNER_EXTRA_LINES, - FOOTER_LINES as FOOTER_LINES, - MAX_EFFICIENCY_BAR_WIDTH as MAX_EFFICIENCY_BAR_WIDTH, - MAX_FUNC_NAME_WIDTH as MAX_FUNC_NAME_WIDTH, - MAX_SAMPLE_RATE_BAR_WIDTH as MAX_SAMPLE_RATE_BAR_WIDTH, - MICROSECONDS_PER_SECOND as MICROSECONDS_PER_SECOND, - MIN_BAR_WIDTH as MIN_BAR_WIDTH, - MIN_FUNC_NAME_WIDTH as MIN_FUNC_NAME_WIDTH, - MIN_SAMPLE_RATE_FOR_SCALING as MIN_SAMPLE_RATE_FOR_SCALING, - OPCODE_PANEL_HEIGHT as OPCODE_PANEL_HEIGHT, - TOP_FUNCTIONS_DISPLAY_COUNT as TOP_FUNCTIONS_DISPLAY_COUNT, - WIDTH_THRESHOLD_CUMTIME as WIDTH_THRESHOLD_CUMTIME, - WIDTH_THRESHOLD_CUMUL_PCT as WIDTH_THRESHOLD_CUMUL_PCT, - WIDTH_THRESHOLD_SAMPLE_PCT as WIDTH_THRESHOLD_SAMPLE_PCT, - WIDTH_THRESHOLD_TOTTIME as WIDTH_THRESHOLD_TOTTIME, -) -from .display import DisplayInterface as DisplayInterface - -class Widget(ABC, metaclass=abc.ABCMeta): - display: DisplayInterface - colors: dict[str, int] - def __init__(self, display: DisplayInterface, colors: dict[str, int]) -> None: ... - @abstractmethod - def render(self, line: int, width: int, **kwargs: Any) -> int: ... - def add_str(self, line: int, col: int, text: str, attr: int = 0) -> None: ... - -class ProgressBarWidget(Widget): - def render(self, line: int, width: int, **kwargs: Any) -> None: ... - def render_bar( - self, filled: float, total: float, max_width: int, fill_char: str = "█", empty_char: str = "░" - ) -> tuple[str, int]: ... - -class HeaderWidget(Widget): - collector: LiveStatsCollector - progress_bar: ProgressBarWidget - def __init__(self, display: DisplayInterface, colors: dict[str, int], collector: LiveStatsCollector) -> None: ... - def render(self, line: int, width: int, **kwargs: Any) -> int: ... - def format_uptime(self, elapsed: float) -> str: ... - def draw_header_info(self, line: int, width: int, elapsed: float) -> int: ... - def format_rate_with_units(self, rate_hz: float) -> str: ... - def draw_sample_stats(self, line: int, width: int, elapsed: float) -> int: ... - def draw_efficiency_bar(self, line: int, width: int) -> int: ... - def draw_thread_status(self, line: int, width: int) -> int: ... - def draw_function_stats(self, line: int, width: int, stats_list: list[Any]) -> int: ... - def draw_top_functions(self, line: int, width: int, stats_list: list[Any]) -> int: ... - def draw_finished_banner(self, line: int, width: int) -> int: ... - -class TableWidget(Widget): - collector: LiveStatsCollector - def __init__(self, display: DisplayInterface, colors: dict[str, int], collector: LiveStatsCollector) -> None: ... - def render(self, line: int, width: int, **kwargs: Any) -> int: ... - def draw_column_headers(self, line: int, width: int) -> int: ... - def draw_stats_rows(self, line: int, height: int, width: int, stats_list: list[Any], column_flags: Any) -> int: ... - -class FooterWidget(Widget): - collector: LiveStatsCollector - def __init__(self, display: DisplayInterface, colors: dict[str, int], collector: LiveStatsCollector) -> None: ... - def render(self, line: int, width: int, **kwargs: Any) -> int: ... - def render_filter_input_prompt(self, line: int, width: int) -> None: ... - -class HelpWidget(Widget): - def render(self, line: int, width: int, **kwargs: Any) -> int: ... - -class OpcodePanel(Widget): - collector: LiveStatsCollector - def __init__(self, display: DisplayInterface, colors: dict[str, int], collector: LiveStatsCollector) -> None: ... - def render(self, line: int, width: int, **kwargs: Any) -> int: ... From 36e4dde714693ea9fe7468a4cde55554ba13f69e Mon Sep 17 00:00:00 2001 From: johnslavik Date: Mon, 20 Apr 2026 06:27:16 +0200 Subject: [PATCH 8/9] Restore MICROSECONDS_PER_SECOND to sampling/constants.pyi MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It was removed in the first cleanup commit as "re-exported only internally", but pstats_collector.pyi legitimately imports it from sampling.constants — where it is actually defined in the source. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- stdlib/profiling/sampling/constants.pyi | 1 + 1 file changed, 1 insertion(+) diff --git a/stdlib/profiling/sampling/constants.pyi b/stdlib/profiling/sampling/constants.pyi index 92408b59e85f..915b3896351d 100644 --- a/stdlib/profiling/sampling/constants.pyi +++ b/stdlib/profiling/sampling/constants.pyi @@ -1,3 +1,4 @@ +MICROSECONDS_PER_SECOND: int PROFILING_MODE_WALL: int PROFILING_MODE_CPU: int PROFILING_MODE_GIL: int From 25c9d89cad5969d3ae4caa2d1ec0da7e78e4f08c Mon Sep 17 00:00:00 2001 From: johnslavik Date: Mon, 20 Apr 2026 06:35:46 +0200 Subject: [PATCH 9/9] Align stubs with documented/declared public API surface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Judgement calls, per the principle: will people want to import these symbols in their code that uses the profiling API? profiling.tracing ----------------- - Remove Profile.snapshot_stats(): not in __all__, not in docs. It is an internal implementation step called by create_stats(); users call create_stats() directly. - Remove module-level main(): not in __all__, not in docs. It is the CLI entry point wired up by the (already-deleted) __main__.py stub. - Keep dump_stats, runcall, create_stats, print_stats: all explicitly documented in the profiling.tracing module reference. - Keep private _T, _P, _Label aliases: needed internally by the stub to express the runcall() overload and stats dict type. profiling.sampling — collector submodule stubs ----------------------------------------------- - Remove normalize_location, extract_lineno, filter_internal_frames from collector.pyi: implementation helpers used inside collector subclasses; not in sampling.__all__, not documented. Users subclass Collector with collect() and export() only. - Remove DEFAULT_LOCATION and THREAD_STATUS_* re-exports from collector.pyi: leaked from constants.py; import from profiling.sampling.constants if needed. - Remove get_opcode_mapping from opcode_utils.pyi: internal mapping used to build opcode-name look-up tables; not documented, never imported by user code. - Remove re-exports of filter_internal_frames, format_opcode, get_opcode_info from gecko_collector.pyi: imported for internal use, not part of GeckoCollector's public contract. - Strip GeckoCollector internal tracking attributes (has_gil_start, no_gil_start, on_cpu_start, etc.): 20+ state dicts tracking GIL/CPU marker transitions; purely implementation detail. Users call __init__ → collect → export. - Remove FileStats, TreeNode, get_python_path_info, extract_module_name from heatmap_collector.pyi: internal data structures and path helpers for the HTML generation pipeline; not in sampling.__all__, not documented. - Strip HeatmapCollector internal state counters (line_samples, file_samples, call_graph, etc.): implementation detail of the line-level sample accumulation. - Remove re-exports of extract_lineno, normalize_location, format_opcode, get_opcode_info from heatmap_collector.pyi: same pattern — imported for internal use only. - Remove re-exports of extract_lineno, get_opcode_mapping, StringTable from stack_collector.pyi. - Strip CollapsedStackCollector.stack_counter and FlamegraphCollector internal state (thread_status_counts, per_thread_stats, etc.): sample accumulation internals. - Strip DiffFlamegraphCollector.baseline_binary_path attribute: constructor parameter, not a user-facing attribute. - Remove extract_lineno, MICROSECONDS_PER_SECOND, PROFILING_MODE_CPU re-exports from pstats_collector.pyi. - Strip PstatsCollector internal state (result, stats, callers, skip_idle attributes): implementation detail of sample aggregation. - Remove GeckoCollector, PstatsCollector, CollapsedStackCollector, FlamegraphCollector re-exports from binary_reader.pyi: imported internally by binary_reader.py, not part of BinaryReader's API. profiling.sampling — sample and cli modules ------------------------------------------- - Strip sample.pyi of all re-exports (BinaryCollector, Collector, all five PROFILING_MODE_* constants, GeckoCollector, HeatmapCollector, LiveStatsCollector, PstatsCollector, CollapsedStackCollector, FlamegraphCollector): sample.py imports these for internal use; they are not part of sample's public interface and are already accessible from their own modules. - Remove unwinder: Incomplete from SampleProfiler: internal C extension handle, not user-accessible. - Strip cli.pyi to main() only: cli.py is the CLI implementation; all the symbols it re-exported (ChildProcessMonitor, BinaryCollector, BinaryReader, 6 SORT_MODE constants) belong to their own modules. - Delete _child_monitor.pyi: private module (_-prefixed); previously kept because cli.pyi re-exported ChildProcessMonitor, but now that cli.pyi is stripped there is no public re-export, and users have no reason to import from profiling.sampling._child_monitor directly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- stdlib/profiling/sampling/_child_monitor.pyi | 16 -------- stdlib/profiling/sampling/binary_reader.pyi | 3 -- stdlib/profiling/sampling/cli.pyi | 17 -------- stdlib/profiling/sampling/collector.pyi | 13 ------ stdlib/profiling/sampling/gecko_collector.pyi | 26 +----------- .../profiling/sampling/heatmap_collector.pyi | 40 ------------------- stdlib/profiling/sampling/opcode_utils.pyi | 1 - .../profiling/sampling/pstats_collector.pyi | 8 +--- stdlib/profiling/sampling/sample.pyi | 15 ------- stdlib/profiling/sampling/stack_collector.pyi | 11 +---- stdlib/profiling/tracing/__init__.pyi | 3 -- 11 files changed, 3 insertions(+), 150 deletions(-) delete mode 100644 stdlib/profiling/sampling/_child_monitor.pyi diff --git a/stdlib/profiling/sampling/_child_monitor.pyi b/stdlib/profiling/sampling/_child_monitor.pyi deleted file mode 100644 index bdfb44b92498..000000000000 --- a/stdlib/profiling/sampling/_child_monitor.pyi +++ /dev/null @@ -1,16 +0,0 @@ -import types -from typing import Any -from typing_extensions import Self - -class ChildProcessMonitor: - parent_pid: int - cli_args: list[str] - output_pattern: str - def __init__(self, pid: int, cli_args: list[str], output_pattern: str) -> None: ... - def __enter__(self) -> Self: ... - def __exit__( - self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: types.TracebackType | None - ) -> None: ... - @property - def spawned_profilers(self) -> list[Any]: ... - def wait_for_profilers(self, timeout: float = ...) -> None: ... diff --git a/stdlib/profiling/sampling/binary_reader.pyi b/stdlib/profiling/sampling/binary_reader.pyi index 09e011a268c2..6a76400abab1 100644 --- a/stdlib/profiling/sampling/binary_reader.pyi +++ b/stdlib/profiling/sampling/binary_reader.pyi @@ -5,9 +5,6 @@ from typing import Any from typing_extensions import Self from .collector import Collector as Collector -from .gecko_collector import GeckoCollector as GeckoCollector -from .pstats_collector import PstatsCollector as PstatsCollector -from .stack_collector import CollapsedStackCollector as CollapsedStackCollector, FlamegraphCollector as FlamegraphCollector class BinaryReader: filename: str diff --git a/stdlib/profiling/sampling/cli.pyi b/stdlib/profiling/sampling/cli.pyi index 9d5fd0747d6a..7e7363e797f3 100644 --- a/stdlib/profiling/sampling/cli.pyi +++ b/stdlib/profiling/sampling/cli.pyi @@ -1,18 +1 @@ -from ._child_monitor import ChildProcessMonitor as ChildProcessMonitor -from .binary_collector import BinaryCollector as BinaryCollector -from .binary_reader import BinaryReader as BinaryReader -from .constants import ( - PROFILING_MODE_ALL as PROFILING_MODE_ALL, - PROFILING_MODE_CPU as PROFILING_MODE_CPU, - PROFILING_MODE_EXCEPTION as PROFILING_MODE_EXCEPTION, - PROFILING_MODE_GIL as PROFILING_MODE_GIL, - PROFILING_MODE_WALL as PROFILING_MODE_WALL, - SORT_MODE_CUMTIME as SORT_MODE_CUMTIME, - SORT_MODE_CUMUL_PCT as SORT_MODE_CUMUL_PCT, - SORT_MODE_NSAMPLES as SORT_MODE_NSAMPLES, - SORT_MODE_NSAMPLES_CUMUL as SORT_MODE_NSAMPLES_CUMUL, - SORT_MODE_SAMPLE_PCT as SORT_MODE_SAMPLE_PCT, - SORT_MODE_TOTTIME as SORT_MODE_TOTTIME, -) - def main() -> None: ... diff --git a/stdlib/profiling/sampling/collector.pyi b/stdlib/profiling/sampling/collector.pyi index 47490c9feafa..7592e8de6dff 100644 --- a/stdlib/profiling/sampling/collector.pyi +++ b/stdlib/profiling/sampling/collector.pyi @@ -3,19 +3,6 @@ from _typeshed import StrOrBytesPath from abc import ABC, abstractmethod from typing import Any -from .constants import ( - DEFAULT_LOCATION as DEFAULT_LOCATION, - THREAD_STATUS_GIL_REQUESTED as THREAD_STATUS_GIL_REQUESTED, - THREAD_STATUS_HAS_EXCEPTION as THREAD_STATUS_HAS_EXCEPTION, - THREAD_STATUS_HAS_GIL as THREAD_STATUS_HAS_GIL, - THREAD_STATUS_ON_CPU as THREAD_STATUS_ON_CPU, - THREAD_STATUS_UNKNOWN as THREAD_STATUS_UNKNOWN, -) - -def normalize_location(location: tuple[int, int, int, int] | None) -> tuple[int, int, int, int]: ... -def extract_lineno(location: tuple[int, int, int, int] | None) -> int: ... -def filter_internal_frames(frames: list[Any]) -> list[Any]: ... - class Collector(ABC, metaclass=abc.ABCMeta): @abstractmethod def collect(self, stack_frames: Any, timestamps_us: list[int] | None = None) -> None: ... diff --git a/stdlib/profiling/sampling/gecko_collector.pyi b/stdlib/profiling/sampling/gecko_collector.pyi index 4b1db27f05b3..84204cf7fb97 100644 --- a/stdlib/profiling/sampling/gecko_collector.pyi +++ b/stdlib/profiling/sampling/gecko_collector.pyi @@ -1,32 +1,8 @@ from typing import Any -from .collector import Collector as Collector, filter_internal_frames as filter_internal_frames -from .opcode_utils import format_opcode as format_opcode, get_opcode_info as get_opcode_info +from .collector import Collector as Collector class GeckoCollector(Collector): - sample_interval_usec: int - skip_idle: bool - opcodes_enabled: bool - start_time: float - global_strings: list[str] - global_string_map: dict[str, int] - threads: dict[int, Any] - libs: list[Any] - sample_count: int - last_sample_time: int - interval: float - has_gil_start: dict[int, float] - no_gil_start: dict[int, float] - on_cpu_start: dict[int, float] - off_cpu_start: dict[int, float] - python_code_start: dict[int, float] - native_code_start: dict[int, float] - gil_wait_start: dict[int, float] - exception_start: dict[int, float] - no_exception_start: dict[int, float] - gc_start_per_thread: dict[int, float] - initialized_threads: set[int] - opcode_state: dict[Any, Any] def __init__(self, sample_interval_usec: int, *, skip_idle: bool = False, opcodes: bool = False) -> None: ... def collect(self, stack_frames: Any, timestamps_us: list[int] | None = None) -> None: ... def export(self, filename: str) -> None: ... diff --git a/stdlib/profiling/sampling/heatmap_collector.pyi b/stdlib/profiling/sampling/heatmap_collector.pyi index 51e1591d1503..5e6a5e0c730d 100644 --- a/stdlib/profiling/sampling/heatmap_collector.pyi +++ b/stdlib/profiling/sampling/heatmap_collector.pyi @@ -1,49 +1,9 @@ from _typeshed import StrOrBytesPath -from collections import Counter -from dataclasses import dataclass from typing import Any -from .collector import extract_lineno as extract_lineno, normalize_location as normalize_location -from .opcode_utils import format_opcode as format_opcode, get_opcode_info as get_opcode_info from .stack_collector import StackTraceCollector as StackTraceCollector -@dataclass -class FileStats: - filename: str - module_name: str - module_type: str - total_samples: int - total_self_samples: int - num_lines: int - max_samples: int - max_self_samples: int - percentage: float = ... - -@dataclass -class TreeNode: - files: list[FileStats] = ... - samples: int = ... - count: int = ... - children: dict[str, TreeNode] = ... - -def get_python_path_info() -> dict[str, Any]: ... -def extract_module_name(filename: str, path_info: dict[str, Any]) -> str: ... - class HeatmapCollector(StackTraceCollector): - FILE_INDEX_FORMAT: str - line_samples: Counter[Any] - file_samples: dict[str, Counter[int]] - line_self_samples: Counter[Any] - file_self_samples: dict[str, Counter[int]] - call_graph: dict[Any, set[Any]] - callers_graph: dict[Any, set[Any]] - function_definitions: dict[Any, Any] - line_to_function: dict[Any, Any] - edge_samples: Counter[Any] - line_opcodes: dict[Any, dict[Any, Any]] - stats: dict[str, Any] - opcodes_enabled: bool - file_index: dict[str, str] def __init__(self, *args: Any, **kwargs: Any) -> None: ... def set_stats( self, diff --git a/stdlib/profiling/sampling/opcode_utils.pyi b/stdlib/profiling/sampling/opcode_utils.pyi index 7190ae2e706f..2c21b36f16fa 100644 --- a/stdlib/profiling/sampling/opcode_utils.pyi +++ b/stdlib/profiling/sampling/opcode_utils.pyi @@ -1,3 +1,2 @@ def get_opcode_info(opcode_num: int) -> dict[str, str | bool]: ... def format_opcode(opcode_num: int) -> str: ... -def get_opcode_mapping() -> dict[str, dict[int, str] | dict[int, int]]: ... diff --git a/stdlib/profiling/sampling/pstats_collector.pyi b/stdlib/profiling/sampling/pstats_collector.pyi index 0f853d505d79..025cf1a875fd 100644 --- a/stdlib/profiling/sampling/pstats_collector.pyi +++ b/stdlib/profiling/sampling/pstats_collector.pyi @@ -1,15 +1,9 @@ from _typeshed import StrOrBytesPath from typing import Any -from .collector import Collector as Collector, extract_lineno as extract_lineno -from .constants import MICROSECONDS_PER_SECOND as MICROSECONDS_PER_SECOND, PROFILING_MODE_CPU as PROFILING_MODE_CPU +from .collector import Collector as Collector class PstatsCollector(Collector): - result: dict[Any, Any] - stats: dict[Any, Any] - sample_interval_usec: int - callers: dict[Any, Any] - skip_idle: bool def __init__(self, sample_interval_usec: int, *, skip_idle: bool = False) -> None: ... def collect(self, stack_frames: Any, timestamps_us: list[int] | None = None) -> None: ... def export(self, filename: StrOrBytesPath) -> None: ... diff --git a/stdlib/profiling/sampling/sample.pyi b/stdlib/profiling/sampling/sample.pyi index aa9dfeec4993..b6618e4731cb 100644 --- a/stdlib/profiling/sampling/sample.pyi +++ b/stdlib/profiling/sampling/sample.pyi @@ -1,20 +1,6 @@ -from _typeshed import Incomplete from collections import deque -from .binary_collector import BinaryCollector as BinaryCollector from .collector import Collector as Collector -from .constants import ( - PROFILING_MODE_ALL as PROFILING_MODE_ALL, - PROFILING_MODE_CPU as PROFILING_MODE_CPU, - PROFILING_MODE_EXCEPTION as PROFILING_MODE_EXCEPTION, - PROFILING_MODE_GIL as PROFILING_MODE_GIL, - PROFILING_MODE_WALL as PROFILING_MODE_WALL, -) -from .gecko_collector import GeckoCollector as GeckoCollector -from .heatmap_collector import HeatmapCollector as HeatmapCollector -from .live_collector import LiveStatsCollector as LiveStatsCollector -from .pstats_collector import PstatsCollector as PstatsCollector -from .stack_collector import CollapsedStackCollector as CollapsedStackCollector, FlamegraphCollector as FlamegraphCollector class SampleProfiler: pid: int @@ -23,7 +9,6 @@ class SampleProfiler: mode: int collect_stats: bool blocking: bool - unwinder: Incomplete sample_intervals: deque[float] total_samples: int realtime_stats: bool diff --git a/stdlib/profiling/sampling/stack_collector.pyi b/stdlib/profiling/sampling/stack_collector.pyi index 4fde66f47fe6..574f4de9b736 100644 --- a/stdlib/profiling/sampling/stack_collector.pyi +++ b/stdlib/profiling/sampling/stack_collector.pyi @@ -1,11 +1,8 @@ import abc from _typeshed import StrOrBytesPath -from collections import Counter from typing import Any -from .collector import Collector as Collector, extract_lineno as extract_lineno -from .opcode_utils import get_opcode_mapping as get_opcode_mapping -from .string_table import StringTable as StringTable +from .collector import Collector as Collector class StackTraceCollector(Collector, metaclass=abc.ABCMeta): sample_interval_usec: int @@ -15,16 +12,11 @@ class StackTraceCollector(Collector, metaclass=abc.ABCMeta): def process_frames(self, frames: list[Any], thread_id: int, weight: int = 1) -> None: ... class CollapsedStackCollector(StackTraceCollector): - stack_counter: Counter[str] def __init__(self, *args: Any, **kwargs: Any) -> None: ... def process_frames(self, frames: list[Any], thread_id: int, weight: int = 1) -> None: ... def export(self, filename: StrOrBytesPath) -> None: ... class FlamegraphCollector(StackTraceCollector): - stats: dict[str, Any] - thread_status_counts: dict[str, int] - samples_with_gc_frames: int - per_thread_stats: dict[int, Any] def __init__(self, *args: Any, **kwargs: Any) -> None: ... def collect(self, stack_frames: Any, timestamps_us: list[int] | None = None) -> None: ... def set_stats( @@ -40,5 +32,4 @@ class FlamegraphCollector(StackTraceCollector): def process_frames(self, frames: list[Any], thread_id: int, weight: int = 1) -> None: ... class DiffFlamegraphCollector(FlamegraphCollector): - baseline_binary_path: str def __init__(self, sample_interval_usec: int, *, baseline_binary_path: str, skip_idle: bool = False) -> None: ... diff --git a/stdlib/profiling/tracing/__init__.pyi b/stdlib/profiling/tracing/__init__.pyi index e2c4d6e302c9..5e81b180f494 100644 --- a/stdlib/profiling/tracing/__init__.pyi +++ b/stdlib/profiling/tracing/__init__.pyi @@ -20,11 +20,8 @@ class Profile(_lsprof.Profiler): def print_stats(self, sort: str | int = -1) -> None: ... def dump_stats(self, file: StrOrBytesPath) -> None: ... def create_stats(self) -> None: ... - def snapshot_stats(self) -> None: ... def run(self, cmd: str) -> Self: ... def runctx(self, cmd: str, globals: dict[str, Any], locals: Mapping[str, Any]) -> Self: ... def runcall(self, func: Callable[_P, _T], /, *args: _P.args, **kw: _P.kwargs) -> _T: ... def __enter__(self) -> Self: ... def __exit__(self, *exc_info: Unused) -> None: ... - -def main() -> None: ...