Skip to content

Commit 2dbba2c

Browse files
committed
Add performance benchmark workflow for PR merges
Runs data loader performance benchmarks automatically when PRs are merged to main, posting results as a comment on the PR. Also supports manual triggering via workflow_dispatch for testing. Closes #42
1 parent 8ce25f1 commit 2dbba2c

File tree

1 file changed

+136
-0
lines changed

1 file changed

+136
-0
lines changed

.github/workflows/benchmark.yml

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
name: Performance Benchmarks
2+
3+
permissions:
4+
contents: read
5+
pull-requests: write
6+
7+
on:
8+
pull_request:
9+
types: [closed]
10+
branches: [main]
11+
# Allow manual triggering for testing
12+
workflow_dispatch:
13+
14+
jobs:
15+
benchmark:
16+
# Run on merged PRs or manual trigger
17+
if: github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch'
18+
runs-on: ubuntu-latest
19+
20+
steps:
21+
- uses: actions/checkout@v4
22+
23+
- name: Install uv
24+
uses: astral-sh/setup-uv@v4
25+
with:
26+
enable-cache: true
27+
cache-dependency-glob: "**/pyproject.toml"
28+
29+
- name: Set up Python 3.12
30+
run: uv python install 3.12
31+
32+
- name: Install all dependencies
33+
run: uv sync --group all_loaders --group test --group dev
34+
35+
- name: Run performance benchmarks
36+
env:
37+
USE_TESTCONTAINERS: "true"
38+
TESTCONTAINERS_RYUK_DISABLED: "true"
39+
PERF_ENV: "github-actions"
40+
run: |
41+
uv run pytest tests/performance/ -v -m "performance" \
42+
--tb=short \
43+
-k "not snowflake" \
44+
2>&1 | tee benchmark_output.txt
45+
46+
# Copy benchmark results for the comment
47+
if [ -f performance_benchmarks.json ]; then
48+
cp performance_benchmarks.json benchmark_results.json
49+
else
50+
echo '{}' > benchmark_results.json
51+
fi
52+
53+
- name: Write results to job summary
54+
run: |
55+
echo "## Performance Benchmark Results" >> $GITHUB_STEP_SUMMARY
56+
echo "" >> $GITHUB_STEP_SUMMARY
57+
echo "**Git Commit:** \`${GITHUB_SHA::8}\`" >> $GITHUB_STEP_SUMMARY
58+
echo "**Environment:** GitHub Actions" >> $GITHUB_STEP_SUMMARY
59+
echo "" >> $GITHUB_STEP_SUMMARY
60+
echo "### Raw Results" >> $GITHUB_STEP_SUMMARY
61+
echo "" >> $GITHUB_STEP_SUMMARY
62+
echo "\`\`\`json" >> $GITHUB_STEP_SUMMARY
63+
cat benchmark_results.json >> $GITHUB_STEP_SUMMARY
64+
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
65+
66+
- name: Post benchmark results to PR
67+
if: github.event_name == 'pull_request'
68+
uses: actions/github-script@v7
69+
with:
70+
script: |
71+
const fs = require('fs');
72+
73+
// Read benchmark results
74+
let benchmarkData = {};
75+
try {
76+
benchmarkData = JSON.parse(fs.readFileSync('benchmark_results.json', 'utf8'));
77+
} catch (e) {
78+
console.log('No benchmark results file found');
79+
}
80+
81+
// Read test output for summary
82+
let testOutput = '';
83+
try {
84+
testOutput = fs.readFileSync('benchmark_output.txt', 'utf8');
85+
} catch (e) {
86+
console.log('No test output file found');
87+
}
88+
89+
// Extract summary from pytest output
90+
const summaryMatch = testOutput.match(/=+ ([\d\w\s,]+) in [\d.]+s =+/);
91+
const summary = summaryMatch ? summaryMatch[1] : 'Unknown';
92+
93+
// Format benchmark results as a table
94+
let resultsTable = '';
95+
const entries = Object.entries(benchmarkData);
96+
97+
if (entries.length > 0) {
98+
resultsTable = '| Loader | Test | Throughput (rows/sec) | Memory (MB) | Duration (s) | Dataset Size |\n';
99+
resultsTable += '|--------|------|----------------------|-------------|--------------|-------------|\n';
100+
101+
for (const [key, data] of entries) {
102+
resultsTable += `| ${data.loader_type} | ${data.test_name} | ${data.throughput_rows_per_sec?.toFixed(0) || 'N/A'} | ${data.memory_mb?.toFixed(2) || 'N/A'} | ${data.duration_seconds?.toFixed(2) || 'N/A'} | ${data.dataset_size?.toLocaleString() || 'N/A'} |\n`;
103+
}
104+
} else {
105+
resultsTable = '_No benchmark data recorded_';
106+
}
107+
108+
// Create comment body using array join to avoid YAML parsing issues
109+
const body = [
110+
'## Performance Benchmark Results',
111+
'',
112+
`**Test Summary:** ${summary}`,
113+
`**Git Commit:** \`${context.sha.substring(0, 8)}\``,
114+
'**Environment:** GitHub Actions',
115+
'',
116+
'### Results',
117+
'',
118+
resultsTable,
119+
'',
120+
'<details>',
121+
'<summary>Raw JSON Results</summary>',
122+
'',
123+
'```json',
124+
JSON.stringify(benchmarkData, null, 2),
125+
'```',
126+
'',
127+
'</details>'
128+
].join('\n');
129+
130+
// Post comment to PR
131+
await github.rest.issues.createComment({
132+
owner: context.repo.owner,
133+
repo: context.repo.repo,
134+
issue_number: context.payload.pull_request.number,
135+
body: body
136+
});

0 commit comments

Comments
 (0)