LNMP 环境下的 PHP 页面生成时间基准测试

Kevin WordPress 31
<?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);

?>
  • 暂无回复内容

请输入关键词…