1<?php 2 3require_once __DIR__ . '/shared.php'; 4 5foreach (array("mbstring", "sockets", "mysqli", "openssl", "gmp") as $ext) { 6 if (!extension_loaded($ext)) { 7 throw new LogicException("Extension $ext is required."); 8 } 9} 10 11$storeResult = ($argv[1] ?? 'false') === 'true'; 12$phpCgi = $argv[2] ?? dirname(PHP_BINARY) . '/php-cgi'; 13if (!file_exists($phpCgi)) { 14 fwrite(STDERR, "php-cgi not found\n"); 15 exit(1); 16} 17 18function main() { 19 global $storeResult; 20 21 $profilesDir = __DIR__ . '/profiles'; 22 if (!is_dir($profilesDir)) { 23 mkdir($profilesDir, 0755, true); 24 } 25 26 $data = []; 27 if (false !== $branch = getenv('GITHUB_REF_NAME')) { 28 $data['branch'] = $branch; 29 } 30 $data['Zend/bench.php'] = runBench(false); 31 $data['Zend/bench.php JIT'] = runBench(true); 32 $data['Symfony Demo 2.2.3'] = runSymfonyDemo(false); 33 $data['Symfony Demo 2.2.3 JIT'] = runSymfonyDemo(true); 34 $data['Wordpress 6.2'] = runWordpress(false); 35 $data['Wordpress 6.2 JIT'] = runWordpress(true); 36 $result = json_encode($data, JSON_PRETTY_PRINT) . "\n"; 37 38 fwrite(STDOUT, $result); 39 40 if ($storeResult) { 41 storeResult($result); 42 } 43} 44 45function storeResult(string $result) { 46 $repo = __DIR__ . '/repos/data'; 47 cloneRepo($repo, 'git@github.com:php/benchmarking-data.git'); 48 49 $commitHash = getPhpSrcCommitHash(); 50 $dir = $repo . '/' . substr($commitHash, 0, 2) . '/' . $commitHash; 51 $summaryFile = $dir . '/summary.json'; 52 if (!is_dir($dir)) { 53 mkdir($dir, 0755, true); 54 } 55 file_put_contents($summaryFile, $result); 56} 57 58function getPhpSrcCommitHash(): string { 59 $result = runCommand(['git', 'log', '--pretty=format:%H', '-n', '1'], dirname(__DIR__)); 60 return $result->stdout; 61} 62 63function runBench(bool $jit): array { 64 return runValgrindPhpCgiCommand('bench', [dirname(__DIR__) . '/Zend/bench.php'], jit: $jit); 65} 66 67function runSymfonyDemo(bool $jit): array { 68 $dir = __DIR__ . '/repos/symfony-demo-2.2.3'; 69 cloneRepo($dir, 'https://github.com/php/benchmarking-symfony-demo-2.2.3.git'); 70 runPhpCommand([$dir . '/bin/console', 'cache:clear']); 71 runPhpCommand([$dir . '/bin/console', 'cache:warmup']); 72 return runValgrindPhpCgiCommand('symfony-demo', [$dir . '/public/index.php'], cwd: $dir, jit: $jit, warmup: 50, repeat: 50); 73} 74 75function runWordpress(bool $jit): array { 76 $dir = __DIR__ . '/repos/wordpress-6.2'; 77 cloneRepo($dir, 'https://github.com/php/benchmarking-wordpress-6.2.git'); 78 79 /* FIXME: It might be better to use a stable version of PHP for this command because we can't 80 * easily alter the phar file */ 81 runPhpCommand([ 82 '-d error_reporting=0', 83 'wp-cli.phar', 84 'core', 85 'install', 86 '--url=wordpress.local', 87 '--title="Wordpress"', 88 '--admin_user=wordpress', 89 '--admin_password=wordpress', 90 '--admin_email=benchmark@php.net', 91 ], $dir); 92 93 // Warmup 94 runPhpCommand([$dir . '/index.php'], $dir); 95 return runValgrindPhpCgiCommand('wordpress', [$dir . '/index.php'], cwd: $dir, jit: $jit, warmup: 50, repeat: 50); 96} 97 98function runPhpCommand(array $args, ?string $cwd = null): ProcessResult { 99 return runCommand([PHP_BINARY, ...$args], $cwd); 100} 101 102function runValgrindPhpCgiCommand( 103 string $name, 104 array $args, 105 ?string $cwd = null, 106 bool $jit = false, 107 int $warmup = 0, 108 int $repeat = 1, 109): array { 110 global $phpCgi; 111 112 $profileOut = __DIR__ . "/profiles/callgrind.out.$name"; 113 if ($jit) { 114 $profileOut .= '.jit'; 115 } 116 117 $process = runCommand([ 118 'valgrind', 119 '--tool=callgrind', 120 '--dump-instr=yes', 121 "--callgrind-out-file=$profileOut", 122 '--', 123 $phpCgi, 124 '-T' . ($warmup ? $warmup . ',' : '') . $repeat, 125 '-d max_execution_time=0', 126 '-d opcache.enable=1', 127 '-d opcache.jit=' . ($jit ? 'tracing' : 'disable'), 128 '-d opcache.jit_buffer_size=128M', 129 '-d opcache.validate_timestamps=0', 130 ...$args, 131 ]); 132 $instructions = extractInstructionsFromValgrindOutput($process->stderr); 133 if ($repeat > 1) { 134 $instructions = gmp_strval(gmp_div_q($instructions, $repeat)); 135 } 136 return ['instructions' => $instructions]; 137} 138 139function extractInstructionsFromValgrindOutput(string $output): string { 140 preg_match("(==[0-9]+== Events : Ir\n==[0-9]+== Collected : (?<instructions>[0-9]+))", $output, $matches); 141 return $matches['instructions'] ?? throw new \Exception('Unexpected valgrind output'); 142} 143 144main(); 145