django-devbar 0.4.0__tar.gz → 0.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {django_devbar-0.4.0 → django_devbar-0.5.0}/PKG-INFO +14 -6
- {django_devbar-0.4.0 → django_devbar-0.5.0}/README.md +13 -5
- {django_devbar-0.4.0 → django_devbar-0.5.0}/pyproject.toml +1 -1
- {django_devbar-0.4.0 → django_devbar-0.5.0}/src/django_devbar/middleware.py +30 -9
- django_devbar-0.5.0/src/django_devbar/tracker.py +287 -0
- django_devbar-0.4.0/src/django_devbar/tracker.py +0 -60
- {django_devbar-0.4.0 → django_devbar-0.5.0}/src/django_devbar/__init__.py +0 -0
- {django_devbar-0.4.0 → django_devbar-0.5.0}/src/django_devbar/conf.py +0 -0
- {django_devbar-0.4.0 → django_devbar-0.5.0}/src/django_devbar/templates/django_devbar/devbar.html +0 -0
- {django_devbar-0.4.0 → django_devbar-0.5.0}/src/django_devbar/templates/django_devbar/duplicates.html +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: django-devbar
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Lightweight performance devbar for Django
|
|
5
5
|
Keywords: django,debug,performance,monitoring,development
|
|
6
6
|
Author: amureki
|
|
@@ -31,16 +31,24 @@ Description-Content-Type: text/markdown
|
|
|
31
31
|
|
|
32
32
|
# django-devbar
|
|
33
33
|
|
|
34
|
+
> [!WARNING]
|
|
35
|
+
> This package is experimental and may introduce breaking changes in minor versions.
|
|
36
|
+
|
|
34
37
|
Lightweight performance devbar for Django. Shows DB query count, query duration, application time, and detects duplicate queries with visual severity indicators.
|
|
35
38
|
|
|
36
|
-
|
|
39
|
+
## Showcase
|
|
37
40
|
|
|
38
|
-
|
|
41
|
+
Normal operation – showing query count, duration, and application time:
|
|
39
42
|
|
|
40
|
-
|
|
43
|
+

|
|
41
44
|
|
|
42
|
-
|
|
43
|
-
|
|
45
|
+
Duplicate queries detected – severity indicator and details shown:
|
|
46
|
+
|
|
47
|
+

