--- /dev/null
+.. currentmodule:: asyncio
+
+.. _asyncio-introspection-tools:
+
+================================
+Command-line introspection tools
+================================
+
+**Source code:** :source:`Lib/asyncio/tools.py`
+
+-------------------------------------
+
+The :mod:`!asyncio` module can be invoked as a script via ``python -m
+asyncio`` to inspect the task graph of another running Python process without
+modifying it or restarting it. The :mod:`!asyncio.tools` submodule implements
+this interface.
+
+The following commands inspect the process identified by ``PID``:
+
+.. code-block:: shell-session
+
+ $ python -m asyncio pstree [--retries N] PID
+ $ python -m asyncio ps [--retries N] PID
+
+The commands read the target process state without executing any code in it.
+They are only available on supported platforms and may require permission to
+inspect another process. See the :ref:`permission-requirements <permission-requirements>` for details.
+
+.. seealso::
+
+ :ref:`asyncio-graph`
+ Programmatic APIs for inspecting the async call graph of a task or
+ future in the current process.
+
+The command examples below use this program, which creates a task hierarchy
+suitable for inspection and prints its process ID:
+
+.. code-block:: python
+ :caption: example.py
+
+ import asyncio
+ import os
+
+ async def play(track):
+ await asyncio.sleep(3600)
+ print(f"🎵 Finished: {track}")
+
+ async def album(name, tracks):
+ async with asyncio.TaskGroup() as tg:
+ for track in tracks:
+ tg.create_task(play(track), name=track)
+
+ async def main():
+ print(f"PID: {os.getpid()}")
+ async with asyncio.TaskGroup() as tg:
+ tg.create_task(
+ album("Sundowning", ["TNDNBTG", "Levitate"]),
+ name="Sundowning",
+ )
+ tg.create_task(
+ album("TMBTE", ["DYWTYLM", "Aqua Regia"]),
+ name="TMBTE",
+ )
+
+ asyncio.run(main())
+
+Run the program in one terminal and leave it running:
+
+.. code-block:: shell-session
+
+ $ python example.py
+ PID: 12345
+
+Then pass the printed process ID to the commands from another terminal.
+Thread IDs, task IDs, file paths, and line numbers vary between runs and
+source layouts.
+
+.. versionadded:: 3.14
+
+Command-line options
+====================
+
+.. option:: pstree PID
+
+ Display task and coroutine relationships as a tree. Each task is shown
+ with its full coroutine stack, nested under the task (if any) that is
+ awaiting it. This subcommand is useful for quickly identifying which branch
+ of a task hierarchy is blocked and where in its coroutine stack execution
+ has paused:
+
+ .. code-block:: shell-session
+
+ $ python -m asyncio pstree 12345
+ └── (T) Task-1
+ └── main example.py:12
+ └── TaskGroup.__aexit__ Lib/asyncio/taskgroups.py:75
+ └── TaskGroup._aexit Lib/asyncio/taskgroups.py:124
+ ├── (T) Sundowning
+ │ └── album example.py:7
+ │ └── TaskGroup.__aexit__ Lib/asyncio/taskgroups.py:75
+ │ └── TaskGroup._aexit Lib/asyncio/taskgroups.py:124
+ │ ├── (T) TNDNBTG
+ │ │ └── play example.py:4
+ │ │ └── sleep Lib/asyncio/tasks.py:702
+ │ └── (T) Levitate
+ │ └── play example.py:4
+ │ └── sleep Lib/asyncio/tasks.py:702
+ └── (T) TMBTE
+ └── album example.py:7
+ └── TaskGroup.__aexit__ Lib/asyncio/taskgroups.py:75
+ └── TaskGroup._aexit Lib/asyncio/taskgroups.py:124
+ ├── (T) DYWTYLM
+ │ └── play example.py:4
+ │ └── sleep Lib/asyncio/tasks.py:702
+ └── (T) Aqua Regia
+ └── play example.py:4
+ └── sleep Lib/asyncio/tasks.py:702
+
+ If the await graph contains a cycle, ``pstree`` reports an error instead
+ of printing a tree. A cycle in the await graph is unusual and typically
+ indicates a programming error:
+
+ .. code-block:: shell-session
+
+ $ python -m asyncio pstree 12345
+ ERROR: await-graph contains cycles - cannot print a tree!
+
+ cycle: Task-2 → Task-3 → Task-2
+
+.. option:: ps PID
+
+ Display a flat table of all pending tasks in the process *PID*. Each row
+ shows the event-loop thread ID, task ID and name, coroutine stack, and the
+ awaiting task's stack, name, and ID, if any.
+
+ This subcommand prints all tasks regardless of whether the await graph
+ contains cycles:
+
+ .. code-block:: shell-session
+
+ $ python -m asyncio ps 12345
+ tid task id task name coroutine stack awaiter chain awaiter name awaiter id
+ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ 18445801 0x10a456060 Task-1 TaskGroup._aexit -> TaskGroup.__aexit__ -> main 0x0
+ 18445801 0x10a439f60 Sundowning TaskGroup._aexit -> TaskGroup.__aexit__ -> album TaskGroup._aexit -> TaskGroup.__aexit__ -> main Task-1 0x10a456060
+ 18445801 0x10a439d70 TMBTE TaskGroup._aexit -> TaskGroup.__aexit__ -> album TaskGroup._aexit -> TaskGroup.__aexit__ -> main Task-1 0x10a456060
+ 18445801 0x10a2a3a80 TNDNBTG sleep -> play TaskGroup._aexit -> TaskGroup.__aexit__ -> album Sundowning 0x10a439f60
+ 18445801 0x10a2a38a0 Levitate sleep -> play TaskGroup._aexit -> TaskGroup.__aexit__ -> album Sundowning 0x10a439f60
+ 18445801 0x10a2d7150 DYWTYLM sleep -> play TaskGroup._aexit -> TaskGroup.__aexit__ -> album TMBTE 0x10a439d70
+ 18445801 0x10a6bdaa0 Aqua Regia sleep -> play TaskGroup._aexit -> TaskGroup.__aexit__ -> album TMBTE 0x10a439d70
+
+.. option:: --retries N
+
+ Retry failed attempts to inspect the target process up to *N* times. This
+ can help when the target process changes while its state is being read.
+
+ .. versionadded:: 3.15