diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index ec0f14fe1..37256a4b9 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -83,8 +83,8 @@ jobs: name: perf-stat - name: Extract performance data run: | - mkdir -p build/perf_stat_dir - unzip -o perf-stat.zip -d . + mkdir -p build + unzip -o perf-stat.zip -d build - name: CMake configure run: | cmake -S . -B build -DUSE_SCOREBOARD=ON diff --git a/scoreboard/main.py b/scoreboard/main.py index dceaf820f..f8c707b8b 100644 --- a/scoreboard/main.py +++ b/scoreboard/main.py @@ -13,19 +13,44 @@ logger = logging.getLogger(__name__) task_types = ["all", "mpi", "omp", "seq", "stl", "tbb"] +task_types_threads = ["all", "omp", "seq", "stl", "tbb"] +task_types_processes = ["mpi", "seq"] script_dir = Path(__file__).parent tasks_dir = script_dir.parent / "tasks" +def _read_tasks_type(task_dir: Path) -> str | None: + """Read tasks_type from settings.json in the task directory (if present).""" + settings_path = task_dir / "settings.json" + if settings_path.exists(): + try: + import json + + with open(settings_path, "r") as f: + data = json.load(f) + return data.get("tasks_type") # "threads" or "processes" + except Exception as e: + logger.warning("Failed to parse %s: %s", settings_path, e) + return None + + def discover_tasks(tasks_dir, task_types): - """Discover tasks and their implementation status from the filesystem.""" + """Discover tasks and their implementation status from the filesystem. + + Returns: + directories: dict[task_name][task_type] -> status + tasks_type_map: dict[task_name] -> "threads" | "processes" | None + """ directories = defaultdict(dict) + tasks_type_map: dict[str, str | None] = {} if tasks_dir.exists() and tasks_dir.is_dir(): for task_name_dir in tasks_dir.iterdir(): if task_name_dir.is_dir() and task_name_dir.name not in ["common"]: task_name = task_name_dir.name + # Save tasks_type from settings.json if present + tasks_type_map[task_name] = _read_tasks_type(task_name_dir) for task_type in task_types: task_type_dir = task_name_dir / task_type if task_type_dir.exists() and task_type_dir.is_dir(): @@ -35,10 +60,10 @@ def discover_tasks(tasks_dir, task_types): else: directories[task_name][task_type] = "done" - return directories + return directories, tasks_type_map -directories = discover_tasks(tasks_dir, task_types) +directories, tasks_type_map = discover_tasks(tasks_dir, task_types) def load_performance_data(perf_stat_file_path): @@ -163,24 +188,20 @@ def load_configurations(): return cfg, eff_num_proc, deadlines_cfg, plagiarism_cfg -def main(): - """Main function to generate the scoreboard.""" - cfg, eff_num_proc, deadlines_cfg, plagiarism_cfg = load_configurations() - - env = Environment(loader=FileSystemLoader(Path(__file__).parent / "templates")) - - perf_stat_file_path = ( - script_dir.parent / "build" / "perf_stat_dir" / "task_run_perf_table.csv" - ) - - # Read and parse performance statistics CSV - perf_stats = load_performance_data(perf_stat_file_path) - +def _build_rows_for_task_types( + selected_task_types: list[str], + dir_names: list[str], + perf_stats: dict, + cfg, + eff_num_proc, + deadlines_cfg, +): + """Build rows for the given list of task directories and selected task types.""" rows = [] - for dir in sorted(directories.keys()): + for dir in sorted(dir_names): row_types = [] total_count = 0 - for task_type in task_types: + for task_type in selected_task_types: status = directories[dir].get(task_type) sol_points, solution_style = get_solution_points_and_style( task_type, status, cfg @@ -219,22 +240,118 @@ def main(): total_count += task_points rows.append({"task": dir, "types": row_types, "total": total_count}) + return rows + + +def main(): + """Main function to generate the scoreboard. + + Now generates three pages in the output dir: + - index.html: simple menu linking to threads.html and processes.html + - threads.html: scoreboard for thread-based tasks + - processes.html: scoreboard for process-based tasks + """ + cfg, eff_num_proc, deadlines_cfg, plagiarism_cfg_local = load_configurations() - template = env.get_template("index.html.j2") - html_content = template.render(task_types=task_types, rows=rows) + # Make plagiarism config available to rows builder + global plagiarism_cfg + plagiarism_cfg = plagiarism_cfg_local + + env = Environment(loader=FileSystemLoader(Path(__file__).parent / "templates")) + + # Locate perf CSV from CI or local runs + candidates = [ + script_dir.parent / "build" / "perf_stat_dir" / "task_run_perf_table.csv", + script_dir.parent / "perf_stat_dir" / "task_run_perf_table.csv", + ] + perf_stat_file_path = next((p for p in candidates if p.exists()), candidates[0]) + + # Read and parse performance statistics CSV + perf_stats = load_performance_data(perf_stat_file_path) + + # Partition tasks by tasks_type from settings.json + threads_task_dirs = [ + name for name, ttype in tasks_type_map.items() if ttype == "threads" + ] + processes_task_dirs = [ + name for name, ttype in tasks_type_map.items() if ttype == "processes" + ] + + # Fallback: if settings.json is missing, guess by directory name heuristic + for name in directories.keys(): + if name not in tasks_type_map or tasks_type_map[name] is None: + if "threads" in name: + threads_task_dirs.append(name) + elif "processes" in name: + processes_task_dirs.append(name) + + # Build rows for each page + threads_rows = _build_rows_for_task_types( + task_types_threads, + threads_task_dirs, + perf_stats, + cfg, + eff_num_proc, + deadlines_cfg, + ) + processes_rows = _build_rows_for_task_types( + task_types_processes, + processes_task_dirs, + perf_stats, + cfg, + eff_num_proc, + deadlines_cfg, + ) parser = argparse.ArgumentParser(description="Generate HTML scoreboard.") parser.add_argument( - "-o", "--output", type=str, required=True, help="Output file path" + "-o", "--output", type=str, required=True, help="Output directory path" ) args = parser.parse_args() output_path = Path(args.output) output_path.mkdir(parents=True, exist_ok=True) - output_file = output_path / "index.html" - with open(output_file, "w") as file: - file.write(html_content) + # Render tables + table_template = env.get_template("index.html.j2") + threads_html = table_template.render( + task_types=task_types_threads, rows=threads_rows + ) + processes_html = table_template.render( + task_types=task_types_processes, rows=processes_rows + ) + + with open(output_path / "threads.html", "w") as f: + f.write(threads_html) + with open(output_path / "processes.html", "w") as f: + f.write(processes_html) + + # Render index menu page + try: + menu_template = env.get_template("menu_index.html.j2") + except Exception: + # Simple fallback menu if template missing + menu_html_content = ( + 'Scoreboard' + "

