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/binary_collector.pyi b/stdlib/profiling/sampling/binary_collector.pyi new file mode 100644 index 000000000000..7ca65916d2c5 --- /dev/null +++ b/stdlib/profiling/sampling/binary_collector.pyi @@ -0,0 +1,24 @@ +import types +from _typeshed import StrOrBytesPath +from typing import Any +from typing_extensions import Self + +from .collector import Collector as Collector + +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..6a76400abab1 --- /dev/null +++ b/stdlib/profiling/sampling/binary_reader.pyi @@ -0,0 +1,28 @@ +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 + +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..7e7363e797f3 --- /dev/null +++ b/stdlib/profiling/sampling/cli.pyi @@ -0,0 +1 @@ +def main() -> None: ... diff --git a/stdlib/profiling/sampling/collector.pyi b/stdlib/profiling/sampling/collector.pyi new file mode 100644 index 000000000000..7592e8de6dff --- /dev/null +++ b/stdlib/profiling/sampling/collector.pyi @@ -0,0 +1,11 @@ +import abc +from _typeshed import StrOrBytesPath +from abc import ABC, abstractmethod +from typing import 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..915b3896351d --- /dev/null +++ b/stdlib/profiling/sampling/constants.pyi @@ -0,0 +1,19 @@ +MICROSECONDS_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] +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..84204cf7fb97 --- /dev/null +++ b/stdlib/profiling/sampling/gecko_collector.pyi @@ -0,0 +1,8 @@ +from typing import Any + +from .collector import Collector as Collector + +class GeckoCollector(Collector): + 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..5e6a5e0c730d --- /dev/null +++ b/stdlib/profiling/sampling/heatmap_collector.pyi @@ -0,0 +1,18 @@ +from _typeshed import StrOrBytesPath +from typing import Any + +from .stack_collector import StackTraceCollector as StackTraceCollector + +class HeatmapCollector(StackTraceCollector): + 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..75d83a73e182 --- /dev/null +++ b/stdlib/profiling/sampling/live_collector/__init__.pyi @@ -0,0 +1,4 @@ +from .collector import LiveStatsCollector as LiveStatsCollector +from .display import DisplayInterface as DisplayInterface + +__all__ = ["LiveStatsCollector", "DisplayInterface"] diff --git a/stdlib/profiling/sampling/live_collector/collector.pyi b/stdlib/profiling/sampling/live_collector/collector.pyi new file mode 100644 index 000000000000..effb140f55e0 --- /dev/null +++ b/stdlib/profiling/sampling/live_collector/collector.pyi @@ -0,0 +1,50 @@ +from _typeshed import StrOrBytesPath +from typing import Any + +from ..collector import Collector as Collector +from .display import DisplayInterface as DisplayInterface + +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 + successful_samples: int + failed_samples: int + start_time: float | 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 + display: DisplayInterface | None + thread_ids: list[int] + 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 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 export(self, filename: StrOrBytesPath) -> None: ... diff --git a/stdlib/profiling/sampling/live_collector/display.pyi b/stdlib/profiling/sampling/live_collector/display.pyi new file mode 100644 index 000000000000..f99a418b1975 --- /dev/null +++ b/stdlib/profiling/sampling/live_collector/display.pyi @@ -0,0 +1,26 @@ +import abc +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: ... diff --git a/stdlib/profiling/sampling/opcode_utils.pyi b/stdlib/profiling/sampling/opcode_utils.pyi new file mode 100644 index 000000000000..2c21b36f16fa --- /dev/null +++ b/stdlib/profiling/sampling/opcode_utils.pyi @@ -0,0 +1,2 @@ +def get_opcode_info(opcode_num: int) -> dict[str, str | bool]: ... +def format_opcode(opcode_num: int) -> str: ... diff --git a/stdlib/profiling/sampling/pstats_collector.pyi b/stdlib/profiling/sampling/pstats_collector.pyi new file mode 100644 index 000000000000..025cf1a875fd --- /dev/null +++ b/stdlib/profiling/sampling/pstats_collector.pyi @@ -0,0 +1,13 @@ +from _typeshed import StrOrBytesPath +from typing import Any + +from .collector import Collector as Collector + +class PstatsCollector(Collector): + 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..b6618e4731cb --- /dev/null +++ b/stdlib/profiling/sampling/sample.pyi @@ -0,0 +1,58 @@ +from collections import deque + +from .collector import Collector as Collector + +class SampleProfiler: + pid: int + sample_interval_usec: int + all_threads: bool + mode: int + collect_stats: bool + blocking: bool + 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..574f4de9b736 --- /dev/null +++ b/stdlib/profiling/sampling/stack_collector.pyi @@ -0,0 +1,35 @@ +import abc +from _typeshed import StrOrBytesPath +from typing import Any + +from .collector import Collector as Collector + +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): + 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): + 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): + 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..5e81b180f494 --- /dev/null +++ b/stdlib/profiling/tracing/__init__.pyi @@ -0,0 +1,27 @@ +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 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: ...