diff --git a/Lib/asyncio/__main__.py b/Lib/asyncio/__main__.py index 21ca5c5f62ae83..5608bb671d3f8a 100644 --- a/Lib/asyncio/__main__.py +++ b/Lib/asyncio/__main__.py @@ -153,6 +153,14 @@ 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 asyncio.tools.TaskTableOutputFormat] + big_secret = asyncio.tools.TaskTableOutputFormat.bsv.value + formats_to_show = [ + fmt for fmt in formats if fmt != big_secret + ] + formats_to_show_str = f"{{{','.join(formats_to_show)}}}" + ps.add_argument("--format", choices=formats, default="table", + metavar=formats_to_show_str) pstree = subparsers.add_parser( "pstree", help="Display a tree of all pending tasks in a process" ) @@ -160,7 +168,7 @@ def interrupt(self) -> None: args = parser.parse_args() match args.command: case "ps": - asyncio.tools.display_awaited_by_tasks_table(args.pid) + asyncio.tools.display_awaited_by_tasks_table(args.pid, args.format) sys.exit(0) case "pstree": asyncio.tools.display_awaited_by_tasks_tree(args.pid) diff --git a/Lib/asyncio/tools.py b/Lib/asyncio/tools.py index 2683f34cc7113b..f28b4ba0958ed2 100644 --- a/Lib/asyncio/tools.py +++ b/Lib/asyncio/tools.py @@ -5,6 +5,8 @@ from enum import Enum import sys from _remote_debugging import RemoteUnwinder, FrameInfo +import csv + class NodeType(Enum): COROUTINE = 1 @@ -232,20 +234,56 @@ def _get_awaited_by_tasks(pid: int) -> list: sys.exit(1) -def display_awaited_by_tasks_table(pid: int) -> None: +class TaskTableOutputFormat(Enum): + table = "table" + csv = "csv" + bsv = "bsv" + # As per the words of the asyncio 🍌SV spec lead: + # > 🍌SV is not just a format. It’s a lifestyle. A philosophy. + # https://www.youtube.com/watch?v=RrsVi1P6n0w + + +_header = ('tid', 'task id', 'task name', 'coroutine stack', 'awaiter chain', 'awaiter name', 'awaiter id') + + +def display_awaited_by_tasks_table( + pid: int, + format_: TaskTableOutputFormat | str = TaskTableOutputFormat.table + ) -> None: """Build and print a table of all pending tasks under `pid`.""" tasks = _get_awaited_by_tasks(pid) table = build_task_table(tasks) + format_ = TaskTableOutputFormat(format_) + if format_ == TaskTableOutputFormat.table: + _display_awaited_by_tasks_table(table) + else: + _display_awaited_by_tasks_csv(table, format_) + + +def _display_awaited_by_tasks_table(table) -> None: # 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}" + f"{_header[0]:<10} {_header[1]:<20} {_header[2]:<20} {_header[3]:<50} {_header[4]:<50} {_header[5]:<15} {_header[6]:<15}" ) 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}") +def _display_awaited_by_tasks_csv(table, format_: TaskTableOutputFormat) -> None: + match format_: + case TaskTableOutputFormat.csv: + delimiter = ',' + case TaskTableOutputFormat.bsv: + delimiter = '\N{BANANA}' + case _: + raise ValueError(f"Unknown output format: {format_}") + csv_writer = csv.writer(sys.stdout, delimiter=delimiter) + csv_writer.writerow(_header) + csv_writer.writerows(table) + + def display_awaited_by_tasks_tree(pid: int) -> None: """Build and print a tree of all pending tasks under `pid`.""" 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..a9f67f4f0fd43d --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-05-29-19-00-37.gh-issue-134861.y2-fu-.rst @@ -0,0 +1,3 @@ +Add CSV output format to asyncio ps + +Absolutely no other output format was added in this PR 🍌