Skip to content

Commit 7118877

Browse files
authored
Generate separated tables (#644)
1 parent e2f62a9 commit 7118877

File tree

4 files changed

+183
-38
lines changed

4 files changed

+183
-38
lines changed

.github/workflows/pages.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,8 @@ jobs:
8383
name: perf-stat
8484
- name: Extract performance data
8585
run: |
86-
mkdir -p build/perf_stat_dir
87-
unzip -o perf-stat.zip -d .
86+
mkdir -p build
87+
unzip -o perf-stat.zip -d build
8888
- name: CMake configure
8989
run: |
9090
cmake -S . -B build -DUSE_SCOREBOARD=ON

scoreboard/main.py

Lines changed: 145 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,44 @@
1313
logger = logging.getLogger(__name__)
1414

1515
task_types = ["all", "mpi", "omp", "seq", "stl", "tbb"]
16+
task_types_threads = ["all", "omp", "seq", "stl", "tbb"]
17+
task_types_processes = ["mpi", "seq"]
1618

1719
script_dir = Path(__file__).parent
1820
tasks_dir = script_dir.parent / "tasks"
1921

2022

23+
def _read_tasks_type(task_dir: Path) -> str | None:
24+
"""Read tasks_type from settings.json in the task directory (if present)."""
25+
settings_path = task_dir / "settings.json"
26+
if settings_path.exists():
27+
try:
28+
import json
29+
30+
with open(settings_path, "r") as f:
31+
data = json.load(f)
32+
return data.get("tasks_type") # "threads" or "processes"
33+
except Exception as e:
34+
logger.warning("Failed to parse %s: %s", settings_path, e)
35+
return None
36+
37+
2138
def discover_tasks(tasks_dir, task_types):
22-
"""Discover tasks and their implementation status from the filesystem."""
39+
"""Discover tasks and their implementation status from the filesystem.
40+
41+
Returns:
42+
directories: dict[task_name][task_type] -> status
43+
tasks_type_map: dict[task_name] -> "threads" | "processes" | None
44+
"""
2345
directories = defaultdict(dict)
46+
tasks_type_map: dict[str, str | None] = {}
2447

2548
if tasks_dir.exists() and tasks_dir.is_dir():
2649
for task_name_dir in tasks_dir.iterdir():
2750
if task_name_dir.is_dir() and task_name_dir.name not in ["common"]:
2851
task_name = task_name_dir.name
52+
# Save tasks_type from settings.json if present
53+
tasks_type_map[task_name] = _read_tasks_type(task_name_dir)
2954
for task_type in task_types:
3055
task_type_dir = task_name_dir / task_type
3156
if task_type_dir.exists() and task_type_dir.is_dir():
@@ -35,10 +60,10 @@ def discover_tasks(tasks_dir, task_types):
3560
else:
3661
directories[task_name][task_type] = "done"
3762

38-
return directories
63+
return directories, tasks_type_map
3964

4065

41-
directories = discover_tasks(tasks_dir, task_types)
66+
directories, tasks_type_map = discover_tasks(tasks_dir, task_types)
4267

4368

4469
def load_performance_data(perf_stat_file_path):
@@ -163,24 +188,20 @@ def load_configurations():
163188
return cfg, eff_num_proc, deadlines_cfg, plagiarism_cfg
164189

165190

166-
def main():
167-
"""Main function to generate the scoreboard."""
168-
cfg, eff_num_proc, deadlines_cfg, plagiarism_cfg = load_configurations()
169-
170-
env = Environment(loader=FileSystemLoader(Path(__file__).parent / "templates"))
171-
172-
perf_stat_file_path = (
173-
script_dir.parent / "build" / "perf_stat_dir" / "task_run_perf_table.csv"
174-
)
175-
176-
# Read and parse performance statistics CSV
177-
perf_stats = load_performance_data(perf_stat_file_path)
178-
191+
def _build_rows_for_task_types(
192+
selected_task_types: list[str],
193+
dir_names: list[str],
194+
perf_stats: dict,
195+
cfg,
196+
eff_num_proc,
197+
deadlines_cfg,
198+
):
199+
"""Build rows for the given list of task directories and selected task types."""
179200
rows = []
180-
for dir in sorted(directories.keys()):
201+
for dir in sorted(dir_names):
181202
row_types = []
182203
total_count = 0
183-
for task_type in task_types:
204+
for task_type in selected_task_types:
184205
status = directories[dir].get(task_type)
185206
sol_points, solution_style = get_solution_points_and_style(
186207
task_type, status, cfg
@@ -219,22 +240,118 @@ def main():
219240
total_count += task_points
220241

221242
rows.append({"task": dir, "types": row_types, "total": total_count})
243+
return rows
244+
245+
246+
def main():
247+
"""Main function to generate the scoreboard.
248+
249+
Now generates three pages in the output dir:
250+
- index.html: simple menu linking to threads.html and processes.html
251+
- threads.html: scoreboard for thread-based tasks
252+
- processes.html: scoreboard for process-based tasks
253+
"""
254+
cfg, eff_num_proc, deadlines_cfg, plagiarism_cfg_local = load_configurations()
222255

223-
template = env.get_template("index.html.j2")
224-
html_content = template.render(task_types=task_types, rows=rows)
256+
# Make plagiarism config available to rows builder
257+
global plagiarism_cfg
258+
plagiarism_cfg = plagiarism_cfg_local
259+
260+
env = Environment(loader=FileSystemLoader(Path(__file__).parent / "templates"))
261+
262+
# Locate perf CSV from CI or local runs
263+
candidates = [
264+
script_dir.parent / "build" / "perf_stat_dir" / "task_run_perf_table.csv",
265+
script_dir.parent / "perf_stat_dir" / "task_run_perf_table.csv",
266+
]
267+
perf_stat_file_path = next((p for p in candidates if p.exists()), candidates[0])
268+
269+
# Read and parse performance statistics CSV
270+
perf_stats = load_performance_data(perf_stat_file_path)
271+
272+
# Partition tasks by tasks_type from settings.json
273+
threads_task_dirs = [
274+
name for name, ttype in tasks_type_map.items() if ttype == "threads"
275+
]
276+
processes_task_dirs = [
277+
name for name, ttype in tasks_type_map.items() if ttype == "processes"
278+
]
279+
280+
# Fallback: if settings.json is missing, guess by directory name heuristic
281+
for name in directories.keys():
282+
if name not in tasks_type_map or tasks_type_map[name] is None:
283+
if "threads" in name:
284+
threads_task_dirs.append(name)
285+
elif "processes" in name:
286+
processes_task_dirs.append(name)
287+
288+
# Build rows for each page
289+
threads_rows = _build_rows_for_task_types(
290+
task_types_threads,
291+
threads_task_dirs,
292+
perf_stats,
293+
cfg,
294+
eff_num_proc,
295+
deadlines_cfg,
296+
)
297+
processes_rows = _build_rows_for_task_types(
298+
task_types_processes,
299+
processes_task_dirs,
300+
perf_stats,
301+
cfg,
302+
eff_num_proc,
303+
deadlines_cfg,
304+
)
225305

226306
parser = argparse.ArgumentParser(description="Generate HTML scoreboard.")
227307
parser.add_argument(
228-
"-o", "--output", type=str, required=True, help="Output file path"
308+
"-o", "--output", type=str, required=True, help="Output directory path"
229309
)
230310
args = parser.parse_args()
231311

232312
output_path = Path(args.output)
233313
output_path.mkdir(parents=True, exist_ok=True)
234-
output_file = output_path / "index.html"
235-
with open(output_file, "w") as file:
236-
file.write(html_content)
237314

315+
# Render tables
316+
table_template = env.get_template("index.html.j2")
317+
threads_html = table_template.render(
318+
task_types=task_types_threads, rows=threads_rows
319+
)
320+
processes_html = table_template.render(
321+
task_types=task_types_processes, rows=processes_rows
322+
)
323+
324+
with open(output_path / "threads.html", "w") as f:
325+
f.write(threads_html)
326+
with open(output_path / "processes.html", "w") as f:
327+
f.write(processes_html)
328+
329+
# Render index menu page
330+
try:
331+
menu_template = env.get_template("menu_index.html.j2")
332+
except Exception:
333+
# Simple fallback menu if template missing
334+
menu_html_content = (
335+
'<html><head><title>Scoreboard</title><link rel="stylesheet" '
336+
'type="text/css" href="static/main.css"></head><body>'
337+
"<h1>Scoreboard</h1>"
338+
"<ul>"
339+
'<li><a href="threads.html">Threads Scoreboard</a></li>'
340+
'<li><a href="processes.html">Processes Scoreboard</a></li>'
341+
"</ul></body></html>"
342+
)
343+
else:
344+
menu_html_content = menu_template.render(
345+
pages=[
346+
{"href": "threads.html", "title": "Threads Scoreboard"},
347+
{"href": "processes.html", "title": "Processes Scoreboard"},
348+
]
349+
)
350+
351+
with open(output_path / "index.html", "w") as f:
352+
f.write(menu_html_content)
353+
354+
# Copy static assets
238355
static_src = script_dir / "static"
239356
static_dst = output_path / "static"
240357
if static_src.exists():
@@ -245,7 +362,10 @@ def main():
245362
else:
246363
logger.warning("Static directory not found at %s", static_src)
247364

248-
logger.info("HTML page generated at %s", output_file)
365+
logger.info(
366+
"HTML pages generated at %s (index.html, threads.html, processes.html)",
367+
output_path,
368+
)
249369

250370

251371
if __name__ == "__main__":

scoreboard/templates/index.html.j2

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,7 @@
55
<link rel="stylesheet" type="text/css" href="static/main.css">
66
</head>
77
<body>
8-
<h1>Scoreboard</h1>
9-
<h3 style="color: red;">Note:</b> This is experimental and results are for reference only!</h3>
10-
<p>
11-
<b>(S)olution</b> - The correctness and completeness of the implemented solution.<br/>
12-
<b>(A)cceleration</b> - The process of speeding up software to improve performance.
13-
Speedup = T(seq) / T(parallel)<br/>
14-
<b>(E)fficiency</b> - Optimizing software speed-up by improving CPU utilization and resource management.
15-
Efficiency = Speedup / NumProcs * 100%<br/>
16-
<b>(D)eadline</b> - The timeliness of the submission in relation to the given deadline.<br/>
17-
<b>(P)lagiarism</b> - The originality of the work, ensuring no copied content from external sources.<br/>
18-
</p>
8+
199
<table>
2010
<tr>
2111
<th rowspan="2">Tasks</th>
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8" />
5+
<title>Scoreboard Menu</title>
6+
<link rel="stylesheet" type="text/css" href="static/main.css">
7+
<style>
8+
nav { margin: 16px 0; }
9+
nav a { margin-right: 16px; font-weight: bold; }
10+
.note { color: #444; font-size: 0.9em; margin-top: 12px; }
11+
</style>
12+
<script>
13+
function openPage(href) {
14+
document.getElementById('content').src = href;
15+
}
16+
</script>
17+
</head>
18+
<body>
19+
<h1>Scoreboard</h1>
20+
<nav>
21+
{% for page in pages %}
22+
<a href="{{ page.href }}" onclick="openPage('{{ page.href }}'); return false;">{{ page.title }}</a>
23+
{% endfor %}
24+
</nav>
25+
<p>
26+
<b>(S)olution</b> - The correctness and completeness of the implemented solution.<br/>
27+
<b>(A)cceleration</b> - The process of speeding up software to improve performance. Speedup = T(seq) / T(parallel)<br/>
28+
<b>(E)fficiency</b> - Optimizing software speed-up by improving CPU utilization and resource management. Efficiency = Speedup / NumProcs * 100%<br/>
29+
<b>(D)eadline</b> - The timeliness of the submission in relation to the given deadline.<br/>
30+
<b>(P)lagiarism</b> - The originality of the work, ensuring no copied content from external sources.
31+
</p>
32+
<div class="note">Choose a scoreboard above to view. Defaults to Threads.</div>
33+
<iframe id="content" src="threads.html" style="width: 100%; height: 85vh; border: 1px solid #ddd;"></iframe>
34+
</body>
35+
</html>

0 commit comments

Comments
 (0)