129 lines
3.3 KiB
Python
129 lines
3.3 KiB
Python
|
from datetime import datetime
|
||
|
from structures import Gantt, Row, Job
|
||
|
from gantt_types import Second, Millisecond
|
||
|
import structures
|
||
|
import utility
|
||
|
import operator
|
||
|
|
||
|
COLORS = [
|
||
|
"#d50000",
|
||
|
"#00bfa5",
|
||
|
"#ff6f00",
|
||
|
"#aa00ff",
|
||
|
"#006064",
|
||
|
"#ffd600",
|
||
|
"#64dd17",
|
||
|
]
|
||
|
|
||
|
HEADER_COLUMN_WIDTH = 240
|
||
|
|
||
|
|
||
|
def ms_to_px(ms: Millisecond) -> float:
|
||
|
return ms / 10
|
||
|
|
||
|
|
||
|
def job_to_html(job: Job, start: datetime, color: str) -> str:
|
||
|
left = ms_to_px(utility.duration_ms(start, job.start)) + HEADER_COLUMN_WIDTH
|
||
|
width = ms_to_px(structures.job_duration_ms(job))
|
||
|
|
||
|
return f"""<span class="job" data-descr="{job.name}{chr(10)}Duration: {utility.ms_to_s(structures.job_duration_ms(job)):.2f}s" style="left: {left}px; width: {width}px; background-color: {color}"></span>"""
|
||
|
|
||
|
|
||
|
def row_to_html(
|
||
|
row: Row, start: datetime, process_num: int, color: str, width: float
|
||
|
) -> str:
|
||
|
legend_html = f"""<span class="legend" style="width: {HEADER_COLUMN_WIDTH}px">Concurrency Slot {process_num} ({utility.ms_to_s(structures.row_computing_duration_ms(row)):.1f}s)</span>"""
|
||
|
|
||
|
jobs_html = "\n".join([job_to_html(job, start, color) for job in row.jobs])
|
||
|
|
||
|
return (
|
||
|
f"""<div class="row" style="width: {width}px;">{legend_html}{jobs_html}</div>"""
|
||
|
)
|
||
|
|
||
|
|
||
|
def rownum_to_top(num: int) -> float:
|
||
|
return num * 2
|
||
|
|
||
|
|
||
|
def make_axis_span(left: float, s: Second) -> str:
|
||
|
return f"""<span class="axis-tick" style="left: {left}px;">{s} sec</span>"""
|
||
|
|
||
|
|
||
|
def make_axis_html(max_seconds: Second) -> str:
|
||
|
seconds = [Second(i * 2) for i in range(1000)]
|
||
|
|
||
|
seconds = [i for i in seconds if i < max_seconds]
|
||
|
|
||
|
axis_spans = "".join(
|
||
|
[
|
||
|
make_axis_span(ms_to_px(utility.s_to_ms(s)) + HEADER_COLUMN_WIDTH, s)
|
||
|
for s in seconds
|
||
|
]
|
||
|
)
|
||
|
|
||
|
return f"""<div class="row axis">
|
||
|
<span class="legend" style="width: {HEADER_COLUMN_WIDTH}px">Total Processing Time</span>
|
||
|
{axis_spans}
|
||
|
</div>"""
|
||
|
|
||
|
|
||
|
def gantt_to_html(g: Gantt) -> str:
|
||
|
if not g:
|
||
|
return ""
|
||
|
|
||
|
start = min([row.jobs[0].start for row in g])
|
||
|
|
||
|
max_seconds = max([utility.ms_to_s(structures.row_duration_ms(row)) for row in g])
|
||
|
|
||
|
rows_html = "\n".join(
|
||
|
[
|
||
|
row_to_html(
|
||
|
row,
|
||
|
start,
|
||
|
num + 1,
|
||
|
COLORS[num % len(COLORS)],
|
||
|
ms_to_px(utility.s_to_ms(max_seconds)) + HEADER_COLUMN_WIDTH,
|
||
|
)
|
||
|
for num, row in enumerate(
|
||
|
sorted(
|
||
|
g,
|
||
|
reverse=True,
|
||
|
key=lambda r: structures.row_computing_duration_ms(r),
|
||
|
)
|
||
|
)
|
||
|
]
|
||
|
)
|
||
|
|
||
|
return f"""<div class="gantt">{make_axis_html(max_seconds)}{rows_html}</div>"""
|
||
|
|
||
|
|
||
|
def style() -> str:
|
||
|
with open("./gantt/output.css") as css:
|
||
|
return f"""<style>{css.read()}</style>"""
|
||
|
|
||
|
|
||
|
def html(g: Gantt) -> str:
|
||
|
html = f"""
|
||
|
<html>
|
||
|
<head></head>
|
||
|
<body>
|
||
|
<main>
|
||
|
<h1>Gantt Chart</h1>
|
||
|
<p>Max parallelism: {len(g)}</p>
|
||
|
{gantt_to_html(g)}
|
||
|
<h1>Explanation</h1>
|
||
|
<p>
|
||
|
<ul>
|
||
|
<li>Each row represents a parallelism "slot"; if "maxParallelism" was 4, then there are 4 rows.</li>
|
||
|
<li>Each colored block is a job; hover with a mouse to show the name and how long it took.</li>
|
||
|
<li>Each row shows the total time spent doing jobs to highlight bottlenecks.</li>
|
||
|
</ul>
|
||
|
</p>
|
||
|
</main>
|
||
|
{style()}
|
||
|
</body>
|
||
|
</html>
|
||
|
"""
|
||
|
|
||
|
return html if g else ""
|