698 lines
29 KiB
PHP
698 lines
29 KiB
PHP
<?php
|
||
/**
|
||
* Server Benchmark – single file
|
||
* Autor: ChatGPT (GPT-5 Thinking)
|
||
* Wersja: 2025-09-02
|
||
*/
|
||
|
||
declare(strict_types=1);
|
||
error_reporting(E_ALL);
|
||
ini_set('display_errors', '1');
|
||
@set_time_limit(0);
|
||
|
||
// ============================ KONFIGURACJA DB ============================
|
||
// Uzupełnij poniżej lub przekaż przez env (DB_DSN, DB_USER, DB_PASS).
|
||
$DB_DSN = getenv('DB_DSN') ?: 'mysql:host=localhost;port=3306;dbname=host117523_cmspro;charset=utf8mb4';
|
||
$DB_USER = getenv('DB_USER') ?: 'host117523_cmspro';
|
||
$DB_PASS = getenv('DB_PASS') ?: '3sJADeqKHLqHddfavDeR';
|
||
|
||
// ============================ USTAWIENIA DOMYŚLNE ============================
|
||
$defaults = [
|
||
'cpu_iterations' => 10,
|
||
'cpu_size' => 2000000, // liczba operacji w teście CPU
|
||
'disk_passes' => 1,
|
||
'disk_files' => 8,
|
||
'disk_sizes' => '64K,1M,8M,16MB,32MB',
|
||
'db_rows' => 500000,
|
||
'smallops_files' => 200000, // liczba małych plików (4KB)
|
||
];
|
||
|
||
// ============================ BASELINE'Y DO SCORE ===========================
|
||
// Dobierz wg swojej infrastruktury; score jest skalowany i "capowany".
|
||
$BASE = [
|
||
'CPU_ops_per_s' => 1_000_000.0, // CPU: ~1e6 "umownych" operacji/s = 100 pkt
|
||
'DISK_read_MBps' => 400.0, // Dysk odczyt 400 MB/s => 100 pkt
|
||
'DISK_write_MBps' => 300.0, // Dysk zapis 300 MB/s => 100 pkt
|
||
'DISK_smallops_ops_s' => 5000.0, // FS small-ops (twórz/usuń/odczyt) ~5k/s => 100 pkt
|
||
'DB_insert_rows_s' => 20000.0, // DB insert 20k rows/s => 100 pkt
|
||
'DB_txn_inv_s' => 2.0, // 1 / transaction_time_s: 2 (czyli ~0.5s) => 100 pkt
|
||
'MEM_alloc_MBps' => 2000.0, // Alokacja pamięci ~2000 MB/s => 100 pkt
|
||
];
|
||
|
||
// Wagi (sumują się do 1). Dysk rozbijamy: 0.20 sequential + 0.10 small-ops
|
||
$WEIGHTS = [
|
||
'DB' => 0.45,
|
||
'DISK_seq' => 0.20,
|
||
'DISK_small' => 0.10,
|
||
'CPU' => 0.20,
|
||
'MEM' => 0.05,
|
||
];
|
||
|
||
// ============================ POMOCNICZE ===================================
|
||
function now(): float { return microtime(true); }
|
||
function dt(float $t0): float { return microtime(true) - $t0; }
|
||
|
||
function human_bytes(float $bytes): string {
|
||
$units = ['B','KB','MB','GB','TB'];
|
||
$i = 0;
|
||
while ($bytes >= 1024 && $i < count($units)-1) { $bytes /= 1024; $i++; }
|
||
return sprintf('%.2f %s', $bytes, $units[$i]);
|
||
}
|
||
function human_rate(float $bytes, float $seconds): string {
|
||
if ($seconds <= 0) return '∞ B/s';
|
||
return human_bytes($bytes / $seconds) . '/s';
|
||
}
|
||
function parse_size_to_bytes(string $val): int {
|
||
$val = trim($val);
|
||
if ($val === '') return 0;
|
||
$u = strtoupper(substr($val, -1));
|
||
$n = (float)$val;
|
||
switch ($u) {
|
||
case 'K': return (int)($n * 1024);
|
||
case 'M': return (int)($n * 1024 * 1024);
|
||
case 'G': return (int)($n * 1024 * 1024 * 1024);
|
||
default: return (int)$n;
|
||
}
|
||
}
|
||
function sys_temp_dir(): string {
|
||
$dir = sys_get_temp_dir();
|
||
if (!is_dir($dir) || !is_writable($dir)) $dir = __DIR__;
|
||
return rtrim($dir, DIRECTORY_SEPARATOR);
|
||
}
|
||
function rnd_data(int $bytes): string {
|
||
if ($bytes <= 0) return '';
|
||
try { return random_bytes($bytes); }
|
||
catch (Throwable $e) {
|
||
$chunk = md5((string)mt_rand(), true);
|
||
$out = '';
|
||
while (strlen($out) < $bytes) $out .= $chunk;
|
||
return substr($out, 0, $bytes);
|
||
}
|
||
}
|
||
function php_info_summary(): array {
|
||
$opcache = function_exists('opcache_get_status') ? (opcache_get_status(false) ?: []) : [];
|
||
return [
|
||
'php_version' => PHP_VERSION,
|
||
'sapi' => PHP_SAPI,
|
||
'os' => php_uname(),
|
||
'memory_limit' => ini_get('memory_limit'),
|
||
'upload_max_filesize' => ini_get('upload_max_filesize'),
|
||
'post_max_size' => ini_get('post_max_size'),
|
||
'opcache_enabled' => ($opcache['opcache_enabled'] ?? false) ? 'yes' : 'no',
|
||
'opcache_memory' => isset($opcache['memory_usage']['used_memory'])
|
||
? human_bytes((float)$opcache['memory_usage']['used_memory']).' used / '.human_bytes((float)$opcache['memory_usage']['free_memory']).' free'
|
||
: 'n/a',
|
||
'extensions' => implode(', ', get_loaded_extensions()),
|
||
'temp_dir' => sys_temp_dir(),
|
||
'disk_free_space' => human_bytes((float)@disk_free_space(sys_temp_dir())),
|
||
'disk_total_space' => human_bytes((float)@disk_total_space(sys_temp_dir())),
|
||
];
|
||
}
|
||
|
||
// ============================ TEST: CPU =====================================
|
||
function bench_cpu(int $iterations, int $size): array {
|
||
$results = [];
|
||
for ($i = 1; $i <= $iterations; $i++) {
|
||
$t0 = now();
|
||
$acc = 0.0;
|
||
for ($k = 1; $k <= $size; $k++) {
|
||
$acc += sqrt($k) + sin($k) + log($k + 1);
|
||
}
|
||
$t_num = dt($t0);
|
||
|
||
$t1 = now();
|
||
$arr = [];
|
||
for ($k = 0; $k < (int)($size / 10); $k++) $arr[] = ($k * 2654435761) % 1_000_003;
|
||
shuffle($arr);
|
||
sort($arr, SORT_NUMERIC);
|
||
$t_arr = dt($t1);
|
||
|
||
$t2 = now();
|
||
$hash = '';
|
||
$chunk = str_repeat('A', 1024);
|
||
for ($k = 0; $k < (int)($size / 1000); $k++) $hash = hash('sha256', $hash . $chunk . $k, true);
|
||
$t_hash = dt($t2);
|
||
|
||
$total = dt($t0);
|
||
$results[] = [
|
||
'iteration' => $i,
|
||
'numeric_s' => $t_num,
|
||
'array_s' => $t_arr,
|
||
'hash_s' => $t_hash,
|
||
'total_s' => $total,
|
||
'checksum' => substr(bin2hex($hash), 0, 16),
|
||
'acc_sample'=> round($acc, 4),
|
||
];
|
||
}
|
||
$avg = [
|
||
'numeric_s' => array_sum(array_column($results, 'numeric_s'))/count($results),
|
||
'array_s' => array_sum(array_column($results, 'array_s'))/count($results),
|
||
'hash_s' => array_sum(array_column($results, 'hash_s'))/count($results),
|
||
'total_s' => array_sum(array_column($results, 'total_s'))/count($results),
|
||
];
|
||
return ['runs' => $results, 'avg' => $avg, 'size' => $size];
|
||
}
|
||
|
||
// ============================ TEST: DYSK ====================================
|
||
function bench_disk(int $passes, int $filesPerSize, array $sizesBytes): array {
|
||
$baseDir = sys_temp_dir() . DIRECTORY_SEPARATOR . 'php_bench_' . getmypid() . '_' . uniqid();
|
||
if (!@mkdir($baseDir, 0777, true) && !is_dir($baseDir)) throw new RuntimeException("Nie mogę utworzyć katalogu: $baseDir");
|
||
|
||
$summary = [];
|
||
try {
|
||
foreach ($sizesBytes as $size) {
|
||
$wBytesTotal = 0; $wTimeTotal = 0.0;
|
||
$rBytesTotal = 0; $rTimeTotal = 0.0;
|
||
|
||
for ($p = 0; $p < $passes; $p++) {
|
||
// ZAPIS
|
||
$t0 = now();
|
||
for ($i = 0; $i < $filesPerSize; $i++) {
|
||
$fn = $baseDir . DIRECTORY_SEPARATOR . "file_{$size}_{$p}_{$i}.bin";
|
||
$h = fopen($fn, 'wb'); if (!$h) throw new RuntimeException("Nie mogę zapisać pliku: $fn");
|
||
$block = rnd_data(min($size, 1024 * 1024));
|
||
$written = 0;
|
||
while ($written < $size) {
|
||
$chunk = min($size - $written, strlen($block));
|
||
$w = fwrite($h, substr($block, 0, $chunk)); if ($w === false) throw new RuntimeException("Błąd zapisu do $fn");
|
||
$written += $w;
|
||
}
|
||
fflush($h); fclose($h);
|
||
$wBytesTotal += $size;
|
||
}
|
||
$wTimeTotal += dt($t0);
|
||
|
||
// ODCZYT
|
||
$t1 = now();
|
||
for ($i = 0; $i < $filesPerSize; $i++) {
|
||
$fn = $baseDir . DIRECTORY_SEPARATOR . "file_{$size}_{$p}_{$i}.bin";
|
||
$h = fopen($fn, 'rb'); if (!$h) throw new RuntimeException("Nie mogę odczytać pliku: $fn");
|
||
while (!feof($h)) {
|
||
$data = fread($h, 1024 * 1024); if ($data === false) throw new RuntimeException("Błąd odczytu z $fn");
|
||
$rBytesTotal += strlen($data);
|
||
}
|
||
fclose($h);
|
||
}
|
||
$rTimeTotal += dt($t1);
|
||
}
|
||
|
||
$summary[] = [
|
||
'size' => $size,
|
||
'files' => $filesPerSize * $passes,
|
||
'written_bytes' => $wBytesTotal,
|
||
'write_time_s' => $wTimeTotal,
|
||
'write_speed' => human_rate($wBytesTotal, $wTimeTotal),
|
||
'write_MBps' => ($wTimeTotal>0 ? ($wBytesTotal/$wTimeTotal)/(1024*1024) : INF),
|
||
'read_bytes' => $rBytesTotal,
|
||
'read_time_s' => $rTimeTotal,
|
||
'read_speed' => human_rate($rBytesTotal, $rTimeTotal),
|
||
'read_MBps' => ($rTimeTotal>0 ? ($rBytesTotal/$rTimeTotal)/(1024*1024) : INF),
|
||
];
|
||
}
|
||
} finally {
|
||
$it = @new RecursiveIteratorIterator(new RecursiveDirectoryIterator($baseDir, FilesystemIterator::SKIP_DOTS), RecursiveIteratorIterator::CHILD_FIRST);
|
||
if ($it) foreach ($it as $file) { if ($file->isDir()) @rmdir($file->getPathname()); else @unlink($file->getPathname()); }
|
||
@rmdir($baseDir);
|
||
}
|
||
return $summary;
|
||
}
|
||
|
||
// ============================ TEST: FS SMALL-OPS ============================
|
||
function bench_small_ops(int $files, int $sizeBytes = 4096): array {
|
||
$dir = sys_temp_dir() . DIRECTORY_SEPARATOR . 'php_smallops_' . getmypid() . '_' . uniqid();
|
||
if (!@mkdir($dir, 0777, true) && !is_dir($dir)) throw new RuntimeException("Nie mogę utworzyć katalogu: $dir");
|
||
$data = rnd_data($sizeBytes);
|
||
|
||
$created = 0; $tCreate0 = now();
|
||
for ($i=0; $i<$files; $i++) {
|
||
$fn = $dir . DIRECTORY_SEPARATOR . "s_$i.bin";
|
||
$h = fopen($fn, 'wb'); if (!$h) break;
|
||
fwrite($h, $data); fclose($h); $created++;
|
||
}
|
||
$tCreate = dt($tCreate0);
|
||
|
||
$readBytes = 0; $tRead0 = now();
|
||
for ($i=0; $i<$created; $i++) {
|
||
$fn = $dir . DIRECTORY_SEPARATOR . "s_$i.bin";
|
||
$readBytes += filesize($fn);
|
||
file_get_contents($fn);
|
||
}
|
||
$tRead = dt($tRead0);
|
||
|
||
$deleted = 0; $tDel0 = now();
|
||
for ($i=0; $i<$created; $i++) {
|
||
$fn = $dir . DIRECTORY_SEPARATOR . "s_$i.bin";
|
||
if (@unlink($fn)) $deleted++;
|
||
}
|
||
$tDel = dt($tDel0);
|
||
|
||
@rmdir($dir);
|
||
|
||
return [
|
||
'files' => $files,
|
||
'created' => $created,
|
||
'create_time_s' => $tCreate,
|
||
'create_ops_s' => ($tCreate>0? $created/$tCreate : INF),
|
||
'read_time_s' => $tRead,
|
||
'read_MBps' => ($tRead>0? ($readBytes/$tRead)/(1024*1024) : INF),
|
||
'deleted' => $deleted,
|
||
'delete_time_s' => $tDel,
|
||
'delete_ops_s' => ($tDel>0? $deleted/$tDel : INF),
|
||
'file_size' => $sizeBytes,
|
||
];
|
||
}
|
||
|
||
// ============================ TEST: DB ======================================
|
||
function db_connect(string $dsn, string $user, string $pass): PDO {
|
||
return new PDO($dsn, $user, $pass, [
|
||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||
PDO::ATTR_EMULATE_PREPARES => false,
|
||
]);
|
||
}
|
||
function bench_db(string $dsn, string $user, string $pass, int $rows): array {
|
||
$pdo = db_connect($dsn, $user, $pass);
|
||
$pdo->exec("
|
||
CREATE TABLE IF NOT EXISTS bench_tmp (
|
||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||
payload VARBINARY(255) NOT NULL,
|
||
val INT NOT NULL,
|
||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||
) ENGINE=InnoDB
|
||
");
|
||
$pdo->exec("TRUNCATE TABLE bench_tmp");
|
||
|
||
$t_total0 = now();
|
||
$pdo->beginTransaction();
|
||
|
||
$t0 = now();
|
||
$stmt = $pdo->prepare("INSERT INTO bench_tmp (payload, val) VALUES (?, ?)");
|
||
for ($i = 0; $i < $rows; $i++) $stmt->execute([rnd_data(64), $i]);
|
||
$t_insert = dt($t0);
|
||
|
||
$t1 = now();
|
||
$sum = $pdo->query("SELECT COUNT(*) AS c, SUM(val) AS s, AVG(val) AS a FROM bench_tmp")->fetch();
|
||
$t_select_aggr = dt($t1);
|
||
|
||
$t2 = now();
|
||
$stmt2 = $pdo->query("SELECT * FROM bench_tmp WHERE val % 97 = 0");
|
||
$rowsFetched = $stmt2->fetchAll();
|
||
$t_select_fetch = dt($t2);
|
||
|
||
$t3 = now();
|
||
$upd = $pdo->exec("UPDATE bench_tmp SET val = val + 1 WHERE id % 50 = 0");
|
||
$t_update = dt($t3);
|
||
|
||
$t4 = now();
|
||
$del = $pdo->exec("DELETE FROM bench_tmp WHERE id % 100 = 0");
|
||
$t_delete = dt($t4);
|
||
|
||
$pdo->commit();
|
||
$t_total = dt($t_total0);
|
||
|
||
return [
|
||
'insert_rows' => $rows,
|
||
'insert_time_s' => $t_insert,
|
||
'insert_rows_s' => ($t_insert>0? $rows/$t_insert : INF),
|
||
'select_aggr_s' => $t_select_aggr,
|
||
'select_fetch_s' => $t_select_fetch,
|
||
'fetched_rows' => count($rowsFetched),
|
||
'update_affected' => $upd,
|
||
'update_time_s' => $t_update,
|
||
'delete_affected' => $del,
|
||
'delete_time_s' => $t_delete,
|
||
'transaction_s' => $t_total,
|
||
'txn_inv_s' => ($t_total>0? 1/$t_total : INF),
|
||
];
|
||
}
|
||
|
||
// ============================ DODATKOWE: PAMIĘĆ =============================
|
||
function bench_memory(int $blocks = 200, int $blockSize = 256 * 1024): array {
|
||
$t0 = now();
|
||
$arr = [];
|
||
for ($i = 0; $i < $blocks; $i++) $arr[] = rnd_data($blockSize);
|
||
$allocTime = dt($t0);
|
||
|
||
$t1 = now();
|
||
shuffle($arr);
|
||
$hash = 'x';
|
||
foreach ($arr as $b) $hash = md5($hash . substr($b, 0, 32), true);
|
||
$walkTime = dt($t1);
|
||
|
||
$bytes = (float)$blocks * $blockSize;
|
||
unset($arr);
|
||
return [
|
||
'allocated' => human_bytes($bytes),
|
||
'allocated_B' => $bytes,
|
||
'alloc_time' => $allocTime,
|
||
'walk_time' => $walkTime,
|
||
'approx_MBps' => ($allocTime>0? ($bytes/$allocTime)/(1024*1024) : INF),
|
||
'checksum' => substr(bin2hex($hash), 0, 16),
|
||
];
|
||
}
|
||
|
||
// ============================ SCORE (0–100) =================================
|
||
function clamp100(float $v): float { return max(0.0, min(100.0, $v)); }
|
||
|
||
function calc_scores(array $all, array $BASE, array $WEIGHTS): array {
|
||
$parts = [];
|
||
$weights = $WEIGHTS;
|
||
|
||
// CPU score – przeliczamy „umowne operacje/s”: cpu_size / avg_total_s
|
||
if (!empty($all['cpu'])) {
|
||
$ops_s = ($all['cpu']['avg']['total_s']>0 ? $all['cpu']['size'] / $all['cpu']['avg']['total_s'] : INF);
|
||
$parts['CPU'] = clamp100(($ops_s / $BASE['CPU_ops_per_s']) * 100);
|
||
}
|
||
|
||
// DISK sequential – średnia z MB/s (read+write) po rozmiarach
|
||
if (!empty($all['disk'])) {
|
||
$r=[];$w=[];
|
||
foreach ($all['disk'] as $row){ $r[]=$row['read_MBps']; $w[]=$row['write_MBps']; }
|
||
$avgR = (!empty($r)? array_sum($r)/count($r) : 0);
|
||
$avgW = (!empty($w)? array_sum($w)/count($w) : 0);
|
||
$sR = ($avgR/$BASE['DISK_read_MBps'])*100;
|
||
$sW = ($avgW/$BASE['DISK_write_MBps'])*100;
|
||
$parts['DISK_seq'] = clamp100(($sR + $sW)/2);
|
||
}
|
||
|
||
// DISK small-ops – bierzemy średnią z create_ops_s, delete_ops_s i przeliczamy vs baseline
|
||
if (!empty($all['smallops'])) {
|
||
$ops = [];
|
||
if (is_finite($all['smallops']['create_ops_s'])) $ops[] = $all['smallops']['create_ops_s'];
|
||
if (is_finite($all['smallops']['delete_ops_s'])) $ops[] = $all['smallops']['delete_ops_s'];
|
||
// Odczyt też ważny – przeliczamy read_MBps na „ops/s” przeskalowując do 4KB
|
||
if (is_finite($all['smallops']['read_MBps'])) {
|
||
$ops[] = ($all['smallops']['read_MBps'] * 1024 * 1024) / max(1, $all['smallops']['file_size']);
|
||
}
|
||
$avgOps = (!empty($ops)? array_sum($ops)/count($ops) : 0);
|
||
$parts['DISK_small'] = clamp100(($avgOps / $BASE['DISK_smallops_ops_s']) * 100);
|
||
}
|
||
|
||
// DB – 60% insert_rows_s + 40% txn_inv_s (1/transaction_time)
|
||
if (!empty($all['db'])) {
|
||
$sIns = clamp100(($all['db']['insert_rows_s'] / $BASE['DB_insert_rows_s']) * 100);
|
||
$sTxn = clamp100(($all['db']['txn_inv_s'] / $BASE['DB_txn_inv_s']) * 100);
|
||
$parts['DB'] = clamp100(0.6*$sIns + 0.4*$sTxn);
|
||
}
|
||
|
||
// MEM – alokacja MB/s
|
||
if (!empty($all['memory'])) {
|
||
$mbps = $all['memory']['approx_MBps'];
|
||
$parts['MEM'] = clamp100(($mbps / $BASE['MEM_alloc_MBps']) * 100);
|
||
}
|
||
|
||
// Renormalizacja wag (używamy tylko dostępnych komponentów)
|
||
$activeWeights = 0.0;
|
||
foreach ($weights as $k=>$w) if (isset($parts[$k])) $activeWeights += $w;
|
||
$score = 0.0;
|
||
if ($activeWeights > 0) {
|
||
foreach ($parts as $k=>$val) {
|
||
$score += $val * ($weights[$k] / $activeWeights);
|
||
}
|
||
}
|
||
return ['parts'=>$parts, 'score'=>round($score, 1)];
|
||
}
|
||
|
||
// ============================ ROUTING / UI ==================================
|
||
function getParam(string $key, $default) {
|
||
if (isset($_POST[$key])) return $_POST[$key];
|
||
if (isset($_GET[$key])) return $_GET[$key];
|
||
return $default;
|
||
}
|
||
|
||
$action = (string)getParam('run', '');
|
||
$params = [
|
||
'cpu_iterations' => (int)getParam('cpu_iterations', $defaults['cpu_iterations']),
|
||
'cpu_size' => (int)getParam('cpu_size', $defaults['cpu_size']),
|
||
'disk_passes' => (int)getParam('disk_passes', $defaults['disk_passes']),
|
||
'disk_files' => (int)getParam('disk_files', $defaults['disk_files']),
|
||
'disk_sizes' => (string)getParam('disk_sizes', $defaults['disk_sizes']),
|
||
'db_rows' => (int)getParam('db_rows', $defaults['db_rows']),
|
||
'smallops_files' => (int)getParam('smallops_files', $defaults['smallops_files']),
|
||
];
|
||
$diskSizes = array_filter(array_map('trim', explode(',', $params['disk_sizes'])), fn($v) => $v !== '');
|
||
$diskBytes = array_map('parse_size_to_bytes', $diskSizes);
|
||
|
||
$results = [];
|
||
$error = null;
|
||
|
||
try {
|
||
if ($action === 'cpu' || $action === 'all' || $action === 'score') {
|
||
$results['cpu'] = bench_cpu($params['cpu_iterations'], max(1000, $params['cpu_size']));
|
||
}
|
||
if ($action === 'disk' || $action === 'all' || $action === 'score') {
|
||
$results['disk'] = bench_disk(
|
||
max(1, $params['disk_passes']),
|
||
max(1, $params['disk_files']),
|
||
array_map(fn($v) => max(4096, (int)$v), $diskBytes ?: [64*1024, 1024*1024, 8*1024*1024])
|
||
);
|
||
}
|
||
if ($action === 'smallops' || $action === 'all' || $action === 'score') {
|
||
$results['smallops'] = bench_small_ops(max(100, $params['smallops_files']));
|
||
}
|
||
if ($action === 'db' || $action === 'all' || $action === 'score') {
|
||
$results['db'] = bench_db($DB_DSN, $DB_USER, $DB_PASS, max(100, $params['db_rows']));
|
||
}
|
||
if ($action === 'memory' || $action === 'all' || $action === 'score') {
|
||
$results['memory'] = bench_memory();
|
||
}
|
||
} catch (Throwable $e) {
|
||
$error = $e->getMessage();
|
||
}
|
||
|
||
// SCORE (na żywo liczymy z tego, co mamy w $results)
|
||
$score = (!empty($results) ? calc_scores($results, $BASE, $WEIGHTS) : null);
|
||
|
||
// ============================ RENDER HTML ===================================
|
||
function h($s) { return htmlspecialchars((string)$s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); }
|
||
$info = php_info_summary();
|
||
?>
|
||
<!doctype html>
|
||
<html lang="pl">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<title>Server Benchmark (PHP – score + small-ops)</title>
|
||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||
<style>
|
||
body { font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; margin: 24px; color:#222; }
|
||
h1 { margin: 0 0 8px; font-size: 22px; }
|
||
.note { background:#fff3cd; border:1px solid #ffeeba; padding:10px 12px; border-radius:8px; margin:12px 0; }
|
||
fieldset { border:1px solid #ddd; padding:12px; border-radius:10px; margin-bottom:16px; }
|
||
legend { padding:0 8px; color:#555; }
|
||
label { display:block; margin:6px 0 2px; color:#333; font-size:13px; }
|
||
input[type="text"], input[type="number"] { width:220px; padding:6px 8px; border:1px solid #ccc; border-radius:6px; }
|
||
.grid { display:grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap:16px; }
|
||
button { padding:8px 12px; border:0; background:#2563eb; color:#fff; border-radius:8px; cursor:pointer; }
|
||
button.secondary { background:#6b7280; }
|
||
table { width:100%; border-collapse: collapse; margin:12px 0; }
|
||
th, td { border:1px solid #eee; padding:8px 10px; text-align:left; font-size:13px; }
|
||
th { background:#f9fafb; }
|
||
code { background:#f3f4f6; padding:2px 6px; border-radius:6px; }
|
||
.section { margin: 24px 0; }
|
||
.error { color:#b91c1c; font-weight:600; }
|
||
.small { color:#666; font-size:12px; }
|
||
.scorebox { background:#f0f9ff; border:1px solid #bae6fd; padding:12px; border-radius:12px; }
|
||
.scorenum { font-size:32px; font-weight:800; }
|
||
.chip { display:inline-block; padding:2px 8px; border-radius:999px; background:#eef2ff; border:1px solid #c7d2fe; margin-right:6px; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<h1>Server Benchmark (PHP – score + small-ops)</h1>
|
||
<div class="note">
|
||
<strong>Uwaga:</strong> Testy korzystają z katalogu tymczasowego (<code><?=h($info['temp_dir'])?></code>) i tabeli <code>bench_tmp</code> (DSN: <code><?=h($DB_DSN)?></code>).
|
||
</div>
|
||
|
||
<details>
|
||
<summary><strong>Informacje o środowisku</strong></summary>
|
||
<table>
|
||
<tr><th>PHP</th><td><?=h($info['php_version'])?> (<?=h($info['sapi'])?>)</td></tr>
|
||
<tr><th>System</th><td><?=h($info['os'])?></td></tr>
|
||
<tr><th>OPcache</th><td>enabled: <?=h($info['opcache_enabled'])?> | memory: <?=h($info['opcache_memory'])?></td></tr>
|
||
<tr><th>Limity</th><td>memory_limit: <?=h($info['memory_limit'])?> | upload_max_filesize: <?=h($info['upload_max_filesize'])?> | post_max_size: <?=h($info['post_max_size'])?></td></tr>
|
||
<tr><th>Temp dir</th><td><?=h($info['temp_dir'])?></td></tr>
|
||
<tr><th>Dysk (temp)</th><td>free: <?=h($info['disk_free_space'])?> / total: <?=h($info['disk_total_space'])?></td></tr>
|
||
<tr><th>Rozszerzenia</th><td class="small"><?=h($info['extensions'])?></td></tr>
|
||
</table>
|
||
</details>
|
||
|
||
<form method="post">
|
||
<fieldset>
|
||
<legend>Parametry</legend>
|
||
<div class="grid">
|
||
<div>
|
||
<label>CPU: powtórzenia</label>
|
||
<input type="number" name="cpu_iterations" min="1" value="<?=h($params['cpu_iterations'])?>">
|
||
<label>CPU: rozmiar (liczba operacji)</label>
|
||
<input type="number" name="cpu_size" min="1000" value="<?=h($params['cpu_size'])?>">
|
||
</div>
|
||
<div>
|
||
<label>DYSK: przebiegi</label>
|
||
<input type="number" name="disk_passes" min="1" value="<?=h($params['disk_passes'])?>">
|
||
<label>DYSK: pliki na rozmiar</label>
|
||
<input type="number" name="disk_files" min="1" value="<?=h($params['disk_files'])?>">
|
||
<label>DYSK: rozmiary (np. 64K,1M,8M)</label>
|
||
<input type="text" name="disk_sizes" value="<?=h($params['disk_sizes'])?>">
|
||
</div>
|
||
<div>
|
||
<label>DB: liczba wierszy</label>
|
||
<input type="number" name="db_rows" min="100" value="<?=h($params['db_rows'])?>">
|
||
<label>Small-ops: liczba plików (4KB)</label>
|
||
<input type="number" name="smallops_files" min="100" value="<?=h($params['smallops_files'])?>">
|
||
</div>
|
||
</div>
|
||
<div style="margin-top:12px; display:flex; gap:8px; flex-wrap:wrap;">
|
||
<button name="run" value="cpu">Start CPU</button>
|
||
<button name="run" value="disk">Start Dysk</button>
|
||
<button name="run" value="smallops">Start FS small-ops</button>
|
||
<button name="run" value="db">Start DB</button>
|
||
<button name="run" value="memory">Start Pamięć</button>
|
||
<button name="run" value="all" class="secondary">Uruchom wszystko</button>
|
||
<button name="run" value="score" class="secondary">Policz SCORE (uruchomi brakujące)</button>
|
||
</div>
|
||
</fieldset>
|
||
</form>
|
||
|
||
<?php if ($error): ?>
|
||
<div class="section error">Błąd: <?=h($error)?></div>
|
||
<?php endif; ?>
|
||
|
||
<?php if (!empty($results['cpu'])): ?>
|
||
<div class="section">
|
||
<h2>Wyniki – CPU</h2>
|
||
<table>
|
||
<thead><tr><th>Iteracja</th><th>Numeric [s]</th><th>Array [s]</th><th>Hash [s]</th><th>Łącznie [s]</th><th>Checksum</th><th>Acc sample</th></tr></thead>
|
||
<tbody>
|
||
<?php foreach ($results['cpu']['runs'] as $r): ?>
|
||
<tr>
|
||
<td><?=h($r['iteration'])?></td>
|
||
<td><?=number_format($r['numeric_s'], 6)?></td>
|
||
<td><?=number_format($r['array_s'], 6)?></td>
|
||
<td><?=number_format($r['hash_s'], 6)?></td>
|
||
<td><?=number_format($r['total_s'], 6)?></td>
|
||
<td><code><?=h($r['checksum'])?></code></td>
|
||
<td><?=h($r['acc_sample'])?></td>
|
||
</tr>
|
||
<?php endforeach; ?>
|
||
</tbody>
|
||
<tfoot>
|
||
<tr>
|
||
<th>Średnio</th>
|
||
<th><?=number_format($results['cpu']['avg']['numeric_s'], 6)?></th>
|
||
<th><?=number_format($results['cpu']['avg']['array_s'], 6)?></th>
|
||
<th><?=number_format($results['cpu']['avg']['hash_s'], 6)?></th>
|
||
<th><?=number_format($results['cpu']['avg']['total_s'], 6)?></th>
|
||
<th colspan="2">CPU size: <?=h($results['cpu']['size'])?></th>
|
||
</tr>
|
||
</tfoot>
|
||
</table>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<?php if (!empty($results['disk'])): ?>
|
||
<div class="section">
|
||
<h2>Wyniki – Dysk (sekwencyjne)</h2>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>Rozmiar pliku</th><th>Plików</th>
|
||
<th>Zapis: bajty</th><th>Zapis: czas [s]</th><th>Zapis: prędkość</th><th>MB/s</th>
|
||
<th>Odczyt: bajty</th><th>Odczyt: czas [s]</th><th>Odczyt: prędkość</th><th>MB/s</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<?php foreach ($results['disk'] as $row): ?>
|
||
<tr>
|
||
<td><?=h(human_bytes((float)$row['size']))?></td>
|
||
<td><?=h($row['files'])?></td>
|
||
<td><?=h(number_format($row['written_bytes']))?></td>
|
||
<td><?=h(number_format($row['write_time_s'], 6))?></td>
|
||
<td><?=h($row['write_speed'])?></td>
|
||
<td><?=h(number_format($row['write_MBps'],2))?></td>
|
||
<td><?=h(number_format($row['read_bytes']))?></td>
|
||
<td><?=h(number_format($row['read_time_s'], 6))?></td>
|
||
<td><?=h($row['read_speed'])?></td>
|
||
<td><?=h(number_format($row['read_MBps'],2))?></td>
|
||
</tr>
|
||
<?php endforeach; ?>
|
||
</tbody>
|
||
</table>
|
||
<div class="small">Pliki robocze po teście są automatycznie usuwane.</div>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<?php if (!empty($results['smallops'])): ?>
|
||
<div class="section">
|
||
<h2>Wyniki – FS small-ops (małe pliki ~4KB)</h2>
|
||
<table>
|
||
<tbody>
|
||
<tr><th>Docelowa liczba plików</th><td><?=h($results['smallops']['files'])?></td></tr>
|
||
<tr><th>Utworzono</th><td><?=h($results['smallops']['created'])?></td></tr>
|
||
<tr><th>Czas tworzenia</th><td><?=number_format($results['smallops']['create_time_s'], 6)?> s (<?=number_format($results['smallops']['create_ops_s'],0)?> ops/s)</td></tr>
|
||
<tr><th>Czas odczytu</th><td><?=number_format($results['smallops']['read_time_s'], 6)?> s (<?=number_format($results['smallops']['read_MBps'],2)?> MB/s)</td></tr>
|
||
<tr><th>Usunięto</th><td><?=h($results['smallops']['deleted'])?> (<?=number_format($results['smallops']['delete_ops_s'],0)?> ops/s)</td></tr>
|
||
<tr><th>Rozmiar pojedynczego pliku</th><td><?=h(human_bytes($results['smallops']['file_size']))?></td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<?php if (!empty($results['db'])): ?>
|
||
<div class="section">
|
||
<h2>Wyniki – Baza danych</h2>
|
||
<table>
|
||
<tbody>
|
||
<tr><th>INSERT – wierszy</th><td><?=h($results['db']['insert_rows'])?></td></tr>
|
||
<tr><th>INSERT – czas</th><td><?=number_format($results['db']['insert_time_s'], 6)?> s (<?=number_format($results['db']['insert_rows_s'],0)?> rows/s)</td></tr>
|
||
<tr><th>SELECT – agregaty</th><td><?=number_format($results['db']['select_aggr_s'], 6)?> s</td></tr>
|
||
<tr><th>SELECT – fetch</th><td><?=number_format($results['db']['select_fetch_s'], 6)?> s, pobrano <?=h($results['db']['fetched_rows'])?> wierszy</td></tr>
|
||
<tr><th>UPDATE</th><td><?=h($results['db']['update_affected'])?> wierszy w <?=number_format($results['db']['update_time_s'], 6)?> s</td></tr>
|
||
<tr><th>DELETE</th><td><?=h($results['db']['delete_affected'])?> wierszy w <?=number_format($results['db']['delete_time_s'], 6)?> s</td></tr>
|
||
<tr><th>Transakcja – łączny czas</th><td><?=number_format($results['db']['transaction_s'], 6)?> s (inv: <?=number_format($results['db']['txn_inv_s'],2)?> 1/s)</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<?php if (!empty($results['memory'])): ?>
|
||
<div class="section">
|
||
<h2>Wyniki – Pamięć</h2>
|
||
<table>
|
||
<tbody>
|
||
<tr><th>Alokowano</th><td><?=h($results['memory']['allocated'])?></td></tr>
|
||
<tr><th>Czas alokacji</th><td><?=number_format($results['memory']['alloc_time'], 6)?> s (<?=number_format($results['memory']['approx_MBps'],0)?> MB/s)</td></tr>
|
||
<tr><th>Czas przejścia</th><td><?=number_format($results['memory']['walk_time'], 6)?> s</td></tr>
|
||
<tr><th>Checksum</th><td><code><?=h($results['memory']['checksum'])?></code></td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<?php if (!empty($score)): ?>
|
||
<div class="section scorebox">
|
||
<h2>Hosting Score</h2>
|
||
<div class="scorenum"><?=h($score['score'])?> / 100</div>
|
||
<div class="small" style="margin:8px 0 12px;">
|
||
<span class="chip">DB: <?=isset($score['parts']['DB'])?number_format($score['parts']['DB'],1):'—'?></span>
|
||
<span class="chip">Dysk (seq): <?=isset($score['parts']['DISK_seq'])?number_format($score['parts']['DISK_seq'],1):'—'?></span>
|
||
<span class="chip">Dysk (small-ops): <?=isset($score['parts']['DISK_small'])?number_format($score['parts']['DISK_small'],1):'—'?></span>
|
||
<span class="chip">CPU: <?=isset($score['parts']['CPU'])?number_format($score['parts']['CPU'],1):'—'?></span>
|
||
<span class="chip">Pamięć: <?=isset($score['parts']['MEM'])?number_format($score['parts']['MEM'],1):'—'?></span>
|
||
</div>
|
||
<div class="small">
|
||
Wagi (DB 45%, Dysk 30% = 20% seq + 10% small-ops, CPU 20%, Pamięć 5%).<br>
|
||
Wagi są automatycznie renormalizowane, jeśli nie uruchomisz wszystkich testów.
|
||
</div>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<div class="section small">
|
||
Tipy:
|
||
<ul>
|
||
<li>Chcesz bardziej „żyłować” dysk? Dodaj większe rozmiary (np. <strong>32M,64M</strong>) i zwiększ liczbę plików.</li>
|
||
<li>Jeśli test DB jest wolny, sprawdź <code>innodb_flush_log_at_trx_commit</code>, <code>sync_binlog</code> i I/O dysku.</li>
|
||
<li>Małe pliki (~4KB) świetnie ujawniają limity IOPS/metadata – ważne przy cache, thumbnailach, logach.</li>
|
||
<li>Baseline’y do SCORE zmienisz w tablicy <code>$BASE</code> na górze pliku.</li>
|
||
</ul>
|
||
</div>
|
||
</body>
|
||
</html>
|