-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
ci(perf): Add benchmark history with JSON-in-comment storage #1289
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
ef865fb
3828132
ee092dd
b34dab8
29e5b6e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,20 +25,95 @@ jobs: | |
| GH_REPO: ${{ github.repository }} | ||
| PR_NUMBER: ${{ github.event.pull_request.number }} | ||
| COMMIT_SHA: ${{ github.event.pull_request.head.sha }} | ||
| WORKFLOW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | ||
| run: | | ||
| SHORT_SHA="${COMMIT_SHA:0:7}" | ||
| COMMENT_MARKER="<!-- repomix-perf-benchmark -->" | ||
| BODY="${COMMENT_MARKER} | ||
| ## ⚡ Performance Benchmark | ||
|
|
||
| <table><tr><td><strong>Latest commit:</strong></td><td><code>${SHORT_SHA}</code></td></tr> | ||
| <tr><td><strong>Status:</strong></td><td>⚡ Benchmark in progress...</td></tr></table> | ||
|
|
||
| [Workflow run](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID})" | ||
|
|
||
| # Find existing comment by marker | ||
| COMMENT_ID=$(gh api "repos/${GH_REPO}/issues/${PR_NUMBER}/comments" --paginate --jq ".[] | select(.body | startswith(\"${COMMENT_MARKER}\")) | .id" | head -1) | ||
|
|
||
| # Save existing comment body to file for safe parsing | ||
| if [ -n "$COMMENT_ID" ]; then | ||
| gh api "repos/${GH_REPO}/issues/comments/${COMMENT_ID}" --jq '.body' > "$RUNNER_TEMP/old-comment.txt" | ||
| else | ||
| echo "" > "$RUNNER_TEMP/old-comment.txt" | ||
| fi | ||
|
|
||
| # Fetch commit message in shell (avoids escaping issues in Node) | ||
| COMMIT_MSG=$(gh api "repos/${GH_REPO}/commits/${COMMIT_SHA}" --jq '.commit.message | split("\n") | .[0]' 2>/dev/null || echo "") | ||
|
|
||
| # Generate comment with Node (handles JSON history + rendering) | ||
| # shellcheck disable=SC2016 | ||
| COMMIT_MSG="$COMMIT_MSG" node -e ' | ||
| const fs = require("fs"); | ||
| const shortSha = process.env.COMMIT_SHA.slice(0, 7); | ||
| const commitMsg = process.env.COMMIT_MSG; | ||
| const runUrl = process.env.WORKFLOW_RUN_URL; | ||
| const oldBody = fs.readFileSync(process.env.RUNNER_TEMP + "/old-comment.txt", "utf8"); | ||
|
|
||
| const esc = s => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """); | ||
|
|
||
| // Extract history JSON from existing comment | ||
| const jsonMatch = oldBody.match(/<!-- bench-history-json-start ([\s\S]*?) bench-history-json-end -->/); | ||
| let history = []; | ||
| if (jsonMatch) { try { history = JSON.parse(jsonMatch[1]); } catch {} } | ||
|
|
||
| // If previous comment had completed results, archive them into history | ||
| if (oldBody.includes("\u2705 Benchmark complete!")) { | ||
| // Extract only the main result table (before any <details> block) | ||
| const mainSection = oldBody.split("<details>")[0] || ""; | ||
| const commitMatch = mainSection.match(/Latest commit:<\/strong><\/td><td><code>([a-f0-9]+)<\/code>\s*(.*?)<\/td>/); | ||
| const prevSha = commitMatch ? commitMatch[1] : ""; | ||
| const prevMsg = commitMatch ? commitMatch[2] : ""; | ||
| if (prevSha) { | ||
| const rowRe = /<tr><td><strong>(Ubuntu|macOS|Windows):<\/strong><\/td><td>(.*?)<\/td><\/tr>/g; | ||
| const entry = { sha: prevSha, msg: prevMsg }; | ||
| let m; | ||
| while ((m = rowRe.exec(mainSection)) !== null) { | ||
| entry[m[1].toLowerCase()] = m[2]; | ||
| } | ||
|
yamadashy marked this conversation as resolved.
|
||
| if (prevSha !== shortSha && !history.some(h => h.sha === prevSha)) { | ||
| history.unshift(entry); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Keep max 5 entries | ||
| history = history.slice(0, 5); | ||
|
|
||
| // Render history section | ||
| function renderHistory(hist) { | ||
| if (hist.length === 0) return ""; | ||
| return hist.map(h => { | ||
| const label = "<code>" + h.sha + "</code>" + (h.msg ? " " + h.msg : ""); | ||
| const osRows = ["ubuntu", "macos", "windows"] | ||
| .filter(os => h[os] && h[os] !== "-") | ||
| .map(os => { | ||
| const osLabel = os === "ubuntu" ? "Ubuntu" : os === "macos" ? "macOS" : "Windows"; | ||
| return "<tr><td><strong>" + osLabel + ":</strong></td><td>" + h[os] + "</td></tr>"; | ||
| }) | ||
| .join("\n"); | ||
| return label + "\n<table>\n" + osRows + "\n</table>"; | ||
| }).join("\n\n"); | ||
| } | ||
|
|
||
| const jsonComment = "<!-- bench-history-json-start " + JSON.stringify(history) + " bench-history-json-end -->"; | ||
| let body = "<!-- repomix-perf-benchmark -->\n" + jsonComment + "\n"; | ||
| body += "## \u26a1 Performance Benchmark\n\n"; | ||
| body += "<table><tr><td><strong>Latest commit:</strong></td><td><code>" + shortSha + "</code> " + esc(commitMsg) + "</td></tr>\n"; | ||
| body += "<tr><td><strong>Status:</strong></td><td>\u26a1 Benchmark in progress...</td></tr></table>\n\n"; | ||
| body += "[Workflow run](" + runUrl + ")"; | ||
|
yamadashy marked this conversation as resolved.
|
||
|
|
||
| const historyHtml = renderHistory(history); | ||
| if (historyHtml) { | ||
| body += "\n\n<details>\n<summary>History</summary>\n\n" + historyHtml + "\n\n</details>"; | ||
| } | ||
|
|
||
| fs.writeFileSync(process.env.RUNNER_TEMP + "/new-comment.md", body); | ||
| ' | ||
|
|
||
| BODY=$(cat "$RUNNER_TEMP/new-comment.md") | ||
|
|
||
| if [ -n "$COMMENT_ID" ]; then | ||
| gh api "repos/${GH_REPO}/issues/comments/${COMMENT_ID}" -X PATCH -f body="$BODY" | ||
| else | ||
|
|
@@ -153,7 +228,7 @@ jobs: | |
|
|
||
| comment: | ||
| name: Comment Results | ||
| needs: benchmark | ||
| needs: [benchmark, post-pending] | ||
| runs-on: ubuntu-latest | ||
| if: ${{ always() && !cancelled() }} | ||
| permissions: | ||
|
|
@@ -164,86 +239,123 @@ jobs: | |
| path: results | ||
| pattern: bench-result-* | ||
|
|
||
| - name: Generate benchmark report | ||
| id: report | ||
| - name: Comment on PR | ||
| if: ${{ github.event.pull_request.head.repo.fork == false }} | ||
|
Comment on lines
+242
to
+243
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Step summary no longer written for fork PRs due to merged steps In the old code, Prompt for agentsWas this helpful? React with 👍 or 👎 to provide feedback. |
||
| env: | ||
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| GH_REPO: ${{ github.repository }} | ||
| PR_NUMBER: ${{ github.event.pull_request.number }} | ||
| COMMIT_SHA: ${{ github.event.pull_request.head.sha }} | ||
| WORKFLOW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | ||
| run: | | ||
| SHORT_SHA="${COMMIT_SHA:0:7}" | ||
|
|
||
| generate_row() { | ||
| local os=$1 | ||
| local label=$2 | ||
| local file="results/bench-result-${os}/bench-result.json" | ||
|
|
||
| if [ ! -f "$file" ]; then | ||
| echo "<tr><td><strong>${label}:</strong></td><td>-</td></tr>" | ||
| return | ||
| fi | ||
|
|
||
| local data | ||
| data=$(node -e "console.log(JSON.stringify(JSON.parse(require('fs').readFileSync('$file','utf8'))))") | ||
|
|
||
| local pr_ms main_ms pr_iqr main_iqr | ||
| pr_ms=$(node -e "console.log(JSON.parse('$data').pr)") | ||
| main_ms=$(node -e "console.log(JSON.parse('$data').main)") | ||
| pr_iqr=$(node -e "console.log(JSON.parse('$data').prIqr)") | ||
| main_iqr=$(node -e "console.log(JSON.parse('$data').mainIqr)") | ||
|
|
||
| local diff=$((pr_ms - main_ms)) | ||
| local pr_sec main_sec pr_iqr_sec main_iqr_sec diff_sec diff_pct | ||
| pr_sec=$(awk "BEGIN {printf \"%.2f\", $pr_ms / 1000}") | ||
| main_sec=$(awk "BEGIN {printf \"%.2f\", $main_ms / 1000}") | ||
| pr_iqr_sec=$(awk "BEGIN {printf \"%.2f\", $pr_iqr / 1000}") | ||
| main_iqr_sec=$(awk "BEGIN {printf \"%.2f\", $main_iqr / 1000}") | ||
| diff_sec=$(awk "BEGIN {printf \"%+.2f\", $diff / 1000}") | ||
|
|
||
| if [ "$main_ms" -gt 0 ]; then | ||
| diff_pct=$(awk "BEGIN {printf \"%+.1f\", ($diff / $main_ms) * 100}") | ||
| else | ||
| diff_pct="N/A" | ||
| fi | ||
|
|
||
| echo "<tr><td><strong>${label}:</strong></td><td>${main_sec}s (±${main_iqr_sec}s) → ${pr_sec}s (±${pr_iqr_sec}s) · ${diff_sec}s (${diff_pct}%)</td></tr>" | ||
| } | ||
| COMMENT_MARKER="<!-- repomix-perf-benchmark -->" | ||
|
|
||
| BODY="## ⚡ Performance Benchmark | ||
| # Find existing comment and save old body to file | ||
| COMMENT_ID=$(gh api "repos/${GH_REPO}/issues/${PR_NUMBER}/comments" --paginate --jq ".[] | select(.body | startswith(\"${COMMENT_MARKER}\")) | .id" | head -1) | ||
|
|
||
| <table><tr><td><strong>Latest commit:</strong></td><td><code>${SHORT_SHA}</code></td></tr> | ||
| <tr><td><strong>Status:</strong></td><td>✅ Benchmark complete!</td></tr> | ||
| $(generate_row "ubuntu-latest" "Ubuntu") | ||
| $(generate_row "macos-latest" "macOS") | ||
| $(generate_row "windows-latest" "Windows") | ||
| </table> | ||
| if [ -n "$COMMENT_ID" ]; then | ||
| gh api "repos/${GH_REPO}/issues/comments/${COMMENT_ID}" --jq '.body' > "$RUNNER_TEMP/old-comment.txt" | ||
| else | ||
| echo "" > "$RUNNER_TEMP/old-comment.txt" | ||
| fi | ||
|
|
||
| <details> | ||
| <summary>Details</summary> | ||
| # Fetch commit message in shell | ||
| COMMIT_MSG=$(gh api "repos/${GH_REPO}/commits/${COMMIT_SHA}" --jq '.commit.message | split("\n") | .[0]' 2>/dev/null || echo "") | ||
|
|
||
| - Packing the repomix repository with \`node bin/repomix.cjs\` | ||
| - Warmup: 2 runs (discarded) | ||
| - Measurement: 10 runs / 20 on macOS (median ± IQR) | ||
| - [Workflow run](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}) | ||
| # Generate complete comment with Node | ||
| # shellcheck disable=SC2016 | ||
| COMMIT_MSG="$COMMIT_MSG" node -e ' | ||
| const fs = require("fs"); | ||
| const path = require("path"); | ||
|
|
||
| </details>" | ||
| const shortSha = process.env.COMMIT_SHA.slice(0, 7); | ||
| const commitMsg = process.env.COMMIT_MSG; | ||
| const runUrl = process.env.WORKFLOW_RUN_URL; | ||
| const oldBody = fs.readFileSync(process.env.RUNNER_TEMP + "/old-comment.txt", "utf8"); | ||
|
|
||
| echo "$BODY" >> "$GITHUB_STEP_SUMMARY" | ||
| const esc = s => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """); | ||
|
|
||
| # Save report for comment step | ||
| echo "$BODY" > "$RUNNER_TEMP/benchmark-report.md" | ||
| // Extract history JSON from existing comment (set by post-pending) | ||
| const jsonMatch = oldBody.match(/<!-- bench-history-json-start ([\s\S]*?) bench-history-json-end -->/); | ||
| let history = []; | ||
| if (jsonMatch) { try { history = JSON.parse(jsonMatch[1]); } catch {} } | ||
|
|
||
| - name: Comment on PR | ||
| if: ${{ github.event.pull_request.head.repo.fork == false }} | ||
| env: | ||
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| GH_REPO: ${{ github.repository }} | ||
| PR_NUMBER: ${{ github.event.pull_request.number }} | ||
| run: | | ||
| COMMENT_MARKER="<!-- repomix-perf-benchmark -->" | ||
| BODY="${COMMENT_MARKER} | ||
| $(cat "$RUNNER_TEMP/benchmark-report.md")" | ||
| // Read benchmark results from artifacts | ||
| function readResult(os) { | ||
| const file = path.join("results", "bench-result-" + os, "bench-result.json"); | ||
| try { | ||
| return JSON.parse(fs.readFileSync(file, "utf8")); | ||
| } catch { | ||
| return null; | ||
| } | ||
| } | ||
|
|
||
| # Find existing comment by marker | ||
| COMMENT_ID=$(gh api "repos/${GH_REPO}/issues/${PR_NUMBER}/comments" --paginate --jq ".[] | select(.body | startswith(\"${COMMENT_MARKER}\")) | .id" | head -1) | ||
| function formatResult(data) { | ||
| if (!data) return "-"; | ||
| const prSec = (data.pr / 1000).toFixed(2); | ||
| const mainSec = (data.main / 1000).toFixed(2); | ||
| const prIqr = (data.prIqr / 1000).toFixed(2); | ||
| const mainIqr = (data.mainIqr / 1000).toFixed(2); | ||
| const diff = data.pr - data.main; | ||
| const diffSec = (diff >= 0 ? "+" : "") + (diff / 1000).toFixed(2); | ||
| const diffPct = data.main > 0 | ||
| ? (diff >= 0 ? "+" : "") + ((diff / data.main) * 100).toFixed(1) | ||
| : "N/A"; | ||
| return mainSec + "s (\u00b1" + mainIqr + "s) \u2192 " + prSec + "s (\u00b1" + prIqr + "s) \u00b7 " + diffSec + "s (" + diffPct + "%)"; | ||
| } | ||
|
|
||
| const ubuntuStr = formatResult(readResult("ubuntu-latest")); | ||
| const macosStr = formatResult(readResult("macos-latest")); | ||
| const windowsStr = formatResult(readResult("windows-latest")); | ||
|
|
||
| // Render history section | ||
| function renderHistory(hist) { | ||
| if (hist.length === 0) return ""; | ||
| return hist.map(h => { | ||
| const label = "<code>" + h.sha + "</code>" + (h.msg ? " " + h.msg : ""); | ||
| const osRows = ["ubuntu", "macos", "windows"] | ||
| .filter(os => h[os] && h[os] !== "-") | ||
| .map(os => { | ||
| const osLabel = os === "ubuntu" ? "Ubuntu" : os === "macos" ? "macOS" : "Windows"; | ||
| return "<tr><td><strong>" + osLabel + ":</strong></td><td>" + h[os] + "</td></tr>"; | ||
| }) | ||
| .join("\n"); | ||
| return label + "\n<table>\n" + osRows + "\n</table>"; | ||
| }).join("\n\n"); | ||
| } | ||
|
|
||
| const jsonComment = "<!-- bench-history-json-start " + JSON.stringify(history) + " bench-history-json-end -->"; | ||
| let body = "<!-- repomix-perf-benchmark -->\n" + jsonComment + "\n"; | ||
| body += "## \u26a1 Performance Benchmark\n\n"; | ||
| body += "<table><tr><td><strong>Latest commit:</strong></td><td><code>" + shortSha + "</code> " + esc(commitMsg) + "</td></tr>\n"; | ||
| body += "<tr><td><strong>Status:</strong></td><td>\u2705 Benchmark complete!</td></tr>\n"; | ||
| body += "<tr><td><strong>Ubuntu:</strong></td><td>" + ubuntuStr + "</td></tr>\n"; | ||
| body += "<tr><td><strong>macOS:</strong></td><td>" + macosStr + "</td></tr>\n"; | ||
| body += "<tr><td><strong>Windows:</strong></td><td>" + windowsStr + "</td></tr>\n"; | ||
| body += "</table>\n\n"; | ||
| body += "<details>\n<summary>Details</summary>\n\n"; | ||
| body += "- Packing the repomix repository with `node bin/repomix.cjs`\n"; | ||
| body += "- Warmup: 2 runs (discarded)\n"; | ||
| body += "- Measurement: 10 runs / 20 on macOS (median \u00b1 IQR)\n"; | ||
| body += "- [Workflow run](" + runUrl + ")\n\n"; | ||
| body += "</details>"; | ||
|
|
||
| const historyHtml = renderHistory(history); | ||
| if (historyHtml) { | ||
| body += "\n\n<details>\n<summary>History</summary>\n\n" + historyHtml + "\n\n</details>"; | ||
| } | ||
|
|
||
| // Write to step summary (without HTML comments) | ||
| const summaryFile = process.env.GITHUB_STEP_SUMMARY; | ||
| if (summaryFile) { | ||
| const summaryBody = body.split("\n").filter(l => !l.startsWith("<!-- ")).join("\n"); | ||
| fs.appendFileSync(summaryFile, summaryBody + "\n"); | ||
| } | ||
|
|
||
| fs.writeFileSync(process.env.RUNNER_TEMP + "/new-comment.md", body); | ||
| ' | ||
|
|
||
| BODY=$(cat "$RUNNER_TEMP/new-comment.md") | ||
|
|
||
| if [ -n "$COMMENT_ID" ]; then | ||
| gh api "repos/${GH_REPO}/issues/comments/${COMMENT_ID}" -X PATCH -f body="$BODY" | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.