diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 1724ebf18..439143c85 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -182,7 +182,7 @@ jobs: uses: actions/upload-artifact@v7 with: path: repo/notification - name: coverage-pr-notification + name: pr-notification # Create GitHub issue for failed scheduled jobs # This should always be the last job (we want an issue if anything breaks) diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 8b53d95b6..658134075 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -10,7 +10,7 @@ on: paths: ["ci/**", ".github/workflows"] workflow_dispatch: workflow_run: - workflows: ["Code coverage"] + workflows: ["Code coverage", "RTLMeter"] types: [completed] # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages @@ -35,7 +35,7 @@ jobs: name: Build content runs-on: ubuntu-24.04 outputs: - coverage-pr-run-ids: ${{ steps.build.outputs.coverage-pr-run-ids }} + pr-run-ids: ${{ steps.build.outputs.pr-run-ids }} steps: - name: Checkout uses: actions/checkout@v6 @@ -83,5 +83,5 @@ jobs: - name: Comment on PR env: GH_TOKEN: ${{ steps.generate-token.outputs.token }} - COVERAGE_PR_RUN_IDS: ${{ needs.build.outputs.coverage-pr-run-ids }} + PR_RUN_IDS: ${{ needs.build.outputs.pr-run-ids }} run: bash -x ./ci/ci-pages-notify.bash diff --git a/.github/workflows/rtlmeter-pr-results.yml b/.github/workflows/rtlmeter-pr-results.yml deleted file mode 100644 index 2a8bbb2a4..000000000 --- a/.github/workflows/rtlmeter-pr-results.yml +++ /dev/null @@ -1,48 +0,0 @@ ---- -# DESCRIPTION: Github actions config -# This name is key to badges in README.rst, so we use the name build -# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 - -name: RTLMeter PR results - -on: - workflow_run: - workflows: [RTLMeter] - types: [completed] - -jobs: - publish: - name: Publish - runs-on: ubuntu-latest - if: ${{ github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' }} - permissions: - actions: read - pull-requests: write - steps: - - name: Download report - uses: actions/download-artifact@v8 - with: - name: rtlmeter-pr-results - run-id: ${{ github.event.workflow_run.id }} - github-token: ${{ secrets.GITHUB_TOKEN }} - - name: Download PR number - uses: actions/download-artifact@v8 - with: - name: pr-number - run-id: ${{ github.event.workflow_run.id }} - github-token: ${{ secrets.GITHUB_TOKEN }} - # Use the Verilator CI app to post the comment - - name: Generate access token - id: generate-token - uses: actions/create-github-app-token@v3.2.0 - with: - app-id: ${{ vars.VERILATOR_CI_ID }} - private-key: ${{ secrets.VERILATOR_CI_KEY }} - permission-pull-requests: write - - name: Comment on PR - env: - GH_TOKEN: ${{ steps.generate-token.outputs.token }} - run: |- - ls -la - cat report.txt - gh pr --repo ${{ github.repository }} comment $(cat pr-number.txt) --body-file report.txt diff --git a/.github/workflows/rtlmeter.yml b/.github/workflows/rtlmeter.yml index 372df70ae..626bb994c 100644 --- a/.github/workflows/rtlmeter.yml +++ b/.github/workflows/rtlmeter.yml @@ -286,106 +286,45 @@ jobs: with: repository: "verilator/rtlmeter" path: rtlmeter + - name: Setup RTLMeter venv working-directory: rtlmeter run: make venv - - name: Download combined results - uses: actions/download-artifact@v8 + + - name: Checkout Verilator + uses: actions/checkout@v6 with: - pattern: all-results-* - path: all-results - merge-multiple: true - - name: Get scheduled run info - id: scheduled-info - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - ID=$(gh run --repo ${{ github.repository }} list --workflow RTLMeter --event schedule --status success --limit 1 --json databaseId --jq ".[0].databaseId") - echo "id=$ID" >> $GITHUB_OUTPUT - URL=$(gh run --repo ${{ github.repository }} view $ID --json url --jq ".url") - echo "url=$URL" >> $GITHUB_OUTPUT - NUM=$(gh run --repo ${{ github.repository }} view $ID --json number --jq ".number") - echo "num=$NUM" >> $GITHUB_OUTPUT - DATE=$(gh run --repo ${{ github.repository }} view $ID --json createdAt --jq ".createdAt") - echo "date=$DATE" >> $GITHUB_OUTPUT - - name: Download scheduled run results - uses: actions/download-artifact@v8 - with: - name: published-results - path: nightly-results - run-id: ${{ steps.scheduled-info.outputs.id }} - github-token: ${{ secrets.GITHUB_TOKEN }} - - name: Compare results - working-directory: rtlmeter - run: | - for tag in gcc clang gcc-hier; do - ADATA=../nightly-results/all-results-${tag}.json - BDATA=../all-results/all-results-${tag}.json - touch ../verilate-${tag}.txt - touch ../execute-${tag}.txt - touch ../cppbuild-${tag}.txt - if [[ ! -e $ADATA ]]; then - continue - fi - ./rtlmeter compare --cases '* !Example:* !*:hello' --steps "verilate" --metrics "elapsed memory" $ADATA $BDATA > ../verilate-${tag}.txt - cat ../verilate-${tag}.txt - ./rtlmeter compare --cases '* !Example:* !*:hello' --steps "execute" --metrics "speed memory elapsed" $ADATA $BDATA > ../execute-${tag}.txt - cat ../execute-${tag}.txt - ./rtlmeter compare --cases '* !Example:* !*:hello' --steps "cppbuild" --metrics "elapsed memory cpu codeSize" $ADATA $BDATA > ../cppbuild-${tag}.txt - cat ../cppbuild-${tag}.txt - done + path: verilator + - name: Create report + working-directory: verilator env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - set -x - NUM=$(gh run --repo ${{ github.repository }} view ${{ github.run_id }} --json number --jq ".number") - URL=$(gh run --repo ${{ github.repository }} view ${{ github.run_id }} --json url --jq ".url") - echo -n "Performance metrics for PR workflow [#$NUM]($URL) (B) compared to scheduled run" > report.txt - echo -n " [#${{ steps.scheduled-info.outputs.num }}](${{ steps.scheduled-info.outputs.url }}) (A)" >> report.txt - echo " from ${{ steps.scheduled-info.outputs.date }}" >> report.txt - for tag in gcc clang gcc-hier; do - echo "" >> report.txt - if [[ $tag == "gcc" ]]; then - echo "
" >> report.txt - else - echo "
" >> report.txt - fi - echo -n "" >> report.txt - jq -rj ".[0].runName" all-results/all-results-${tag}.json >> report.txt - echo "" >> report.txt - awk -v RS= -v tag=${tag} '{print > sprintf("frag-%02d-verilate-%s.txt",NR,tag)}' verilate-${tag}.txt - awk -v RS= -v tag=${tag} '{print > sprintf("frag-%02d-execute-%s.txt" ,NR,tag)}' execute-${tag}.txt - awk -v RS= -v tag=${tag} '{print > sprintf("frag-$02d-cppbuild-%s.txt",NR,tag)}' cppbuild-${tag}.txt - for f in $(ls -1 frag-*-verilate-${tag}.txt | sort) $(ls -1 frag-*-execute-${tag}.txt | sort) $(ls -1 frag-*-cppbuild-${tag}.txt | sort); do - if [[ $f == frag-01-verilate-${tag}.txt || $f == frag-01-execute-${tag}.txt ]]; then - echo "
" >> report.txt - else - echo "
" >> report.txt - fi - echo -n "" >> report.txt - head -n 1 $f | tr -d '\n' >> report.txt - echo "" >> report.txt - echo '
' >> report.txt
-              tail -n +2 $f >> report.txt
-              echo '
' >> report.txt - echo "
" >> report.txt - done - echo "
" >> report.txt - done - cat report.txt + ln -s ../rtlmeter rtlmeter + gh repo set-default ${{ github.repository }} + # Compare to last successful scheduled run + REF_ID=$(gh run list --workflow RTLMeter --event schedule --status success --limit 1 --json databaseId --jq ".[0].databaseId") + NEW_ID=${{ github.run_id }} + ci/ci-rtlmeter-pr-report.bash $REF_ID $NEW_ID gcc clang gcc-hier + mv rtlmeter-pr-report/report.txt ../ + cat ../report.txt + # Generate notification comment content + mkdir -p ../notification + mv rtlmeter-pr-report/notification.txt ../notification/body.txt + echo ${{ github.event.number }} > ../notification/pr-number.txt + - name: Upload report uses: actions/upload-artifact@v7 with: path: report.txt - name: rtlmeter-pr-results - - name: Save PR number - run: echo ${{ github.event.number }} > pr-number.txt - - name: Upload PR number + name: rtlmeter-pr-report + + - name: Upload notification uses: actions/upload-artifact@v7 with: - path: pr-number.txt - name: pr-number + path: notification + name: pr-notification # Create GitHub issue for failed scheduled jobs # This should always be the last job (we want an issue if anything breaks) diff --git a/Makefile.in b/Makefile.in index 9d92056ae..c2b60d004 100644 --- a/Makefile.in +++ b/Makefile.in @@ -535,6 +535,7 @@ PY_PROGRAMS = \ # Python files, subject to format but not lint PY_FILES = \ $(PY_PROGRAMS) \ + ci/*.py \ test_regress/t/*.py \ # Python files, test_regress tests diff --git a/ci/ci-pages-notify.bash b/ci/ci-pages-notify.bash index 05bb92fe6..e5dd599b6 100755 --- a/ci/ci-pages-notify.bash +++ b/ci/ci-pages-notify.bash @@ -4,7 +4,7 @@ # SPDX-FileCopyrightText: 2025 Geza Lore # SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 -# Notify PRs via comment that their coverage reports are available +# Notify PRs via comment that their workflow reports are available # Get the current repo URL - might differ on a fork readonly REPO_URL=$(gh repo view --json url --jq .url) @@ -13,7 +13,7 @@ readonly REPO_URL=$(gh repo view --json url --jq .url) ARTIFACTS_ROOT=artifacts mkdir -p ${ARTIFACTS_ROOT} -for RUN_ID in ${COVERAGE_PR_RUN_IDS//,/ }; do +for RUN_ID in ${PR_RUN_IDS//,/ }; do echo "@@@ Processing run ${RUN_ID}" # Create workflow artifacts directory @@ -21,7 +21,7 @@ for RUN_ID in ${COVERAGE_PR_RUN_IDS//,/ }; do mkdir -p ${ARTIFACTS_DIR} # Download artifact of this run, if exists - gh run download ${RUN_ID} --name coverage-pr-notification --dir ${ARTIFACTS_DIR} || true + gh run download ${RUN_ID} --name pr-notification --dir ${ARTIFACTS_DIR} || true ls -lsha ${ARTIFACTS_DIR} # Move on if no notification is required @@ -35,7 +35,7 @@ for RUN_ID in ${COVERAGE_PR_RUN_IDS//,/ }; do gh pr comment $(cat ${ARTIFACTS_DIR}/pr-number.txt) --body-file ${ARTIFACTS_DIR}/body.txt # Get the artifact ID - ARTIFACT_ID=$(gh api "repos/{owner}/{repo}/actions/runs/${RUN_ID}/artifacts" --jq '.artifacts[] | select(.name == "coverage-pr-notification") | .id') + ARTIFACT_ID=$(gh api "repos/{owner}/{repo}/actions/runs/${RUN_ID}/artifacts" --jq '.artifacts[] | select(.name == "pr-notification") | .id') # Delete it, so we only notify once gh api --method DELETE "repos/{owner}/{repo}/actions/artifacts/${ARTIFACT_ID}" diff --git a/ci/ci-pages.bash b/ci/ci-pages.bash index ba9013621..44dc3f6d1 100755 --- a/ci/ci-pages.bash +++ b/ci/ci-pages.bash @@ -5,8 +5,7 @@ # SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 # This scipt build the content of the GitHub Pages for the repository. -# Currently this only hosts code coverage reports, but it would be possible to -# add any other contents to the page in parallel here. +# Currently this hosts code coverage reports, and PR RTLMeter detailed reports. # Developer note: You should be able to run this script in your local checkout # if you have GitHub CLI (command 'gh') setup, authenticated ('gh auth login'), @@ -25,6 +24,9 @@ if [[ -z "$GITHUB_OUTPUT" ]]; then GITHUB_OUTPUT=github-output.txt fi +# Run IDs of PR jobs processed +PR_RUN_IDS="" + # Populates ${PAGES_ROOT}/coverage-reports compile_coverage_reports() { # We will process all runs up to and including this date. This is chosen to be @@ -53,9 +55,6 @@ compile_coverage_reports() { local CONTENTS=contents.tmp echo > ${CONTENTS} - # Run IDs of PR jobs processed - local PR_RUN_IDS="" - # Iterate over all unique event types that triggered the workflows for EVENT in $(jq -r 'map(.event) | sort | unique | .[]' completedRuns.json); do echo "@@@ Processing '${EVENT}' runs" @@ -73,11 +72,7 @@ compile_coverage_reports() { # Record run ID of PR job if [[ $EVENT == "pull_request" ]]; then - if [[ -z "$PR_RUN_IDS" ]]; then - PR_RUN_IDS="$RUN_ID" - else - PR_RUN_IDS="$PR_RUN_IDS,$RUN_ID" - fi + PR_RUN_IDS="$PR_RUN_IDS $RUN_ID" fi # Create workflow artifacts directory @@ -148,7 +143,7 @@ CONTENTS_TEMPLATE $(cat ${CONTENTS}) -

