-
Notifications
You must be signed in to change notification settings - Fork 4
Add the option to plot the task graph #359
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
9023ebc
f0def4d
9780fb4
672bc94
b57ca6d
a381556
dbfeee1
b94d0e2
ca8eeff
573499a
dd47564
50ff4da
8be501f
002cc78
f37f522
8b6e870
c2cd5ca
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -67,6 +67,13 @@ def check_refresh_rate(refresh_rate: float): | |||||||||||||||||||||
| ) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| def check_plot_dependency_graph(plot_dependency_graph: bool): | ||||||||||||||||||||||
| if plot_dependency_graph: | ||||||||||||||||||||||
| raise ValueError( | ||||||||||||||||||||||
| "The plot_dependency_graph parameter is only used when disable_dependencies=False." | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
|
Comment on lines
+70
to
+74
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ensure correct usage of - "The plot_dependency_graph parameter is only used when disable_dependencies=False."
+ "The plot_dependency_graph parameter should be set to True to enable task dependency visualization."Committable suggestion
Suggested change
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| def validate_backend( | ||||||||||||||||||||||
| backend: str, flux_installed: bool = False, slurm_installed: bool = False | ||||||||||||||||||||||
| ) -> str: | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| from concurrent.futures import Future | ||
| from typing import Tuple | ||
|
|
||
| import cloudpickle | ||
|
|
||
|
|
||
| def generate_nodes_and_edges( | ||
| task_hash_dict: dict, future_hash_inverse_dict: dict | ||
| ) -> Tuple[list]: | ||
| node_lst, edge_lst = [], [] | ||
| hash_id_dict = {} | ||
| for k, v in task_hash_dict.items(): | ||
| hash_id_dict[k] = len(node_lst) | ||
| node_lst.append({"name": v["fn"].__name__, "id": hash_id_dict[k]}) | ||
| for k, task_dict in task_hash_dict.items(): | ||
| for arg in task_dict["args"]: | ||
| if not isinstance(arg, Future): | ||
| node_id = len(node_lst) | ||
| node_lst.append({"name": str(arg), "id": node_id}) | ||
| edge_lst.append({"start": node_id, "end": hash_id_dict[k], "label": ""}) | ||
| else: | ||
| edge_lst.append( | ||
| { | ||
| "start": hash_id_dict[future_hash_inverse_dict[arg]], | ||
| "end": hash_id_dict[k], | ||
| "label": "", | ||
| } | ||
| ) | ||
| for kw, v in task_dict["kwargs"].items(): | ||
| if not isinstance(v, Future): | ||
| node_id = len(node_lst) | ||
| node_lst.append({"name": str(v), "id": node_id}) | ||
| edge_lst.append( | ||
| {"start": node_id, "end": hash_id_dict[k], "label": str(kw)} | ||
| ) | ||
| else: | ||
| edge_lst.append( | ||
| { | ||
| "start": hash_id_dict[future_hash_inverse_dict[v]], | ||
| "end": hash_id_dict[k], | ||
| "label": str(kw), | ||
| } | ||
| ) | ||
| return node_lst, edge_lst | ||
|
|
||
|
|
||
| def generate_task_hash(task_dict: dict, future_hash_inverse_dict: dict) -> bytes: | ||
| args_for_hash = [ | ||
| arg if not isinstance(arg, Future) else future_hash_inverse_dict[arg] | ||
| for arg in task_dict["args"] | ||
| ] | ||
| kwargs_for_hash = { | ||
| k: v if not isinstance(v, Future) else future_hash_inverse_dict[v] | ||
| for k, v in task_dict["kwargs"].items() | ||
| } | ||
| return cloudpickle.dumps( | ||
| {"fn": task_dict["fn"], "args": args_for_hash, "kwargs": kwargs_for_hash} | ||
| ) | ||
|
|
||
|
|
||
| def draw(node_lst: list, edge_lst: list): | ||
| from IPython.display import SVG, display # noqa | ||
| import matplotlib.pyplot as plt # noqa | ||
| import networkx as nx # noqa | ||
|
|
||
| graph = nx.DiGraph() | ||
| for node in node_lst: | ||
| graph.add_node(node["id"], label=node["name"]) | ||
| for edge in edge_lst: | ||
| graph.add_edge(edge["start"], edge["end"], label=edge["label"]) | ||
| svg = nx.nx_agraph.to_agraph(graph).draw(prog="dot", format="svg") | ||
| display(SVG(svg)) | ||
| plt.show() |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -8,6 +8,15 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from pympipool.interactive import create_executor | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from pympipool.shared.thread import RaisingThread | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from pympipool.shared.executor import execute_tasks_with_dependencies | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from pympipool.shared.plot import generate_nodes_and_edges | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import pygraphviz | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| skip_graphviz_test = False | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except ImportError: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| skip_graphviz_test = True | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def add_function(parameter_1, parameter_2): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -23,6 +32,33 @@ def test_executor(self): | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| future_2 = exe.submit(add_function, 1, parameter_2=future_1) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.assertEqual(future_2.result(), 4) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @unittest.skipIf( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| skip_graphviz_test, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "graphviz is not installed, so the plot_dependency_graph test is skipped.", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def test_executor_dependency_plot(self): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| with Executor( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| max_cores=1, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| backend="local", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| hostname_localhost=True, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| plot_dependency_graph=True, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) as exe: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cloudpickle_register(ind=1) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| future_1 = exe.submit(add_function, 1, parameter_2=2) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| future_2 = exe.submit(add_function, 1, parameter_2=future_1) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.assertTrue(future_1.done()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.assertTrue(future_2.done()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.assertEqual(len(exe._future_hash_dict), 2) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.assertEqual(len(exe._task_hash_dict), 2) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| nodes, edges = generate_nodes_and_edges( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| task_hash_dict=exe._task_hash_dict, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| future_hash_inverse_dict={ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| v: k for k, v in exe._future_hash_dict.items() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.assertEqual(len(nodes), 5) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.assertEqual(len(edges), 4) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+35
to
+61
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added a new test method - import pygraphviz
+ from importlib import util
+ skip_graphviz_test = util.find_spec("pygraphviz") is NoneCommittable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def test_dependency_steps(self): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cloudpickle_register(ind=1) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fs1 = Future() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
submitmethod has been adapted to handle task graph generation when enabled. However, using a mutable default argument (resource_dict) is a common Python pitfall, which could lead to unexpected behavior if the dictionary is modified. This should be corrected as indicated by the static analysis tool.Committable suggestion
Tools
Ruff