Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
278 changes: 278 additions & 0 deletions .github/workflows/ci-performance.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,278 @@
name: ci-performance
on:
pull_request:
branches:
- alpha
- beta
- release
- 'release-[0-9]+.x.x'
- next-major
paths-ignore:
- '**.md'
- 'docs/**'
- '.github/**'
- '!.github/workflows/performance.yml'

env:
NODE_VERSION: 24.11.0
MONGODB_VERSION: 8.0.4

jobs:
performance-check:
name: Run Performance Benchmarks
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: write
pull-requests: write
deployments: write

steps:
- name: Checkout base branch
uses: actions/checkout@v4
with:
ref: ${{ github.base_ref }}
fetch-depth: 1

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'

- name: Install dependencies (base)
run: npm ci

- name: Build Parse Server (base)
run: npm run build

- name: Run baseline benchmarks
id: baseline
run: |
npm run benchmark > baseline-output.txt 2>&1 || true
# Extract JSON from output (last valid JSON block)
grep -A 1000 '^\[' baseline-output.txt | grep -B 1000 '^\]' | head -n -0 > baseline.json || echo '[]' > baseline.json
echo "Baseline benchmark results:"
cat baseline.json
continue-on-error: true

- name: Upload baseline results
uses: actions/upload-artifact@v4
with:
name: baseline-benchmark
path: |
baseline.json
baseline-output.txt
retention-days: 7

- name: Checkout PR branch
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 1
clean: true

- name: Setup Node.js (PR)
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'

- name: Install dependencies (PR)
run: npm ci

- name: Build Parse Server (PR)
run: npm run build

- name: Run PR benchmarks
id: pr-bench
run: |
npm run benchmark > pr-output.txt 2>&1 || true
# Extract JSON from output (last valid JSON block)
grep -A 1000 '^\[' pr-output.txt | grep -B 1000 '^\]' | head -n -0 > pr.json || echo '[]' > pr.json
echo "PR benchmark results:"
cat pr.json
continue-on-error: true

- name: Upload PR results
uses: actions/upload-artifact@v4
with:
name: pr-benchmark
path: |
pr.json
pr-output.txt
retention-days: 7

- name: Store benchmark result (baseline)
uses: benchmark-action/github-action-benchmark@v1
if: github.event_name == 'pull_request'
with:
name: Parse Server Performance (baseline)
tool: 'customBiggerIsBetter'
output-file-path: baseline.json
github-token: ${{ secrets.GITHUB_TOKEN }}
auto-push: false
save-data-file: false
comment-on-alert: false

- name: Store benchmark result (PR)
uses: benchmark-action/github-action-benchmark@v1
if: github.event_name == 'pull_request'
with:
name: Parse Server Performance
tool: 'customBiggerIsBetter'
output-file-path: pr.json
github-token: ${{ secrets.GITHUB_TOKEN }}
auto-push: false
save-data-file: false
alert-threshold: '110%'
comment-on-alert: true
fail-on-alert: false
alert-comment-cc-users: '@parse-community/maintainers'
summary-always: true

- name: Compare benchmark results
id: compare
run: |
node -e "
const fs = require('fs');

let baseline, pr;
try {
baseline = JSON.parse(fs.readFileSync('baseline.json', 'utf8'));
pr = JSON.parse(fs.readFileSync('pr.json', 'utf8'));
} catch (e) {
console.log('⚠️ Could not parse benchmark results');
process.exit(0);
}

if (!Array.isArray(baseline) || !Array.isArray(pr) || baseline.length === 0 || pr.length === 0) {
console.log('⚠️ Benchmark results are empty or invalid');
process.exit(0);
}

console.log('# Performance Comparison\n');
console.log('| Benchmark | Baseline | PR | Change | Status |');
console.log('|-----------|----------|----|---------| ------ |');

let hasRegression = false;
let hasImprovement = false;

baseline.forEach(baseResult => {
const prResult = pr.find(p => p.name === baseResult.name);
if (!prResult) {
console.log(\`| \${baseResult.name} | \${baseResult.value.toFixed(2)} ms | N/A | - | ⚠️ Missing |\`);
return;
}

const baseValue = parseFloat(baseResult.value);
const prValue = parseFloat(prResult.value);
const change = ((prValue - baseValue) / baseValue * 100);
const changeStr = change > 0 ? \`+\${change.toFixed(1)}%\` : \`\${change.toFixed(1)}%\`;

let status = '✅';
if (change > 10) {
status = '⚠️ Slower';
hasRegression = true;
} else if (change > 20) {
status = '❌ Much Slower';
hasRegression = true;
} else if (change < -10) {
status = '🚀 Faster';
hasImprovement = true;
}

console.log(\`| \${baseResult.name} | \${baseValue.toFixed(2)} ms | \${prValue.toFixed(2)} ms | \${changeStr} | \${status} |\`);
});

console.log('');
if (hasRegression) {
console.log('⚠️ **Performance regressions detected.** Please review the changes.');
} else if (hasImprovement) {
console.log('🚀 **Performance improvements detected!** Great work!');
} else {
console.log('✅ **No significant performance changes.**');
}
" | tee comparison.md

- name: Upload comparison
uses: actions/upload-artifact@v4
with:
name: benchmark-comparison
path: comparison.md
retention-days: 30

- name: Comment PR with results
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
continue-on-error: true
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const fs = require('fs');

let comparisonMd = '';
try {
comparisonMd = fs.readFileSync('comparison.md', 'utf8');
} catch (e) {
comparisonMd = '⚠️ Could not generate performance comparison.';
}

const body = `## Performance Impact Report

${comparisonMd}

<details>
<summary>📊 View detailed results</summary>

### Baseline Results
\`\`\`json
${fs.readFileSync('baseline.json', 'utf8')}
\`\`\`

### PR Results
\`\`\`json
${fs.readFileSync('pr.json', 'utf8')}
\`\`\`

</details>

*Benchmarks ran with ${process.env.BENCHMARK_ITERATIONS || '100'} iterations per test on Node.js ${process.env.NODE_VERSION}*
`;

// Find existing performance comment
const comments = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});

const existingComment = comments.data.find(comment =>
comment.user.login === 'github-actions[bot]' &&
comment.body.includes('Performance Impact Report')
);

if (existingComment) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existingComment.id,
body: body,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: body,
});
}

- name: Generate job summary
if: always()
run: |
if [ -f comparison.md ]; then
cat comparison.md >> $GITHUB_STEP_SUMMARY
else
echo "⚠️ Benchmark comparison not available" >> $GITHUB_STEP_SUMMARY
fi
Loading
Loading