Assembled $(date --iso-8601=minutes --utc)

+

Assembled $(date --iso-8601=minutes --utc)

@@ -156,12 +151,159 @@ INDEX_TEMPLATE # Report size du -shc ${COVERAGE_ROOT}/* +} - # Set output - echo "coverage-pr-run-ids=${PR_RUN_IDS}" >> $GITHUB_OUTPUT +# Populates ${PAGES_ROOT}/rtlmeter-reports +compile_rtlmeter_reports() { + # We will process all runs up to and including this date. This is chosen to be + # slightly less than the artifact retention period for simplicity. + local OLDEST=$(date --date="28 days ago" --iso-8601=date) + + # Gather all RTLMeter workflow runs within the time window + gh run list -w rtlmeter.yml --limit 1000 --created ">=${OLDEST}" --json "databaseId,event,status,conclusion,createdAt,number" > recentRuns.json + echo @@@ Recent runs: + jq "." recentRuns.json + + # Select completd runs that were not cancelled or skipped, sort by descending run number + jq 'sort_by(-.number) | map(select(.status == "completed" and (.conclusion == "success" or .conclusion == "failure")))' recentRuns.json > completedRuns.json + echo @@@ Completed with success or failure: + jq "." completedRuns.json + + # Create artifacts root directory + local ARTIFACTS_ROOT=artifacts + mkdir -p ${ARTIFACTS_ROOT} + + # Create rtlmeter reports root directory + local RTLMETER_ROOT=${PAGES_ROOT}/rtlmeter-reports + mkdir -p ${RTLMETER_ROOT} + + # Create index page contents fragment + local CONTENTS=contents.tmp + echo > ${CONTENTS} + + # Iterate over all unique event types that triggered the workflows + for EVENT in $(jq -r 'map(.event) | sort | unique | .[]' completedRuns.json); do + echo "@@@ Processing '${EVENT}' runs" + + # Emit section header if a report exists with this event type + EMIT_SECTION_HEADER=1 + + # For each worfklow run that was triggered by this event type + for RUN_ID in $(jq ".[] | select(.event == \"${EVENT}\") |.databaseId" completedRuns.json); do + echo "@@@ Processing run ${RUN_ID}" + + # Extract the info of this run + jq ".[] | select(.databaseId == $RUN_ID)" completedRuns.json > workflow.json + jq "." workflow.json + + # Record run ID of PR job + if [[ $EVENT == "pull_request" ]]; then + PR_RUN_IDS="$PR_RUN_IDS $RUN_ID" + fi + + # Create workflow artifacts directory + local ARTIFACTS_DIR=${ARTIFACTS_ROOT}/${RUN_ID} + mkdir -p ${ARTIFACTS_DIR} + + # Download artifacts of this run, if exists + gh run download ${RUN_ID} --name rtlmeter-pr-report --dir ${ARTIFACTS_DIR} || true + ls -lsha ${ARTIFACTS_DIR} + + # Move on if no RTLMeter report is available + if [ ! -f ${ARTIFACTS_DIR}/report.txt ]; then + echo "No RTLMeter report found" + continue + fi + echo "RTLMeter report found" + + # Emit section header + if [[ -n $EMIT_SECTION_HEADER ]]; then + unset EMIT_SECTION_HEADER + echo "

