Skip to content
Permalink
Browse files

Added ?_trace=1 option to trace SQL

Currently just dumps all SQL statements out on the console.
  • Loading branch information...
simonw committed Apr 21, 2019
1 parent 31f36e1 commit 7d01ca34a10b5f8a993859cfd05790eb2870b94e
Showing with 69 additions and 4 deletions.
  1. +26 −4 datasette/app.py
  2. +41 −0 datasette/tracer.py
  3. +2 −0 pytest.ini
@@ -2,6 +2,7 @@
import click
import collections
import hashlib
import json
import os
import sys
import threading
@@ -38,6 +39,7 @@
to_css_class
)
from .inspect import inspect_hash, inspect_views, inspect_tables
from .tracer import capture_traces, trace
from .plugins import pm, DEFAULT_PLUGINS
from .version import __version__

@@ -622,12 +624,25 @@ def sql_operation_in_thread(conn):
else:
return Results(rows, False, cursor.description)

return await self.execute_against_connection_in_thread(
db_name, sql_operation_in_thread
)
with trace("sql", (db_name, sql, params)):
results = await self.execute_against_connection_in_thread(
db_name, sql_operation_in_thread
)
return results

def app(self):
app = Sanic(__name__)

class TracingSanic(Sanic):
async def handle_request(self, request, write_callback, stream_callback):
if request.args.get("_trace"):
request["traces"] = []
with capture_traces(request["traces"]):
res = await super().handle_request(request, write_callback, stream_callback)
else:
res = await super().handle_request(request, write_callback, stream_callback)
return res

app = TracingSanic(__name__)
default_templates = str(app_root / "datasette" / "templates")
template_paths = []
if self.template_dir:
@@ -702,6 +717,7 @@ def app(self):
r"/<db_name:[^/]+>/<table:[^/]+?>/<pk_path:[^/]+?><as_format:(\.jsono?)?$>",
)
self.register_custom_units()

# On 404 with a trailing slash redirect to path without that slash:
# pylint: disable=unused-variable
@app.middleware("response")
@@ -712,6 +728,12 @@ def redirect_on_404_with_trailing_slash(request, original_response):
path = "{}?{}".format(path, request.query_string)
return response.redirect(path)

@app.middleware("response")
async def print_traces(request, response):
if request.get("traces") is not None:
print(json.dumps(request["traces"], indent=2))
print("Num traces: {}".format(len(request["traces"])))

@app.exception(Exception)
def on_exception(request, exception):
title = None
@@ -0,0 +1,41 @@
import asyncio
from contextlib import contextmanager
import time

tracers = {}


def get_task_id():
try:
loop = asyncio.get_event_loop()
except RuntimeError:
return None
return id(asyncio.Task.current_task(loop=loop))


@contextmanager
def trace(type, action):
task_id = get_task_id()
if task_id is None:
yield
return
tracer = tracers.get(task_id)
if tracer is None:
yield
return
begin = time.time()
yield
end = time.time()
tracer.append((type, action, begin, end, 1000 * (end - begin)))


@contextmanager
def capture_traces(tracer):
# tracer is a list
task_id = get_task_id()
if task_id is None:
yield
return
tracers[task_id] = tracer
yield
del tracers[task_id]
@@ -6,3 +6,5 @@ filterwarnings=
ignore:Using or importing the ABCs::bs4.element
# Sanic verify_ssl=True
ignore:verify_ssl is deprecated::sanic
# Python 3.7 PendingDeprecationWarning: Task.current_task()
ignore:.*current_task.*:PendingDeprecationWarning

0 comments on commit 7d01ca3

Please sign in to comment.
You can’t perform that action at this time.