From 4d4b6d1656037e4e88f36db9cad446a7a56f39d7 Mon Sep 17 00:00:00 2001 From: Palash Tyagi <23239946+Magnus167@users.noreply.github.com> Date: Mon, 5 May 2025 21:05:48 +0100 Subject: [PATCH 01/31] Add initial HTML landing page --- .github/htmldocs/index.html | 71 +++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 .github/htmldocs/index.html diff --git a/.github/htmldocs/index.html b/.github/htmldocs/index.html new file mode 100644 index 0000000..eb43b87 --- /dev/null +++ b/.github/htmldocs/index.html @@ -0,0 +1,71 @@ + + + + + + + Rustframe + + + + + +
+

+ Rustframe Logo
+ Rustframe +

+

A lightweight dataframe & math toolkit for Rust

+

+ ๐Ÿ“š Docs +

+ ๐Ÿฆ€ Crates.io | + ๐Ÿ”– docs.rs +

+ ๐Ÿ™ GitHub | + ๐ŸŒ Gitea mirror +

+
+ + + \ No newline at end of file From d8154a117537b717e3a3b6c81e4b9745ddf28741 Mon Sep 17 00:00:00 2001 From: Palash Tyagi <23239946+Magnus167@users.noreply.github.com> Date: Mon, 5 May 2025 21:32:51 +0100 Subject: [PATCH 02/31] Enhance CI workflow to download and include benchmark reports in documentation output --- .github/workflows/docs-and-testcov.yml | 31 ++++++++++++++++++++++++++ .github/workflows/run-benchmarks.yml | 2 +- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docs-and-testcov.yml b/.github/workflows/docs-and-testcov.yml index 8ace0a8..2f1f92e 100644 --- a/.github/workflows/docs-and-testcov.yml +++ b/.github/workflows/docs-and-testcov.yml @@ -10,6 +10,10 @@ on: # pull_request: # branches: [main] workflow_dispatch: + workflow_run: + workflows: ["run-benchmarks"] + types: + - completed permissions: contents: read @@ -100,6 +104,30 @@ jobs: <(echo '{}') \ > last-commit-date.json + - name: Download last available benchmark report + run: | + artifact_url=$(gh api -H "Accept: application/vnd.github+json" \ + /repos/${{ github.repository }}/actions/artifacts \ + | jq -r '.artifacts[] | select(.name | startswith("benchmark-reports")) | .archive_download_url' | head -n 1) + + if [ -z "$artifact_url" ]; then + echo "No benchmark artifact found!" + exit 1 + fi + + curl -L -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \ + -o benchmark-report.zip "$artifact_url" + + mkdir -p benchmark-report + unzip benchmark-report.zip -d benchmark-report + # there will be a tar file in the benchmark-report directory + # called artifact.tar. unzip it into the benchmark-report + tar -xvf benchmark-report/artifact.tar -C benchmark-report + # remove the artifact.tar file + rm benchmark-report/artifact.tar + # add an index.html that points to benchmark-report/report/index.html + echo "" > benchmark-report/index.html + - name: Copy files to output directory run: | # mkdir docs @@ -116,6 +144,9 @@ jobs: echo "" > target/doc/index.html touch target/doc/.nojekyll + # copy the benchmark report to the output directory + cp -r benchmark-report target/doc/ + # verify that logo exists in the output directory - name: Verify logo directory run: | diff --git a/.github/workflows/run-benchmarks.yml b/.github/workflows/run-benchmarks.yml index 50ec98c..baea81a 100644 --- a/.github/workflows/run-benchmarks.yml +++ b/.github/workflows/run-benchmarks.yml @@ -1,4 +1,4 @@ -name: Run benchmarks +name: run-benchmarks on: workflow_dispatch: From db8b756a748d1e7a65b063d92c96e7fb09095b27 Mon Sep 17 00:00:00 2001 From: Palash Tyagi <23239946+Magnus167@users.noreply.github.com> Date: Mon, 5 May 2025 21:33:35 +0100 Subject: [PATCH 03/31] Update push trigger to include 'docs_page' branch in CI workflow --- .github/workflows/docs-and-testcov.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs-and-testcov.yml b/.github/workflows/docs-and-testcov.yml index 2f1f92e..81a184f 100644 --- a/.github/workflows/docs-and-testcov.yml +++ b/.github/workflows/docs-and-testcov.yml @@ -6,7 +6,7 @@ concurrency: on: push: - branches: [main] + branches: [main, docs_page] # pull_request: # branches: [main] workflow_dispatch: From f520f29f11f9882a54450e48ce576738ffd2fedb Mon Sep 17 00:00:00 2001 From: Palash Tyagi <23239946+Magnus167@users.noreply.github.com> Date: Mon, 5 May 2025 21:55:56 +0100 Subject: [PATCH 04/31] Add installation of GitHub CLI in Dockerfile --- .github/runners/runner-x64/Dockerfile | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/runners/runner-x64/Dockerfile b/.github/runners/runner-x64/Dockerfile index 79ee4e3..ee137b2 100644 --- a/.github/runners/runner-x64/Dockerfile +++ b/.github/runners/runner-x64/Dockerfile @@ -15,6 +15,12 @@ RUN apt install -y --no-install-recommends \ # Rust and Cargo dependencies gcc cmake +# Install GitHub CLI +RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg \ + && chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg \ + && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | tee /etc/apt/sources.list.d/github-cli.list > /dev/null \ + && apt update -y && apt install -y gh \ + && rm -rf /var/lib/apt/lists/* # Install Rust and Cargo RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y From 659e93c27d8bdb88afec8452da45ca294e4942be Mon Sep 17 00:00:00 2001 From: Palash Tyagi <23239946+Magnus167@users.noreply.github.com> Date: Mon, 5 May 2025 21:57:43 +0100 Subject: [PATCH 05/31] testing runners From 054b3c828e2cb3ed3bb2c7e441163c461cc10bea Mon Sep 17 00:00:00 2001 From: Palash Tyagi <23239946+Magnus167@users.noreply.github.com> Date: Mon, 5 May 2025 22:00:23 +0100 Subject: [PATCH 06/31] Add 'unzip' to the list of installed packages in Dockerfile --- .github/runners/runner-x64/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/runners/runner-x64/Dockerfile b/.github/runners/runner-x64/Dockerfile index ee137b2..de3cb24 100644 --- a/.github/runners/runner-x64/Dockerfile +++ b/.github/runners/runner-x64/Dockerfile @@ -7,7 +7,7 @@ ARG DEBIAN_FRONTEND=noninteractive RUN apt update -y && apt upgrade -y && useradd -m docker RUN apt install -y --no-install-recommends \ - curl jq git \ + curl jq git unzip \ # dev dependencies build-essential libssl-dev libffi-dev python3 python3-venv python3-dev python3-pip \ # dot net core dependencies From aec6278a50fb8bb271ed4fac3772137ef9794bb5 Mon Sep 17 00:00:00 2001 From: Palash Tyagi <23239946+Magnus167@users.noreply.github.com> Date: Mon, 5 May 2025 22:22:50 +0100 Subject: [PATCH 07/31] Add debugging output for benchmark report extraction in CI workflow --- .github/workflows/docs-and-testcov.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/docs-and-testcov.yml b/.github/workflows/docs-and-testcov.yml index 81a184f..53a3495 100644 --- a/.github/workflows/docs-and-testcov.yml +++ b/.github/workflows/docs-and-testcov.yml @@ -119,6 +119,8 @@ jobs: -o benchmark-report.zip "$artifact_url" mkdir -p benchmark-report + # print ls -a + echo "$( ls -a benchmark-report )" unzip benchmark-report.zip -d benchmark-report # there will be a tar file in the benchmark-report directory # called artifact.tar. unzip it into the benchmark-report From bbcdbb4151cd65c4d32ccdfae2177e7beb1b441c Mon Sep 17 00:00:00 2001 From: Palash Tyagi <23239946+Magnus167@users.noreply.github.com> Date: Mon, 5 May 2025 22:28:14 +0100 Subject: [PATCH 08/31] Add error handling for missing benchmark report zip file in CI workflow --- .github/workflows/docs-and-testcov.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/docs-and-testcov.yml b/.github/workflows/docs-and-testcov.yml index 53a3495..cef1082 100644 --- a/.github/workflows/docs-and-testcov.yml +++ b/.github/workflows/docs-and-testcov.yml @@ -118,9 +118,13 @@ jobs: curl -L -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \ -o benchmark-report.zip "$artifact_url" - mkdir -p benchmark-report - # print ls -a - echo "$( ls -a benchmark-report )" + + # check if there is a benchmark-report.zip file + if [ ! -f benchmark-report.zip ]; then + echo "benchmark-report.zip not found!" + exit 1 + fi + unzip benchmark-report.zip -d benchmark-report # there will be a tar file in the benchmark-report directory # called artifact.tar. unzip it into the benchmark-report From f0de677b6984c5b2f82ea1cf29df88f493dd704b Mon Sep 17 00:00:00 2001 From: Palash Tyagi <23239946+Magnus167@users.noreply.github.com> Date: Mon, 5 May 2025 22:31:31 +0100 Subject: [PATCH 09/31] Add validation for benchmark report zip file and list directory contents --- .github/workflows/docs-and-testcov.yml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/docs-and-testcov.yml b/.github/workflows/docs-and-testcov.yml index cef1082..215926c 100644 --- a/.github/workflows/docs-and-testcov.yml +++ b/.github/workflows/docs-and-testcov.yml @@ -118,13 +118,16 @@ jobs: curl -L -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \ -o benchmark-report.zip "$artifact_url" - - # check if there is a benchmark-report.zip file - if [ ! -f benchmark-report.zip ]; then - echo "benchmark-report.zip not found!" + # check if the zip file is valid + if ! unzip -tq benchmark-report.zip; then + echo "benchmark-report.zip is invalid or corrupted!" exit 1 fi - + + # Print all files in the current directory + echo "Files in the current directory:" + ls -al + unzip benchmark-report.zip -d benchmark-report # there will be a tar file in the benchmark-report directory # called artifact.tar. unzip it into the benchmark-report From 33fea1d126612c240c9bd470d678cd0d278a8fc3 Mon Sep 17 00:00:00 2001 From: Palash Tyagi <23239946+Magnus167@users.noreply.github.com> Date: Mon, 5 May 2025 22:33:25 +0100 Subject: [PATCH 10/31] Refactor benchmark report download step to improve error handling and add directory listing --- .github/workflows/docs-and-testcov.yml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/docs-and-testcov.yml b/.github/workflows/docs-and-testcov.yml index 215926c..ad6fe1e 100644 --- a/.github/workflows/docs-and-testcov.yml +++ b/.github/workflows/docs-and-testcov.yml @@ -115,8 +115,13 @@ jobs: exit 1 fi - curl -L -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \ - -o benchmark-report.zip "$artifact_url" + curl -L -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" "$artifact_url" + + + + # Print all files in the current directory + echo "Files in the current directory:" + ls -al # check if the zip file is valid if ! unzip -tq benchmark-report.zip; then @@ -124,10 +129,6 @@ jobs: exit 1 fi - # Print all files in the current directory - echo "Files in the current directory:" - ls -al - unzip benchmark-report.zip -d benchmark-report # there will be a tar file in the benchmark-report directory # called artifact.tar. unzip it into the benchmark-report From efe44c7399b1fc28c8f9bf74d7aaa228b89da437 Mon Sep 17 00:00:00 2001 From: Palash Tyagi <23239946+Magnus167@users.noreply.github.com> Date: Mon, 5 May 2025 22:34:41 +0100 Subject: [PATCH 11/31] Update benchmark report download step to use custom GitHub token for authorization --- .github/workflows/docs-and-testcov.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs-and-testcov.yml b/.github/workflows/docs-and-testcov.yml index ad6fe1e..2d4f7ee 100644 --- a/.github/workflows/docs-and-testcov.yml +++ b/.github/workflows/docs-and-testcov.yml @@ -115,7 +115,7 @@ jobs: exit 1 fi - curl -L -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" "$artifact_url" + curl -L -H "Authorization: Bearer ${{ secrets.CUSTOM_GH_TOKEN }}" "$artifact_url" From eab1c5eec1da096f7b504b287f26efb62cb960a7 Mon Sep 17 00:00:00 2001 From: Palash Tyagi <23239946+Magnus167@users.noreply.github.com> Date: Mon, 5 May 2025 22:36:00 +0100 Subject: [PATCH 12/31] Download benchmark report zip file in CI workflow --- .github/workflows/docs-and-testcov.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docs-and-testcov.yml b/.github/workflows/docs-and-testcov.yml index 2d4f7ee..1d0fbde 100644 --- a/.github/workflows/docs-and-testcov.yml +++ b/.github/workflows/docs-and-testcov.yml @@ -115,7 +115,8 @@ jobs: exit 1 fi - curl -L -H "Authorization: Bearer ${{ secrets.CUSTOM_GH_TOKEN }}" "$artifact_url" + curl -L -H "Authorization: Bearer ${{ secrets.CUSTOM_GH_TOKEN }}" \ + "$artifact_url" -o benchmark-report.zip From eeabfcfff6f14d8a2083198cf480ccd037ba64e2 Mon Sep 17 00:00:00 2001 From: Palash Tyagi <23239946+Magnus167@users.noreply.github.com> Date: Mon, 5 May 2025 22:37:25 +0100 Subject: [PATCH 13/31] Simplify benchmark report extraction process by using quiet unzip and removing unnecessary steps --- .github/workflows/docs-and-testcov.yml | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/.github/workflows/docs-and-testcov.yml b/.github/workflows/docs-and-testcov.yml index 1d0fbde..193ea12 100644 --- a/.github/workflows/docs-and-testcov.yml +++ b/.github/workflows/docs-and-testcov.yml @@ -130,13 +130,8 @@ jobs: exit 1 fi - unzip benchmark-report.zip -d benchmark-report - # there will be a tar file in the benchmark-report directory - # called artifact.tar. unzip it into the benchmark-report - tar -xvf benchmark-report/artifact.tar -C benchmark-report - # remove the artifact.tar file - rm benchmark-report/artifact.tar - # add an index.html that points to benchmark-report/report/index.html + unzip -q benchmark-report.zip -d benchmark-report + echo "" > benchmark-report/index.html - name: Copy files to output directory From b3b0e5e3ae977f71d2d30c1b03f8242a5f42748f Mon Sep 17 00:00:00 2001 From: Palash Tyagi <23239946+Magnus167@users.noreply.github.com> Date: Mon, 5 May 2025 23:21:33 +0100 Subject: [PATCH 14/31] Update index.html to enhance documentation links and add benchmarks section --- .github/htmldocs/index.html | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/htmldocs/index.html b/.github/htmldocs/index.html index eb43b87..cfcda2f 100644 --- a/.github/htmldocs/index.html +++ b/.github/htmldocs/index.html @@ -56,8 +56,11 @@ Rustframe

