From d6b6c9171f3fd945c4e5e4144923ac831c43c208 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Wed, 4 Dec 2019 22:46:39 -0800 Subject: [PATCH] Include asyncio task information in /-/threads debug page --- datasette/app.py | 21 ++++++++++++++++++++- datasette/templates/show_json.html | 2 +- datasette/views/special.py | 5 ++++- docs/introspection.rst | 8 +++++++- tests/test_api.py | 9 +++++++++ 5 files changed, 41 insertions(+), 4 deletions(-) diff --git a/datasette/app.py b/datasette/app.py index 119d0e19..5c1c3e83 100644 --- a/datasette/app.py +++ b/datasette/app.py @@ -2,6 +2,7 @@ import asyncio import collections import hashlib import os +import re import sys import threading import traceback @@ -477,12 +478,19 @@ class Datasette: def threads(self): threads = list(threading.enumerate()) - return { + d = { "num_threads": len(threads), "threads": [ {"name": t.name, "ident": t.ident, "daemon": t.daemon} for t in threads ], } + # Only available in Python 3.7+ + if hasattr(asyncio, "all_tasks"): + tasks = asyncio.all_tasks() + d.update( + {"num_tasks": len(tasks), "tasks": [_cleaner_task_str(t) for t in tasks]} + ) + return d def table_metadata(self, database, table): "Fetch table-specific metadata." @@ -684,3 +692,14 @@ class DatasetteRouter(AsgiRouter): await asgi_send_html( send, await template.render_async(info), status=status, headers=headers ) + + +_cleaner_task_str_re = re.compile(r"\S*site-packages/") + + +def _cleaner_task_str(task): + s = str(task) + # This has something like the following in it: + # running at /Users/simonw/Dropbox/Development/datasette/venv-3.7.5/lib/python3.7/site-packages/uvicorn/main.py:361> + # Clean up everything up to and including site-packages + return _cleaner_task_str_re.sub("", s) diff --git a/datasette/templates/show_json.html b/datasette/templates/show_json.html index c87ec9f7..b9e49eb2 100644 --- a/datasette/templates/show_json.html +++ b/datasette/templates/show_json.html @@ -7,6 +7,6 @@ {% block content %}

{{ filename }}

-
{{ data|tojson(indent=4) }}
+
{{ data_json }}
{% endblock %} diff --git a/datasette/views/special.py b/datasette/views/special.py index 45e948f6..2b31028d 100644 --- a/datasette/views/special.py +++ b/datasette/views/special.py @@ -27,5 +27,8 @@ class JsonDataView(BaseView): return await self.render( ["show_json.html"], request=request, - context={"filename": self.filename, "data": data}, + context={ + "filename": self.filename, + "data_json": json.dumps(data, indent=4), + }, ) diff --git a/docs/introspection.rst b/docs/introspection.rst index 02552bab..3cd4a40f 100644 --- a/docs/introspection.rst +++ b/docs/introspection.rst @@ -113,7 +113,7 @@ Shows currently attached databases. `Databases example `_:: +Shows details of threads and ``asyncio`` tasks. `Threads example `_:: { "num_threads": 2, @@ -128,5 +128,11 @@ Shows details of threads. `Threads example cb=[set.discard()]>", + " wait_for=()]> cb=[run_until_complete..()]>", + " wait_for=()]>>" ] } diff --git a/tests/test_api.py b/tests/test_api.py index 34eef4ce..cf09c1ba 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -18,6 +18,7 @@ from .fixtures import ( # noqa ) import json import pytest +import sys import urllib @@ -1229,6 +1230,14 @@ def test_metadata_json(app_client): assert METADATA == response.json +def test_threads_json(app_client): + response = app_client.get("/-/threads.json") + expected_keys = {"threads", "num_threads"} + if sys.version_info >= (3, 7, 0): + expected_keys.update({"tasks", "num_tasks"}) + assert expected_keys == set(response.json.keys()) + + def test_plugins_json(app_client): response = app_client.get("/-/plugins.json") assert [