nombre de fichiers, 'bytes' => taille totale en octets] */ function getCacheStats($cacheDir) { $files = glob($cacheDir . '/cache_*.json') ?: []; $bytes = 0; foreach ($files as $file) { $bytes += (int) filesize($file); } return ['files' => count($files), 'bytes' => $bytes]; } /** * Formate une taille en octets de façon lisible * * @param int $bytes Taille en octets * @return string Taille formatée (o, Ko ou Mo) */ function formatBytes($bytes) { if ($bytes >= 1048576) { return round($bytes / 1048576, 2) . ' Mo'; } if ($bytes >= 1024) { return round($bytes / 1024, 1) . ' Ko'; } return $bytes . ' o'; } /** * Aligne une chaîne à droite sur une largeur donnée (compatible UTF-8) * * @param string $str Chaîne à compléter * @param int $len Largeur cible * @return string Chaîne complétée d'espaces */ function padRight($str, $len) { $width = function_exists('mb_strwidth') ? mb_strwidth($str) : strlen($str); return $str . str_repeat(' ', max(0, $len - $width)); } /** * Démarre un serveur PHP local (php -S) sur la racine du projet * * @param string $rootDir Racine du projet (document root) * @param int $port Port d'écoute * @return array [resource $process, string $logFile] */ function startPhpServer($rootDir, $port) { $logFile = tempnam(sys_get_temp_dir(), 'benchmark-php-server-'); $process = proc_open( ['php', '-S', '127.0.0.1:' . $port, '-t', $rootDir], [ 0 => ['file', '/dev/null', 'r'], 1 => ['file', $logFile, 'w'], 2 => ['file', $logFile, 'a'], ], $pipes, $rootDir ); if ($process === false) { exit("❌ Impossible de démarrer php -S\n"); } // Attendre que le serveur accepte des connexions $deadline = microtime(true) + STARTUP_TIMEOUT; while (microtime(true) < $deadline) { $status = proc_get_status($process); if (!$status['running']) { echo "❌ Le serveur PHP s'est arrêté au démarrage :\n" . (string) @file_get_contents($logFile); exit(1); } $fp = @fsockopen('127.0.0.1', $port, $errno, $errstr, 0.5); if ($fp !== false) { fclose($fp); return [$process, $logFile]; } usleep(200000); } echo "❌ Le serveur PHP ne répond pas après " . STARTUP_TIMEOUT . " s\n"; exit(1); } /** * Mesure le TTFB et le temps total d'une page via HTTP * * @param string $url URL complète de la page * @return array ['code' => int, 'ttfb' => float, 'total' => float] (secondes) */ function measurePage($url) { $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => false, CURLOPT_TIMEOUT => REQUEST_TIMEOUT, ]); curl_exec($ch); $result = [ 'code' => (int) curl_getinfo($ch, CURLINFO_RESPONSE_CODE), 'ttfb' => curl_getinfo($ch, CURLINFO_STARTTRANSFER_TIME), 'total' => curl_getinfo($ch, CURLINFO_TOTAL_TIME), ]; curl_close($ch); return $result; } /** * Calcule le TTFB moyen d'une série de mesures * * @param array $measures Mesures retournées par measurePage() * @return float TTFB moyen en secondes */ function averageTtfb($measures) { return array_sum(array_column($measures, 'ttfb')) / count($measures); } // --- Découverte des identifiants réels via l'API PeerTube --- echo "🔎 Découverte d'une vidéo et d'une catégorie via l'API PeerTube…\n"; $pages = ['Accueil (/)' => '/index.php']; $recentVideos = getRecentVideos(1); if (!empty($recentVideos)) { $pages['Vidéo (/video.php?id=…)'] = '/video.php?id=' . urlencode($recentVideos[0]['id']); } else { echo "⚠️ Aucune vidéo trouvée (instance PeerTube injoignable ?) : page vidéo ignorée\n"; } $displayCategories = getDisplayCategories(); if (!empty($displayCategories)) { $pages['Catégorie (/categories.php?id=…)'] = '/categories.php?id=' . urlencode($displayCategories[0]['id']); } else { echo "⚠️ Aucune catégorie trouvée : page catégorie ignorée\n"; } // --- Préparation : état du cache, purge et démarrage du serveur --- $initialStats = getCacheStats($cacheDir); $purged = purgeApiCache($cacheDir); $port = findFreePort(); echo "🚀 Démarrage de php -S sur 127.0.0.1:{$port}…\n"; [$serverProcess, $serverLogFile] = startPhpServer($rootDir, $port); $baseUrl = 'http://127.0.0.1:' . $port; // --- Mesure à froid (cache API vide) --- echo "\n📏 Mesure à froid (cache API vidé, {$purged} fichier(s) supprimé(s))…\n"; $results = []; foreach ($pages as $label => $path) { $cold = measurePage($baseUrl . $path); $results[$label] = ['path' => $path, 'cold' => $cold, 'warm' => []]; printf(" - %s HTTP %d TTFB %7.0f ms\n", padRight($label, 34), $cold['code'], $cold['ttfb'] * 1000); } // --- Mesure à chaud (cache API rempli) --- echo "\n📏 Mesure à chaud (" . WARM_RUNS . " requêtes par page, moyenne)…\n"; foreach ($results as $label => &$result) { for ($i = 0; $i < WARM_RUNS; $i++) { $result['warm'][] = measurePage($baseUrl . $result['path']); } printf(" - %s HTTP %d TTFB %7.0f ms\n", padRight($label, 34), $result['warm'][0]['code'], averageTtfb($result['warm']) * 1000); } unset($result); $finalStats = getCacheStats($cacheDir); // --- Résumé --- echo "\n===== Résumé =====\n"; printf("%s %14s %14s %8s\n", padRight('Page', 34), 'TTFB froid', 'TTFB chaud', 'Gain'); foreach ($results as $label => $result) { $coldTtfb = $result['cold']['ttfb']; $warmTtfb = averageTtfb($result['warm']); $gain = $coldTtfb > 0 ? (int) round((1 - $warmTtfb / $coldTtfb) * 100) : 0; printf("%s %12.0f ms %12.0f ms %7d %%\n", padRight($label, 34), $coldTtfb * 1000, $warmTtfb * 1000, $gain); } echo "\nCache API au départ : {$initialStats['files']} fichier(s), " . formatBytes($initialStats['bytes']) . "\n"; echo "Cache API après mesures : {$finalStats['files']} fichier(s), " . formatBytes($finalStats['bytes']) . "\n";