|
| 1 | +module LuxorExt |
| 2 | + |
| 3 | +if isdefined(Base, :get_extension) |
| 4 | + using Luxor |
| 5 | +else |
| 6 | + using ..Luxor |
| 7 | +end |
| 8 | + |
| 9 | +import Dagger |
| 10 | +import Dagger: Chunk, Processor |
| 11 | +import Dagger.TimespanLogging: Timespan |
| 12 | + |
| 13 | +import .Luxor: Drawing, finish, Point, background, sethue, fontsize, rect, text |
| 14 | + |
| 15 | +function proclt(p1::T, p2::R) where {T,R} |
| 16 | + if p1.owner != p2.owner |
| 17 | + return p1.owner < p2.owner |
| 18 | + else |
| 19 | + return repr(T) < repr(R) |
| 20 | + end |
| 21 | +end |
| 22 | +function proclt(p1::T, p2::T) where {T} |
| 23 | + if p1.owner != p2.owner |
| 24 | + return p1.owner < p2.owner |
| 25 | + else |
| 26 | + for field in fieldnames(T) |
| 27 | + f1 = getfield(p1, field) |
| 28 | + f2 = getfield(p2, field) |
| 29 | + if f1 != f2 |
| 30 | + return f1 < f2 |
| 31 | + end |
| 32 | + end |
| 33 | + end |
| 34 | + false |
| 35 | +end |
| 36 | +proclt(p1::Dagger.OSProc, p2::Dagger.OSProc) = p1.pid < p2.pid |
| 37 | +proclt(p1::Dagger.OSProc, p2) = p1.pid < p2.owner |
| 38 | +proclt(p1, p2::Dagger.OSProc) = p1.owner < p2.pid |
| 39 | + |
| 40 | +function update_window_logs!(window_logs, logs; root_time, window_start) |
| 41 | + if !isempty(logs) |
| 42 | + for id in keys(logs) |
| 43 | + append!(window_logs, map(x->(x,), filter(x->x.category==:compute||x.category==:scheduler_init, logs[id]))) |
| 44 | + end |
| 45 | + end |
| 46 | + for idx in length(window_logs):-1:1 |
| 47 | + log = window_logs[idx] |
| 48 | + if length(log) == 2 |
| 49 | + # Clear out finished events older than window start |
| 50 | + log_finish_s = (log[2].timestamp-root_time)/(1000^3) |
| 51 | + if log_finish_s < window_start |
| 52 | + @debug "Gantt: Deleted event" |
| 53 | + deleteat!(window_logs, idx) |
| 54 | + end |
| 55 | + elseif log[1] isa Dagger.Event{:finish} |
| 56 | + # Pair finish events with start events |
| 57 | + sidx = findfirst(x->length(x) == 1 && |
| 58 | + x[1] isa Dagger.Event{:start} && |
| 59 | + x[1].id==log[1].id, window_logs) |
| 60 | + if sidx === nothing |
| 61 | + @debug "Gantt: Removed unpaired finish" |
| 62 | + deleteat!(window_logs, idx) |
| 63 | + continue |
| 64 | + end |
| 65 | + window_logs[sidx] = (window_logs[sidx][1], log[1]) |
| 66 | + @debug "Gantt: Paired event" |
| 67 | + deleteat!(window_logs, idx) |
| 68 | + end |
| 69 | + end |
| 70 | +end |
| 71 | +function Dagger.render_plan(logs::Dict, ::Val{:luxor_gantt}; delay=2, width=1000, height=640, window_length=20) |
| 72 | + root_time = time_ns() |
| 73 | + window_logs = [] |
| 74 | + if window_length !== nothing |
| 75 | + window_start = -window_length |
| 76 | + else |
| 77 | + window_start = 0 |
| 78 | + end |
| 79 | + window_finish = 0 |
| 80 | + procs = Dagger.all_processors() |
| 81 | + if (height/length(procs)) < 50 |
| 82 | + height = length(procs) * 50 |
| 83 | + @warn "SVG height too small; resizing to $height pixels" |
| 84 | + end |
| 85 | + |
| 86 | + update_window_logs!(window_logs, logs; root_time=root_time, window_start=window_start) |
| 87 | + isempty(window_logs) && return |
| 88 | + |
| 89 | + for proc in unique(map(x->x[1].timeline[2], filter(x->x[1].category==:compute, window_logs))) |
| 90 | + push!(procs, proc) |
| 91 | + end |
| 92 | + colors = Colors.distinguishable_colors(length(procs)) |
| 93 | + procs_len = length(procs) |
| 94 | + proc_height = height/(3procs_len) |
| 95 | + @debug "Gantt: Start" |
| 96 | + if isfile(svg_path) |
| 97 | + Drawing(width, height, svg_path) |
| 98 | + else |
| 99 | + Drawing(width, height, joinpath(svg_path, repr(image_idx) * ".svg")) |
| 100 | + end |
| 101 | + background("white") |
| 102 | + for (proc_idx, proc) in enumerate(sort(collect(procs); lt=proclt)) |
| 103 | + ypos = (proc_idx-0.5)*(height/procs_len) |
| 104 | + sethue("grey15") |
| 105 | + fontsize(round(Int,proc_height)) |
| 106 | + text(getname(proc), Point(width/2,ypos-(proc_height/2))) |
| 107 | + rect(Point(1,ypos+(proc_height/3)),width-2,proc_height,:stroke) |
| 108 | + fontsize(8) |
| 109 | + text("$(window_start) s", Point(0,ypos); halign=:left) |
| 110 | + text("$(window_finish) s", Point(width-8,ypos); halign=:right) |
| 111 | + proc_color = colors[proc_idx] |
| 112 | + for log in filter(x->x[1].timeline[2]==proc, filter(x->x[1].category==:compute, window_logs)) |
| 113 | + length(log) == 1 && log[1] isa Dagger.Event{:finish} && error("Unpaired finish!") |
| 114 | + log_start_s = (log[1].timestamp-root_time)/(1000^3) |
| 115 | + log_finish_s = if length(log) == 2 |
| 116 | + log_finish_s = (log[2].timestamp-root_time)/(1000^3) |
| 117 | + else |
| 118 | + window_finish+1 |
| 119 | + end |
| 120 | + xstart = ((log_start_s-window_start)/(window_finish-window_start))*width |
| 121 | + xfinish = ((log_finish_s-window_start)/(window_finish-window_start))*width |
| 122 | + sethue(proc_color) |
| 123 | + rect(Point(xstart,ypos+(proc_height/3)+1),xfinish-xstart,proc_height-2,:fill) |
| 124 | + sethue("black") |
| 125 | + rect(Point(xstart,ypos+(proc_height/3)+1),xfinish-xstart,proc_height-2,:stroke) |
| 126 | + end |
| 127 | + for log in filter(x->x[1].category==:scheduler_init, window_logs) |
| 128 | + log_start_s = (log[1].timestamp-root_time)/(1000^3) |
| 129 | + log_finish_s = if length(log) == 2 |
| 130 | + log_finish_s = (log[2].timestamp-root_time)/(1000^3) |
| 131 | + else |
| 132 | + window_finish+1 |
| 133 | + end |
| 134 | + xstart = ((log_start_s-window_start)/(window_finish-window_start))*width |
| 135 | + xfinish = ((log_finish_s-window_start)/(window_finish-window_start))*width |
| 136 | + sethue("red") |
| 137 | + rect(Point(xstart,0),#=xfinish-xstart=#1,height) |
| 138 | + end |
| 139 | + end |
| 140 | + finish() |
| 141 | +end |
| 142 | + |
| 143 | +end # module LuxorExt |
0 commit comments