mirror of
https://github.com/python/cpython
synced 2026-11-06 09:02:40 +03:00
* [3.15] gh-153364: Make frame, coroutine, and task-waiter chain walks iterative and bounded (GH-153365) (#158813) gh-153364: Make frame, coroutine, and task-waiter chain walks iterative and bounded (GH-153365) * let me declare single limit * use our new limit in process_frame_chain() * add it in parse_async_frame_chain() * parse_coro_chain() * NEWS * async in the message? * test * no race * process_task_awaited_by * process_task_awaited_by limit test * NEWS * MAX_TASK_WAITER_CHAIN_DEPTH * TASK_WAITER_CHAIN_DEPTH in test * TASK_WAITER_CHAIN_DEPTH 256 * prevent the drift with the comment * better naming, better style * MAX_TASK_WAITER_CHAIN_DEPTH comment * task-waiter iterative bfs walk * iterative coro-walk * nicer news * 1 << 14 * comment * unused read_Py_ssize_t * fix tombstones * simplify * correct msg * better test * news for tombstones * left-over from when testing buggy version * redundant new line (cherry picked from commite0861c6ae7) Co-authored-by: Maurycy Pawłowski-Wieroński <maurycy@maurycy.com> (cherry picked from commit9e401cf7f5) * [3.15] gh-155811: Add a seqcount to `gc_stats` to prevent torn reads (GH-155828) (#158829) * update_seq * no need for XCHGL, MOVL is enough? * gh-155811: Retry an inconsistent GC snapshot once --------- (cherry picked from commit5fecd448bb) Co-authored-by: Pablo Galindo Salgado <Pablogsal@gmail.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> (cherry picked from commit028124039c) * [3.15] gh-151292: `_remote_debugging`: Do not corrupt the binary file when hitting `OverflowError` (GH-152892) (#158830) gh-151292: `_remote_debugging`: Do not corrupt the binary file when hitting `OverflowError` (GH-152892) * the kolektor * test * better test * news * =Q, move const to the base, not self.running * gh-151292: Track binary writer finalization state --------- (cherry picked from commitf839c061d3) Co-authored-by: Maurycy Pawłowski-Wieroński <maurycy@maurycy.com> Co-authored-by: Pablo Galindo Salgado <Pablogsal@gmail.com> (cherry picked from commit8a7c23f316) * [3.15] gh-158583: Fix uninitialized memory read in bytes.fromhex() (GH-158584) (#158691) gh-158583: Fix uninitialized memory read in bytes.fromhex() (GH-158584) (cherry picked from commit9d22a5334b) Co-authored-by: Victor Stinner <vstinner@python.org> (cherry picked from commit4f7af46e25) * [3.15] gh-154194: Degrade frames in Tachyon instead of failing the sample (GH-154195) (#158831) * gh-154194: Degrade frames in Tachyon instead of failing the sample (#154195) * degrade gracefully * news * better NEWS wording * do not raise on MAX_REMOTE_STR_READ * bye MAX_REMOTE_STR_READ * fix -m asyncio ps|pstree * test truncation and linetable sentinel * simpler * simpler * redundant now * respect #157790 in the news --------- Co-authored-by: Pablo Galindo Salgado <Pablogsal@gmail.com> (cherry picked from commit7d25916b41) * Preserve the stable ABI when creating fallback frame names --------- Co-authored-by: Maurycy Pawłowski-Wieroński <maurycy@maurycy.com> (cherry picked from commitc27f494061) * [3.15] Add MSan to CI (GH-158625) (#158832) Run a job that the test suite with MSan to the CI (#158625) * Run the test suite with MSan in CI * Additional fixes * Add `_Py_MSAN_UNPOISON_STRING` * Apply Victor's suggestions * Apply Victor's suggestions --------- (cherry picked from commitb93fb19a6e) Co-authored-by: Stan Ulbrych <stan@python.org> Co-authored-by: Victor Stinner <victor.stinner@gmail.com> (cherry picked from commit1bc78dea18) * [3.15] gh-156810: Write the profiler's collapsed-stack export as UTF-8 (GH-156811) (#156814) gh-156810: Write the profiler's collapsed-stack export as UTF-8 (GH-156811) (cherry picked from commitc3706f4c68) Co-authored-by: tonghuaroot (童话) <tonghuaroot@gmail.com> (cherry picked from commitfce28da9b7) * [3.15] gh-158552: Wait for Windows threads to suspend before blocking sampling (GH-158802) (#158845) gh-158552: Wait for Windows threads to suspend before blocking sampling (GH-158802) * gh-158552: Wait for Windows threads to suspend before blocking sampling * Use a named Windows thread enumeration status constant (cherry picked from commit1643525f0f) Co-authored-by: Pablo Galindo Salgado <Pablogsal@gmail.com> (cherry picked from commit31288da21d) * [3.15] gh-152721: Fix quadratic RLE replay time in the profiling binary reader (GH-152722) (#158850) Backport of GH-152722. Co-authored-by: tonghuaroot <tonghuaroot@gmail.com> (cherry picked from commitebaca2ac2d) * [3.15] gh-156545: Fix flamegraph export RecursionError on deeply recursive programs (GH-156546) (#158851) Backport of GH-156546. Co-authored-by: tonghuaroot (童话) <tonghuaroot@gmail.com> (cherry picked from commit0ac7217040) * [3.15] gh-158540: Add the profiled script's directory to sys.path (GH-158548) (#158844) gh-158540: Add the profiled script's directory to sys.path (GH-158548) * gh-158540: Add the profiled script's directory to sys.path When a script is profiled with ``python -m profiling.sampling run script.py`` from another directory, the script cannot import modules placed next to it, because ``_sync_coordinator._execute_script()`` executes it with the working directory (added by ``_setup_environment()`` for the module case) as ``sys.path[0]`` instead of the script's own directory. Make the script's directory importable in ``_execute_script()``, matching the behavior of ``python script.py``. Add a regression test that runs the coordinator on a script importing a sibling module. * Update _sync_coordinator.py Comment simplified * gh-158540: Resolve symlinks when adding the script directory to sys.path ``python script.py`` resolves symlinks when computing ``sys.path[0]``, so a script reached through a symlink (``link.py -> sub/where.py``) imports modules from the real script's directory. Apply ``os.path.realpath()`` before taking the directory name, and make sure the result is placed at the front of ``sys.path`` even if it was already listed. Add a regression test for a symlinked script. --------- (cherry picked from commit3f02aab9fa) Co-authored-by: he_tao <53343436+hetaozdh@users.noreply.github.com> Co-authored-by: Eduardo Villalpando Mello <eduardo.villalpando.mello@gmail.com> (cherry picked from commitf52d831397) * [3.15] gh-153838: Skip non-regular source files in the heatmap exporter (GH-153839) (#158853) Backport of GH-153839. Co-authored-by: tonghuaroot <tonghuaroot@gmail.com> (cherry picked from commit48998df251) * [3.15] gh-158539: Fix exception mode missing handlers in generators/coroutines (GH-158581) (#158852) * [3.15] gh-158539: Fix exception mode missing handlers in generators/coroutines (GH-158581) Backport of GH-158581. Co-authored-by: LucasZhou <donghao.zhou@outlook.com> * [3.15] gh-158539: Use portable static assertion messages * [3.15] gh-158539: Keep layout assertions with debug-offset validation * [3.15] gh-158539: Use the platform guard for in-process inspection tests --------- Co-authored-by: LucasZhou <donghao.zhou@outlook.com> (cherry picked from commitd625ecbb8d) * [3.15] gh-158522: Fix truncated stack for a task whose coroutine recurses (GH-158526) (#158870) Co-authored-by: Timofei Ivankov <128279579+deadlovelll@users.noreply.github.com> (cherry picked from commita904b397ed) * gh-156545, gh-158539: Fix deep flamegraph export on small C stacks and macOS runtime lookup (#158874) (cherry picked from commit114de198c0) Include the C-stack test helper from main, introduced byce5ae29ef9, which the regression test requires but 3.15 does not yet provide. --------- Co-authored-by: Miss Islington (bot) <31488909+miss-islington@users.noreply.github.com> Co-authored-by: Maurycy Pawłowski-Wieroński <maurycy@maurycy.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> Co-authored-by: Victor Stinner <vstinner@python.org> Co-authored-by: Stan Ulbrych <stan@python.org> Co-authored-by: Victor Stinner <victor.stinner@gmail.com> Co-authored-by: tonghuaroot (童话) <tonghuaroot@gmail.com> Co-authored-by: he_tao <53343436+hetaozdh@users.noreply.github.com> Co-authored-by: Eduardo Villalpando Mello <eduardo.villalpando.mello@gmail.com> Co-authored-by: LucasZhou <donghao.zhou@outlook.com> Co-authored-by: Timofei Ivankov <128279579+deadlovelll@users.noreply.github.com>
159 lines
5.1 KiB
Python
159 lines
5.1 KiB
Python
"""Thin Python wrapper around C binary writer for profiling data."""
|
|
|
|
import sys
|
|
import time
|
|
|
|
import _remote_debugging
|
|
|
|
from .collector import Collector
|
|
|
|
# Compression type constants (must match binary_io.h)
|
|
COMPRESSION_NONE = 0
|
|
COMPRESSION_ZSTD = 1
|
|
|
|
CAPTURE_FEATURES = {
|
|
"all_threads": 1 << 0,
|
|
"native": 1 << 1,
|
|
"gc": 1 << 2,
|
|
"opcodes": 1 << 3,
|
|
"blocking": 1 << 4,
|
|
}
|
|
|
|
|
|
def encode_capture_config(capture_config):
|
|
if capture_config is None:
|
|
return -1
|
|
return sum(
|
|
bit for name, bit in CAPTURE_FEATURES.items()
|
|
if capture_config.get(name, False)
|
|
)
|
|
|
|
|
|
def _resolve_compression(compression):
|
|
"""Resolve compression type from string or int.
|
|
|
|
Args:
|
|
compression: 'auto', 'zstd', 'none', or int (0/1)
|
|
|
|
Returns:
|
|
int: Compression type constant
|
|
"""
|
|
if isinstance(compression, int):
|
|
return compression
|
|
|
|
compression = compression.lower()
|
|
if compression == 'none':
|
|
return COMPRESSION_NONE
|
|
elif compression == 'zstd':
|
|
return COMPRESSION_ZSTD
|
|
elif compression == 'auto':
|
|
# Auto: use zstd if available, otherwise none
|
|
if _remote_debugging.zstd_available():
|
|
return COMPRESSION_ZSTD
|
|
return COMPRESSION_NONE
|
|
else:
|
|
raise ValueError(f"Unknown compression type: {compression}")
|
|
|
|
|
|
class BinaryCollector(Collector):
|
|
"""High-performance binary collector using C implementation.
|
|
|
|
This collector writes profiling data directly to a binary file format
|
|
with optional zstd compression. All I/O is performed in C for maximum
|
|
throughput.
|
|
|
|
The binary format uses string/frame deduplication and varint encoding
|
|
for efficient storage.
|
|
"""
|
|
|
|
def __init__(self, filename, sample_interval_usec, *, skip_idle=False,
|
|
compression='auto', mode=None, capture_config=None):
|
|
"""Create a new binary collector.
|
|
|
|
Args:
|
|
filename: Path to output binary file
|
|
sample_interval_usec: Sampling interval in microseconds
|
|
skip_idle: If True, skip idle threads (not used in binary format)
|
|
compression: 'auto', 'zstd', 'none', or int (0=none, 1=zstd)
|
|
mode: Profiling mode, or None if unknown
|
|
capture_config: Mapping of capture feature names to booleans, or
|
|
None if the capture configuration is unknown
|
|
"""
|
|
self.filename = filename
|
|
self.sample_interval_usec = sample_interval_usec
|
|
self.skip_idle = skip_idle
|
|
self.running = True
|
|
|
|
compression_type = _resolve_compression(compression)
|
|
start_time_us = int(time.monotonic() * 1_000_000)
|
|
self._writer = _remote_debugging.BinaryWriter(
|
|
filename, sample_interval_usec, start_time_us,
|
|
compression=compression_type,
|
|
mode=-1 if mode is None else mode,
|
|
capture_features=encode_capture_config(capture_config),
|
|
)
|
|
|
|
def collect(self, stack_frames, timestamp_us=None):
|
|
"""Collect profiling data from stack frames.
|
|
|
|
This passes stack_frames directly to the C writer which handles
|
|
all encoding and buffering.
|
|
|
|
Args:
|
|
stack_frames: List of InterpreterInfo objects from _remote_debugging
|
|
timestamp_us: Optional timestamp in microseconds. If not provided,
|
|
uses time.monotonic() to generate one.
|
|
"""
|
|
if not self.running:
|
|
return
|
|
if timestamp_us is None:
|
|
timestamp_us = int(time.monotonic() * 1_000_000)
|
|
try:
|
|
self._writer.write_sample(stack_frames, timestamp_us)
|
|
except OverflowError as e:
|
|
if not self._writer.limit_reached:
|
|
raise
|
|
self.running = False
|
|
print(f"Warning: {e}; stopping early and keeping the data "
|
|
"collected so far.",
|
|
file=sys.stderr)
|
|
|
|
def collect_failed_sample(self):
|
|
"""Record a failed sample attempt (no-op for binary format)."""
|
|
pass
|
|
|
|
def set_stats(self, sample_interval_usec, duration_sec, sample_rate,
|
|
error_rate=None, missed_samples=None, **kwargs):
|
|
"""Persist measured statistics for later replay."""
|
|
self._writer.set_stats(
|
|
duration_sec, sample_rate, error_rate, missed_samples
|
|
)
|
|
|
|
def export(self, filename=None):
|
|
"""Finalize and close the binary file.
|
|
|
|
Args:
|
|
filename: Ignored (binary files are written incrementally)
|
|
"""
|
|
self._writer.finalize()
|
|
return True
|
|
|
|
@property
|
|
def total_samples(self):
|
|
return self._writer.total_samples
|
|
|
|
def get_stats(self):
|
|
"""Get encoding statistics.
|
|
|
|
Returns:
|
|
Dict with encoding statistics including repeat/full/suffix/pop-push
|
|
record counts, frames written/saved, and compression ratio.
|
|
"""
|
|
return self._writer.get_stats()
|
|
|
|
def __enter__(self):
|
|
return self
|
|
|
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
"""Finalize if the writer can still produce a valid file."""
|
|
return self._writer.__exit__(exc_type, exc_val, exc_tb)
|