Coverage for src/asgi_profiler/detectors/slow_query.py: 100%
22 statements
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-22 16:59 +0000
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-22 16:59 +0000
1"""One statement that is slow on its own."""
3from __future__ import annotations
5from collections.abc import Iterator
7from ..models import Problem, Profile
8from .settings import Settings
9from .utils import fingerprint
11TYPE = "slow_db_query"
14def detect(profile: Profile, settings: Settings) -> Iterator[Problem]:
15 """Statements over `slow_query_ms`, one problem per distinct statement.
17 Reported per statement rather than per execution: an endpoint that runs
18 the same slow query three times has one problem to fix, not three.
20 Truncated statements are skipped. Their stored text ends in the original
21 character count, so two executions of the same oversized statement hash
22 differently and the "problem" would be a new one every time -- worse than
23 silence, because it would fill the issues page with noise that never
24 resolves.
25 """
26 threshold = settings.slow_query_ms
27 worst: dict[str, tuple[int, float]] = {}
28 hits: dict[str, list[int]] = {}
29 for index, query in enumerate(profile.queries):
30 if query.duration_ms < threshold or query.truncated:
31 continue
32 key = query.sql_hash
33 hits.setdefault(key, []).append(index)
34 if key not in worst or query.duration_ms > worst[key][1]:
35 worst[key] = (index, query.duration_ms)
37 for key, (index, duration) in worst.items():
38 query = profile.queries[index]
39 occurrences = hits[key]
40 times = "once" if len(occurrences) == 1 else f"{len(occurrences)} times"
41 yield Problem(
42 type=TYPE,
43 fingerprint=fingerprint(TYPE, key),
44 title=f"Slow {query.operation}: {duration:.0f} ms",
45 detail=(
46 f"One statement took {duration:.1f} ms, which is "
47 f"{duration / profile.duration_ms:.0%} of the request. "
48 f"It ran {times}."
49 ),
50 evidence={
51 "sql": query.sql,
52 "duration_ms": round(duration, 3),
53 "occurrences": len(occurrences),
54 "frame": query.stack[-1] if query.stack else "",
55 "operation": query.operation,
56 },
57 offenders=occurrences[:50],
58 time_ms=duration,
59 )