A lightweight dataframe & math toolkit for Rust

+

- ๐Ÿ“š Docs + ๐Ÿ“š Docs | + ๐Ÿ“Š Benchmarks +

๐Ÿฆ€ Crates.io | ๐Ÿ”– docs.rs From bca112100402cea7c15f1ecddbff9355c238d9ed Mon Sep 17 00:00:00 2001 From: Palash Tyagi <23239946+Magnus167@users.noreply.github.com> Date: Mon, 5 May 2025 23:27:18 +0100 Subject: [PATCH 15/31] Fix icon path in index.html and update .gitignore to include extra copies rustframe_logo.png --- .github/htmldocs/index.html | 4 ++-- .gitignore | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/htmldocs/index.html b/.github/htmldocs/index.html index cfcda2f..b39f850 100644 --- a/.github/htmldocs/index.html +++ b/.github/htmldocs/index.html @@ -5,7 +5,7 @@ Rustframe - + + """ + + html_doc_start = f""" + + + + + Criterion Benchmark Results + {css_styles} + + +

+

Criterion Benchmark Results

+""" + + html_doc_end = """ +
+ +""" + if not results: - return "

No benchmark results found or loaded.

" + return f"""{html_doc_start} +

No benchmark results found or loaded.

+{html_doc_end}""" - # Get all unique sizes (columns) and test names (rows) - # Using ordered dictionaries to maintain insertion order from loading, then sorting keys - # Or simply sort the keys after extraction: - all_sizes = sorted(list(set(size for test_data in results.values() for size in test_data.keys()))) + all_sizes = sorted( + list(set(size for test_data in results.values() for size in test_data.keys())) + ) all_test_names = sorted(list(results.keys())) - html_string = """ - -