Scoreboard

" + "" + ) + else: + menu_html_content = menu_template.render( + pages=[ + {"href": "threads.html", "title": "Threads Scoreboard"}, + {"href": "processes.html", "title": "Processes Scoreboard"}, + ] + ) + + with open(output_path / "index.html", "w") as f: + f.write(menu_html_content) + + # Copy static assets static_src = script_dir / "static" static_dst = output_path / "static" if static_src.exists(): @@ -245,7 +362,10 @@ def main(): else: logger.warning("Static directory not found at %s", static_src) - logger.info("HTML page generated at %s", output_file) + logger.info( + "HTML pages generated at %s (index.html, threads.html, processes.html)", + output_path, + ) if __name__ == "__main__": diff --git a/scoreboard/templates/index.html.j2 b/scoreboard/templates/index.html.j2 index 4e4d9dbb9..ccd919087 100644 --- a/scoreboard/templates/index.html.j2 +++ b/scoreboard/templates/index.html.j2 @@ -5,17 +5,7 @@ -

Scoreboard

-

Note: This is experimental and results are for reference only!

-

- (S)olution - The correctness and completeness of the implemented solution.
- (A)cceleration - The process of speeding up software to improve performance. - Speedup = T(seq) / T(parallel)
- (E)fficiency - Optimizing software speed-up by improving CPU utilization and resource management. - Efficiency = Speedup / NumProcs * 100%
- (D)eadline - The timeliness of the submission in relation to the given deadline.
- (P)lagiarism - The originality of the work, ensuring no copied content from external sources.
-

+ diff --git a/scoreboard/templates/menu_index.html.j2 b/scoreboard/templates/menu_index.html.j2 new file mode 100644 index 000000000..88e54d4aa --- /dev/null +++ b/scoreboard/templates/menu_index.html.j2 @@ -0,0 +1,35 @@ + + + + + Scoreboard Menu + + + + + +

Scoreboard

+ +

+ (S)olution - The correctness and completeness of the implemented solution.
+ (A)cceleration - The process of speeding up software to improve performance. Speedup = T(seq) / T(parallel)
+ (E)fficiency - Optimizing software speed-up by improving CPU utilization and resource management. Efficiency = Speedup / NumProcs * 100%
+ (D)eadline - The timeliness of the submission in relation to the given deadline.
+ (P)lagiarism - The originality of the work, ensuring no copied content from external sources. +

+
Choose a scoreboard above to view. Defaults to Threads.
+ + +
Tasks