RTLMeter reports for '${EVENT}' runs:

" >> ${CONTENTS} + fi + + # Extract run metadata + local WORKFLOW_CREATED=$(jq -r '.createdAt' workflow.json) + local WOFKRLOW_NUMBER=$(jq -r '.number' workflow.json) + + # Wrap the report into an HTML page. The report content is already HTML + # (produced by ci-rtlmeter-pr-report.bash), so we just embed it in the + # page body. + cat > ${RTLMETER_ROOT}/${RUN_ID}.html < + + + Verilator RTLMeter report #${WOFKRLOW_NUMBER} + + + + +$(cat ${ARTIFACTS_DIR}/report.txt) + + + +REPORT_TEMPLATE + + # Add index page content + cat >> ${CONTENTS} <#${WOFKRLOW_NUMBER} + | GitHub: ${RUN_ID} + | started at: ${WORKFLOW_CREATED} +CONTENTS_TEMPLATE + echo "
" >> ${CONTENTS} + done + + # Section break + if [[ -z "$EMIT_SECTION_HEADER" ]]; then + echo "
" >> ${CONTENTS} + fi + done + + # Write rtlmeter report index.html + cat > ${RTLMETER_ROOT}/index.html < + + + Verilator CI RTLMeter reports + + + + + $(cat ${CONTENTS}) +

