Skip to content

Commit 0d56bcf

Browse files
authored
ES-2276 | support level 2 query profiling (#355)
* ES-2276 | initial commit * fix: `profile` typing
1 parent 542ed5b commit 0d56bcf

File tree

4 files changed

+42
-6
lines changed

4 files changed

+42
-6
lines changed

arango/aql.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ def execute(
264264
cache: Optional[bool] = None,
265265
memory_limit: int = 0,
266266
fail_on_warning: Optional[bool] = None,
267-
profile: Optional[bool] = None,
267+
profile: Optional[Union[bool, int]] = None,
268268
max_transaction_size: Optional[int] = None,
269269
max_warning_count: Optional[int] = None,
270270
intermediate_commit_count: Optional[int] = None,
@@ -317,8 +317,12 @@ def execute(
317317
this behaviour, so it does not need to be set per-query.
318318
:type fail_on_warning: bool
319319
:param profile: Return additional profiling details in the cursor,
320-
unless the query cache is used.
321-
:type profile: bool
320+
unless the query cache is used. If set to True or 1, then query profiling
321+
information can be fetched with `cursor.profile()`. If set to 2, additional
322+
execution stats per query plan node are included via "nodes" in
323+
`cursor.statistics()`, as well as a the query plan which can be fetched
324+
with `cursor.plan()`.
325+
:type profile: bool | int
322326
:param max_transaction_size: Transaction size limit in bytes.
323327
:type max_transaction_size: int
324328
:param max_warning_count: Max number of warnings returned.

arango/cursor.py

+19
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ class Cursor:
4040
"_count",
4141
"_cached",
4242
"_stats",
43+
"_plan",
4344
"_profile",
4445
"_warnings",
4546
"_has_more",
@@ -63,6 +64,7 @@ def __init__(
6364
self._count: Optional[int] = None
6465
self._cached = None
6566
self._stats = None
67+
self._plan = None
6668
self._profile = None
6769
self._warnings = None
6870
self._next_batch_id: Optional[str] = None
@@ -132,12 +134,18 @@ def _update(self, data: Json) -> Json:
132134
self._warnings = extra["warnings"]
133135
result["warnings"] = extra["warnings"]
134136

137+
if "plan" in extra:
138+
self._plan = extra["plan"]
139+
result["plan"] = extra["plan"]
140+
135141
if "stats" in extra:
136142
stats = extra["stats"]
137143
if "writesExecuted" in stats:
138144
stats["modified"] = stats.pop("writesExecuted")
139145
if "writesIgnored" in stats:
140146
stats["ignored"] = stats.pop("writesIgnored")
147+
if "documentLookups" in stats:
148+
stats["lookups"] = stats.pop("documentLookups")
141149
if "scannedFull" in stats:
142150
stats["scanned_full"] = stats.pop("scannedFull")
143151
if "scannedIndex" in stats:
@@ -159,6 +167,9 @@ def _update(self, data: Json) -> Json:
159167
if "peakMemoryUsage" in stats:
160168
stats["peak_memory_usage"] = stats.pop("peakMemoryUsage")
161169

170+
if "intermediateCommits" in stats:
171+
stats["intermediate_commits"] = stats.pop("intermediateCommits")
172+
162173
self._stats = stats
163174
result["statistics"] = stats
164175

@@ -239,6 +250,14 @@ def warnings(self) -> Optional[Sequence[Json]]:
239250
"""
240251
return self._warnings
241252

253+
def plan(self) -> Optional[Json]:
254+
"""Return query execution plan.
255+
256+
:return: Query execution plan.
257+
:rtype: dict
258+
"""
259+
return self._plan
260+
242261
def empty(self) -> bool:
243262
"""Check if the current batch is empty.
244263

arango/request.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ def normalize_headers(
1212
if driver_flags is not None:
1313
for flag in driver_flags:
1414
flags = flags + flag + ";"
15-
driver_version = "8.1.1"
15+
driver_version = "8.2.0"
1616
driver_header = "python-arango/" + driver_version + " (" + flags + ")"
1717
normalized_headers: Headers = {
1818
"charset": "utf-8",

tests/test_cursor.py

+15-2
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ def test_cursor_from_execute_query(db, col, docs):
2222
batch_size=2,
2323
ttl=1000,
2424
optimizer_rules=["+all"],
25-
profile=True,
25+
profile=2,
2626
)
2727
cursor_id = cursor.id
2828
assert "Cursor" in repr(cursor)
@@ -41,12 +41,25 @@ def test_cursor_from_execute_query(db, col, docs):
4141
assert "http_requests" in statistics
4242
assert "scanned_full" in statistics
4343
assert "scanned_index" in statistics
44+
assert "nodes" in statistics
45+
4446
assert cursor.warnings() == []
4547

4648
profile = cursor.profile()
4749
assert profile["initializing"] > 0
4850
assert profile["parsing"] > 0
4951

52+
plan = cursor.plan()
53+
assert set(plan.keys()) == {
54+
"nodes",
55+
"rules",
56+
"collections",
57+
"variables",
58+
"estimatedCost",
59+
"estimatedNrItems",
60+
"isModificationQuery",
61+
}
62+
5063
assert clean_doc(cursor.next()) == docs[0]
5164
assert cursor.id == cursor_id
5265
assert cursor.has_more() is True
@@ -106,7 +119,7 @@ def test_cursor_write_query(db, col, docs):
106119
batch_size=1,
107120
ttl=1000,
108121
optimizer_rules=["+all"],
109-
profile=True,
122+
profile=1,
110123
max_runtime=0.0,
111124
)
112125
cursor_id = cursor.id

0 commit comments

Comments
 (0)