|
|
48
|
+
|
|
49
|
+
[optional] Chrome extension - showing detailed metrics in DevTools panel:
|
|
50
|
+
|
|
51
|
+
[](#chrome-extension)
|
|
44
52
|
|
|
45
53
|
## Motivation
|
|
46
54
|
|
|
@@ -1,15 +1,23 @@
|
|
|
1
1
|
# django-devbar
|
|
2
2
|
|
|
3
|
+
> [!WARNING]
|
|
4
|
+
> This package is experimental and may introduce breaking changes in minor versions.
|
|
5
|
+
|
|
3
6
|
Lightweight performance devbar for Django. Shows DB query count, query duration, application time, and detects duplicate queries with visual severity indicators.
|
|
4
7
|
|
|
5
|
-
|
|
8
|
+
## Showcase
|
|
6
9
|
|
|
7
|
-
|
|
10
|
+
Normal operation – showing query count, duration, and application time:
|
|
8
11
|
|
|
9
|
-
|
|
12
|
+

|
|
10
13
|
|
|
11
|
-
|
|
12
|
-
|
|
14
|
+
Duplicate queries detected – severity indicator and details shown:
|
|
15
|
+
|
|
16
|
+

|
|
17
|
+
|
|
18
|
+
[optional] Chrome extension - showing detailed metrics in DevTools panel:
|
|
19
|
+
|
|
20
|
+
[](#chrome-extension)
|
|
13
21
|
|
|
14
22
|
## Motivation
|
|
15
23
|
|
|
@@ -13,6 +13,7 @@ from .conf import (
|
|
|
13
13
|
get_position,
|
|
14
14
|
get_show_bar,
|
|
15
15
|
)
|
|
16
|
+
from .tracker import format_sql, truncate_sql
|
|
16
17
|
|
|
17
18
|
BODY_CLOSE_RE = re.compile(rb"</body\s*>", re.IGNORECASE)
|
|
18
19
|
|
|
@@ -41,10 +42,10 @@ class DevBarMiddleware:
|
|
|
41
42
|
stats = tracker.get_stats()
|
|
42
43
|
|
|
43
44
|
db_time = stats["duration"]
|
|
44
|
-
python_time = max(0, total_time - db_time)
|
|
45
|
+
python_time = round(max(0, total_time - db_time), 2)
|
|
45
46
|
|
|
46
47
|
stats["python_time"] = python_time
|
|
47
|
-
stats["total_time"] = total_time
|
|
48
|
+
stats["total_time"] = round(total_time, 2)
|
|
48
49
|
|
|
49
50
|
if get_enable_devtools_data():
|
|
50
51
|
self._add_devtools_data_header(response, stats)
|
|
@@ -57,14 +58,31 @@ class DevBarMiddleware:
|
|
|
57
58
|
return response
|
|
58
59
|
|
|
59
60
|
def _add_devtools_data_header(self, response, stats):
|
|
61
|
+
# Abbreviated keys used to minimize DevBar-Data header size
|
|
60
62
|
extension_data = {
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"
|
|
63
|
+
"c": stats["count"],
|
|
64
|
+
"db": round(stats["duration"], 2),
|
|
65
|
+
"app": stats["python_time"],
|
|
66
|
+
"full": stats["total_time"],
|
|
65
67
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
+
|
|
69
|
+
all_queries = stats.get("queries", [])
|
|
70
|
+
duplicates = stats.get("duplicate_queries", [])
|
|
71
|
+
|
|
72
|
+
if all_queries:
|
|
73
|
+
processed_queries = [
|
|
74
|
+
{
|
|
75
|
+
"s": truncate_sql(q["sql"]),
|
|
76
|
+
"dur": q["duration"],
|
|
77
|
+
"dup": 1 if q["is_duplicate"] else 0,
|
|
78
|
+
}
|
|
79
|
+
for q in all_queries
|
|
80
|
+
]
|
|
81
|
+
extension_data["q"] = processed_queries
|
|
82
|
+
|
|
83
|
+
if duplicates:
|
|
84
|
+
marked_duplicates = [{**d, "dup": 1} for d in duplicates]
|
|
85
|
+
extension_data["dup"] = marked_duplicates
|
|
68
86
|
|
|
69
87
|
response["DevBar-Data"] = json.dumps(extension_data)
|
|
70
88
|
|
|
@@ -118,5 +136,8 @@ class DevBarMiddleware:
|
|
|
118
136
|
def _build_duplicates_html(self, duplicates):
|
|
119
137
|
if not duplicates:
|
|
120
138
|
return ""
|
|
139
|
+
formatted_duplicates = [
|
|
140
|
+
{**dup, "sql": format_sql(dup["sql"])} for dup in duplicates
|
|
141
|
+
]
|
|
121
142
|
template = _template_engine.get_template("django_devbar/duplicates.html")
|
|
122
|
-
return template.render(Context({"duplicates":
|
|
143
|
+
return template.render(Context({"duplicates": formatted_duplicates}))
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from contextvars import ContextVar
|
|
3
|
+
from time import perf_counter
|
|
4
|
+
|
|
5
|
+
SQL_KEYWORDS_RE = re.compile(
|
|
6
|
+
r"\b(SELECT|FROM|WHERE|JOIN|LEFT JOIN|RIGHT JOIN|INNER JOIN|OUTER JOIN|ON|AND|OR|ORDER BY|GROUP BY|HAVING|LIMIT|OFFSET|INSERT INTO|VALUES|UPDATE|SET|DELETE|CREATE TABLE|ALTER TABLE|DROP TABLE|DISTINCT|COUNT|SUM|AVG|MAX|MIN|UNION|EXISTS|IN|IS NULL|IS NOT NULL|LIKE|BETWEEN|CASE|WHEN|THEN|ELSE|END|ASC|DESC|AS|WITH|RECURSIVE)\b",
|
|
7
|
+
re.IGNORECASE,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
SQL_CLAUSES_RE = re.compile(
|
|
11
|
+
r"\b(FROM|WHERE|JOIN|LEFT JOIN|RIGHT JOIN|INNER JOIN|OUTER JOIN|GROUP BY|ORDER BY|HAVING|LIMIT|OFFSET|ON|AND|OR|UNION|WITH|CASE|WHEN|THEN|ELSE)\b",
|
|
12
|
+
re.IGNORECASE,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
_query_count: ContextVar[int] = ContextVar("query_count", default=0)
|
|
16
|
+
_query_duration: ContextVar[float] = ContextVar("query_duration", default=0.0)
|
|
17
|
+
_seen_queries: ContextVar[dict] = ContextVar("seen_queries", default={})
|
|
18
|
+
_duplicate_log: ContextVar[list] = ContextVar("duplicate_log", default=[])
|
|
19
|
+
_query_log: ContextVar[list] = ContextVar("query_log", default=[])
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def reset():
|
|
23
|
+
_query_count.set(0)
|
|
24
|
+
_query_duration.set(0.0)
|
|
25
|
+
_seen_queries.set({})
|
|
26
|
+
_duplicate_log.set([])
|
|
27
|
+
_query_log.set([])
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def get_stats():
|
|
31
|
+
duplicate_queries = _duplicate_log.get()
|
|
32
|
+
duplicate_sqls = {d["sql"] for d in duplicate_queries}
|
|
33
|
+
|
|
34
|
+
queries = _query_log.get()
|
|
35
|
+
for q in queries:
|
|
36
|
+
q["is_duplicate"] = q["sql"] in duplicate_sqls
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
"count": _query_count.get(),
|
|
40
|
+
"duration": _query_duration.get(),
|
|
41
|
+
"duplicate_queries": duplicate_queries,
|
|
42
|
+
"queries": queries,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _hash_params(params):
|
|
47
|
+
try:
|
|
48
|
+
return hash(tuple(params)) if params else 0
|
|
49
|
+
except TypeError:
|
|
50
|
+
return hash(str(params))
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _record(sql, params, duration):
|
|
54
|
+
_query_count.set(_query_count.get() + 1)
|
|
55
|
+
_query_duration.set(_query_duration.get() + duration)
|
|
56
|
+
|
|
57
|
+
seen = _seen_queries.get()
|
|
58
|
+
params_hash = _hash_params(params)
|
|
59
|
+
|
|
60
|
+
query_log = _query_log.get()
|
|
61
|
+
query_log.append(
|
|
62
|
+
{
|
|
63
|
+
"sql": sql,
|
|
64
|
+
"duration": round(duration, 2),
|
|
65
|
+
}
|
|
66
|
+
)
|
|
67
|
+
_query_log.set(query_log)
|
|
68
|
+
|
|
69
|
+
if sql in seen:
|
|
70
|
+
if params_hash in seen[sql]:
|
|
71
|
+
duplicates = _duplicate_log.get()
|
|
72
|
+
duplicates.append({"sql": sql, "duration": round(duration, 2)})
|
|
73
|
+
else:
|
|
74
|
+
seen[sql].add(params_hash)
|
|
75
|
+
else:
|
|
76
|
+
seen[sql] = {params_hash}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def format_sql(sql):
|
|
80
|
+
"""Highlight SQL keywords with HTML spans for visual formatting.
|
|
81
|
+
|
|
82
|
+
Escapes HTML first for security, then wraps SQL keywords in span tags.
|
|
83
|
+
Used for rendering duplicates in the injected devbar HTML.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
sql: SQL query string
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
Safe HTML string with keywords wrapped in spans
|
|
90
|
+
"""
|
|
91
|
+
from django.utils.html import escape
|
|
92
|
+
from django.utils.safestring import mark_safe
|
|
93
|
+
|
|
94
|
+
escaped_sql = escape(sql)
|
|
95
|
+
|
|
96
|
+
def replace_keyword(match):
|
|
97
|
+
return f'<span class="sql-keyword">{match.group(0)}</span>'
|
|
98
|
+
|
|
99
|
+
highlighted = SQL_KEYWORDS_RE.sub(replace_keyword, escaped_sql)
|
|
100
|
+
return mark_safe(highlighted)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class SQLTruncator:
|
|
104
|
+
"""Handles intelligent SQL query truncation while preserving the structure."""
|
|
105
|
+
|
|
106
|
+
def __init__(self, max_length=150):
|
|
107
|
+
self.max_length = max_length
|
|
108
|
+
|
|
109
|
+
def truncate(self, sql):
|
|
110
|
+
"""Intelligently truncate long SQL queries while preserving the structure.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
sql: SQL query string
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
Truncated SQL string with "..." at the end if shortened
|
|
117
|
+
"""
|
|
118
|
+
if len(sql) <= self.max_length:
|
|
119
|
+
return sql
|
|
120
|
+
|
|
121
|
+
sql_upper = sql.upper()
|
|
122
|
+
|
|
123
|
+
if sql_upper.startswith("SELECT"):
|
|
124
|
+
result = self._truncate_select(sql, sql_upper)
|
|
125
|
+
if result:
|
|
126
|
+
return result
|
|
127
|
+
|
|
128
|
+
return self._fallback_truncate(sql)
|
|
129
|
+
|
|
130
|
+
def _truncate_select(self, sql, sql_upper):
|
|
131
|
+
"""Truncate SELECT queries while preserving columns and joins."""
|
|
132
|
+
distinct_match = re.search(r"\bDISTINCT\b", sql_upper[:20])
|
|
133
|
+
has_distinct = distinct_match is not None
|
|
134
|
+
from_match = re.search(r"\bFROM\b", sql_upper)
|
|
135
|
+
|
|
136
|
+
if not from_match:
|
|
137
|
+
return None
|
|
138
|
+
|
|
139
|
+
from_pos = from_match.start()
|
|
140
|
+
|
|
141
|
+
if has_distinct:
|
|
142
|
+
distinct_end = distinct_match.end()
|
|
143
|
+
select_part = sql[distinct_end:from_pos].strip()
|
|
144
|
+
else:
|
|
145
|
+
select_part = sql[6:from_pos].strip()
|
|
146
|
+
|
|
147
|
+
first_column = self._extract_first_column(select_part)
|
|
148
|
+
select_clause = "SELECT DISTINCT" if has_distinct else "SELECT"
|
|
149
|
+
|
|
150
|
+
from_part = sql[from_pos:]
|
|
151
|
+
table_match = re.search(r'FROM\s+(["\[]?)(\w+)', from_part, re.IGNORECASE)
|
|
152
|
+
|
|
153
|
+
if not table_match:
|
|
154
|
+
return None
|
|
155
|
+
|
|
156
|
+
table_name = table_match.group(2)
|
|
157
|
+
result = f"{select_clause} {first_column} FROM {table_name}"
|
|
158
|
+
|
|
159
|
+
# Add joins if they fit
|
|
160
|
+
join_result = self._add_joins_if_fit(result, from_part)
|
|
161
|
+
if join_result:
|
|
162
|
+
return join_result
|
|
163
|
+
|
|
164
|
+
return result if len(result) <= self.max_length else None
|
|
165
|
+
|
|
166
|
+
def _extract_first_column(self, select_part):
|
|
167
|
+
"""Extract the first column from SELECT clause, handling quotes and parentheses."""
|
|
168
|
+
if not select_part.strip():
|
|
169
|
+
return "..."
|
|
170
|
+
columns = self._parse_columns(select_part)
|
|
171
|
+
return columns[0] + ", ..." if columns else "..."
|
|
172
|
+
|
|
173
|
+
def _parse_columns(self, select_part):
|
|
174
|
+
"""Parse columns from SELECT clause, handling complex expressions."""
|
|
175
|
+
columns = []
|
|
176
|
+
in_quotes = False
|
|
177
|
+
quote_char = None
|
|
178
|
+
in_parens = False
|
|
179
|
+
paren_depth = 0
|
|
180
|
+
current_col = ""
|
|
181
|
+
|
|
182
|
+
for i, char in enumerate(select_part):
|
|
183
|
+
if char in ('"', "'") and (i == 0 or select_part[i - 1] != "\\"):
|
|
184
|
+
if not in_quotes:
|
|
185
|
+
in_quotes = True
|
|
186
|
+
quote_char = char
|
|
187
|
+
elif char == quote_char:
|
|
188
|
+
in_quotes = False
|
|
189
|
+
quote_char = None
|
|
190
|
+
current_col += char
|
|
191
|
+
elif in_quotes:
|
|
192
|
+
current_col += char
|
|
193
|
+
elif char == "(":
|
|
194
|
+
in_parens = True
|
|
195
|
+
paren_depth += 1
|
|
196
|
+
current_col += char
|
|
197
|
+
elif char == ")":
|
|
198
|
+
paren_depth -= 1
|
|
199
|
+
if paren_depth == 0:
|
|
200
|
+
in_parens = False
|
|
201
|
+
current_col += char
|
|
202
|
+
elif char == "," and not in_parens and not in_quotes:
|
|
203
|
+
if current_col.strip():
|
|
204
|
+
columns.append(current_col.strip())
|
|
205
|
+
break
|
|
206
|
+
current_col = ""
|
|
207
|
+
elif char in " \t\n" and not in_parens and not in_quotes:
|
|
208
|
+
if current_col.strip():
|
|
209
|
+
columns.append(current_col.strip())
|
|
210
|
+
current_col = ""
|
|
211
|
+
else:
|
|
212
|
+
current_col += char
|
|
213
|
+
|
|
214
|
+
if not columns and current_col.strip():
|
|
215
|
+
columns.append(current_col.strip())
|
|
216
|
+
|
|
217
|
+
return columns
|
|
218
|
+
|
|
219
|
+
def _add_joins_if_fit(self, base_result, from_part):
|
|
220
|
+
"""Add JOIN clauses to result if they fit within max_length."""
|
|
221
|
+
join_matches = re.finditer(
|
|
222
|
+
r'\b(LEFT\s+|RIGHT\s+|INNER\s+|OUTER\s+)?JOIN\s+(["\[]?)(\w+)',
|
|
223
|
+
from_part,
|
|
224
|
+
re.IGNORECASE,
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
result = base_result
|
|
228
|
+
|
|
229
|
+
for join_match in join_matches:
|
|
230
|
+
join_type = (join_match.group(1) or "").strip()
|
|
231
|
+
join_table = join_match.group(3)
|
|
232
|
+
new_result = (
|
|
233
|
+
f"{result} {join_type} JOIN {join_table}"
|
|
234
|
+
if join_type
|
|
235
|
+
else f"{result} JOIN {join_table}"
|
|
236
|
+
)
|
|
237
|
+
if len(new_result) <= self.max_length:
|
|
238
|
+
result = new_result
|
|
239
|
+
else:
|
|
240
|
+
break
|
|
241
|
+
|
|
242
|
+
return result if len(result) <= self.max_length else None
|
|
243
|
+
|
|
244
|
+
def _fallback_truncate(self, sql):
|
|
245
|
+
"""Fallback truncation at clause boundaries or simple length limit."""
|
|
246
|
+
# First try simple length truncation
|
|
247
|
+
if len(sql) <= self.max_length:
|
|
248
|
+
return sql
|
|
249
|
+
|
|
250
|
+
# Try clause boundary truncation
|
|
251
|
+
clause_positions = [
|
|
252
|
+
(match.start(), match.group())
|
|
253
|
+
for match in SQL_CLAUSES_RE.finditer(sql[: self.max_length + 50])
|
|
254
|
+
]
|
|
255
|
+
|
|
256
|
+
if clause_positions:
|
|
257
|
+
best_pos = self._find_best_truncate_position(clause_positions)
|
|
258
|
+
if best_pos > 0:
|
|
259
|
+
return sql[:best_pos].rstrip() + "..."
|
|
260
|
+
|
|
261
|
+
# Final fallback: simple truncation
|
|
262
|
+
return sql[: self.max_length].rstrip() + "..."
|
|
263
|
+
|
|
264
|
+
def _find_best_truncate_position(self, clause_positions):
|
|
265
|
+
"""Find the best position to truncate based on clause boundaries."""
|
|
266
|
+
best_pos = 0
|
|
267
|
+
for pos, _ in clause_positions:
|
|
268
|
+
if self.max_length > pos > best_pos:
|
|
269
|
+
best_pos = pos
|
|
270
|
+
|
|
271
|
+
if best_pos > self.max_length - 30:
|
|
272
|
+
best_pos = 0
|
|
273
|
+
|
|
274
|
+
return best_pos
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def truncate_sql(sql, max_length=150):
|
|
278
|
+
truncator = SQLTruncator(max_length)
|
|
279
|
+
return truncator.truncate(sql)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def tracking_wrapper(execute, sql, params, many, context):
|
|
283
|
+
start = perf_counter()
|
|
284
|
+
try:
|
|
285
|
+
return execute(sql, params, many, context)
|
|
286
|
+
finally:
|
|
287
|
+
_record(sql, params, (perf_counter() - start) * 1000)
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
from contextvars import ContextVar
|
|
2
|
-
from time import perf_counter
|
|
3
|
-
|
|
4
|
-
_query_count: ContextVar[int] = ContextVar("query_count", default=0)
|
|
5
|
-
_query_duration: ContextVar[float] = ContextVar("query_duration", default=0.0)
|
|
6
|
-
_seen_queries: ContextVar[dict] = ContextVar("seen_queries", default={})
|
|
7
|
-
_duplicate_log: ContextVar[list] = ContextVar("duplicate_log", default=[])
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def reset():
|
|
11
|
-
_query_count.set(0)
|
|
12
|
-
_query_duration.set(0.0)
|
|
13
|
-
_seen_queries.set({})
|
|
14
|
-
_duplicate_log.set([])
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def get_stats():
|
|
18
|
-
return {
|
|
19
|
-
"count": _query_count.get(),
|
|
20
|
-
"duration": _query_duration.get(),
|
|
21
|
-
"duplicate_queries": _duplicate_log.get(),
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def _hash_params(params):
|
|
26
|
-
try:
|
|
27
|
-
return hash(tuple(params)) if params else 0
|
|
28
|
-
except TypeError:
|
|
29
|
-
return hash(str(params))
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def _record(sql, params, duration):
|
|
33
|
-
_query_count.set(_query_count.get() + 1)
|
|
34
|
-
_query_duration.set(_query_duration.get() + duration)
|
|
35
|
-
|
|
36
|
-
seen = _seen_queries.get()
|
|
37
|
-
params_hash = _hash_params(params)
|
|
38
|
-
|
|
39
|
-
if sql in seen:
|
|
40
|
-
if params_hash in seen[sql]:
|
|
41
|
-
param_str = str(params)
|
|
42
|
-
if len(param_str) > 200:
|
|
43
|
-
param_str = param_str[:200] + "..."
|
|
44
|
-
|
|
45
|
-
duplicates = _duplicate_log.get()
|
|
46
|
-
duplicates.append(
|
|
47
|
-
{"sql": sql, "params": param_str, "duration": round(duration, 2)}
|
|
48
|
-
)
|
|
49
|
-
else:
|
|
50
|
-
seen[sql].add(params_hash)
|
|
51
|
-
else:
|
|
52
|
-
seen[sql] = {params_hash}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
def tracking_wrapper(execute, sql, params, many, context):
|
|
56
|
-
start = perf_counter()
|
|
57
|
-
try:
|
|
58
|
-
return execute(sql, params, many, context)
|
|
59
|
-
finally:
|
|
60
|
-
_record(sql, params, (perf_counter() - start) * 1000)
|
|
File without changes
|
|
File without changes
|
{django_devbar-0.4.0 → django_devbar-0.5.0}/src/django_devbar/templates/django_devbar/devbar.html
RENAMED
|
File without changes
|
|
File without changes
|