Assembled $(date --iso-8601=minutes --utc)

+ + + +INDEX_TEMPLATE + + # Report size + du -shc ${RTLMETER_ROOT}/* } # Compilie coverage reports compile_coverage_reports; +# Compile RTLMeter reports +compile_rtlmeter_reports; + # You can build any other content here to be put under ${PAGES_ROOT} + +# Set output to all affected unique PR run IDs (comma separated, no trailing comma) +IDS=$(echo ${PR_RUN_IDS} | xargs -n1 | sort -u | paste -sd,) +echo "pr-run-ids=${IDS}" >> $GITHUB_OUTPUT diff --git a/ci/ci-rtlmeter-pr-report.bash b/ci/ci-rtlmeter-pr-report.bash new file mode 100755 index 000000000..f44f074e2 --- /dev/null +++ b/ci/ci-rtlmeter-pr-report.bash @@ -0,0 +1,135 @@ +#!/usr/bin/env bash +# DESCRIPTION: Verilator: CI script for 'rtlmeter.yml' PR results +# +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +# This scipt builds the content of the response comment posten on PRs +# at the end of RTLMeter runs. + +# Developer note: You should be able to run this script in your local checkout +# if you have GitHub CLI (command 'gh') setup, authenticated ('gh auth login'), +# and have set a default repository ('gh repo set-default'). + +# Trace when running in the CI +[ "$GITHUB_ACTIONS" != "true" ] || set -x + +# Arguments: +# 1. reference run ID +# 2. new run ID +# rest: run tags +REF_ID=$1; shift +NEW_ID=$1; shift +RUNS="$@" + +# $VERILATOR_CHECKOUT/ci directory +SCRIPT_DIR=$(readlink -f $(dirname ${BASH_SOURCE[0]})) + +# Move into a temporary directory +rm -rf rtlmeter-pr-report +mkdir rtlmeter-pr-report +pushd rtlmeter-pr-report &> /dev/null +TMP_DIR=$(readlink -f .) + +# Artifacts to download +DOWNLOAD_ARTIFACTS="" +for r in $RUNS; do + DOWNLOAD_ARTIFACTS="$DOWNLOAD_ARTIFACTS --name all-results-$r" +done + +# Download reference run results +mkdir ref +REF_DIR=$(readlink -f ref) +gh run download ${REF_ID} $DOWNLOAD_ARTIFACTS --dir $REF_DIR +mv $REF_DIR/*/*.json $REF_DIR/ +find $REF_DIR -mindepth 1 -type d -delete + +# Download current run results +mkdir new +NEW_DIR=$(readlink -f new) +gh run download ${NEW_ID} $DOWNLOAD_ARTIFACTS --dir $NEW_DIR +mv $NEW_DIR/*/*.json $NEW_DIR/ +find $NEW_DIR -mindepth 1 -type d -delete + +# Get Some metadata about the runs +REF_URL=$(gh run view $REF_ID --json url --jq ".url") +REF_NUM=$(gh run view $REF_ID --json number --jq ".number") +REF_DATE=$(gh run view $REF_ID --json createdAt --jq ".createdAt") +NEW_URL=$(gh run view $NEW_ID --json url --jq ".url") +NEW_NUM=$(gh run view $NEW_ID --json number --jq ".number") + +# Go back to root directory +popd &> /dev/null +# Go to RTLMeter directory +cd rtlmeter + +# Compare results +SUMMARY_ARGS=() +for r in $RUNS; do + CMP_JSON=$TMP_DIR/cmp-$r.json + # Gather args for summary script + SUMMARY_ARGS+=($REF_DIR/all-results-$r.json $CMP_JSON) + # Create JSON + ./rtlmeter compare --format json --steps "*" --metrics "*" \ + $REF_DIR/all-results-$r.json $NEW_DIR/all-results-$r.json > $CMP_JSON + # Also create detailed tables + ./rtlmeter compare --format ascii --steps 'verilate' --metrics '* !system !user' $REF_DIR/all-results-$r.json $NEW_DIR/all-results-$r.json > $TMP_DIR/verilate-$r.txt + ./rtlmeter compare --format ascii --steps 'cppbuild' --metrics '* !system !user' $REF_DIR/all-results-$r.json $NEW_DIR/all-results-$r.json > $TMP_DIR/cppbuild-$r.txt + ./rtlmeter compare --format ascii --steps 'execute' --metrics '* !system !user' $REF_DIR/all-results-$r.json $NEW_DIR/all-results-$r.json > $TMP_DIR/execute-$r.txt + # Chop them at new lines, into one table per file + awk -v RS= -v prefix=$TMP_DIR/$r-frag '{print > sprintf("%s-verilate-%02d.txt",prefix,NR)}' $TMP_DIR/verilate-$r.txt + awk -v RS= -v prefix=$TMP_DIR/$r-frag '{print > sprintf("%s-cppbuild-%02d.txt",prefix,NR)}' $TMP_DIR/cppbuild-$r.txt + awk -v RS= -v prefix=$TMP_DIR/$r-frag '{print > sprintf("%s-execute-%02d.txt" ,prefix,NR)}' $TMP_DIR/execute-$r.txt +done + +# Create summary +venv/bin/python3 $SCRIPT_DIR/ci-rtlmeter-pr-report.py ${SUMMARY_ARGS[@]} > $TMP_DIR/summary.txt +# Print it +cat $TMP_DIR/summary.txt + +# Create notification comment content +NOTIFICATION=$TMP_DIR/notification.txt +cat > $NOTIFICATION < + Summary of all runs +
+$(cat $TMP_DIR/summary.txt)
+  
+
+Blah Blah Blah +NOTIFICATION_TEMPLATE + +# Create detailed report +REPORT=$TMP_DIR/report.txt +cat > $REPORT < + Summary of all runs +
+$(cat $TMP_DIR/summary.txt)
+  
+
+SUMMARY_TEMPLATE +echo "
" >> $REPORT +echo " Detailed results" >> $REPORT +for r in $RUNS; do + RUN_NAME=$(jq -rj ".[0].runName" $REF_DIR/all-results-$r.json) + echo "
" >> $REPORT + echo " $RUN_NAME" >> $REPORT + for f in $(ls -1 $TMP_DIR/$r-frag-verilate-*.txt | sort) \ + $(ls -1 $TMP_DIR/$r-frag-cppbuild-*.txt | sort) \ + $(ls -1 $TMP_DIR/$r-frag-execute-*.txt | sort); do + cat >> $REPORT < + $(head -n 1 $f | tr -d '\n') +
+$(tail -n +2 $f)
+      
+
+DETAIL_TAMPLATE + done + echo "
" >> $REPORT +done +echo "" >> $REPORT diff --git a/ci/ci-rtlmeter-pr-report.py b/ci/ci-rtlmeter-pr-report.py new file mode 100644 index 000000000..e743f67da --- /dev/null +++ b/ci/ci-rtlmeter-pr-report.py @@ -0,0 +1,103 @@ +# DESCRIPTION: Verilator: CI script for 'rtlmeter.yml' PR results +# +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +############################################################################### +# This script runs with the venv of **RTLMeter** +############################################################################### + +import sys +import json +import tabulate +from typing import Final, List + +tabulate.PRESERVE_WHITESPACE = True +tabulate.MIN_PADDING = 0 + +_ASCII_TABLE_FORMAT: Final = tabulate.TableFormat( + lineabove=tabulate.Line("╒═", "═", "═╤═", "═╕"), + linebelowheader=tabulate.Line("╞═", "═", "═╪═", "═╡"), + linebelow=tabulate.Line("╘═", "═", "═╧═", "═╛"), + headerrow=tabulate.DataRow("│ ", " │ ", " │"), + datarow=tabulate.DataRow("│ ", " │ ", " │"), + linebetweenrows=None, + padding=0, + with_header_hide=None, +) + + +def printTable(table: List[List[str]], **kwargs) -> None: + print(tabulate.tabulate(table, tablefmt=_ASCII_TABLE_FORMAT, **kwargs)) + + +# fmt: off +stepMetric = ( + ("verilate", "elapsed"), + ("verilate", "memory"), + ("verilate", "cpu"), + ("cppbuild", "elapsed"), + ("cppbuild", "memory"), + ("cppbuild", "cpu"), + ("cppbuild", "codeSize"), + ("execute", "speed"), + ("execute", "clocks"), + ("execute", "memory"), + ("execute", "cpu"), +) +# fmt: on + +table = [] + +for ref, cmp in zip(sys.argv[1::2], sys.argv[2::2]): + with open(ref, "r", encoding="utf-8") as f: + ref_json = json.load(f)[0] + with open(cmp, "r", encoding="utf-8") as f: + cmp_json = json.load(f) + if table: + table.append(tabulate.SEPARATING_LINE) + runName = ref_json["runName"] + for step, metric in stepMetric: + if step not in cmp_json: + continue + data = cmp_json[step] + if metric not in data: + continue + data = data[metric] + minGain = float("inf") + maxGain = float("-inf") + meanGain = 1 + count = 0 + for _, _, _, _, _, g, _ in data["table"]: + minGain = min(minGain, g) + maxGain = max(maxGain, g) + meanGain *= g + count += 1 + meanGain = meanGain**(1 / count) + + if metric == "clocks": + # Clock cycles should match exactly + status = "❌" if minGain != 1 or maxGain != 1 else "✅" + else: + # Otherwise use some arbitrary brackets + status = "❌" + if (meanGain > 0.95): + status = "⚠️" + if (meanGain > 0.98): + status = "✅" + if (meanGain > 1.02): + status = "💡" + if (meanGain > 1.05): + status = "⭐" + + table.append([ + runName, step, ref_json["metrics"][metric]["header"], f"{meanGain:.2f}x {status} ", + f"{minGain:.2f}x", f"{maxGain:.2f}x" + ]) + +printTable( + table, + headers=("Run", "Step", "Metric", "Improvement", "Min", "Max"), + colalign=("left", "left", "left", "right", "right", "right"), + disable_numparse=True, +) diff --git a/test_regress/t/t_dist_whitespace.py b/test_regress/t/t_dist_whitespace.py index c6678f5b6..87f1fe8ef 100755 --- a/test_regress/t/t_dist_whitespace.py +++ b/test_regress/t/t_dist_whitespace.py @@ -13,7 +13,7 @@ test.scenarios('dist') Tabs_Exempt_Re = r'(\.out$)|(/fstcpp)|(Makefile)|(\.mk$)|(\.mk\.in$)|test_regress/t/t_preproc\.v|install-sh' -Unicode_Exempt_Re = r'(Changes$|CONTRIBUTORS$|LICENSES?|contributors.rst$|spelling.txt$)' +Unicode_Exempt_Re = r'(Changes$|CONTRIBUTORS$|LICENSES?|contributors.rst$|spelling.txt$|ci-rtlmeter-pr-report.py)' def get_source_files():