@@ -25,17 +25,83 @@ jobs:
2525 - uses : shivammathur/setup-php@v2
2626 with :
2727 php-version : ${{ steps.php_min.outputs.version }}
28+ coverage : none
2829 - run : composer install --no-interaction --prefer-dist
2930 - run : composer bin all install --no-interaction --prefer-dist
3031
3132 - name : Run benchmarks (strict CI mode with verbose output)
3233 run : |
33- composer benchmark:run:ci
34+ composer benchmark:run:ci | tee build/benchmark-output.txt
3435
3536 echo "✓ Benchmarks completed"
3637 echo ""
3738 echo "Results saved to build/benchmark-results.xml"
3839 echo "Review: Ensure no individual benchmark takes >10ms average"
40+
41+ - name : Validate regression against baseline
42+ if : github.event_name == 'pull_request'
43+ run : |
44+ python3 - <<'PY'
45+ import json
46+ import pathlib
47+ import re
48+ import sys
49+
50+ baseline_path = pathlib.Path('.github/.performance/baseline.json')
51+ output_path = pathlib.Path('build/benchmark-output.txt')
52+
53+ if not baseline_path.exists():
54+ print('Baseline file not found, skipping regression validation.')
55+ sys.exit(0)
56+
57+ if not output_path.exists():
58+ print('Benchmark output not found, cannot validate regression.')
59+ sys.exit(1)
60+
61+ baseline = json.loads(baseline_path.read_text())
62+ output = output_path.read_text()
63+
64+ patterns = {
65+ 'LibreSign\\XObjectTemplate\\Benchmarks\\CompilerBench::benchSimpleHtml': r'benchSimpleHtml.*?Mo([0-9]+(?:\\.[0-9]+)?)(μs|us|ms)',
66+ 'LibreSign\\XObjectTemplate\\Benchmarks\\CompilerBench::benchComplexHtml': r'benchComplexHtml.*?Mo([0-9]+(?:\\.[0-9]+)?)(μs|us|ms)',
67+ }
68+
69+ tolerance = float(baseline.get('allowed_regression_pct', 25.0)) / 100.0
70+ benchmark_baseline = baseline.get('benchmarks', {})
71+
72+ failures = []
73+ for name, pattern in patterns.items():
74+ match = re.search(pattern, output)
75+ if not match:
76+ failures.append(f'{name}: metric not found in benchmark output')
77+ continue
78+
79+ value = float(match.group(1))
80+ unit = match.group(2)
81+ current_ms = value / 1000.0 if unit in ('μs', 'us') else value
82+
83+ if name not in benchmark_baseline:
84+ failures.append(f'{name}: missing baseline entry')
85+ continue
86+
87+ baseline_ms = float(benchmark_baseline[name]['mean'])
88+ limit_ms = baseline_ms * (1.0 + tolerance)
89+
90+ print(f'{name}: current={current_ms:.6f}ms baseline={baseline_ms:.6f}ms limit={limit_ms:.6f}ms')
91+
92+ if current_ms > limit_ms:
93+ failures.append(
94+ f'{name}: regression detected ({current_ms:.6f}ms > {limit_ms:.6f}ms)'
95+ )
96+
97+ if failures:
98+ print('\nPerformance regression gate failed:')
99+ for failure in failures:
100+ print(f' - {failure}')
101+ sys.exit(1)
102+
103+ print('\nPerformance regression gate passed.')
104+ PY
39105
40106 - name : Save benchmark results as artifact
41107 if : always()
0 commit comments