Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 46 additions & 2 deletions .github/workflows/ci-perf-report.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ on:

concurrency:
group: perf-${{ github.ref }}
cancel-in-progress: true
cancel-in-progress: false

permissions:
contents: read
Expand All @@ -26,12 +26,15 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
permissions:
contents: read
contents: write
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need write here?. This job checks out PR-controlled code and then runs repo-controlled actions/scripts from that checkout, and actions/checkout persists authenticated git credentials by default.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch - fixed in c634daa. added persist-credentials: false to the checkout step so PR-triggered runs never have write-capable git credentials available to PR-controlled code. contents: write is still needed at the job level because the baseline push step (push-to-main only) does git push origin HEAD:perf-data, but credentials are now set up explicitly only in that step via git config url."https://x-access-token:...".insteadOf, so write access is scoped to exactly where it's needed.

packages: read
actions: read

steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
persist-credentials: false

- name: Setup frontend
uses: ./.github/actions/setup-frontend
Expand Down Expand Up @@ -68,3 +71,44 @@ jobs:
with:
name: perf-meta
path: temp/perf-meta/

- name: Save perf baseline to perf-data branch
if: github.event_name == 'push' && github.ref == 'refs/heads/main' && steps.perf.outcome == 'success'
continue-on-error: true
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git config url."https://x-access-token:${GH_TOKEN}@github.com/".insteadOf "https://github.com/"

cp test-results/perf-metrics.json /tmp/perf-metrics.json

git fetch origin perf-data || {
echo "Creating perf-data branch"
git checkout --orphan perf-data
git rm -rf . 2>/dev/null || true
echo "# Performance Baselines" > README.md
mkdir -p baselines
git add README.md baselines
git commit -m "Initialize perf-data branch"
git push origin perf-data
git fetch origin perf-data
}

git worktree add /tmp/perf-data origin/perf-data

TIMESTAMP=$(date -u +%Y%m%dT%H%M%SZ)
SHA=$(echo "${{ github.sha }}" | cut -c1-8)
mkdir -p /tmp/perf-data/baselines
cp /tmp/perf-metrics.json "/tmp/perf-data/baselines/perf-${TIMESTAMP}-${SHA}.json"

# Keep only last 20 baselines
cd /tmp/perf-data
ls -t baselines/perf-*.json 2>/dev/null | tail -n +21 | xargs -r rm

git -C /tmp/perf-data add baselines/
git -C /tmp/perf-data commit -m "perf: add baseline for ${SHA}" || echo "No changes to commit"
git -C /tmp/perf-data push origin HEAD:perf-data

git worktree remove /tmp/perf-data --force 2>/dev/null || true
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
46 changes: 46 additions & 0 deletions .github/workflows/pr-perf-report.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ permissions:
contents: read
pull-requests: write
issues: write
actions: read

jobs:
comment:
Expand Down Expand Up @@ -73,14 +74,36 @@ jobs:
core.setOutput('number', String(pr.number));
core.setOutput('base', trustedBase);

- name: Check if results are still current
id: sha-check
uses: actions/github-script@v8
with:
script: |
const prNumber = Number('${{ steps.pr-meta.outputs.number }}');
const { data: pr } = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber,
});
const runSha = context.payload.workflow_run.head_sha;
const currentSha = pr.head.sha;
if (runSha !== currentSha) {
core.info(`Skipping stale report: run SHA ${runSha} != current PR SHA ${currentSha}`);
core.setOutput('stale', 'true');
} else {
core.setOutput('stale', 'false');
}

- name: Download PR perf metrics
if: steps.sha-check.outputs.stale != 'true'
uses: dawidd6/action-download-artifact@0bd50d53a6d7fb5cb921e607957e9cc12b4ce392 # v12
with:
name: perf-metrics
run_id: ${{ github.event.workflow_run.id }}
path: test-results/

- name: Download baseline perf metrics
if: steps.sha-check.outputs.stale != 'true'
uses: dawidd6/action-download-artifact@0bd50d53a6d7fb5cb921e607957e9cc12b4ce392 # v12
with:
branch: ${{ steps.pr-meta.outputs.base }}
Expand All @@ -90,10 +113,33 @@ jobs:
path: temp/perf-baseline/
if_no_artifact_found: warn

- name: Load historical baselines from perf-data branch
if: steps.sha-check.outputs.stale != 'true'
continue-on-error: true
run: |
mkdir -p temp/perf-history

git fetch origin perf-data 2>/dev/null || {
echo "perf-data branch not found, skipping historical data"
exit 0
}

INDEX=0
for file in $(git ls-tree --name-only origin/perf-data baselines/ 2>/dev/null | sort -r | head -5); do
DIR="temp/perf-history/$INDEX"
mkdir -p "$DIR"
git show "origin/perf-data:${file}" > "$DIR/perf-metrics.json" 2>/dev/null || true
INDEX=$((INDEX + 1))
done

echo "Loaded $INDEX historical baselines"

- name: Generate perf report
if: steps.sha-check.outputs.stale != 'true'
run: npx --yes tsx scripts/perf-report.ts > perf-report.md

- name: Post PR comment
if: steps.sha-check.outputs.stale != 'true'
uses: ./.github/actions/post-pr-report-comment
with:
pr-number: ${{ steps.pr-meta.outputs.number }}
Expand Down
Loading