<?php
/**
* t.php —— LNMP 环境下的 PHP 页面生成时间基准测试
*
* 用途:
* - 在真实服务器环境中,批量请求指定 URL,统计后端执行耗时。
* - 可用于对比不同服务器(例如 BCC vs 阿里云)在相同代码和数据下的性能差异。
* - 输出包含 min / p50 / p90 / max / avg 等统计指标,便于分析稳定性与尾延迟。
*
* 使用方法:
* 1. 上传到网站根目录,例如 https://example.com/t.php
* 2. 在浏览器或命令行访问:
*
* 基础测试(匿名路径):
* https://example.com/t.php?url=http://127.0.0.1/&runs=40&warmup=8
*
* 登录态测试(带 Cookie,与 Query Monitor/已登录用户一致):
* https://example.com/t.php?url=http://127.0.0.1/&host=example.com&runs=40&warmup=8&cookie=1
*
* 参数说明:
* - url : 要测试的本机地址,推荐写成 http://127.0.0.1/ 排除网络因素
* - host : (可选)Host 头,确保命中正确虚拟主机和 Cookie 域
* - runs : 总共执行多少次测试(建议 30~50)
* - warmup : 前多少次预热丢弃(避免首轮冷启动影响统计)
* - cookie : 设为 1 时,会把当前浏览器的 Cookie/UA 一并转发,模拟已登录态
*
* 输出结果:
* JSON 格式,包含:
* - total_s : 每次请求总耗时(包含 HTTP 往返与解析)
* - backend_s : 后端生成时间(PHP 实际执行时间,最接近 WordPress 的 timer_stop(0))
* 每项都给出 min / p50 / p90 / max / avg 统计值。
*
* 注意事项:
* - 必须用 127.0.0.1 直连,排除外网延迟干扰。
* - 登录态测试时需带上 host=域名 & cookie=1,否则不会触发 WordPress 的已登录逻辑。
* - 建议在阿里云和 BCC 各自运行同样参数,再对比 backend_s 的平均值与 p90。
*/
declare(strict_types=1);
@ini_set('display_errors', '1');
@error_reporting(E_ALL);
header('Content-Type: application/json; charset=utf-8');
// -------- 小工具 --------
function pct(array $arr, $p) {
sort($arr, SORT_NUMERIC);
$n = count($arr);
if ($n === 0) return 0.0;
$idx = ($p/100) * ($n - 1);
$lo = (int)floor($idx);
$hi = (int)ceil($idx);
if ($lo === $hi) return (float)$arr[$lo];
$h = $idx - $lo;
return (float)($arr[$lo] + ($arr[$hi]-$arr[$lo])*$h);
}
function stats(array $arr) {
$n = count($arr);
if ($n === 0) return ['n'=>0,'min'=>0,'p50'=>0,'p90'=>0,'max'=>0,'avg'=>0];
return [
'n'=>$n,'min'=>min($arr),'p50'=>pct($arr,50),'p90'=>pct($arr,90),
'max'=>max($arr),'avg'=>array_sum($arr)/$n
];
}
function parse_gen_time_from_headers($headersRaw) { // 返回秒或 null
// X-Gen-Time: 0.275 或 275ms
if (preg_match('/^X-Gen-Time:\s*([0-9.]+)(ms)?/im', $headersRaw, $m)) {
return isset($m[2]) && $m[2] ? ((float)$m[1]/1000.0) : (float)$m[1];
}
// X-Generation-Time: 275ms
if (preg_match('/^X-Generation-Time:\s*([0-9.]+)(ms)?/im', $headersRaw, $m)) {
return isset($m[2]) && $m[2] ? ((float)$m[1]/1000.0) : (float)$m[1];
}
// Server-Timing: app;dur=275.2
if (preg_match('/^Server-Timing:.*?dur=([0-9.]+)/im', $headersRaw, $m)) {
return ((float)$m[1])/1000.0;
}
return null;
}
// -------- 参数 --------
$url = isset($_GET['url']) ? (string)$_GET['url'] : null;
$runs = isset($_GET['runs']) ? max(1, (int)$_GET['runs']) : 30;
$warmup = isset($_GET['warmup']) ? max(0, (int)$_GET['warmup']) : 5;
$verbose = isset($_GET['verbose']) && (int)$_GET['verbose'] === 1;
$fwdCook = isset($_GET['cookie']) && (int)$_GET['cookie'] === 1;
// -------- 本地段落基准(你的原始代码,循环多次) --------
function bench_local(int $runs, int $warmup, bool $verbose): array {
$samples = [];
$all = $warmup + $runs;
for ($i=0; $i<$all; $i++) {
$T0 = microtime(true);
$T1 = microtime(true);
// 若你的页面平时会 session_start,这里保留;否则注释掉减少干扰
@session_start();
$T2 = microtime(true);
// 这里保留钩子,方便你按需加入模板/渲染
// include render();
$T3 = microtime(true);
// 典型文件 stat(你也可以替换为站点内更贴近的路径)
@file_exists(__DIR__ . '/wp-includes/version.php');
@file_exists(__DIR__ . '/wp-settings.php');
@file_exists(__DIR__ . '/wp-includes/functions.php');
$T4 = microtime(true);
$row = [
'session_ms' => ($T2-$T1)*1000,
'require_ms' => ($T3-$T2)*1000,
'fs_ms' => ($T4-$T3)*1000,
'total_ms' => ($T4-$T0)*1000,
'_warm' => ($i<$warmup),
];
if (!$row['_warm']) $samples[] = $row;
if ($verbose) {
error_log(sprintf("[local]%s #%02d sess=%.2f req=%.2f fs=%.2f tot=%.2f",
$row['_warm']?'W':'R', $i+1, $row['session_ms'], $row['require_ms'], $row['fs_ms'], $row['total_ms']
));
}
}
$col = function($k) use($samples){ return array_map(function($x) use($k){return $x[$k];}, $samples); };
return [
'samples' => $verbose ? $samples : array_slice($samples, 0, 10),
'stats_ms' => [
'session' => stats($col('session_ms')),
'require' => stats($col('require_ms')),
'fs_misc' => stats($col('fs_ms')),
'total' => stats($col('total_ms')),
],
'note' => $verbose ? null : '只展示前10条样本,?verbose=1 查看全部',
];
}
// -------- URL 连打基准(尽量接近页面生成时间) --------
function bench_url(string $url, int $runs, int $warmup, bool $verbose, bool $fwdCook): array {
if (!function_exists('curl_init')) {
return ['error' => 'curl 扩展未启用'];
}
$samples = []; $total=[]; $backend=[]; $xgen=[];
for ($i=0; $i<($warmup+$runs); $i++) {
$ch = curl_init();
$u = $url . (strpos($url,'?')!==false ? '&' : '?') . 'bm=' . microtime(true);
$headers = [];
// 在读取参数处加:
$host = isset($_GET['host']) ? trim($_GET['host']) : null;
// 在设置 $headers 的地方加:
if ($host) { $headers[] = 'Host: ' . $host; }
if ($fwdCook) {
if (!empty($_SERVER['HTTP_COOKIE'])) $headers[] = 'Cookie: ' . $_SERVER['HTTP_COOKIE'];
if (!empty($_SERVER['HTTP_ACCEPT_LANGUAGE'])) $headers[] = 'Accept-Language: ' . $_SERVER['HTTP_ACCEPT_LANGUAGE'];
}
curl_setopt($ch, CURLOPT_URL, $u);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
curl_setopt($ch, CURLOPT_TCP_FASTOPEN, true);
if ($fwdCook && !empty($_SERVER['HTTP_USER_AGENT'])) {
curl_setopt($ch, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']);
} else {
curl_setopt($ch, CURLOPT_USERAGENT, 'tbench/1.0');
}
if (!empty($headers)) curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
$resp = curl_exec($ch);
if ($resp === false) {
$samples[] = ['error'=>curl_error($ch), '_warm'=>($i<$warmup)];
curl_close($ch);
continue;
}
$hsz = (int)curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$hdr = substr($resp, 0, $hsz);
$tot = (float)curl_getinfo($ch, CURLINFO_TOTAL_TIME);
$pre = (float)curl_getinfo($ch, CURLINFO_PRETRANSFER_TIME);
$ttf = (float)curl_getinfo($ch, CURLINFO_STARTTRANSFER_TIME); // TTFB
$code= (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$be = max(0.0, $ttf - $pre); // 近似“后端生成时间”
$gt = parse_gen_time_from_headers($hdr); // s 或 null
$warm = ($i < $warmup);
$row = ['http'=>$code,'total_s'=>$tot,'backend_s'=>$be,'xgen_s'=>$gt,'_warm'=>$warm];
$samples[] = $row;
if (!$warm) {
$total[] = $tot; $backend[] = $be; if ($gt !== null) $xgen[] = $gt;
}
if ($verbose) {
error_log(sprintf("[url]%s #%02d http=%d total=%.3f backend≈%.3f%s",
$warm?'W':'R', $i+1, $code, $tot, $be, $gt!==null?(" xgen=".number_format($gt,3)."s"):""
));
}
}
$out = [
'samples' => $verbose ? $samples : array_slice($samples, 0, 10),
'stats' => ['total_s'=>stats($total), 'backend_s'=>stats($backend)],
'note' => $verbose ? null : '只展示前10条样本,?verbose=1 查看全部',
];
if (count($xgen)>0) $out['stats']['xgen_s'] = stats($xgen);
return $out;
}
// -------- 主流程 --------
$result = [
'meta' => [
'mode' => $url ? 'url' : 'local',
'url' => $url,
'runs' => $runs,
'warmup' => $warmup,
'cookie_forward' => $fwdCook,
'ts' => date('c'),
],
'bench' => null,
];
if ($url) {
$result['bench'] = bench_url($url, $runs, $warmup, $verbose, $fwdCook);
} else {
$result['bench'] = bench_local($runs, $warmup, $verbose);
}
echo json_encode($result, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
?>
LNMP 环境下的 PHP 页面生成时间基准测试
WordPress 31