From 745726dad722621d4d58b964a095a26f59c43987 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20FAMIBELLE-PRONZOLA?= Date: Mon, 27 Jul 2026 09:16:05 +0400 Subject: [PATCH] feat: add performance benchmark script --- README.adoc | 56 ++++++--- scripts/benchmark.php | 260 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 298 insertions(+), 18 deletions(-) create mode 100644 scripts/benchmark.php diff --git a/README.adoc b/README.adoc index 667437d..7c2b350 100644 --- a/README.adoc +++ b/README.adoc @@ -182,9 +182,6 @@ Toutes ces sources sont modifiables dans `includes/config.local.php` (voir < 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";