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
125 changes: 123 additions & 2 deletions Doc/library/profiling.sampling.rst
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,10 @@ Attach to a running process by PID::

python -m profiling.sampling attach 12345

Print a single snapshot of a running process's stack::

python -m profiling.sampling dump 12345

Use live mode for real-time monitoring (press ``q`` to quit)::

python -m profiling.sampling run --live script.py
Expand All @@ -173,8 +177,9 @@ Enable opcode-level profiling to see which bytecode instructions are executing::
Commands
========

Tachyon operates through two subcommands that determine how to obtain the
target process.
Tachyon operates through several subcommands. ``run`` and ``attach`` collect
samples over time; ``dump`` captures a single snapshot; ``replay`` converts
binary profiles to other formats.


The ``run`` command
Expand Down Expand Up @@ -217,6 +222,81 @@ On most systems, attaching to another process requires appropriate permissions.
See :ref:`profiling-permissions` for platform-specific requirements.


.. _dump-command:

The ``dump`` command
--------------------

The ``dump`` command prints a single snapshot of a running process's Python
stack and exits, similar to a traceback::

python -m profiling.sampling dump 12345

Unlike ``attach``, ``dump`` does not run a sampling loop: it reads the
stack once. This is useful for investigating hung or unresponsive
processes, or for answering "what is this process doing right now?".

The output mirrors a traceback (most recent call last) and annotates each
thread with its current state (main thread, has GIL, on CPU, waiting for
GIL, has exception, or idle):

.. code-block:: text

Stack dump for PID 12345, thread 140735 (main thread, has GIL, on CPU; most recent call last):
File "server.py", line 28, in serve
await handle_request(req)
File "handler.py", line 91, in handle_request
result = expensive_call(req)

When the target's source files are readable, ``dump`` prints the source
line for each frame and highlights the executing expression. If a source
file's modification time is newer than the target process's start time,
``dump`` replaces the line with ``[source file changed after process
started]`` to avoid showing misleading code.

Like ``attach``, ``dump`` requires permission to read the target process's
memory. See :ref:`profiling-permissions`.

The ``dump`` command supports the following options:

``-a``, ``--all-threads``
Dump every thread in the target process. Without this flag only the main
thread is shown.

``--native``
Include synthetic ``<native>`` frames marking transitions into C
extensions or other non-Python code.

``--no-gc``
Hide the synthetic ``<GC>`` frames that mark active garbage collection.

``--opcodes``
Annotate each frame with the bytecode opcode the thread is currently
executing (for example, ``opcode=CALL_KW``). Useful for
instruction-level investigation, including identifying specializations
chosen by the adaptive interpreter.

``--async-aware``
Reconstruct stacks across ``await`` boundaries. ``dump`` walks the task
graph and emits one section per task, with ``<task>`` markers separating
coroutines awaiting each other.

``--async-mode {running,all}``
Controls which tasks are included when ``--async-aware`` is enabled.
``running`` shows only the task currently executing on each thread;
``all`` (the default for ``dump``) also includes tasks suspended on a
wait. ``attach``'s default for this flag is ``running``; ``dump``
defaults to ``all`` because a single snapshot is most useful when it
shows the full task graph.

``--blocking``
Pause every thread in the target while reading its stack and resume
them after. Guarantees a fully consistent snapshot at the cost of
briefly stopping the target. Without it, ``dump`` reads memory while
the target keeps running, which is faster but can occasionally produce
a torn stack.


.. _replay-command:

The ``replay`` command
Expand Down Expand Up @@ -1441,11 +1521,52 @@ Global options

Attach to and profile a running process by PID.

.. option:: dump

Print a single one-shot snapshot of a running process's Python stack.

.. option:: replay

Convert a binary profile file to another output format.


Dump options
------------

The following options apply to the ``dump`` subcommand:

.. option:: -a, --all-threads

Dump all threads in the target process instead of just the main thread.

.. option:: --native

