Skip to content

Commit deecb0d

Browse files
committed
add CI to detect performance regressions
Compares two release builds of cpp-linter binary: 1. the previous commit (for push events) or the base branch of a PR 2. the newest commit on the branch Caching is enabled to reduce CI runtime. Results are output to the CI workflow's job summary. This CI does not (currently) fail when a regression is detected.
1 parent 023c170 commit deecb0d

File tree

2 files changed

+161
-0
lines changed

2 files changed

+161
-0
lines changed

.github/workflows/bench.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import argparse
2+
import json
3+
from os import environ
4+
from pathlib import Path
5+
from typing import List, Any, Dict
6+
7+
8+
class Args(argparse.Namespace):
9+
json_file: Path
10+
11+
12+
def main():
13+
arg_parser = argparse.ArgumentParser()
14+
arg_parser.add_argument("json_file", type=Path)
15+
arg_parser.parse_args(namespace=Args)
16+
17+
bench_json = Args.json_file.read_text(encoding="utf-8")
18+
bench: List[Dict[str, Any]] = json.loads(bench_json)["results"]
19+
20+
assert len(bench) == 2
21+
assert bench[0]["command"] == "previous-build"
22+
assert bench[1]["command"] == "current-build"
23+
24+
old_mean: float = bench[0]["mean"]
25+
new_mean: float = bench[1]["mean"]
26+
27+
diff = round(new_mean - old_mean, 2)
28+
scalar = round(new_mean / old_mean, 2)
29+
30+
output = []
31+
if diff > 0.5:
32+
output.extend(
33+
[
34+
"> [!CAUTION]",
35+
"> Detected a performance regression in new changes:",
36+
]
37+
)
38+
elif diff < -0.5:
39+
output.extend(
40+
[
41+
"> [!TIP]",
42+
"> Detect a performance improvement in new changes:",
43+
]
44+
)
45+
else:
46+
output.extend(
47+
[
48+
"> [!NOTE]",
49+
"> Determined a negligible difference in performance with new changes:",
50+
]
51+
)
52+
output[-1] += f" {diff}s ({scalar} %)"
53+
annotation = "\n".join(output)
54+
55+
if "GITHUB_STEP_SUMMARY" in environ:
56+
with open(environ["GITHUB_STEP_SUMMARY"], "a") as summary:
57+
summary.write(f"\n{annotation}\n")
58+
else:
59+
print(annotation)
60+
61+
62+
if __name__ == "__main__":
63+
main()

.github/workflows/perf-test.yml

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
name: Performance Regression
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
build:
11+
name: Build ${{ matrix.name }}
12+
runs-on: ubuntu-latest
13+
strategy:
14+
matrix:
15+
include:
16+
- commit: ${{ github.sha }}
17+
name: current
18+
- commit: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.before }}
19+
name: previous
20+
steps:
21+
- name: Checkout ${{ matrix.name }}
22+
uses: actions/checkout@v4
23+
with:
24+
ref: ${{ matrix.commit }}
25+
- name: Cache base ref build
26+
uses: actions/cache@v4
27+
id: cache
28+
with:
29+
key: bin-cache-${{ matrix.name }}-${{ matrix.commit }}
30+
path: target/release/cpp-linter
31+
- run: rustup update --no-self-update
32+
if: steps.cache.outputs.cache-hit != 'true'
33+
- run: cargo build --bin cpp-linter --release
34+
if: steps.cache.outputs.cache-hit != 'true'
35+
- name: Upload build artifact
36+
uses: actions/upload-artifact@v4
37+
with:
38+
name: ${{ matrix.name }}
39+
path: target/release/cpp-linter
40+
41+
benchmark:
42+
name: Measure Performance Difference
43+
needs: [build]
44+
runs-on: ubuntu-latest
45+
steps:
46+
- name: Checkout libgit2
47+
uses: actions/checkout@v4
48+
with:
49+
repository: libgit2/libgit2
50+
ref: v1.8.1
51+
path: libgit2
52+
- uses: actions/checkout@v4
53+
- name: Download built binaries
54+
uses: actions/download-artifact@v4
55+
- name: Make binaries executable
56+
run: chmod +x ./*/cpp-linter
57+
- name: Generate compilation database
58+
run: |
59+
mkdir build && cd build
60+
cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON ..
61+
- name: Install cargo-binstall
62+
uses: cargo-bins/cargo-binstall@main
63+
- name: Install hyperfine
64+
run: cargo binstall -y hyperfine
65+
- name: Warmup and list files
66+
env:
67+
CPP_LINTER_COLOR: true
68+
working-directory: libgit2
69+
# Use previous build for stability. This will
70+
# - create the .cpp-linter_cache folder
71+
# - list the files concerning the benchmark test
72+
# NOTE: This does not actually invoke clang tools.
73+
run: ./previous/cpp-linter -l 0 -p build -i='|!src/libgit2' -s="" -c="-*"
74+
- name: Run hyperfine tool
75+
# using the generated compilation database,
76+
# we will use cpp-linter (both builds) to scan libgit2 sources.
77+
working-directory: libgit2
78+
run: >-
79+
hyperfine
80+
--runs 3
81+
--style color
82+
--export-markdown '${{ runner.temp }}/benchmark.md'
83+
--export-json '${{ runner.temp }}/benchmark.json'
84+
--command-name=previous-build
85+
"../previous/cpp-linter -l 0 -p build -i='|!src/libgit2'"
86+
--command-name=current-build
87+
"../current/cpp-linter -l 0 -p build -i='|!src/libgit2'"
88+
- name: Append report to job summary
89+
run: cat ${{ runner.temp }}/benchmark.md >> $GITHUB_STEP_SUMMARY
90+
- name: Upload JSON results
91+
uses: actions/upload-artifact@v4
92+
with:
93+
path: ${{ runner.temp }}/benchmark.json
94+
- uses: actions/setup-python@v5
95+
with:
96+
python-version: 3.x
97+
- name: Annotate summary
98+
run: python .github/workflows/bench.py "${{ runner.temp }}/benchmark.json"

0 commit comments

Comments
 (0)