Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 4f61b1e

Browse files
authored
profiled execution plan (#152)
* initial work on profiled execution plan * address review
1 parent b27bc08 commit 4f61b1e

File tree

3 files changed

+102
-9
lines changed

3 files changed

+102
-9
lines changed

‎redisgraph/execution_plan.py

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,33 @@
1+
import re
2+
3+
4+
class ProfileStats:
5+
"""
6+
ProfileStats, runtime execution statistics of operation.
7+
"""
8+
9+
def __init__(self, records_produced, execution_time):
10+
self.records_produced = records_produced
11+
self.execution_time = execution_time
12+
13+
114
class Operation:
215
"""
316
Operation, single operation within execution plan.
417
"""
518

6-
def __init__(self, name, args=None):
19+
def __init__(self, name, args=None, profile_stats=None):
720
"""
821
Create a new operation.
922
1023
Args:
1124
name: string that represents the name of the operation
1225
args: operation arguments
26+
profile_stats: profile statistics
1327
"""
1428
self.name = name
1529
self.args = args
30+
self.profile_stats = profile_stats
1631
self.children = []
1732

1833
def append_child(self, child):
@@ -32,7 +47,7 @@ def __eq__(self, o: object) -> bool:
3247
return (self.name == o.name and self.args == o.args)
3348

3449
def __str__(self) -> str:
35-
args_str = "" if self.args is None else f" | {self.args}"
50+
args_str = "" if self.args is None else " | "+self.args
3651
return f"{self.name}{args_str}"
3752

3853

@@ -131,21 +146,30 @@ def _operation_tree(self):
131146
stack = []
132147
current = None
133148

149+
def _create_operation(args):
150+
profile_stats = None
151+
name = args[0].strip()
152+
args.pop(0)
153+
if len(args) > 0 and "Records produced" in args[-1]:
154+
records_produced = int(re.search("Records produced: (\\d+)", args[-1]).group(1))
155+
execution_time = float(re.search("Execution time: (\\d+.\\d+) ms", args[-1]).group(1))
156+
profile_stats = ProfileStats(records_produced, execution_time)
157+
args.pop(-1)
158+
return Operation(name, None if len(args) == 0 else args[0].strip(), profile_stats)
159+
134160
# iterate plan operations
135161
while i < len(self.plan):
136162
current_op = self.plan[i]
137163
op_level = current_op.count(" ")
138164
if op_level == level:
139165
# if the operation level equal to the current level
140166
# set the current operation and move next
141-
args = current_op.split("|")
142-
current = Operation(args[0].strip(), None if len(args) == 1 else args[1].strip())
167+
current = _create_operation(current_op.split("|"))
143168
i += 1
144169
elif op_level == level + 1:
145170
# if the operation is child of the current operation
146171
# add it as child and set as current operation
147-
args = current_op.split("|")
148-
child = Operation(args[0].strip(), None if len(args) == 1 else args[1].strip())
172+
child = _create_operation(current_op.split("|"))
149173
current.append_child(child)
150174
stack.append(current)
151175
current = child

‎redisgraph/graph.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ def query(self, q, params=None, timeout=None, read_only=False):
219219
def execution_plan(self, query, params=None):
220220
"""
221221
Get the execution plan for given query,
222-
GRAPH.EXPLAIN returns an array of operations.
222+
GRAPH.EXPLAIN returns ExecutionPlan object.
223223
224224
Args:
225225
query: the query that will be executed
@@ -231,6 +231,21 @@ def execution_plan(self, query, params=None):
231231
plan = self.redis_con.execute_command("GRAPH.EXPLAIN", self.name, query)
232232
return ExecutionPlan(plan)
233233

234+
def profile(self, query, params=None):
235+
"""
236+
Get the profield execution plan for given query,
237+
GRAPH.PROFILE returns ExecutionPlan object.
238+
239+
Args:
240+
query: the query that will be executed
241+
params: query parameters
242+
"""
243+
if params is not None:
244+
query = self._build_params_header(params) + query
245+
246+
plan = self.redis_con.execute_command("GRAPH.PROFILE", self.name, query)
247+
return ExecutionPlan(plan)
248+
234249
def delete(self):
235250
"""
236251
Deletes graph.

‎tests/functional/test_all.py

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,7 @@ def test_cached_execution(self):
246246

247247
def test_execution_plan(self):
248248
redis_graph = Graph('execution_plan', self.r)
249+
# graph creation / population
249250
create_query = """CREATE
250251
(:Rider {name:'Valentino Rossi'})-[:rides]->(:Team {name:'Yamaha'}),
251252
(:Rider {name:'Dani Pedrosa'})-[:rides]->(:Team {name:'Honda'}),
@@ -254,11 +255,11 @@ def test_execution_plan(self):
254255

255256
result = redis_graph.execution_plan("""MATCH (r:Rider)-[:rides]->(t:Team)
256257
WHERE t.name = $name
257-
RETURN r.name, t.name, $params
258+
RETURN r.name, t.name
258259
UNION
259260
MATCH (r:Rider)-[:rides]->(t:Team)
260261
WHERE t.name = $name
261-
RETURN r.name, t.name, $params""", {'name': 'Yehuda'})
262+
RETURN r.name, t.name""", {'name': 'Yamaha'})
262263
expected = '''\
263264
Results
264265
Distinct
@@ -290,6 +291,59 @@ def test_execution_plan(self):
290291

291292
redis_graph.delete()
292293

294+
def test_profile(self):
295+
redis_graph = Graph('profile', self.r)
296+
# graph creation / population
297+
create_query = """UNWIND range(1, 30) as x CREATE (:Person {id: x})"""
298+
redis_graph.query(create_query)
299+
300+
plan = redis_graph.profile("""MATCH (p:Person)
301+
WHERE p.id > 15
302+
RETURN p""")
303+
304+
results = plan.structured_plan
305+
self.assertEqual(results.name, "Results")
306+
self.assertEqual(results.profile_stats.records_produced, 15)
307+
self.assertGreater(results.profile_stats.execution_time, 0)
308+
309+
project = results.children[0]
310+
self.assertEqual(project.name, "Project")
311+
self.assertEqual(project.profile_stats.records_produced, 15)
312+
self.assertGreater(project.profile_stats.execution_time, 0)
313+
314+
filter = project.children[0]
315+
self.assertEqual(filter.name, "Filter")
316+
self.assertEqual(filter.profile_stats.records_produced, 15)
317+
self.assertGreater(filter.profile_stats.execution_time, 0)
318+
319+
node_by_label_scan = filter.children[0]
320+
self.assertEqual(node_by_label_scan.name, "Node By Label Scan")
321+
self.assertEqual(node_by_label_scan.profile_stats.records_produced, 30)
322+
self.assertGreater(node_by_label_scan.profile_stats.execution_time, 0)
323+
324+
redis_graph.query("CREATE INDEX FOR (p:Person) ON (p.id)")
325+
326+
plan = redis_graph.profile("""MATCH (p:Person)
327+
WHERE p.id > 15
328+
RETURN p""")
329+
330+
results = plan.structured_plan
331+
self.assertEqual(results.name, "Results")
332+
self.assertEqual(results.profile_stats.records_produced, 15)
333+
self.assertGreater(results.profile_stats.execution_time, 0)
334+
335+
project = results.children[0]
336+
self.assertEqual(project.name, "Project")
337+
self.assertEqual(project.profile_stats.records_produced, 15)
338+
self.assertGreater(project.profile_stats.execution_time, 0)
339+
340+
node_by_index_scan = project.children[0]
341+
self.assertEqual(node_by_index_scan.name, "Node By Index Scan")
342+
self.assertEqual(node_by_index_scan.profile_stats.records_produced, 15)
343+
self.assertGreater(node_by_index_scan.profile_stats.execution_time, 0)
344+
345+
redis_graph.delete()
346+
293347
def test_query_timeout(self):
294348
redis_graph = Graph('timeout', self.r)
295349
# Build a sample graph with 1000 nodes.

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /