Skip to content

Commit d4bca5d

Browse files
committed
feat(benchmark): split pytest-benchmark runners
1 parent 67c7d3d commit d4bca5d

File tree

2 files changed

+101
-16
lines changed

2 files changed

+101
-16
lines changed

.github/workflows/benchmark.yaml

Lines changed: 59 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ jobs:
2626
benchmark:
2727
name: Run Benchmarks
2828
runs-on: ubuntu-latest
29+
strategy:
30+
fail-fast: false
31+
matrix:
32+
shard: [1, 2, 3, 4]
2933
steps:
3034
- uses: actions/checkout@v5
3135
- name: Set up Python
@@ -66,25 +70,67 @@ jobs:
6670
run: |
6771
rm -r faster_web3
6872
rm -r faster_ens
69-
70-
- name: Run Pytest Benchmark & Save Output
71-
run: pytest --benchmark-only --benchmark-json=benchmark.json benchmarks/
73+
74+
- name: Collect and split test nodeids for sharding
75+
id: split
76+
env:
77+
SHARD: ${{ matrix.shard }}
78+
SHARD_COUNT: 4
79+
run: |
80+
pytest --collect-only -q benchmarks/ > nodeids.txt
81+
total=$(wc -l < nodeids.txt)
82+
per_shard=$(( (total + SHARD_COUNT - 1) / SHARD_COUNT ))
83+
start=$(( (SHARD - 1) * per_shard + 1 ))
84+
end=$(( SHARD * per_shard ))
85+
sed -n "${start},${end}p" nodeids.txt > shard_nodeids.txt
86+
if [ ! -s shard_nodeids.txt ]; then
87+
echo "No tests for this shard" > shard_nodeids.txt
88+
fi
89+
90+
- name: Run Pytest Benchmark & Save Output (sharded)
91+
if: ${{ hashFiles('shard_nodeids.txt') != '' && hashFiles('shard_nodeids.txt') != 'No tests for this shard' }}
92+
run: |
93+
ids=$(paste -sd ' or ' shard_nodeids.txt)
94+
if [ -s shard_nodeids.txt ] && [ "$ids" != "No tests for this shard" ]; then
95+
pytest --benchmark-only --benchmark-json=benchmark.json benchmarks/ -k "$ids"
96+
else
97+
echo "No tests for this shard"
98+
fi
7299
73100
- name: Upload Pytest Benchmark Results
101+
uses: actions/upload-artifact@v5
102+
with:
103+
name: pytest-benchmark-results-shard-${{ matrix.shard }}
104+
path: benchmark.json
105+
106+
join-benchmark-results:
107+
name: Join Benchmark Results
108+
runs-on: ubuntu-latest
109+
needs: benchmark
110+
steps:
111+
- uses: actions/checkout@v5
112+
- name: Download all shard benchmark results
113+
uses: actions/download-artifact@v6
114+
with:
115+
pattern: pytest-benchmark-results-shard-*
116+
path: shards
117+
- name: Merge benchmark.json files
118+
run: python scripts/merge_pytest_benchmark_json.py
119+
- name: Upload Merged Benchmark Results
74120
uses: actions/upload-artifact@v5
75121
with:
76122
name: pytest-benchmark-results
77123
path: benchmark.json
78-
124+
79125
- name: Parse Pytest Benchmark Output
80126
run: python scripts/benchmark/parse_benchmark_output.py benchmark.json pytest_benchmark_results.json
81-
127+
82128
- name: Compare Pytest Benchmark Results
83129
run: python scripts/benchmark/compare_benchmark_results.py pytest_benchmark_results.json pytest_benchmark_diff.json
84-
130+
85131
- name: Generate Markdown Benchmark Results
86132
run: python scripts/benchmark/generate_benchmark_markdown.py
87-
133+
88134
- name: Commit and Push Markdown Benchmark Results
89135
continue-on-error: true
90136
run: |
@@ -101,19 +147,19 @@ jobs:
101147
fi
102148
env:
103149
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
104-
150+
105151
- name: Upload Markdown Benchmark Results
106152
uses: actions/upload-artifact@v5
107153
with:
108154
name: markdown-benchmark-results
109155
path: benchmarks/results/*.md
110-
156+
111157
- name: Upload Pytest Benchmark Diff
112158
uses: actions/upload-artifact@v5
113159
with:
114160
name: pytest-benchmark-diff
115161
path: pytest_benchmark_diff.json
116-
162+
117163
- name: Post Pytest Benchmark Diff as PR Comment
118164
uses: actions/github-script@v8
119165
with:
@@ -160,9 +206,6 @@ jobs:
160206
body
161207
});
162208
if: github.event_name == 'pull_request'
163-
164-
- name: Save ccache cache
165-
uses: actions/cache/save@v4
166-
with:
167-
path: ~/.cache/ccache
168-
key: ccache-${{ runner.os }}-${{ hashFiles('pyproject.toml') }}-${{ hashFiles('**/*.h', '**/*.c', '**/*.py') }}
209+
210+
- name: Cleanup Shard Artifacts
211+
run: rm -rf shards
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
"""
2+
merge_pytest_benchmark_json.py
3+
4+
Merges multiple pytest-benchmark JSON result files (from sharded CI runs) into a single benchmark.json file.
5+
- Scans for all benchmark.json files in the given search path (default: shards/**/benchmark.json).
6+
- Combines all 'benchmarks' entries into a single report, preserving the pytest-benchmark format.
7+
- Intended for use in CI workflows where benchmarks are run in parallel shards and results need to be aggregated.
8+
9+
Usage:
10+
python faster_web3/scripts/merge_pytest_benchmark_json.py
11+
12+
Arguments:
13+
--output_path: Path to write the merged benchmark JSON (default: benchmark.json)
14+
--search_path: Glob pattern to find input benchmark.json files (default: shards/**/benchmark.json)
15+
"""
16+
17+
import json
18+
import glob
19+
import os
20+
21+
def merge_benchmark_json_files(output_path="benchmark.json", search_path="shards/**/benchmark.json"):
22+
files = sorted(glob.glob(search_path, recursive=True))
23+
benchmarks = []
24+
for f in files:
25+
with open(f) as fh:
26+
data = json.load(fh)
27+
if 'benchmarks' in data:
28+
benchmarks.extend(data['benchmarks'])
29+
else:
30+
benchmarks.append(data)
31+
if benchmarks and isinstance(benchmarks[0], dict) and 'benchmarks' in benchmarks[0]:
32+
# If each file is a full pytest-benchmark report
33+
merged = benchmarks[0]
34+
for b in benchmarks[1:]:
35+
merged['benchmarks'].extend(b['benchmarks'])
36+
else:
37+
merged = {'benchmarks': benchmarks}
38+
with open(output_path, 'w') as out:
39+
json.dump(merged, out)
40+
41+
if __name__ == "__main__":
42+
merge_benchmark_json_files()

0 commit comments

Comments
 (0)