|
7 | 7 | end
|
8 | 8 |
|
9 | 9 | import Dagger
|
10 |
| -import Dagger: DTask, Chunk, Processor |
11 |
| -import Dagger.TimespanLogging: Timespan |
12 |
| -import Graphs: SimpleDiGraph, add_edge!, add_vertex!, inneighbors, outneighbors, vertices, is_directed, edges, nv, src, dst |
13 |
| - |
14 |
| -function pretty_time(t; digits=3) |
15 |
| - r(t) = round(t; digits) |
16 |
| - if t > 1000^3 |
17 |
| - "$(r(t/(1000^3))) s" |
18 |
| - elseif t > 1000^2 |
19 |
| - "$(r(t/(1000^2))) ms" |
20 |
| - elseif t > 1000 |
21 |
| - "$(r(t/1000)) us" |
22 |
| - else |
23 |
| - "$(r(t)) ns" |
24 |
| - end |
25 |
| -end |
26 | 10 |
|
27 | 11 | """
|
28 |
| - Dagger.render_logs(logs::Dict, ::Val{:graphviz}; disconnected=false, |
29 |
| - color_by=:fn, layout_engine="dot", |
30 |
| - times::Bool=true, times_digits::Integer=3) |
| 12 | + render_logs(logs::Dict, ::Val{:graphviz}; disconnected=false, |
| 13 | + color_by=:fn, layout_engine="dot", |
| 14 | + times::Bool=true, times_digits::Integer=3, |
| 15 | + colors=Dagger.Viz.default_colors, |
| 16 | + name_to_color=Dagger.Viz.name_to_color) |
31 | 17 |
|
32 | 18 | Render a graph of the task dependencies and data dependencies in `logs` using GraphViz.
|
33 | 19 |
|
34 |
| -Requires the following events enabled in `enable_logging!`: `taskdeps`, `tasknames`, `taskargs`, `taskargmoves` |
| 20 | +Requires the `all_task_deps` event enabled in `enable_logging!` |
35 | 21 |
|
36 | 22 | Options:
|
37 | 23 | - `disconnected`: If `true`, render disconnected vertices (tasks or arguments without upstream/downstream dependencies)
|
38 | 24 | - `color_by`: How to color tasks; if `:fn`, then color by unique function name, if `:proc`, then color by unique processor
|
39 |
| -- `layout_engine`: The layout engine to use for GraphViz |
| 25 | +- `layout_engine`: The layout engine to use for GraphViz rendering |
40 | 26 | - `times`: If `true`, annotate each task with its start and finish times
|
41 | 27 | - `times_digits`: Number of digits to display in the time annotations
|
| 28 | +- `colors`: A list of colors to use for coloring tasks |
| 29 | +- `name_to_color`: A function that maps task names to colors |
42 | 30 | """
|
43 | 31 | function Dagger.render_logs(logs::Dict, ::Val{:graphviz}; disconnected=false,
|
44 | 32 | color_by=:fn, layout_engine="dot",
|
45 |
| - times::Bool=true, times_digits::Integer=3) |
46 |
| - # Lookup all relevant task/argument dependencies and values in logs |
47 |
| - g = SimpleDiGraph() |
48 |
| - tid_to_vertex = Dict{Int,Int}() |
49 |
| - task_names = String[] |
50 |
| - tid_to_proc = Dict{Int,Processor}() |
51 |
| - arg_names = Dict{UInt,String}() |
52 |
| - task_args = Dict{Int,Vector{Pair{Union{Int,Symbol},UInt}}}() |
53 |
| - arg_moves = Dict{Int,Vector{Pair{Union{Int,Symbol},Tuple{UInt,UInt}}}}() |
54 |
| - for w in keys(logs) |
55 |
| - for idx in 1:length(logs[w][:core]) |
56 |
| - category = logs[w][:core][idx].category |
57 |
| - kind = logs[w][:core][idx].kind |
58 |
| - id = logs[w][:id][idx] |
59 |
| - if category == :add_thunk && kind == :start |
60 |
| - id::NamedTuple |
61 |
| - taskdeps = logs[w][:taskdeps][idx]::Pair{Int,Vector{Int}} |
62 |
| - taskname = logs[w][:tasknames][idx]::String |
63 |
| - tid, deps = taskdeps |
64 |
| - add_vertex!(g) |
65 |
| - tid_to_vertex[tid] = nv(g) |
66 |
| - push!(task_names, taskname) |
67 |
| - for dep in deps |
68 |
| - add_edge!(g, tid_to_vertex[dep], nv(g)) |
69 |
| - end |
70 |
| - if haskey(logs[w], :taskargs) |
71 |
| - id, args = logs[w][:taskargs][idx]::Pair{Int,<:Vector} |
72 |
| - append!(get!(Vector{Pair{Union{Int,Symbol},UInt}}, task_args, id), args) |
73 |
| - end |
74 |
| - elseif category == :compute && kind == :start |
75 |
| - id::NamedTuple |
76 |
| - tid = id.thunk_id |
77 |
| - proc = id.processor |
78 |
| - tid_to_proc[tid] = proc |
79 |
| - elseif category == :move && kind == :finish |
80 |
| - if haskey(logs[w], :taskargmoves) |
81 |
| - move_info = logs[w][:taskargmoves][idx] |
82 |
| - move_info === nothing && continue |
83 |
| - tid, pos, pre_objid, post_objid = move_info |
84 |
| - v = get!(Vector{Pair{Union{Int,Symbol},Tuple{UInt,UInt}}}, arg_moves, tid) |
85 |
| - push!(v, pos => (pre_objid, post_objid)) |
86 |
| - end |
87 |
| - elseif category == :data_annotation && kind == :start |
88 |
| - id::NamedTuple |
89 |
| - objid = id.objectid |
90 |
| - name = id.name |
91 |
| - arg_names[objid] = name |
92 |
| - end |
93 |
| - end |
94 |
| - end |
95 |
| - tids_sorted = map(first, sort(collect(tid_to_vertex); by=last)) |
96 |
| - task_procs = Processor[tid_to_proc[tid] for tid in tids_sorted] |
97 |
| - |
98 |
| - # Find all connected and disconnected vertices |
99 |
| - if !disconnected |
100 |
| - discon_vs = filter(v->isempty(inneighbors(g, v)) && isempty(outneighbors(g, v)), vertices(g)) |
101 |
| - con_vs = filter(v->!in(v, discon_vs), vertices(g)) |
102 |
| - else |
103 |
| - con_vs = vertices(g) |
104 |
| - end |
105 |
| - |
106 |
| - # Assign colors |
107 |
| - labels = task_names |
108 |
| - all_fns = unique(map(label->first(split(label, " ")), labels[con_vs])) |
109 |
| - all_procs = unique(task_procs) |
110 |
| - all_colors = ("red", "orange", "green", "blue", "purple", "pink", "silver") |
111 |
| - if color_by == :fn |
112 |
| - _colors = [all_colors[mod1(i, length(all_colors))] for i in 1:length(all_fns)] |
113 |
| - colors = Dict(v=>_colors[findfirst(fn->occursin(fn, labels[v]), all_fns)] for v in con_vs) |
114 |
| - elseif color_by == :proc |
115 |
| - _colors = [all_colors[mod1(i, length(all_colors))] for i in 1:length(all_procs)] |
116 |
| - colors = Dict(v=>_colors[findfirst(proc->proc==task_procs[v], all_procs)] for v in con_vs) |
117 |
| - else |
118 |
| - throw(ArgumentError("Unknown `color_by` value: $color_by\nAllowed: :fn, :proc")) |
119 |
| - end |
120 |
| - |
121 |
| - str = is_directed(g) ? "digraph mygraph {\n" : "graph mygraph {\n" |
122 |
| - |
123 |
| - # Add raw arguments |
124 |
| - for (id, name) in arg_names |
125 |
| - str *= "a$id [label=\"$name\", shape=box]\n" |
126 |
| - end |
127 |
| - |
128 |
| - if times |
129 |
| - vertex_to_tid = Dict{Int,Int}(v=>k for (k,v) in tid_to_vertex) |
130 |
| - |
131 |
| - # Determine per-worker start times |
132 |
| - worker_start_times = Dict{Int,UInt64}() |
133 |
| - for w in keys(logs) |
134 |
| - start = typemax(UInt64) |
135 |
| - for idx in 1:length(logs[w][:core]) |
136 |
| - if logs[w][:core][idx].category == :compute && logs[w][:core][idx].kind == :start |
137 |
| - tid = logs[w][:id][idx].thunk_id |
138 |
| - haskey(tid_to_vertex, tid) || continue |
139 |
| - id = tid_to_vertex[tid] |
140 |
| - id in con_vs || continue |
141 |
| - start = min(start, logs[w][:core][idx].timestamp) |
142 |
| - end |
143 |
| - end |
144 |
| - worker_start_times[w] = start |
145 |
| - end |
146 |
| - |
147 |
| - # Determine per-task start and finish times |
148 |
| - start_times = Dict{Int,UInt64}() |
149 |
| - finish_times = Dict{Int,UInt64}() |
150 |
| - for w in keys(logs) |
151 |
| - start = typemax(UInt64) |
152 |
| - for idx in 1:length(logs[w][:core]) |
153 |
| - if logs[w][:core][idx].category == :compute |
154 |
| - tid = logs[w][:id][idx].thunk_id |
155 |
| - if logs[w][:core][idx].kind == :start |
156 |
| - start_times[tid] = logs[w][:core][idx].timestamp - worker_start_times[w] |
157 |
| - else |
158 |
| - finish_times[tid] = logs[w][:core][idx].timestamp - worker_start_times[w] |
159 |
| - end |
160 |
| - end |
161 |
| - end |
162 |
| - end |
163 |
| - end |
164 |
| - |
165 |
| - # Add argument moves |
166 |
| - for (tid, moves) in arg_moves |
167 |
| - for (pos, (pre_objid, post_objid)) in moves |
168 |
| - move_str = "a$pre_objid -> a$post_objid [label=\"move\"]\n" |
169 |
| - str *= move_str |
170 |
| - end |
171 |
| - end |
172 |
| - |
173 |
| - # Add tasks |
174 |
| - for v in con_vs |
175 |
| - if !disconnected && (v in discon_vs) |
176 |
| - continue |
177 |
| - end |
178 |
| - label = labels[v] |
179 |
| - color = colors[v] |
180 |
| - proc = task_procs[v] |
181 |
| - proc_str = '(' * Dagger.short_name(task_procs[v]) * ')' |
182 |
| - label_str = "$label\\n$proc_str" |
183 |
| - if times |
184 |
| - tid = vertex_to_tid[v] |
185 |
| - start_time = pretty_time(start_times[tid]; digits=times_digits) |
186 |
| - finish_time = pretty_time(finish_times[tid]; digits=times_digits) |
187 |
| - label_str *= "\\n[+$start_time -> +$finish_time]" |
188 |
| - end |
189 |
| - str *= "v$v [label=\"$label_str\", color=\"$color\", penwidth=2.0]\n" |
190 |
| - end |
191 |
| - |
192 |
| - # Add task dependencies |
193 |
| - edge_sep = is_directed(g) ? "->" : "--" |
194 |
| - for edge in edges(g) |
195 |
| - # FIXME: Label syncdeps with associated arguments and datadeps directions |
196 |
| - str *= "v$(src(edge)) $edge_sep v$(dst(edge)) [label=\"syncdep\"]\n" |
197 |
| - end |
198 |
| - |
199 |
| - # Add task arguments |
200 |
| - con_args = Vector{UInt}(collect(keys(arg_names))) |
201 |
| - for moves in values(arg_moves) |
202 |
| - for (_, (pre_objid, post_objid)) in moves |
203 |
| - push!(con_args, pre_objid) |
204 |
| - push!(con_args, post_objid) |
205 |
| - end |
206 |
| - end |
207 |
| - for (tid, args) in task_args |
208 |
| - id = tid_to_vertex[tid] |
209 |
| - id in con_vs || continue |
210 |
| - for (pos, arg) in args |
211 |
| - if !disconnected && !(arg in con_args) |
212 |
| - continue |
213 |
| - end |
214 |
| - arg_str = pos isa Int ? "arg $pos" : "kwarg $pos" |
215 |
| - str *= "a$arg $edge_sep v$id [label=\"$arg_str\"]\n" |
216 |
| - end |
217 |
| - end |
218 |
| - |
219 |
| - str *= "}\n" |
220 |
| - gv = GraphViz.Graph(str) |
| 33 | + times::Bool=true, times_digits::Integer=3, |
| 34 | + colors=Dagger.Viz.default_colors, |
| 35 | + name_to_color=Dagger.Viz.name_to_color) |
| 36 | + dot = Dagger.Viz.logs_to_dot(logs; disconnected, times, times_digits, |
| 37 | + color_by, colors, name_to_color) |
| 38 | + gv = GraphViz.Graph(dot) |
221 | 39 | GraphViz.layout!(gv; engine=layout_engine)
|
222 | 40 | return gv
|
223 | 41 | end
|
|
0 commit comments