From d51431a1c39d3740b9c91a827187ec09515b836f Mon Sep 17 00:00:00 2001 From: Alexander Nesterov Date: Wed, 2 Jul 2025 18:46:03 +0200 Subject: [PATCH 1/3] refactor CMake scripts: centralize target linking functionality --- CMakeLists.txt | 1 + cmake/gtest.cmake | 12 +++++++ cmake/json.cmake | 11 +++++++ cmake/libenvpp.cmake | 18 ++++++++++ cmake/mpi.cmake | 17 +++++++++- cmake/onetbb.cmake | 14 ++++++++ cmake/openmp.cmake | 10 ++++++ cmake/stb.cmake | 5 +++ modules/core/CMakeLists.txt | 65 +++++-------------------------------- 9 files changed, 95 insertions(+), 58 deletions(-) create mode 100644 cmake/stb.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 88f343287..1a0982a27 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,6 +31,7 @@ include(cmake/modes.cmake) include(cmake/sanitizers.cmake) include(cmake/json.cmake) include(cmake/libenvpp.cmake) +include(cmake/stb.cmake) ################# Parallel programming technologies ################# diff --git a/cmake/gtest.cmake b/cmake/gtest.cmake index a9bdd6f13..eb41a7cd3 100644 --- a/cmake/gtest.cmake +++ b/cmake/gtest.cmake @@ -24,3 +24,15 @@ ExternalProject_Add( "${CMAKE_COMMAND}" --install "${CMAKE_CURRENT_BINARY_DIR}/ppc_googletest/build" --prefix "${CMAKE_CURRENT_BINARY_DIR}/ppc_googletest/install") + +function(ppc_link_gtest exec_func_lib) + # Add external project include directories + target_include_directories( + ${exec_func_lib} + PUBLIC ${CMAKE_SOURCE_DIR}/3rdparty/googletest/googletest/include) + + add_dependencies(${exec_func_lib} ppc_googletest) + target_link_directories(${exec_func_lib} PUBLIC + "${CMAKE_BINARY_DIR}/ppc_googletest/install/lib") + target_link_libraries(${exec_func_lib} PUBLIC gtest gtest_main) +endfunction() diff --git a/cmake/json.cmake b/cmake/json.cmake index 89070d4b7..882553058 100644 --- a/cmake/json.cmake +++ b/cmake/json.cmake @@ -19,3 +19,14 @@ ExternalProject_Add( INSTALL_COMMAND "${CMAKE_COMMAND}" --install "${CMAKE_CURRENT_BINARY_DIR}/ppc_json/build" --prefix "${CMAKE_CURRENT_BINARY_DIR}/ppc_json/install") + +function(ppc_link_json exec_func_lib) + # Add external project include directories + target_include_directories( + ${exec_func_lib} + PUBLIC ${CMAKE_SOURCE_DIR}/3rdparty/json/include) + + add_dependencies(${exec_func_lib} ppc_json) + target_link_directories(${exec_func_lib} INTERFACE + "${CMAKE_BINARY_DIR}/ppc_json/install/include") +endfunction() \ No newline at end of file diff --git a/cmake/libenvpp.cmake b/cmake/libenvpp.cmake index 564a7d488..e150de19b 100644 --- a/cmake/libenvpp.cmake +++ b/cmake/libenvpp.cmake @@ -38,3 +38,21 @@ if(WIN32) else() set(PPC_ENVPP_LIB_NAME envpp) endif() + +function(ppc_link_envpp exec_func_lib) + # Add external project include directories + target_include_directories( + ${exec_func_lib} + PUBLIC ${CMAKE_SOURCE_DIR}/3rdparty/libenvpp/include) + target_include_directories( + ${exec_func_lib} SYSTEM + PUBLIC ${CMAKE_SOURCE_DIR}/3rdparty/libenvpp/external/fmt/include) + + add_dependencies(${exec_func_lib} ppc_libenvpp) + target_link_directories(${exec_func_lib} PUBLIC + "${CMAKE_BINARY_DIR}/ppc_libenvpp/install/lib") + target_link_directories(${exec_func_lib} PUBLIC + "${CMAKE_BINARY_DIR}/ppc_libenvpp/build") + target_link_libraries(${exec_func_lib} PUBLIC ${PPC_ENVPP_LIB_NAME}) + target_link_libraries(${exec_func_lib} PUBLIC ${PPC_FMT_LIB_NAME}) +endfunction() diff --git a/cmake/mpi.cmake b/cmake/mpi.cmake index 8b307ccdd..9394ff932 100644 --- a/cmake/mpi.cmake +++ b/cmake/mpi.cmake @@ -1,4 +1,19 @@ find_package(MPI REQUIRED) if(NOT MPI_FOUND) message(FATAL_ERROR "MPI NOT FOUND") -endif(MPI_FOUND) +endif() + +function(ppc_link_mpi exec_func_lib) + find_package(MPI REQUIRED) + if(MPI_COMPILE_FLAGS) + set_target_properties(${exec_func_lib} PROPERTIES COMPILE_FLAGS + "${MPI_COMPILE_FLAGS}") + endif(MPI_COMPILE_FLAGS) + + if(MPI_LINK_FLAGS) + set_target_properties(${exec_func_lib} PROPERTIES LINK_FLAGS + "${MPI_LINK_FLAGS}") + endif(MPI_LINK_FLAGS) + target_include_directories(${exec_func_lib} PUBLIC ${MPI_INCLUDE_PATH}) + target_link_libraries(${exec_func_lib} PUBLIC ${MPI_LIBRARIES}) +endfunction() diff --git a/cmake/onetbb.cmake b/cmake/onetbb.cmake index df89aa354..b14b2ed0e 100644 --- a/cmake/onetbb.cmake +++ b/cmake/onetbb.cmake @@ -42,3 +42,17 @@ if(cmake_build_type_lower STREQUAL "debug") else() set(PPC_TBB_LIB_NAME tbb) endif() + +function(ppc_link_tbb exec_func_lib) + # Add external project include directories + target_include_directories( + ${exec_func_lib} + PUBLIC ${CMAKE_SOURCE_DIR}/3rdparty/onetbb/include) + + add_dependencies(${exec_func_lib} ppc_onetbb) + target_link_directories(${exec_func_lib} PUBLIC + ${CMAKE_BINARY_DIR}/ppc_onetbb/install/lib) + if(NOT MSVC) + target_link_libraries(${exec_func_lib} PUBLIC ${PPC_TBB_LIB_NAME}) + endif() +endfunction() diff --git a/cmake/openmp.cmake b/cmake/openmp.cmake index 445815153..33b56e339 100644 --- a/cmake/openmp.cmake +++ b/cmake/openmp.cmake @@ -23,3 +23,13 @@ if(OpenMP_FOUND) else(OpenMP_FOUND) message(FATAL_ERROR "OpenMP NOT FOUND") endif(OpenMP_FOUND) + +function(ppc_link_threads exec_func_lib) + target_link_libraries(${exec_func_lib} PUBLIC Threads::Threads) +endfunction() + +function(ppc_link_openmp exec_func_lib) + find_package(OpenMP REQUIRED) + target_link_libraries(${exec_func_lib} PUBLIC ${OpenMP_libomp_LIBRARY} + OpenMP::OpenMP_CXX) +endfunction() diff --git a/cmake/stb.cmake b/cmake/stb.cmake new file mode 100644 index 000000000..2770d4440 --- /dev/null +++ b/cmake/stb.cmake @@ -0,0 +1,5 @@ +function(ppc_link_stb exec_func_lib) + add_library(stb_image STATIC ${CMAKE_SOURCE_DIR}/3rdparty/stb_image_wrapper.cpp) + target_include_directories(stb_image PUBLIC ${CMAKE_SOURCE_DIR}/3rdparty/stb) + target_link_libraries(${exec_func_lib} PUBLIC stb_image) +endfunction() \ No newline at end of file diff --git a/modules/core/CMakeLists.txt b/modules/core/CMakeLists.txt index 318572711..487b2c9f5 100644 --- a/modules/core/CMakeLists.txt +++ b/modules/core/CMakeLists.txt @@ -29,63 +29,14 @@ target_include_directories( ${exec_func_lib} PUBLIC ${CMAKE_SOURCE_DIR}/3rdparty ${CMAKE_SOURCE_DIR}/modules ${CMAKE_SOURCE_DIR}/tasks) -# Add external project include directories -target_include_directories( - ${exec_func_lib} - PUBLIC ${CMAKE_SOURCE_DIR}/3rdparty/onetbb/include - ${CMAKE_SOURCE_DIR}/3rdparty/json/include - ${CMAKE_SOURCE_DIR}/3rdparty/googletest/googletest/include - ${CMAKE_SOURCE_DIR}/3rdparty/libenvpp/include) -target_include_directories( - ${exec_func_lib} SYSTEM - PUBLIC ${CMAKE_SOURCE_DIR}/3rdparty/libenvpp/external/fmt/include) - -add_dependencies(${exec_func_lib} ppc_libenvpp) -target_link_directories(${exec_func_lib} PUBLIC - "${CMAKE_BINARY_DIR}/ppc_libenvpp/install/lib") -target_link_directories(${exec_func_lib} PUBLIC - "${CMAKE_BINARY_DIR}/ppc_libenvpp/build") -target_link_libraries(${exec_func_lib} PUBLIC ${PPC_ENVPP_LIB_NAME}) -target_link_libraries(${exec_func_lib} PUBLIC ${PPC_FMT_LIB_NAME}) - -add_dependencies(${exec_func_lib} ppc_json) -target_link_directories(${exec_func_lib} INTERFACE - "${CMAKE_BINARY_DIR}/ppc_json/install/include") - -add_dependencies(${exec_func_lib} ppc_googletest) -target_link_directories(${exec_func_lib} PUBLIC - "${CMAKE_BINARY_DIR}/ppc_googletest/install/lib") -target_link_libraries(${exec_func_lib} PUBLIC gtest gtest_main) - -target_link_libraries(${exec_func_lib} PUBLIC Threads::Threads) - -find_package(OpenMP REQUIRED) -target_link_libraries(${exec_func_lib} PUBLIC ${OpenMP_libomp_LIBRARY} - OpenMP::OpenMP_CXX) - -add_dependencies(${exec_func_lib} ppc_onetbb) -target_link_directories(${exec_func_lib} PUBLIC - ${CMAKE_BINARY_DIR}/ppc_onetbb/install/lib) -if(NOT MSVC) - target_link_libraries(${exec_func_lib} PUBLIC ${PPC_TBB_LIB_NAME}) -endif() - -find_package(MPI REQUIRED) -if(MPI_COMPILE_FLAGS) - set_target_properties(${exec_func_lib} PROPERTIES COMPILE_FLAGS - "${MPI_COMPILE_FLAGS}") -endif(MPI_COMPILE_FLAGS) - -if(MPI_LINK_FLAGS) - set_target_properties(${exec_func_lib} PROPERTIES LINK_FLAGS - "${MPI_LINK_FLAGS}") -endif(MPI_LINK_FLAGS) -target_include_directories(${exec_func_lib} PUBLIC ${MPI_INCLUDE_PATH}) -target_link_libraries(${exec_func_lib} PUBLIC ${MPI_LIBRARIES}) - -add_library(stb_image STATIC ${CMAKE_SOURCE_DIR}/3rdparty/stb_image_wrapper.cpp) -target_include_directories(stb_image PUBLIC ${CMAKE_SOURCE_DIR}/3rdparty/stb) -target_link_libraries(${exec_func_lib} PUBLIC stb_image) +ppc_link_envpp(${exec_func_lib}) +ppc_link_json(${exec_func_lib}) +ppc_link_gtest(${exec_func_lib}) +ppc_link_threads(${exec_func_lib}) +ppc_link_openmp(${exec_func_lib}) +ppc_link_tbb(${exec_func_lib}) +ppc_link_mpi(${exec_func_lib}) +ppc_link_stb(${exec_func_lib}) add_executable(${exec_func_tests} ${FUNC_TESTS_SOURCE_FILES}) From e329437dc77129e800bb300fa1a5d9ea496894ea Mon Sep 17 00:00:00 2001 From: Alexander Nesterov Date: Wed, 8 Oct 2025 15:03:30 +0200 Subject: [PATCH 2/3] split HTML scoreboard into threads and processes views; add a menu index page --- .github/workflows/pages.yml | 4 +- scoreboard/main.py | 162 ++++++++++++++++++++---- scoreboard/templates/index.html.j2 | 12 +- scoreboard/templates/menu_index.html.j2 | 35 +++++ 4 files changed, 175 insertions(+), 38 deletions(-) create mode 100644 scoreboard/templates/menu_index.html.j2 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..564515d53 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,113 @@ 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 +357,7 @@ 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.
+ + + From 05f2264e2fceeb4303ecade5d3a6817e60556db1 Mon Sep 17 00:00:00 2001 From: Alexander Nesterov Date: Wed, 8 Oct 2025 15:10:06 +0200 Subject: [PATCH 3/3] refactor scoreboard: improve code formatting and HTML consistency --- scoreboard/main.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/scoreboard/main.py b/scoreboard/main.py index 564515d53..f8c707b8b 100644 --- a/scoreboard/main.py +++ b/scoreboard/main.py @@ -287,7 +287,12 @@ def main(): # 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 + task_types_threads, + threads_task_dirs, + perf_stats, + cfg, + eff_num_proc, + deadlines_cfg, ) processes_rows = _build_rows_for_task_types( task_types_processes, @@ -327,12 +332,12 @@ def main(): except Exception: # Simple fallback menu if template missing menu_html_content = ( - "Scoreboard" + 'Scoreboard' "

Scoreboard

" "" ) else: @@ -357,7 +362,10 @@ def main(): else: logger.warning("Static directory not found at %s", static_src) - logger.info("HTML pages generated at %s (index.html, threads.html, processes.html)", output_path) + logger.info( + "HTML pages generated at %s (index.html, threads.html, processes.html)", + output_path, + ) if __name__ == "__main__":
Tasks