diff --git a/Lib/asyncio/__main__.py b/Lib/asyncio/__main__.py index 21ca5c5f62ae83..9fbc77fab5db4f 100644 --- a/Lib/asyncio/__main__.py +++ b/Lib/asyncio/__main__.py @@ -1,7 +1,6 @@ import argparse import ast import asyncio -import asyncio.tools import concurrent.futures import contextvars import inspect @@ -11,6 +10,9 @@ import threading import types import warnings +from asyncio.tools import (TaskTableOutputFormat, + display_awaited_by_tasks_table, + display_awaited_by_tasks_tree) from _colorize import get_theme from _pyrepl.console import InteractiveColoredConsole @@ -153,6 +155,8 @@ def interrupt(self) -> None: "ps", help="Display a table of all pending tasks in a process" ) ps.add_argument("pid", type=int, help="Process ID to inspect") + formats = [fmt.value for fmt in TaskTableOutputFormat] + ps.add_argument("--format", choices=formats, default="table") pstree = subparsers.add_parser( "pstree", help="Display a tree of all pending tasks in a process" ) @@ -160,10 +164,10 @@ def interrupt(self) -> None: args = parser.parse_args() match args.command: case "ps": - asyncio.tools.display_awaited_by_tasks_table(args.pid) + display_awaited_by_tasks_table(args.pid, format=args.format) sys.exit(0) case "pstree": - asyncio.tools.display_awaited_by_tasks_tree(args.pid) + display_awaited_by_tasks_tree(args.pid) sys.exit(0) case None: pass # continue to the interactive shell diff --git a/Lib/asyncio/tools.py b/Lib/asyncio/tools.py index 2683f34cc7113b..efa8e1844cf3d2 100644 --- a/Lib/asyncio/tools.py +++ b/Lib/asyncio/tools.py @@ -1,8 +1,9 @@ """Tools to analyze tasks running in asyncio programs.""" -from collections import defaultdict, namedtuple +from collections import defaultdict +import csv from itertools import count -from enum import Enum +from enum import Enum, StrEnum, auto import sys from _remote_debugging import RemoteUnwinder, FrameInfo @@ -232,18 +233,51 @@ def _get_awaited_by_tasks(pid: int) -> list: sys.exit(1) -def display_awaited_by_tasks_table(pid: int) -> None: +class TaskTableOutputFormat(StrEnum): + table = auto() + csv = auto() + + +def display_awaited_by_tasks_table(pid, *, format=TaskTableOutputFormat.table): """Build and print a table of all pending tasks under `pid`.""" tasks = _get_awaited_by_tasks(pid) table = build_task_table(tasks) - # Print the table in a simple tabular format - print( - f"{'tid':<10} {'task id':<20} {'task name':<20} {'coroutine stack':<50} {'awaiter chain':<50} {'awaiter name':<15} {'awaiter id':<15}" - ) - print("-" * 180) + format = TaskTableOutputFormat(format) + if format == TaskTableOutputFormat.table: + _display_awaited_by_tasks_table(table) + else: + _display_awaited_by_tasks_csv(table, format=format) + + +_row_header = ('tid', 'task id', 'task name', 'coroutine stack', + 'awaiter chain', 'awaiter name', 'awaiter id') + + +def _display_awaited_by_tasks_table(table): + """Print the table in a simple tabular format.""" + print(_fmt_table_row(*_row_header)) + print('-' * 180) for row in table: - print(f"{row[0]:<10} {row[1]:<20} {row[2]:<20} {row[3]:<50} {row[4]:<50} {row[5]:<15} {row[6]:<15}") + print(_fmt_table_row(*row)) + + +def _fmt_table_row(tid, task_id, task_name, coro_stack, + awaiter_chain, awaiter_name, awaiter_id): + # Format a single row for the table format + return (f'{tid:<10} {task_id:<20} {task_name:<20} {coro_stack:<50} ' + f'{awaiter_chain:<50} {awaiter_name:<15} {awaiter_id:<15}') + + +def _display_awaited_by_tasks_csv(table, *, format): + """Print the table in CSV format""" + if format == TaskTableOutputFormat.csv: + delimiter = ',' + else: + raise ValueError(f"Unknown output format: {format}") + csv_writer = csv.writer(sys.stdout, delimiter=delimiter) + csv_writer.writerow(_row_header) + csv_writer.writerows(table) def display_awaited_by_tasks_tree(pid: int) -> None: diff --git a/Misc/NEWS.d/next/Library/2025-05-29-19-00-37.gh-issue-134861.y2-fu-.rst b/Misc/NEWS.d/next/Library/2025-05-29-19-00-37.gh-issue-134861.y2-fu-.rst new file mode 100644 index 00000000000000..07e4c61b404ba4 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-05-29-19-00-37.gh-issue-134861.y2-fu-.rst @@ -0,0 +1 @@ +Add CSV as an output format for :program:`python -m asyncio ps`.