.. currentmodule:: asyncio
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.
.. 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.
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.