Skip to content

Commit 3fb2745

Browse files
committed
WIP
1 parent 54ff730 commit 3fb2745

20 files changed

+208
-44
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ package-lock.json: package.json
2929
touch $@
3030

3131
test:
32-
DJANGO_SETTINGS_MODULE=tests.settings \
32+
DB_BACKEND=sqlite3 DB_NAME=":memory:" DJANGO_SETTINGS_MODULE=tests.settings \
3333
python -m django test $${TEST_ARGS:-tests}
3434

3535
test_selenium:

debug_toolbar/db_store.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
from debug_toolbar import store
2+
from debug_toolbar.models import PanelStore, ToolbarStore
3+
4+
5+
class DBStore(store.BaseStore):
6+
@classmethod
7+
def ids(cls):
8+
return (
9+
ToolbarStore.objects.using("debug_toolbar")
10+
.values_list("key", flat=True)
11+
.order_by("created")
12+
)
13+
14+
@classmethod
15+
def exists(cls, store_id):
16+
return ToolbarStore.objects.using("debug_toolbar").filter(key=store_id).exists()
17+
18+
@classmethod
19+
def set(cls, store_id):
20+
_, created = ToolbarStore.objects.using("debug_toolbar").get_or_create(
21+
key=store_id
22+
)
23+
if (
24+
created
25+
and ToolbarStore.objects.using("debug_toolbar").all().count()
26+
> cls.config["RESULTS_CACHE_SIZE"]
27+
):
28+
ToolbarStore.objects.using("debug_toolbar").earliest("created").delete()
29+
30+
@classmethod
31+
def delete(cls, store_id):
32+
ToolbarStore.objects.using("debug_toolbar").filter(key=store_id).delete()
33+
34+
@classmethod
35+
def save_panel(cls, store_id, panel_id, stats=None):
36+
toolbar, _ = ToolbarStore.objects.using("debug_toolbar").get_or_create(
37+
key=store_id
38+
)
39+
toolbar.panelstore_set.update_or_create(
40+
panel=panel_id, defaults={"data": store.serialize(stats)}
41+
)
42+
43+
@classmethod
44+
def panel(cls, store_id, panel_id):
45+
panel = (
46+
PanelStore.objects.using("debug_toolbar")
47+
.filter(toolbar__key=store_id, panel=panel_id)
48+
.first()
49+
)
50+
return {} if not panel else store.deserialize(panel.data)
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Generated by Django 3.1.5 on 2021-01-09 17:02
2+
import django.db.models.deletion
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
initial = True
8+
9+
dependencies = []
10+
11+
operations = [
12+
migrations.CreateModel(
13+
name="ToolbarStore",
14+
fields=[
15+
(
16+
"id",
17+
models.AutoField(
18+
auto_created=True,
19+
primary_key=True,
20+
serialize=False,
21+
verbose_name="ID",
22+
),
23+
),
24+
("created", models.DateTimeField(auto_now_add=True)),
25+
("key", models.CharField(max_length=64, unique=True)),
26+
],
27+
),
28+
migrations.CreateModel(
29+
name="PanelStore",
30+
fields=[
31+
(
32+
"id",
33+
models.AutoField(
34+
auto_created=True,
35+
primary_key=True,
36+
serialize=False,
37+
verbose_name="ID",
38+
),
39+
),
40+
("created", models.DateTimeField(auto_now_add=True)),
41+
("panel", models.CharField(max_length=128)),
42+
("data", models.TextField()),
43+
(
44+
"toolbar",
45+
models.ForeignKey(
46+
on_delete=django.db.models.deletion.CASCADE,
47+
to="debug_toolbar.toolbarstore",
48+
),
49+
),
50+
],
51+
),
52+
migrations.AddConstraint(
53+
model_name="panelstore",
54+
constraint=models.UniqueConstraint(
55+
fields=("toolbar", "panel"), name="unique_toolbar_panel"
56+
),
57+
),
58+
]

debug_toolbar/migrations/__init__.py

Whitespace-only changes.

debug_toolbar/models.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from django.db import models
2+
3+
4+
class ToolbarStore(models.Model):
5+
created = models.DateTimeField(auto_now_add=True)
6+
key = models.CharField(max_length=64, unique=True)
7+
8+
9+
class PanelStore(models.Model):
10+
created = models.DateTimeField(auto_now_add=True)
11+
toolbar = models.ForeignKey(ToolbarStore, on_delete=models.CASCADE)
12+
panel = models.CharField(max_length=128)
13+
data = models.TextField()

debug_toolbar/panels/__init__.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from django.template.loader import render_to_string
22

33
from debug_toolbar import settings as dt_settings
4-
from debug_toolbar.store import store
4+
from debug_toolbar.store import get_store
55
from debug_toolbar.utils import get_name_from_obj
66

77