Include ``<native>`` frames for non-Python code.

.. option:: --no-gc

Exclude ``<GC>`` frames for active garbage collection.

.. option:: --opcodes

Show bytecode opcode names when available.

.. option:: --async-aware

Reconstruct the stack across ``await`` boundaries for asyncio
applications.

.. option:: --async-mode <mode>

Async stack mode: ``running`` (only the running task) or ``all``
(all tasks including waiting). Defaults to ``all`` for ``dump``.
Requires :option:`--async-aware`.

.. option:: --blocking

Pause all threads in the target process while reading the stack.


Sampling options
----------------

Expand Down
3 changes: 3 additions & 0 deletions Doc/whatsnew/3.15.rst
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,9 @@ Key features include:
* Profile running processes by PID (``attach``) - attach to already-running applications
* Run and profile scripts directly (``run``) - profile from the very start of execution
* Execute and profile modules (``run -m``) - profile packages run as ``python -m module``
* Capture a one-shot snapshot of a running process (``dump``) - print a
traceback-style stack of every thread (or all asyncio tasks with
``--async-aware``). Useful for investigating hung processes.

* **Multiple profiling modes**: Choose what to measure based on your performance investigation:

Expand Down
21 changes: 21 additions & 0 deletions Lib/_colorize.py
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,23 @@ class LiveProfiler(ThemeSection):
)


@dataclass(frozen=True, kw_only=True)
class ProfilerDump(ThemeSection):
header: str = ANSIColors.BOLD_BLUE
interpreter: str = ANSIColors.GREY
thread: str = ANSIColors.BOLD_CYAN
status: str = ANSIColors.YELLOW
frame_index: str = ANSIColors.GREY
frame: str = ANSIColors.BOLD_GREEN
filename: str = ANSIColors.CYAN
line_no: str = ANSIColors.YELLOW
source: str = ANSIColors.WHITE
source_highlight: str = ANSIColors.BOLD_YELLOW
opcode: str = ANSIColors.GREY
warning: str = ANSIColors.YELLOW
reset: str = ANSIColors.RESET


@dataclass(frozen=True, kw_only=True)
class Pickletools(ThemeSection):
annotation: str = ANSIColors.GREY
Expand Down Expand Up @@ -447,6 +464,7 @@ class Theme:
http_server: HttpServer = field(default_factory=HttpServer)
live_profiler: LiveProfiler = field(default_factory=LiveProfiler)
pickletools: Pickletools = field(default_factory=Pickletools)
profiler_dump: ProfilerDump = field(default_factory=ProfilerDump)
syntax: Syntax = field(default_factory=Syntax)
timeit: Timeit = field(default_factory=Timeit)
tokenize: Tokenize = field(default_factory=Tokenize)
Expand All @@ -463,6 +481,7 @@ def copy_with(
http_server: HttpServer | None = None,
live_profiler: LiveProfiler | None = None,
pickletools: Pickletools | None = None,
profiler_dump: ProfilerDump | None = None,
syntax: Syntax | None = None,
timeit: Timeit | None = None,
tokenize: Tokenize | None = None,
Expand All @@ -482,6 +501,7 @@ def copy_with(
http_server=http_server or self.http_server,
live_profiler=live_profiler or self.live_profiler,
pickletools=pickletools or self.pickletools,
profiler_dump=profiler_dump or self.profiler_dump,
syntax=syntax or self.syntax,
timeit=timeit or self.timeit,
tokenize=tokenize or self.tokenize,
Expand All @@ -505,6 +525,7 @@ def no_colors(cls) -> Self:
http_server=HttpServer.no_colors(),
live_profiler=LiveProfiler.no_colors(),
pickletools=Pickletools.no_colors(),
profiler_dump=ProfilerDump.no_colors(),
syntax=Syntax.no_colors(),
timeit=Timeit.no_colors(),
tokenize=Tokenize.no_colors(),
Expand Down
Loading
Loading