Skip to content

Commit 0333424

Browse files
committed
Setup Continuous Performance
A new workflow, continuous-integration-perf.yml was introduced. It: - Checks out the `benchmarks` branch locally. - Runs the benchmarks, accounting for non-existant baselines and target (main/PR). - Stores the .phpbench storage folder and a human-readable report in the `benchmarks` branch. - Does not make a PR fail, and never reports a failure when merging to main. - Allows workflow_dispatch for quick re-runs, and has an option to reset the baseline in case something changes (GitHub runner setup gets faster/slower, major refactors, etc). Thus, it keeps a historical record of all benchmark results. These results can be viewed by exploring GitHub via the web interface and seeing the changes in `latest.md` (the human file commited). Additionally, one can clone the `benchmarks` branch and run `phpbench log` to explore the history in more detail. Some adjustments to previously added benchmarks were made: - Assertions were included in order to track time and memory tresholds. - The benchmarks are now more surgical, and address the concrete validators instead of the whole chain validate. These changes were made to make benchmarks more isolated, with the intention of adding chain-related benchmarks separately in the future.
1 parent 3270c1f commit 0333424

File tree

4 files changed

+271
-170
lines changed

4 files changed

+271
-170
lines changed
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
name: Continuous Integration (perf)
2+
3+
on:
4+
push:
5+
paths-ignore:
6+
- 'bin/**'
7+
- 'docs/**'
8+
pull_request:
9+
paths-ignore:
10+
- 'bin/**'
11+
- 'docs/**'
12+
workflow_dispatch:
13+
inputs:
14+
baseline:
15+
description: 'Baseline mode. latest: compare against latest benchmarks; rebaseline: store new baseline.'
16+
type: choice
17+
default: 'latest'
18+
options:
19+
- 'latest'
20+
- 'rebaseline'
21+
22+
jobs:
23+
tests:
24+
name: Benchmarks
25+
runs-on: ubuntu-latest
26+
continue-on-error: true # This job is experimental
27+
permissions:
28+
contents: write
29+
pull-requests: write
30+
31+
strategy:
32+
matrix:
33+
php-version:
34+
- "8.5"
35+
36+
steps:
37+
- name: Checkout
38+
uses: actions/checkout@v6
39+
with:
40+
persist-credentials: true
41+
42+
- name: Install PHP
43+
uses: shivammathur/setup-php@v2
44+
with:
45+
php-version: ${{ matrix.php-version }}
46+
extensions: xdebug
47+
48+
- name: Install Dependencies
49+
run: composer install --prefer-dist ${{ matrix.composer-extra-arguments }}
50+
51+
- name: Fetch Benchmarks
52+
run: |
53+
git fetch origin benchmarks
54+
mkdir -p .phpbench
55+
git checkout origin/benchmarks -- .phpbench || echo "No previous benchmarks found"
56+
57+
- name: Run Benchmarks
58+
run: |
59+
# Baseline does not exist or rebaseline requested. Generate it.
60+
if [ -z "$(ls -A .phpbench)" ] || [ "${{ github.event.inputs.baseline || 'latest' }}" = "rebaseline" ]; then
61+
vendor/bin/phpbench run --report=aggregate --progress=plain --store --tag=${GITHUB_SHA}
62+
63+
# On main branch push, update baseline with tolerance for failures.
64+
elif [ "${GITHUB_REF}" = "refs/heads/main" ] && [ "${GITHUB_EVENT_NAME}" = "push" ]; then
65+
vendor/bin/phpbench run --report=aggregate --progress=plain --store --tag=${GITHUB_SHA} --ref=latest --tolerate-failure
66+
67+
# On other branches, compare against latest baseline, fails if worse.
68+
else
69+
vendor/bin/phpbench run --report=aggregate --progress=plain --store --tag=${GITHUB_SHA} --ref=latest
70+
fi
71+
72+
# Generate report for human consumption
73+
vendor/bin/phpbench report --report=aggregate --ref=latest |
74+
tail -n+2 | head -n-2 | tr '+' '|' > report.md
75+
76+
cat report.md
77+
78+
- name: Commit Benchmark Results
79+
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
80+
run: |
81+
set -euo pipefail
82+
git config user.name "github-actions[bot]"
83+
git config user.email "github-actions[bot]@users.noreply.github.com"
84+
git checkout benchmarks
85+
mv -f report.md latest.md
86+
git add .phpbench latest.md
87+
git commit -m "Store benchmark results [skip ci]" || echo "No changes to commit"
88+
git push origin benchmarks

tests/benchmark/ValidatorBench.php

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,26 @@ class ValidatorBench
1818
use SmokeTestProvider;
1919

2020
/** @param array<Validator, mixed> $params */
21+
#[Bench\ParamProviders(['provideValidatorInput'])]
22+
#[Bench\BeforeMethods('setFileUploadMock')]
2123
#[Bench\Iterations(10)]
22-
#[Bench\RetryThreshold(10)]
24+
#[Bench\RetryThreshold(5)]
2325
#[Bench\Revs(5)]
24-
#[Bench\ParamProviders(['provideValidatorInput'])]
25-
public function benchValidate(array $params): void
26+
#[Bench\Warmup(1)]
27+
#[Bench\Assert('mode(variant.time.avg) < mode(baseline.time.avg) +/- 10%')]
28+
#[Bench\Assert('mode(variant.time.net) < mode(baseline.time.net) +/- 10%')]
29+
#[Bench\Assert('mode(variant.mem.peak) < mode(baseline.mem.peak) +/- 10%')]
30+
#[Bench\Assert('mode(variant.mem.real) < mode(baseline.mem.real) +/- 10%')]
31+
#[Bench\Assert('mode(variant.mem.final) < mode(baseline.mem.final) +/- 10%')]
32+
#[Bench\Subject]
33+
public function evaluate(array $params): void
2634
{
2735
[$v, $input] = $params;
28-
$v->validate($input);
36+
$v->evaluate($input);
37+
}
38+
39+
public function setFileUploadMock(): void
40+
{
41+
set_mock_is_uploaded_file_return(true);
2942
}
3043
}

tests/feature/SerializableTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
test('Can be serialized and unserialized', function ($validator, $input): void {
1313
set_mock_is_uploaded_file_return(true);
1414
expect(
15-
unserialize(serialize($validator))->validate($input)->isValid(),
15+
unserialize(serialize($validator))->evaluate($input)->hasPassed,
1616
)->toBeTrue();
1717
})->with(fn(): Generator => (new class {
1818
use SmokeTestProvider {

0 commit comments

Comments
 (0)