Criterion Benchmark Results

-

Each cell links to the detailed Criterion report for that specific benchmark size.

-

Note: Values shown are the midpoint of the mean confidence interval, formatted for readability.

- - - - + table_content = """ +

Each cell links to the detailed Criterion.rs report for that specific benchmark size.

+

Note: Values shown are the midpoint of the mean confidence interval, formatted for readability.

+

[Switch to the standard Criterion.rs report]

+
Benchmark Name
+ + + """ - # Add size headers for size in all_sizes: - html_string += f"\n" + table_content += f"\n" - html_string += """ - - - + table_content += """ + + + """ - # Add data rows for test_name in all_test_names: - html_string += f"\n" - html_string += f" \n" + table_content += f"\n" + table_content += f" \n" - # Iterate through all possible sizes to ensure columns align for size in all_sizes: cell_data = results.get(test_name, {}).get(size) - mean_value = pd.NA # Default value - full_report_url = "#" # Default link to self or dummy + mean_value = pd.NA + full_report_url = "#" - if cell_data and 'json' in cell_data and 'html_path_relative_to_criterion_root' in cell_data: + if ( + cell_data + and "json" in cell_data + and "html_path_relative_to_criterion_root" in cell_data + ): try: - # Extract mean from JSON - mean_data = cell_data['json'].get("mean") + mean_data = cell_data["json"].get("mean") if mean_data and "confidence_interval" in mean_data: ci = mean_data["confidence_interval"] if "lower_bound" in ci and "upper_bound" in ci: - lower, upper = ci["lower_bound"], ci["upper_bound"] - if isinstance(lower, (int, float)) and isinstance(upper, (int, float)): - mean_value = (lower + upper) / 2.0 - else: - print(f"Warning: Non-numeric bounds for {test_name} ({size}).", file=sys.stderr) + lower, upper = ci["lower_bound"], ci["upper_bound"] + if isinstance(lower, (int, float)) and isinstance( + upper, (int, float) + ): + mean_value = (lower + upper) / 2.0 + else: + print( + f"Warning: Non-numeric bounds for {test_name} ({size}).", + file=sys.stderr, + ) else: - print(f"Warning: Missing confidence_interval bounds for {test_name} ({size}).", file=sys.stderr) + print( + f"Warning: Missing confidence_interval bounds for {test_name} ({size}).", + file=sys.stderr, + ) else: - print(f"Warning: Missing 'mean' data for {test_name} ({size}).", file=sys.stderr) - - # Construct the full relative URL - relative_report_path = cell_data['html_path_relative_to_criterion_root'] - full_report_url = f"{html_base_path}{relative_report_path}" - # Ensure forward slashes and resolve potential double slashes if html_base_path ends in / - full_report_url = str(Path(full_report_url)).replace('\\', '/') + print( + f"Warning: Missing 'mean' data for {test_name} ({size}).", + file=sys.stderr, + ) + relative_report_path = cell_data[ + "html_path_relative_to_criterion_root" + ] + joined_path = Path(html_base_path) / relative_report_path + full_report_url = str(joined_path).replace("\\", "/") except Exception as e: - print(f"Error processing cell data for {test_name} ({size}): {e}", file=sys.stderr) - # Keep mean_value as NA and URL as '#' + print( + f"Error processing cell data for {test_name} ({size}): {e}", + file=sys.stderr, + ) - # Format the mean value for display formatted_mean = format_nanoseconds(mean_value) - # Create the link cell - # Only make it a link if a valid report path was found if full_report_url and full_report_url != "#": - html_string += f' \n' + table_content += f' \n' else: - # Display value without a link if no report path - html_string += f' \n' + table_content += f" \n" + table_content += "\n" - - html_string += f"\n" - - html_string += """ - -
Benchmark Name{html.escape(size)}{html.escape(size)}
{html.escape(test_name)}
{html.escape(test_name)}{html.escape(formatted_mean)}{html.escape(formatted_mean)}{html.escape(formatted_mean)}{html.escape(formatted_mean)}
+ table_content += """ + + """ - - return html_string + return f"{html_doc_start}{table_content}{html_doc_end}" if __name__ == "__main__": DEFAULT_CRITERION_PATH = "target/criterion" - # Default relative path from benchmark_results.html to the criterion root on the hosted site - # Assumes benchmark_results.html is in .../doc//benchmarks/ - # And target/criterion is copied to .../doc//target/criterion/ - # So the path from benchmarks/ to target/criterion/ is ../target/criterion/ - DEFAULT_HTML_BASE_PATH = "../target/criterion/" + DEFAULT_OUTPUT_FILE = "./target/criterion/index.html" + DEFAULT_HTML_BASE_PATH = "" parser = argparse.ArgumentParser( description="Load Criterion benchmark results from JSON files and generate an HTML table with links to reports." @@ -250,52 +349,66 @@ if __name__ == "__main__": "--criterion-dir", type=str, default=DEFAULT_CRITERION_PATH, - help=f"Path to the main 'target/criterion' directory (default: {DEFAULT_CRITERION_PATH}) on the runner.", + help=f"Path to the main 'target/criterion' directory (default: {DEFAULT_CRITERION_PATH}) containing benchmark data.", ) parser.add_argument( "--html-base-path", type=str, default=DEFAULT_HTML_BASE_PATH, - help=f"Relative URL path from the output HTML file to the hosted 'target/criterion' directory (default: {DEFAULT_HTML_BASE_PATH}).", + help=( + f"Prefix for HTML links to individual benchmark reports. " + f"This is prepended to each report's relative path (e.g., 'benchmark_name/report/index.html'). " + f"If the main output HTML (default: '{DEFAULT_OUTPUT_FILE}') is in the 'target/criterion/' directory, " + f"this should typically be empty (default: '{DEFAULT_HTML_BASE_PATH}'). " + ), ) parser.add_argument( "--output-file", type=str, - default="benchmark_results.html", - help="Name of the output HTML file (default: benchmark_results.html)." + default=DEFAULT_OUTPUT_FILE, + help=f"Path to save the generated HTML summary report (default: {DEFAULT_OUTPUT_FILE}).", ) - args = parser.parse_args() criterion_path = Path(args.criterion_dir) + output_file_path = Path(args.output_file) + + try: + output_file_path.parent.mkdir(parents=True, exist_ok=True) + except OSError as e: + print( + f"Error: Could not create output directory {output_file_path.parent}: {e}", + file=sys.stderr, + ) + sys.exit(1) + all_results = load_criterion_reports(criterion_path) + # Generate HTML output regardless of whether results were found (handles "no results" page) + html_output = generate_html_table_with_links(all_results, args.html_base_path) + if not all_results: print("\nNo benchmark results found or loaded.") - # Still create an empty file or a file with an error message - try: - with open(args.output_file, "w", encoding="utf-8") as f: - f.write("