@@ -44,7 +44,7 @@ def enabled(self):
4444
== "on"
4545
)
4646
else:
47-
return bool(store.panel(self.toolbar.store_id, self.panel_id))
47+
return bool(get_store().panel(self.toolbar.store_id, self.panel_id))
4848

4949
# Titles and content
5050

@@ -182,15 +182,17 @@ def record_stats(self, stats):
182182
Each call to ``record_stats`` updates the statistics dictionary.
183183
"""
184184
self.toolbar.stats.setdefault(self.panel_id, {}).update(stats)
185-
store.save_panel(
185+
get_store().save_panel(
186186
self.toolbar.store_id, self.panel_id, self.serialize_stats(stats)
187187
)
188188

189189
def get_stats(self):
190190
"""
191191
Access data stored by the panel. Returns a :class:`dict`.
192192
"""
193-
return self.deserialize_stats(store.panel(self.toolbar.store_id, self.panel_id))
193+
return self.deserialize_stats(
194+
get_store().panel(self.toolbar.store_id, self.panel_id)
195+
)
194196

195197
def record_server_timing(self, key, title, value):
196198
"""

debug_toolbar/panels/cache.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -246,14 +246,15 @@ def _store_call_info(
246246

247247
@property
248248
def nav_subtitle(self):
249-
cache_calls = len(self.calls)
249+
stats = self.get_stats()
250+
cache_calls = len(stats["calls"])
250251
return (
251252
ngettext(
252253
"%(cache_calls)d call in %(time).2fms",
253254
"%(cache_calls)d calls in %(time).2fms",
254255
cache_calls,
255256
)
256-
% {"cache_calls": cache_calls, "time": self.total_time}
257+
% {"cache_calls": cache_calls, "time": stats["total_time"]}
257258
)
258259

259260
@property

debug_toolbar/panels/history/panel.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from debug_toolbar.panels import Panel
1313
from debug_toolbar.panels.history import views
1414
from debug_toolbar.panels.history.forms import HistoryStoreForm
15-
from debug_toolbar.store import store
15+
from debug_toolbar.store import get_store
1616

1717

1818
class HistoryPanel(Panel):
@@ -82,13 +82,15 @@ def content(self):
8282
Fetch every store for the toolbar and include it in the template.
8383
"""
8484
histories = OrderedDict()
85-
for id in reversed(store.ids()):
86-
histories[id] = {
87-
"stats": self.deserialize_stats(store.panel(id, self.panel_id)),
88-
"form": SignedDataForm(
89-
initial=HistoryStoreForm(initial={"store_id": id}).initial
90-
),
91-
}
85+
for id in reversed(get_store().ids()):
86+
stats = self.deserialize_stats(get_store().panel(id, self.panel_id))
87+
if stats:
88+
histories[id] = {
89+
"stats": stats,
90+
"form": SignedDataForm(
91+
initial=HistoryStoreForm(initial={"store_id": str(id)}).initial
92+
),
93+
}
9294

9395
return render_to_string(
9496
self.template,

debug_toolbar/panels/history/views.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from debug_toolbar.decorators import require_show_toolbar, signed_data_view
55
from debug_toolbar.forms import SignedDataForm
66
from debug_toolbar.panels.history.forms import HistoryStoreForm
7-
from debug_toolbar.store import store
7+
from debug_toolbar.store import get_store
88
from debug_toolbar.toolbar import stats_only_toolbar
99

1010

@@ -48,7 +48,7 @@ def history_refresh(request, verified_data):
4848

4949
if form.is_valid():
5050
requests = []
51-
for id in reversed(store.ids()):
51+
for id in reversed(get_store().ids()):
5252
toolbar = stats_only_toolbar(id)
5353
requests.append(
5454
{

debug_toolbar/panels/sql/panel.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,18 +110,20 @@ def record(self, alias, **kwargs):
110110

111111
@property
112112
def nav_subtitle(self):
113+
stats = self.get_stats()
114+
num_queries = len(stats["queries"])
113115
return ngettext(
114116
"%(query_count)d query in %(sql_time).2fms",
115117
"%(query_count)d queries in %(sql_time).2fms",
116-
self._num_queries,
118+
num_queries,
117119
) % {
118-
"query_count": self._num_queries,
119-
"sql_time": self._sql_time,
120+
"query_count": num_queries,
121+
"sql_time": stats["sql_time"],
120122
}
121123

122124
@property
123125
def title(self):
124-
count = len(self._databases)
126+
count = len(self.get_stats()["databases"])
125127
return (
126128
ngettext(
127129
"SQL queries from %(count)d connection",
@@ -144,10 +146,14 @@ def get_urls(cls):
144146
def enable_instrumentation(self):
145147
# This is thread-safe because database connections are thread-local.
146148
for connection in connections.all():
149+
if connection.alias == "debug_toolbar":
150+
continue
147151
wrap_cursor(connection, self)
148152

149153
def disable_instrumentation(self):
150154
for connection in connections.all():
155+
if connection.alias == "debug_toolbar":
156+
continue
151157
unwrap_cursor(connection)
152158

153159
def generate_stats(self, request, response):

debug_toolbar/panels/staticfiles.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,10 @@ class StaticFilesPanel(panels.Panel):
8282

8383
@property
8484
def title(self):
85+
stats = self.get_stats()
8586
return _("Static files (%(num_found)s found, %(num_used)s used)") % {
86-
"num_found": self.num_found,
87-
"num_used": self.num_used,
87+
"num_found": stats["num_found"],
88+
"num_used": stats["num_used"],
8889
}
8990

9091
def __init__(self, *args, **kwargs):
@@ -107,7 +108,7 @@ def num_used(self):
107108

108109
@property
109110
def nav_subtitle(self):
110-
num_used = self.num_used
111+
num_used = self.get_stats()["num_used"]
111112
return ngettext(
112113
"%(num_used)s file used", "%(num_used)s files used", num_used
113114
) % {"num_used": num_used}

debug_toolbar/panels/timer.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,9 @@ class TimerPanel(Panel):
1919

2020
def nav_subtitle(self):
2121
stats = self.get_stats()
22-
if hasattr(self, "_start_rusage"):
23-
utime = self._end_rusage.ru_utime - self._start_rusage.ru_utime
24-
stime = self._end_rusage.ru_stime - self._start_rusage.ru_stime
22+
if stats.get("utime"):
2523
return _("CPU: %(cum)0.2fms (%(total)0.2fms)") % {
26-
"cum": (utime + stime) * 1000.0,
24+
"cum": stats["utime"],
2725
"total": stats["total_time"],
2826
}
2927
elif "total_time" in stats:

debug_toolbar/store.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ def exists(cls, store_id):
6363
def set(cls, store_id):
6464
if store_id not in cls._ids:
6565
cls._ids.append(store_id)
66-
if len(cls._ids) > cls.config["RESULTS_CACHE_SIZE"]:
66+
for _ in range(len(cls._ids) - cls.config["RESULTS_CACHE_SIZE"]):
6767
cls.delete(cls._ids[0])
6868

6969
@classmethod
@@ -86,4 +86,5 @@ def panel(cls, store_id, panel_id):
8686
return {} if data is None else deserialize(data)
8787

8888

89-
store = import_string(dt_settings.get_config()["TOOLBAR_STORE_CLASS"])
89+
def get_store():
90+
return import_string(dt_settings.get_config()["TOOLBAR_STORE_CLASS"])

debug_toolbar/toolbar.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from django.utils.module_loading import import_string
1515

1616
from debug_toolbar import settings as dt_settings
17-
from debug_toolbar.store import store
17+
from debug_toolbar.store import get_store
1818

1919

2020
class DebugToolbar:
@@ -89,7 +89,7 @@ def should_render_panels(self):
8989
return render_panels
9090

9191
def store(self):
92-
store.set(self.store_id)
92+
get_store().set(self.store_id)
9393

9494
# Manually implement class-level caching of panel classes and url patterns
9595
# because it's more obvious than going through an abstraction.

debug_toolbar/views.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@
33
from django.utils.translation import gettext as _
44

55
from debug_toolbar.decorators import require_show_toolbar
6-
from debug_toolbar.store import store
6+
from debug_toolbar.store import get_store
77
from debug_toolbar.toolbar import stats_only_toolbar
88

99

1010
@require_show_toolbar
1111
def render_panel(request):
1212
"""Render the contents of a panel"""
1313
store_id = request.GET["store_id"]
14-
if not store.exists(store_id):
14+
if not get_store().exists(store_id):
1515
content = _(
1616
"Data for this panel isn't available anymore. "
1717
"Please reload the page and retry."

tests/base.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@
22
from django.http import HttpResponse
33
from django.test import RequestFactory, TestCase
44

5-
from debug_toolbar.store import store
5+
from debug_toolbar.store import get_store
66
from debug_toolbar.toolbar import DebugToolbar
77

88
rf = RequestFactory()
99

1010

1111
class BaseTestCase(TestCase):
12+
databases = {"default", "debug_toolbar"}
1213
panel_id = None
1314

1415
def setUp(self):
@@ -49,10 +50,12 @@ def assertValidHTML(self, content, msg=None):
4950
class IntegrationTestCase(TestCase):
5051
"""Base TestCase for tests involving clients making requests."""
5152

53+
databases = {"default", "debug_toolbar"}
54+
5255
def setUp(self):
5356
# The HistoryPanel keeps track of previous stores in memory.
5457
# This bleeds into other tests and violates their idempotency.
5558
# Clear the store before each test.
56-
for key in list(store.ids()):
57-
store.delete(key)
59+
for key in list(get_store().ids()):
60+
get_store().delete(key)
5861
super().setUp()

0 commit comments

Comments
 (0)