.. currentmodule:: asyncio


.. _asyncio-graph:

========================
Call graph introspection
========================

**Source code:** :source:`Lib/asyncio/graph.py`

-------------------------------------

asyncio has powerful runtime call graph introspection utilities
to trace the entire call graph of a running *coroutine* or *task*, or
a suspended *future*.  These utilities and the underlying machinery
can be used from within a Python program or by external profilers
and debuggers.

.. seealso::

   :ref:`asyncio-introspection-tools`
      Command-line tools for inspecting tasks in another running Python
      process.

.. versionadded:: 3.14


.. function:: print_call_graph(future=None, /, *, file=None, depth=1, limit=None)

   Print the async call graph for the current task or the provided
   :class:`Task` or :class:`Future`.

   This function prints entries starting from the top frame and going
   down towards the invocation point.

   The function receives an optional *future* argument.
   If not passed, the current running task will be used.

   If the function is called on *the current task*, the optional
   keyword-only *depth* argument can be used to skip the specified
   number of frames from top of the stack.

   If the optional keyword-only *limit* argument is provided, each call stack
   in the resulting graph is truncated to include at most ``abs(limit)``
   entries. If *limit* is positive, the entries left are the closest to
   the invocation point. If *limit* is negative, the topmost entries are
   left. If *limit* is omitted or ``None``, all entries are present.
   If *limit* is ``0``, the call stack is not printed at all, only
   "awaited by" information is printed.

   If *file* is omitted or ``None``, the function will print
   to :data:`sys.stdout`.

   **Example:**

   The following Python code:

   .. code-block:: python

      import asyncio

      async def test():
          asyncio.print_call_graph()

      async def main():
          async with asyncio.TaskGroup() as g:
              g.create_task(test(), name='test')

      asyncio.run(main())

   will print::

      * Task(name='test', id=0x1039f0fe0)
      + Call stack:
      |   File 't2.py', line 4, in async test()
      + Awaited by:
         * Task(name='Task-1', id=0x103a5e060)
            + Call stack:
            |   File 'taskgroups.py', line 107, in async TaskGroup.__aexit__()
            |   File 't2.py', line 7, in async main()

.. function:: format_call_graph(future=None, /, *, depth=1, limit=None)

   Like :func:`print_call_graph`, but returns a string.
   If *future* is ``None`` and there's no current task,
   the function returns an empty string.


.. function:: capture_call_graph(future=None, /, *, depth=1, limit=None)

   Capture the async call graph for the current task or the provided
   :class:`Task` or :class:`Future`.

   The function receives an optional *future* argument.
   If not passed, the current running task will be used. If there's no
   current task, the function returns ``None``.

   If the function is called on *the current task*, the optional
   keyword-only *depth* argument can be used to skip the specified
   number of frames from top of the stack.

   Returns a ``FutureCallGraph`` data class object:

   * ``FutureCallGraph(future, call_stack, awaited_by)``

      Where *future* is a reference to a :class:`Future` or
      a :class:`Task` (or their subclasses.)

      ``call_stack`` is a tuple of ``FrameCallGraphEntry`` objects.

      ``awaited_by`` is a tuple of ``FutureCallGraph`` objects.

   * ``FrameCallGraphEntry(frame)``

      Where *frame* is a frame object of a regular Python function
      in the call stack.


Low level utility functions
===========================

To introspect an async call graph asyncio requires cooperation from
control flow structures, such as :func:`shield` or :class:`TaskGroup`.
Any time an intermediate :class:`Future` object with low-level APIs like
:meth:`Future.add_done_callback() <asyncio.Future.add_done_callback>` is
involved, the following two functions should be used to inform asyncio
about how exactly such intermediate future objects are connected with
the tasks they wrap or control.


.. function:: future_add_to_awaited_by(future, waiter, /)

   Record that *future* is awaited on by *waiter*.

   Both *future* and *waiter* must be instances of
   :class:`Future` or :class:`Task` or their subclasses,
   otherwise the call would have no effect.

   A call to ``future_add_to_awaited_by()`` must be followed by an
   eventual call to the :func:`future_discard_from_awaited_by` function
   with the same arguments.


.. function:: future_discard_from_awaited_by(future, waiter, /)

   Record that *future* is no longer awaited on by *waiter*.

   Both *future* and *waiter* must be instances of
   :class:`Future` or :class:`Task` or their subclasses, otherwise
   the call would have no effect.
