Skip to content

Commit 1694bed

Browse files
committed
Add profiling infrastructure to measure parsing vs rule evaluation bottlenecks
Introduces ProfilingRunner (extends Runner) that instruments each file's parsing and rule evaluation phases separately, and a standalone CLI script (profiling/profile.php) that produces a detailed performance report including: - Time breakdown (parsing vs rule evaluation percentages) - Top 10 slowest files by parse and rule time - Per-rule-type breakdown with average evaluation cost - File size vs parse time correlation analysis - Multi-iteration mode with averages and standard deviation https://claude.ai/code/session_012E8gJ4HyDRb82q4PjesG1w
1 parent 0ff1ce6 commit 1694bed

File tree

2 files changed

+505
-0
lines changed

2 files changed

+505
-0
lines changed

profiling/profile.php

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
#!/usr/bin/env php
2+
<?php
3+
4+
declare(strict_types=1);
5+
6+
/**
7+
* PHPArkitect Profiling Script
8+
*
9+
* Usage:
10+
* php profiling/profile.php # uses default phparkitect.php config
11+
* php profiling/profile.php -c path/to/config.php # uses custom config
12+
* php profiling/profile.php --iterations 5 # run N iterations (avg results)
13+
*
14+
* This script measures parsing vs rule evaluation time to identify
15+
* where the performance bottleneck is in your PHPArkitect setup.
16+
*/
17+
18+
// --- Autoload ---
19+
$autoloadPaths = [
20+
__DIR__ . '/../vendor/autoload.php',
21+
__DIR__ . '/../../autoload.php',
22+
];
23+
24+
$autoloaded = false;
25+
foreach ($autoloadPaths as $path) {
26+
if (file_exists($path)) {
27+
require $path;
28+
$autoloaded = true;
29+
break;
30+
}
31+
}
32+
33+
if (!$autoloaded) {
34+
fwrite(STDERR, "Could not find vendor/autoload.php. Run 'composer install' first.\n");
35+
exit(1);
36+
}
37+
38+
use Arkitect\CLI\Baseline;
39+
use Arkitect\CLI\ConfigBuilder;
40+
use Arkitect\CLI\ProfilingRunner;
41+
use Arkitect\CLI\Progress\VoidProgress;
42+
use Arkitect\CLI\TargetPhpVersion;
43+
use Symfony\Component\Console\Output\ConsoleOutput;
44+
45+
// --- Parse CLI arguments ---
46+
$options = getopt('c:', ['iterations:', 'help']);
47+
48+
if (isset($options['help'])) {
49+
echo <<<HELP
50+
PHPArkitect Profiler
51+
52+
Usage:
53+
php profiling/profile.php [options]
54+
55+
Options:
56+
-c <file> Config file (default: phparkitect.php)
57+
--iterations <N> Number of iterations for averaging (default: 1)
58+
--help Show this help message
59+
60+
HELP;
61+
exit(0);
62+
}
63+
64+
$configFile = $options['c'] ?? 'phparkitect.php';
65+
$iterations = (int) ($options['iterations'] ?? 1);
66+
67+
if ($iterations < 1) {
68+
$iterations = 1;
69+
}
70+
71+
if (!file_exists($configFile)) {
72+
fwrite(STDERR, "Config file '$configFile' not found.\n");
73+
exit(1);
74+
}
75+
76+
ini_set('memory_limit', '-1');
77+
78+
$output = new ConsoleOutput();
79+
80+
$output->writeln('<info>PHPArkitect Profiler</info>');
81+
$output->writeln(sprintf('Config: %s', $configFile));
82+
$output->writeln(sprintf('Iterations: %d', $iterations));
83+
$output->writeln('');
84+
85+
// --- Run profiling ---
86+
$allParseTimes = [];
87+
$allRuleTimes = [];
88+
89+
for ($i = 1; $i <= $iterations; $i++) {
90+
if ($iterations > 1) {
91+
$output->writeln(sprintf('<comment>--- Iteration %d/%d ---</comment>', $i, $iterations));
92+
}
93+
94+
$config = ConfigBuilder::loadFromFile($configFile)
95+
->targetPhpVersion(TargetPhpVersion::latest())
96+
->skipBaseline(true);
97+
98+
$runner = new ProfilingRunner();
99+
$progress = new VoidProgress();
100+
$baseline = Baseline::create(true, null);
101+
102+
$iterationStart = microtime(true);
103+
$runner->run($config, $baseline, $progress);
104+
$iterationTime = microtime(true) - $iterationStart;
105+
106+
$allParseTimes[] = $runner->getTotalParseTime();
107+
$allRuleTimes[] = $runner->getTotalRuleTime();
108+
109+
if ($i === $iterations) {
110+
// Print full report on the last iteration
111+
$runner->printReport($output);
112+
}
113+
114+
if ($iterations > 1) {
115+
$output->writeln(sprintf(
116+
' Parse: %s | Rules: %s | Total: %s',
117+
formatTimeShort($runner->getTotalParseTime()),
118+
formatTimeShort($runner->getTotalRuleTime()),
119+
formatTimeShort($iterationTime)
120+
));
121+
}
122+
}
123+
124+
// --- Print average if multiple iterations ---
125+
if ($iterations > 1) {
126+
$avgParse = array_sum($allParseTimes) / $iterations;
127+
$avgRules = array_sum($allRuleTimes) / $iterations;
128+
$avgTotal = $avgParse + $avgRules;
129+
130+
$output->writeln('');
131+
$output->writeln('<info>--- Average over ' . $iterations . ' iterations ---</info>');
132+
$output->writeln(sprintf(' Avg parsing time: %s', formatTimeShort($avgParse)));
133+
$output->writeln(sprintf(' Avg rule eval time: %s', formatTimeShort($avgRules)));
134+
$output->writeln(sprintf(' Avg total: %s', formatTimeShort($avgTotal)));
135+
136+
// Std deviation
137+
$parseStdDev = stddev($allParseTimes);
138+
$rulesStdDev = stddev($allRuleTimes);
139+
$output->writeln(sprintf(' Parse std dev: %s', formatTimeShort($parseStdDev)));
140+
$output->writeln(sprintf(' Rules std dev: %s', formatTimeShort($rulesStdDev)));
141+
}
142+
143+
$output->writeln('');
144+
$output->writeln(sprintf('Peak memory usage: %s', formatBytes(memory_get_peak_usage(true))));
145+
146+
// --- Helper functions ---
147+
148+
function formatTimeShort(float $seconds): string
149+
{
150+
if ($seconds < 0.001) {
151+
return sprintf('%.0f us', $seconds * 1_000_000);
152+
}
153+
if ($seconds < 1.0) {
154+
return sprintf('%.2f ms', $seconds * 1000);
155+
}
156+
157+
return sprintf('%.2f s', $seconds);
158+
}
159+
160+
function formatBytes(int $bytes): string
161+
{
162+
if ($bytes < 1024) {
163+
return sprintf('%d B', $bytes);
164+
}
165+
if ($bytes < 1048576) {
166+
return sprintf('%.1f KB', $bytes / 1024);
167+
}
168+
169+
return sprintf('%.1f MB', $bytes / 1048576);
170+
}
171+
172+
function stddev(array $values): float
173+
{
174+
$n = count($values);
175+
if ($n < 2) {
176+
return 0.0;
177+
}
178+
$mean = array_sum($values) / $n;
179+
$sumSquaredDiffs = 0.0;
180+
foreach ($values as $v) {
181+
$sumSquaredDiffs += ($v - $mean) ** 2;
182+
}
183+
184+
return sqrt($sumSquaredDiffs / ($n - 1));
185+
}

0 commit comments

Comments
 (0)