Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions Doc/library/profiling.sampling.rst
Original file line number Diff line number Diff line change
Expand Up @@ -387,11 +387,6 @@ This requires one of:
On Windows, the profiler requires administrative privileges or the
``SeDebugPrivilege`` privilege to read another process's memory.

*Note*: On Windows, ``python -m profiling.sampling`` fails inside a virtual
environment because the venv's ``python.exe`` is just a launcher shim that
re-executes the base interpreter as a child process. The shim itself isn't
a Python process and has no ``PyRuntime`` section to attach to. Instead,
run it from the global Python installation.

Version compatibility
---------------------
Expand Down
35 changes: 30 additions & 5 deletions Lib/profiling/sampling/sample.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,38 @@ def _pause_threads(unwinder, blocking):
# Maximum number of consecutive identical samples to keep before flushing.
MAX_PENDING_SAMPLES = 8192


def _resolve_venv_child_pid(pid):
"""On Windows, if pid is a venvlauncher process, return the child Python PID.

The venvlauncher (used as python.exe in venvs) spawns the real Python
interpreter as a child process via CreateProcessW. The RemoteUnwinder
needs the child's PID, not the launcher's.

Returns the original pid if not on Windows, not a venv launcher,
or no child process is found.
"""
if os.name != "nt" or sys.prefix == sys.base_prefix:
return pid
try:
children = _remote_debugging.get_child_pids(pid, recursive=False)
python_children = [
child for child in children
if _remote_debugging.is_python_process(child)
]
if len(python_children) == 1:
return python_children[0]
except (OSError, RuntimeError) as err:
raise SystemExit(
f"Failed to initialize profiler from virtualenv: {err}\n"
f"Try running with the base interpreter: {sys._base_executable}"
) from err
return pid


class SampleProfiler:
def __init__(self, pid, sample_interval_usec, all_threads, *, mode=PROFILING_MODE_WALL, native=False, gc=True, opcodes=False, skip_non_matching_threads=True, collect_stats=False, blocking=False):
self.pid = pid
self.pid = _resolve_venv_child_pid(pid)
self.sample_interval_usec = sample_interval_usec
self.all_threads = all_threads
self.mode = mode # Store mode for later use
Expand All @@ -61,10 +90,6 @@ def __init__(self, pid, sample_interval_usec, all_threads, *, mode=PROFILING_MOD
try:
self.unwinder = self._new_unwinder(native, gc, opcodes, skip_non_matching_threads)
except RuntimeError as err:
if os.name == "nt" and sys.executable.endswith("python.exe"):
raise SystemExit(
"Running profiling.sampling from virtualenv on Windows platform is not supported"
) from err
raise SystemExit(err) from err
# Track sample intervals and total sample count
self.sample_intervals = deque(maxlen=100)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix ``profiling.sampling`` on Windows virtual environments to resolve the actual Python PID from a virtual environment shim.
Loading