Criterion Benchmark Results

No benchmark results found or loaded.

") - print(f"Created empty/error HTML file: {args.output_file}") - except IOError as e: - print(f"Error creating empty/error HTML file {args.output_file}: {e}", file=sys.stderr) - sys.exit(1) # Indicate failure if no data was loaded successfully + # Fallthrough to write the "no results" page generated by generate_html_table_with_links + else: + print("\nSuccessfully loaded benchmark results.") + # pprint(all_results) # Uncomment for debugging - print("\nSuccessfully loaded benchmark results.") - # pprint(all_results) # Uncomment for debugging - - print(f"Generating HTML table with links using base path: {args.html_base_path}") - html_output = generate_html_table_with_links(all_results, args.html_base_path) + print( + f"Generating HTML report with links using HTML base path: '{args.html_base_path}'" + ) try: - with open(args.output_file, "w", encoding="utf-8") as f: + with output_file_path.open("w", encoding="utf-8") as f: f.write(html_output) - print(f"\nSuccessfully wrote HTML table to {args.output_file}") - sys.exit(0) # Exit successfully + print(f"\nSuccessfully wrote HTML report to {output_file_path}") + if not all_results: + sys.exit(1) # Exit with error code if no results, though file is created + sys.exit(0) except IOError as e: - print(f"Error writing HTML output to {args.output_file}: {e}", file=sys.stderr) + print(f"Error writing HTML output to {output_file_path}: {e}", file=sys.stderr) sys.exit(1) except Exception as e: - print(f"An unexpected error occurred while writing HTML: {e}", file=sys.stderr) - sys.exit(1) \ No newline at end of file + print(f"An unexpected error occurred while writing HTML: {e}", file=sys.stderr) + sys.exit(1) From 4e74c2dcfe7b9cb1016ab611451d58ca9fdfa3ee Mon Sep 17 00:00:00 2001 From: Palash Tyagi <23239946+Magnus167@users.noreply.github.com> Date: Wed, 7 May 2025 00:55:09 +0100 Subject: [PATCH 21/31] Refactor benchmark configurations to improve size categorization and sampling settings --- benches/benchmarks.rs | 111 +++++++++++++++++++++++++++--------------- 1 file changed, 72 insertions(+), 39 deletions(-) diff --git a/benches/benchmarks.rs b/benches/benchmarks.rs index 3565dc2..bd17531 100644 --- a/benches/benchmarks.rs +++ b/benches/benchmarks.rs @@ -9,26 +9,36 @@ use rustframe::{ }; use std::time::Duration; -pub fn for_short_runs() -> Criterion { +// Define size categories +const SIZES_SMALL: [usize; 1] = [1]; +const SIZES_MEDIUM: [usize; 3] = [100, 250, 500]; +const SIZES_LARGE: [usize; 1] = [1000]; + +// Configuration functions for different size categories +fn config_small_arrays() -> Criterion { Criterion::default() - // (samples != total iterations) - // limits the number of statistical data points. - .sample_size(50) - // measurement time per sample - .measurement_time(Duration::from_millis(2000)) - // reduce warm-up time as well for faster overall run + .sample_size(500) // More samples for very fast operations + .measurement_time(Duration::from_millis(500)) .warm_up_time(Duration::from_millis(50)) - // can make it much shorter if needed, e.g., 50ms measurement, 100ms warm-up - // .measurement_time(Duration::from_millis(50)) - // .warm_up_time(Duration::from_millis(100)) } -const BENCH_SIZES: [usize; 5] = [1, 100, 250, 500, 1000]; +fn config_medium_arrays() -> Criterion { + Criterion::default() + .sample_size(100) + .measurement_time(Duration::from_millis(2000)) + .warm_up_time(Duration::from_millis(100)) +} -fn bool_matrix_operations_benchmark(c: &mut Criterion) { - let sizes = BENCH_SIZES; +fn config_large_arrays() -> Criterion { + Criterion::default() + .sample_size(50) + .measurement_time(Duration::from_millis(5000)) + .warm_up_time(Duration::from_millis(200)) +} - for &size in &sizes { +// Modified benchmark functions to accept a slice of sizes +fn bool_matrix_operations_benchmark(c: &mut Criterion, sizes: &[usize]) { + for &size in sizes { let data1: Vec = (0..size * size).map(|x| x % 2 == 0).collect(); let data2: Vec = (0..size * size).map(|x| x % 3 == 0).collect(); let bm1 = BoolMatrix::from_vec(data1.clone(), size, size); @@ -60,10 +70,8 @@ fn bool_matrix_operations_benchmark(c: &mut Criterion) { } } -fn matrix_boolean_operations_benchmark(c: &mut Criterion) { - let sizes = BENCH_SIZES; - - for &size in &sizes { +fn matrix_boolean_operations_benchmark(c: &mut Criterion, sizes: &[usize]) { + for &size in sizes { let data1: Vec = (0..size * size).map(|x| x % 2 == 0).collect(); let data2: Vec = (0..size * size).map(|x| x % 3 == 0).collect(); let bm1 = BoolMatrix::from_vec(data1.clone(), size, size); @@ -95,10 +103,8 @@ fn matrix_boolean_operations_benchmark(c: &mut Criterion) { } } -fn matrix_operations_benchmark(c: &mut Criterion) { - let sizes = BENCH_SIZES; - - for &size in &sizes { +fn matrix_operations_benchmark(c: &mut Criterion, sizes: &[usize]) { + for &size in sizes { let data: Vec = (0..size * size).map(|x| x as f64).collect(); let ma = Matrix::from_vec(data.clone(), size, size); @@ -127,8 +133,7 @@ fn matrix_operations_benchmark(c: &mut Criterion) { }); } - // Benchmarking matrix addition - for &size in &sizes { + for &size in sizes { let data1: Vec = (0..size * size).map(|x| x as f64).collect(); let data2: Vec = (0..size * size).map(|x| (x + 1) as f64).collect(); let ma = Matrix::from_vec(data1.clone(), size, size); @@ -167,10 +172,7 @@ fn generate_frame(size: usize) -> Frame { .unwrap() .list() .unwrap(); - - // let col_names= str(i) for i in range(1, 1000) let col_names: Vec = (1..=size).map(|i| format!("col_{}", i)).collect(); - Frame::new( Matrix::from_vec(data.clone(), size, size), col_names, @@ -178,10 +180,8 @@ fn generate_frame(size: usize) -> Frame { ) } -fn benchmark_frame_operations(c: &mut Criterion) { - let sizes = BENCH_SIZES; - - for &size in &sizes { +fn benchmark_frame_operations(c: &mut Criterion, sizes: &[usize]) { + for &size in sizes { let fa = generate_frame(size); let fb = generate_frame(size); @@ -232,13 +232,46 @@ fn benchmark_frame_operations(c: &mut Criterion) { } } -// Define the criterion group and pass the custom configuration function +// Runner functions for each size category +fn run_benchmarks_small(c: &mut Criterion) { + bool_matrix_operations_benchmark(c, &SIZES_SMALL); + matrix_boolean_operations_benchmark(c, &SIZES_SMALL); + matrix_operations_benchmark(c, &SIZES_SMALL); + benchmark_frame_operations(c, &SIZES_SMALL); +} + +fn run_benchmarks_medium(c: &mut Criterion) { + bool_matrix_operations_benchmark(c, &SIZES_MEDIUM); + matrix_boolean_operations_benchmark(c, &SIZES_MEDIUM); + matrix_operations_benchmark(c, &SIZES_MEDIUM); + benchmark_frame_operations(c, &SIZES_MEDIUM); +} + +fn run_benchmarks_large(c: &mut Criterion) { + bool_matrix_operations_benchmark(c, &SIZES_LARGE); + matrix_boolean_operations_benchmark(c, &SIZES_LARGE); + matrix_operations_benchmark(c, &SIZES_LARGE); + benchmark_frame_operations(c, &SIZES_LARGE); +} + criterion_group!( - name = combined_benches; - config = for_short_runs(); // Use the custom configuration here - targets = bool_matrix_operations_benchmark, - matrix_boolean_operations_benchmark, - matrix_operations_benchmark, - benchmark_frame_operations + name = benches_small_arrays; + config = config_small_arrays(); + targets = run_benchmarks_small +); +criterion_group!( + name = benches_medium_arrays; + config = config_medium_arrays(); + targets = run_benchmarks_medium +); +criterion_group!( + name = benches_large_arrays; + config = config_large_arrays(); + targets = run_benchmarks_large +); + +criterion_main!( + benches_small_arrays, + benches_medium_arrays, + benches_large_arrays ); -criterion_main!(combined_benches); From dfe259a3710fe61e34e180b2149849175b76433f Mon Sep 17 00:00:00 2001 From: Palash Tyagi <23239946+Magnus167@users.noreply.github.com> Date: Wed, 7 May 2025 20:20:42 +0100 Subject: [PATCH 22/31] move benchmark config --- benches/benchmarks.rs | 44 +++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/benches/benchmarks.rs b/benches/benchmarks.rs index bd17531..6b94912 100644 --- a/benches/benchmarks.rs +++ b/benches/benchmarks.rs @@ -14,28 +14,6 @@ const SIZES_SMALL: [usize; 1] = [1]; const SIZES_MEDIUM: [usize; 3] = [100, 250, 500]; const SIZES_LARGE: [usize; 1] = [1000]; -// Configuration functions for different size categories -fn config_small_arrays() -> Criterion { - Criterion::default() - .sample_size(500) // More samples for very fast operations - .measurement_time(Duration::from_millis(500)) - .warm_up_time(Duration::from_millis(50)) -} - -fn config_medium_arrays() -> Criterion { - Criterion::default() - .sample_size(100) - .measurement_time(Duration::from_millis(2000)) - .warm_up_time(Duration::from_millis(100)) -} - -fn config_large_arrays() -> Criterion { - Criterion::default() - .sample_size(50) - .measurement_time(Duration::from_millis(5000)) - .warm_up_time(Duration::from_millis(200)) -} - // Modified benchmark functions to accept a slice of sizes fn bool_matrix_operations_benchmark(c: &mut Criterion, sizes: &[usize]) { for &size in sizes { @@ -254,6 +232,28 @@ fn run_benchmarks_large(c: &mut Criterion) { benchmark_frame_operations(c, &SIZES_LARGE); } +// Configuration functions for different size categories +fn config_small_arrays() -> Criterion { + Criterion::default() + .sample_size(500) + .measurement_time(Duration::from_millis(500)) + .warm_up_time(Duration::from_millis(50)) +} + +fn config_medium_arrays() -> Criterion { + Criterion::default() + .sample_size(100) + .measurement_time(Duration::from_millis(2000)) + .warm_up_time(Duration::from_millis(100)) +} + +fn config_large_arrays() -> Criterion { + Criterion::default() + .sample_size(50) + .measurement_time(Duration::from_millis(5000)) + .warm_up_time(Duration::from_millis(200)) +} + criterion_group!( name = benches_small_arrays; config = config_small_arrays(); From 2e980a78fad0e2401e5a4e42d0e76d298a8998d2 Mon Sep 17 00:00:00 2001 From: Palash Tyagi <23239946+Magnus167@users.noreply.github.com> Date: Sun, 11 May 2025 00:46:46 +0100 Subject: [PATCH 23/31] Update documentation for Matrix struct to clarify indexing method --- src/matrix/mat.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/matrix/mat.rs b/src/matrix/mat.rs index 180650a..18eb804 100644 --- a/src/matrix/mat.rs +++ b/src/matrix/mat.rs @@ -2,7 +2,7 @@ use std::ops::{Add, BitAnd, BitOr, BitXor, Div, Index, IndexMut, Mul, Not, Sub}; -/// A columnโ€‘major 2D matrix of `T` +/// A columnโ€‘major 2D matrix of `T`. Index as `Array(row, column)`. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Matrix { rows: usize, From 1a5b8919d3f4a1e3a4c1ec15572a88a7dbc02966 Mon Sep 17 00:00:00 2001 From: Palash Tyagi <23239946+Magnus167@users.noreply.github.com> Date: Sun, 11 May 2025 00:51:24 +0100 Subject: [PATCH 24/31] Adjust benchmark configuration for small arrays to optimize measurement and warm-up times --- benches/benchmarks.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/benches/benchmarks.rs b/benches/benchmarks.rs index 6b94912..bb74a64 100644 --- a/benches/benchmarks.rs +++ b/benches/benchmarks.rs @@ -236,8 +236,8 @@ fn run_benchmarks_large(c: &mut Criterion) { fn config_small_arrays() -> Criterion { Criterion::default() .sample_size(500) - .measurement_time(Duration::from_millis(500)) - .warm_up_time(Duration::from_millis(50)) + .measurement_time(Duration::from_millis(100)) + .warm_up_time(Duration::from_millis(5)) } fn config_medium_arrays() -> Criterion { @@ -254,6 +254,7 @@ fn config_large_arrays() -> Criterion { .warm_up_time(Duration::from_millis(200)) } + criterion_group!( name = benches_small_arrays; config = config_small_arrays(); From 6a9b828adad3a7d63d11bef9fd26097c1e4702e4 Mon Sep 17 00:00:00 2001 From: Palash Tyagi <23239946+Magnus167@users.noreply.github.com> Date: Sun, 11 May 2025 00:51:35 +0100 Subject: [PATCH 25/31] Sort benchmark sizes numerically in HTML report generation --- .github/scripts/custom_benchmark_report.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/scripts/custom_benchmark_report.py b/.github/scripts/custom_benchmark_report.py index 90c2562..f255d98 100644 --- a/.github/scripts/custom_benchmark_report.py +++ b/.github/scripts/custom_benchmark_report.py @@ -247,7 +247,8 @@ def generate_html_table_with_links( {html_doc_end}""" all_sizes = sorted( - list(set(size for test_data in results.values() for size in test_data.keys())) + list(set(size for test_data in results.values() for size in test_data.keys())), + key=(lambda x: int(x.split("x")[0])), ) all_test_names = sorted(list(results.keys())) From 9702b6d5c456ed7d50e7059547f467199b503696 Mon Sep 17 00:00:00 2001 From: Palash Tyagi <23239946+Magnus167@users.noreply.github.com> Date: Sun, 11 May 2025 00:55:29 +0100 Subject: [PATCH 26/31] Add custom benchmark report generation step to workflow --- .github/workflows/run-benchmarks.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/run-benchmarks.yml b/.github/workflows/run-benchmarks.yml index baea81a..bd73ca8 100644 --- a/.github/workflows/run-benchmarks.yml +++ b/.github/workflows/run-benchmarks.yml @@ -36,6 +36,16 @@ jobs: - name: Run benchmarks run: cargo bench --features bench + - name: Generate custom benchmark reports + run: | + if [ -d ./target/criterion ]; then + echo "Found benchmark reports, generating custom report..." + else + echo "No benchmark reports found, skipping custom report generation." + exit 1 + fi + python .github/scripts/custom_benchmark_report.py + - name: Upload benchmark reports uses: actions/upload-artifact@v4 with: From 876f1ccbf37dc7996cb0f3bc88d6c7c40825abbd Mon Sep 17 00:00:00 2001 From: Palash Tyagi <23239946+Magnus167@users.noreply.github.com> Date: Sun, 11 May 2025 01:06:27 +0100 Subject: [PATCH 27/31] Fix Python command to use python3 for custom benchmark report generation --- .github/workflows/run-benchmarks.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-benchmarks.yml b/.github/workflows/run-benchmarks.yml index bd73ca8..d0b9a65 100644 --- a/.github/workflows/run-benchmarks.yml +++ b/.github/workflows/run-benchmarks.yml @@ -44,7 +44,7 @@ jobs: echo "No benchmark reports found, skipping custom report generation." exit 1 fi - python .github/scripts/custom_benchmark_report.py + python3 .github/scripts/custom_benchmark_report.py - name: Upload benchmark reports uses: actions/upload-artifact@v4 From 643c897479f3721b94764fcbba3ac29a04697381 Mon Sep 17 00:00:00 2001 From: Palash Tyagi <23239946+Magnus167@users.noreply.github.com> Date: Sun, 11 May 2025 01:25:27 +0100 Subject: [PATCH 28/31] Install pandas before generating custom benchmark reports --- .github/workflows/run-benchmarks.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/run-benchmarks.yml b/.github/workflows/run-benchmarks.yml index d0b9a65..27e5408 100644 --- a/.github/workflows/run-benchmarks.yml +++ b/.github/workflows/run-benchmarks.yml @@ -44,6 +44,7 @@ jobs: echo "No benchmark reports found, skipping custom report generation." exit 1 fi + pip3 install pandas python3 .github/scripts/custom_benchmark_report.py - name: Upload benchmark reports From e3f4749709adc1286893d288588caa5e3cf226ef Mon Sep 17 00:00:00 2001 From: Palash Tyagi <23239946+Magnus167@users.noreply.github.com> Date: Sun, 11 May 2025 01:40:25 +0100 Subject: [PATCH 29/31] Add dry run option to custom benchmark report script and update workflow to use it --- .github/scripts/custom_benchmark_report.py | 11 +++++++++++ .github/workflows/run-benchmarks.yml | 13 +++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/.github/scripts/custom_benchmark_report.py b/.github/scripts/custom_benchmark_report.py index f255d98..ea3825f 100644 --- a/.github/scripts/custom_benchmark_report.py +++ b/.github/scripts/custom_benchmark_report.py @@ -346,6 +346,11 @@ if __name__ == "__main__": parser = argparse.ArgumentParser( description="Load Criterion benchmark results from JSON files and generate an HTML table with links to reports." ) + parser.add_argument( + "--dry-run", + action="store_true", + help="Perform a dry run without writing the HTML file.", + ) parser.add_argument( "--criterion-dir", type=str, @@ -372,6 +377,12 @@ if __name__ == "__main__": args = parser.parse_args() + if args.dry_run: + print( + "Dry run mode: No files will be written. Use --dry-run to skip writing the HTML file." + ) + sys.exit(0) + criterion_path = Path(args.criterion_dir) output_file_path = Path(args.output_file) diff --git a/.github/workflows/run-benchmarks.yml b/.github/workflows/run-benchmarks.yml index 27e5408..3742725 100644 --- a/.github/workflows/run-benchmarks.yml +++ b/.github/workflows/run-benchmarks.yml @@ -33,6 +33,16 @@ jobs: with: toolchain: stable + - name: Install Python + uses: actions/setup-python@v4 + - name: Install uv + uses: astral-sh/setup-uv@v5 + - name: Setup venv + run: | + uv venv + uv pip install pandas + uv run .github/scripts/custom_benchmark_report.py --dry-run + - name: Run benchmarks run: cargo bench --features bench @@ -44,8 +54,7 @@ jobs: echo "No benchmark reports found, skipping custom report generation." exit 1 fi - pip3 install pandas - python3 .github/scripts/custom_benchmark_report.py + uv run .github/scripts/custom_benchmark_report.py - name: Upload benchmark reports uses: actions/upload-artifact@v4 From c05f1696f035feccc674658e758aa483b9a6c904 Mon Sep 17 00:00:00 2001 From: Palash Tyagi <23239946+Magnus167@users.noreply.github.com> Date: Sun, 11 May 2025 01:55:05 +0100 Subject: [PATCH 30/31] Remove automatic redirect in benchmark report index.html generation --- .github/workflows/docs-and-testcov.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs-and-testcov.yml b/.github/workflows/docs-and-testcov.yml index c845a1a..8b83084 100644 --- a/.github/workflows/docs-and-testcov.yml +++ b/.github/workflows/docs-and-testcov.yml @@ -132,7 +132,7 @@ jobs: unzip -q benchmark-report.zip -d benchmark-report - echo "" > benchmark-report/index.html + # echo "" > benchmark-report/index.html - name: Copy files to output directory run: | From 85482d95697bdbe0e61defe740aec57934247e87 Mon Sep 17 00:00:00 2001 From: Palash Tyagi <23239946+Magnus167@users.noreply.github.com> Date: Sun, 11 May 2025 01:58:04 +0100 Subject: [PATCH 31/31] Restrict push trigger to only the main branch in workflow configuration --- .github/workflows/docs-and-testcov.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs-and-testcov.yml b/.github/workflows/docs-and-testcov.yml index 8b83084..c946b9b 100644 --- a/.github/workflows/docs-and-testcov.yml +++ b/.github/workflows/docs-and-testcov.yml @@ -6,7 +6,7 @@ concurrency: on: push: - branches: [main, docs_page] + branches: [main] # pull_request: # branches: [main] workflow_dispatch: