fix: Update vpn_clients speed columns in ServerMonitoring for real-time display

This commit is contained in:
infosave2007
2026-01-30 15:14:29 +03:00
parent 47b97d9a76
commit 7d4129747a
+121 -85
View File
@@ -14,13 +14,13 @@ class ServerMonitoring
{ {
private VpnServer $server; private VpnServer $server;
private array $serverData; private array $serverData;
public function __construct(int $serverId) public function __construct(int $serverId)
{ {
$this->server = new VpnServer($serverId); $this->server = new VpnServer($serverId);
$this->serverData = $this->server->getData(); $this->serverData = $this->server->getData();
} }
/** /**
* Collect all server metrics * Collect all server metrics
*/ */
@@ -35,12 +35,12 @@ class ServerMonitoring
'network_rx_mbps' => $this->getNetworkRxSpeed(), 'network_rx_mbps' => $this->getNetworkRxSpeed(),
'network_tx_mbps' => $this->getNetworkTxSpeed(), 'network_tx_mbps' => $this->getNetworkTxSpeed(),
]; ];
$this->saveServerMetrics($metrics); $this->saveServerMetrics($metrics);
return $metrics; return $metrics;
} }
/** /**
* Collect client traffic metrics * Collect client traffic metrics
*/ */
@@ -48,10 +48,11 @@ class ServerMonitoring
{ {
$clients = VpnClient::listByServer($this->serverData['id']); $clients = VpnClient::listByServer($this->serverData['id']);
$results = []; $results = [];
foreach ($clients as $client) { foreach ($clients as $client) {
if ($client['status'] !== 'active') continue; if ($client['status'] !== 'active')
continue;
$stats = $this->getClientStats($client); $stats = $this->getClientStats($client);
if ($stats) { if ($stats) {
$this->saveClientMetrics($client['id'], $stats); $this->saveClientMetrics($client['id'], $stats);
@@ -63,10 +64,10 @@ class ServerMonitoring
]; ];
} }
} }
return $results; return $results;
} }
/** /**
* Get CPU usage percentage * Get CPU usage percentage
*/ */
@@ -74,10 +75,10 @@ class ServerMonitoring
{ {
$cmd = "top -bn1 | grep 'Cpu(s)' | sed 's/.*, *\\([0-9.]*\\)%* id.*/\\1/' | awk '{print 100 - \$1}'"; $cmd = "top -bn1 | grep 'Cpu(s)' | sed 's/.*, *\\([0-9.]*\\)%* id.*/\\1/' | awk '{print 100 - \$1}'";
$result = $this->execSSH($cmd); $result = $this->execSSH($cmd);
return $result ? (float)trim($result) : null; return $result ? (float) trim($result) : null;
} }
/** /**
* Get RAM used in MB * Get RAM used in MB
*/ */
@@ -85,10 +86,10 @@ class ServerMonitoring
{ {
$cmd = "free -m | grep Mem | awk '{print \$3}'"; $cmd = "free -m | grep Mem | awk '{print \$3}'";
$result = $this->execSSH($cmd); $result = $this->execSSH($cmd);
return $result ? (int)trim($result) : null; return $result ? (int) trim($result) : null;
} }
/** /**
* Get total RAM in MB * Get total RAM in MB
*/ */
@@ -96,10 +97,10 @@ class ServerMonitoring
{ {
$cmd = "free -m | grep Mem | awk '{print \$2}'"; $cmd = "free -m | grep Mem | awk '{print \$2}'";
$result = $this->execSSH($cmd); $result = $this->execSSH($cmd);
return $result ? (int)trim($result) : null; return $result ? (int) trim($result) : null;
} }
/** /**
* Get disk used in GB * Get disk used in GB
*/ */
@@ -107,10 +108,10 @@ class ServerMonitoring
{ {
$cmd = "df -BG / | tail -1 | awk '{print \$3}' | sed 's/G//'"; $cmd = "df -BG / | tail -1 | awk '{print \$3}' | sed 's/G//'";
$result = $this->execSSH($cmd); $result = $this->execSSH($cmd);
return $result ? (float)trim($result) : null; return $result ? (float) trim($result) : null;
} }
/** /**
* Get total disk in GB * Get total disk in GB
*/ */
@@ -118,10 +119,10 @@ class ServerMonitoring
{ {
$cmd = "df -BG / | tail -1 | awk '{print \$2}' | sed 's/G//'"; $cmd = "df -BG / | tail -1 | awk '{print \$2}' | sed 's/G//'";
$result = $this->execSSH($cmd); $result = $this->execSSH($cmd);
return $result ? (float)trim($result) : null; return $result ? (float) trim($result) : null;
} }
/** /**
* Get network RX speed in Mbps * Get network RX speed in Mbps
*/ */
@@ -130,22 +131,24 @@ class ServerMonitoring
// Get bytes received on main interface // Get bytes received on main interface
$cmd = "cat /sys/class/net/\$(ip route | grep default | awk '{print \$5}' | head -1)/statistics/rx_bytes"; $cmd = "cat /sys/class/net/\$(ip route | grep default | awk '{print \$5}' | head -1)/statistics/rx_bytes";
$bytes1 = $this->execSSH($cmd); $bytes1 = $this->execSSH($cmd);
if (!$bytes1) return null; if (!$bytes1)
return null;
sleep(1); // Wait 1 second sleep(1); // Wait 1 second
$bytes2 = $this->execSSH($cmd); $bytes2 = $this->execSSH($cmd);
if (!$bytes2) return null; if (!$bytes2)
return null;
// Calculate speed in Mbps // Calculate speed in Mbps
$bytesPerSec = (int)$bytes2 - (int)$bytes1; $bytesPerSec = (int) $bytes2 - (int) $bytes1;
$mbps = ($bytesPerSec * 8) / 1000000; $mbps = ($bytesPerSec * 8) / 1000000;
return round($mbps, 2); return round($mbps, 2);
} }
/** /**
* Get network TX speed in Mbps * Get network TX speed in Mbps
*/ */
@@ -154,40 +157,43 @@ class ServerMonitoring
// Get bytes transmitted on main interface // Get bytes transmitted on main interface
$cmd = "cat /sys/class/net/\$(ip route | grep default | awk '{print \$5}' | head -1)/statistics/tx_bytes"; $cmd = "cat /sys/class/net/\$(ip route | grep default | awk '{print \$5}' | head -1)/statistics/tx_bytes";
$bytes1 = $this->execSSH($cmd); $bytes1 = $this->execSSH($cmd);
if (!$bytes1) return null; if (!$bytes1)
return null;
sleep(1); // Wait 1 second sleep(1); // Wait 1 second
$bytes2 = $this->execSSH($cmd); $bytes2 = $this->execSSH($cmd);
if (!$bytes2) return null; if (!$bytes2)
return null;
// Calculate speed in Mbps // Calculate speed in Mbps
$bytesPerSec = (int)$bytes2 - (int)$bytes1; $bytesPerSec = (int) $bytes2 - (int) $bytes1;
$mbps = ($bytesPerSec * 8) / 1000000; $mbps = ($bytesPerSec * 8) / 1000000;
return round($mbps, 2); return round($mbps, 2);
} }
/** /**
* Get client current stats and calculate speed * Get client current stats and calculate speed
*/ */
private function getClientStats(array $client): ?array private function getClientStats(array $client): ?array
{ {
$db = DB::conn(); $db = DB::conn();
// Get current stats from server // Get current stats from server
$containerName = $this->serverData['container_name']; $containerName = $this->serverData['container_name'];
$publicKey = $client['public_key']; $publicKey = $client['public_key'];
$cmd = "docker exec {$containerName} wg show all dump | grep '{$publicKey}' | awk '{print \$6, \$7}'"; $cmd = "docker exec {$containerName} wg show all dump | grep '{$publicKey}' | awk '{print \$6, \$7}'";
$result = $this->execSSH($cmd); $result = $this->execSSH($cmd);
if (!$result) return null; if (!$result)
return null;
list($bytesReceived, $bytesSent) = explode(' ', trim($result)); list($bytesReceived, $bytesSent) = explode(' ', trim($result));
// Get previous metrics (30 seconds ago) // Get previous metrics (30 seconds ago)
$stmt = $db->prepare(" $stmt = $db->prepare("
SELECT bytes_sent, bytes_received, collected_at SELECT bytes_sent, bytes_received, collected_at
@@ -198,43 +204,43 @@ class ServerMonitoring
"); ");
$stmt->execute([$client['id']]); $stmt->execute([$client['id']]);
$previous = $stmt->fetch(PDO::FETCH_ASSOC); $previous = $stmt->fetch(PDO::FETCH_ASSOC);
$speedUp = 0; $speedUp = 0;
$speedDown = 0; $speedDown = 0;
if ($previous) { if ($previous) {
$timeDiff = time() - strtotime($previous['collected_at']); $timeDiff = time() - strtotime($previous['collected_at']);
if ($timeDiff > 0) { if ($timeDiff > 0) {
// Calculate speed in Kbps // Calculate speed in Kbps
$bytesDiffSent = (int)$bytesSent - (int)$previous['bytes_sent']; $bytesDiffSent = (int) $bytesSent - (int) $previous['bytes_sent'];
$bytesDiffReceived = (int)$bytesReceived - (int)$previous['bytes_received']; $bytesDiffReceived = (int) $bytesReceived - (int) $previous['bytes_received'];
$speedUp = round(($bytesDiffSent * 8) / $timeDiff / 1000, 2); $speedUp = round(($bytesDiffSent * 8) / $timeDiff / 1000, 2);
$speedDown = round(($bytesDiffReceived * 8) / $timeDiff / 1000, 2); $speedDown = round(($bytesDiffReceived * 8) / $timeDiff / 1000, 2);
} }
} }
return [ return [
'bytes_sent' => (int)$bytesSent, 'bytes_sent' => (int) $bytesSent,
'bytes_received' => (int)$bytesReceived, 'bytes_received' => (int) $bytesReceived,
'speed_up_kbps' => $speedUp, 'speed_up_kbps' => $speedUp,
'speed_down_kbps' => $speedDown, 'speed_down_kbps' => $speedDown,
]; ];
} }
/** /**
* Save server metrics to database * Save server metrics to database
*/ */
private function saveServerMetrics(array $metrics): void private function saveServerMetrics(array $metrics): void
{ {
$db = DB::conn(); $db = DB::conn();
$stmt = $db->prepare(" $stmt = $db->prepare("
INSERT INTO server_metrics INSERT INTO server_metrics
(server_id, cpu_percent, ram_used_mb, ram_total_mb, disk_used_gb, disk_total_gb, network_rx_mbps, network_tx_mbps) (server_id, cpu_percent, ram_used_mb, ram_total_mb, disk_used_gb, disk_total_gb, network_rx_mbps, network_tx_mbps)
VALUES (?, ?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
"); ");
$stmt->execute([ $stmt->execute([
$this->serverData['id'], $this->serverData['id'],
$metrics['cpu_percent'], $metrics['cpu_percent'],
@@ -246,20 +252,20 @@ class ServerMonitoring
$metrics['network_tx_mbps'], $metrics['network_tx_mbps'],
]); ]);
} }
/** /**
* Save client metrics to database * Save client metrics to database
*/ */
private function saveClientMetrics(int $clientId, array $stats): void private function saveClientMetrics(int $clientId, array $stats): void
{ {
$db = DB::conn(); $db = DB::conn();
$stmt = $db->prepare(" $stmt = $db->prepare("
INSERT INTO client_metrics INSERT INTO client_metrics
(client_id, bytes_sent, bytes_received, speed_up_kbps, speed_down_kbps) (client_id, bytes_sent, bytes_received, speed_up_kbps, speed_down_kbps)
VALUES (?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?)
"); ");
$stmt->execute([ $stmt->execute([
$clientId, $clientId,
$stats['bytes_sent'], $stats['bytes_sent'],
@@ -267,24 +273,54 @@ class ServerMonitoring
$stats['speed_up_kbps'], $stats['speed_up_kbps'],
$stats['speed_down_kbps'], $stats['speed_down_kbps'],
]); ]);
// Update last_handshake in vpn_clients table // Update vpn_clients table with latest stats
$stmt = $db->prepare(" $stmt = $db->prepare("
UPDATE vpn_clients UPDATE vpn_clients
SET last_handshake = NOW() SET bytes_sent = ?, bytes_received = ?, speed_up = ?, speed_down = ?, current_speed = ?, last_handshake = NOW(), last_sync_at = NOW()
WHERE id = ? WHERE id = ?
"); ");
$stmt->execute([$clientId]); $currentSpeed = $stats['speed_up_kbps'] + $stats['speed_down_kbps']; // Total speed in Kbps? Or bytes/s?
// Note: speed_up_kbps is in Kbps (kilobits?).
// VpnClient stores speed in Bytes/s (based on my previous edit: bytesDiff/timeDiff).
// ServerMonitoring calculates: round(($bytesDiffSent * 8) / $timeDiff / 1000, 2) -> Kbps
// Wait! VpnClient implementation I did:
// $speedUp = (int) ($sentDiff / $timeDiff); // Bytes per second
// ServerMonitoring implementation:
// $speedUp = round(($bytesDiffSent * 8) / $timeDiff / 1000, 2); // Kilobits per second
// I need to be consistent.
// Frontend expects KB/s (KiloBYTES).
// VpnClient stores BYTES per second. Twig does `speed / 1024` -> KB/s.
// So I should convert ServerMonitoring stats to Bytes/s before saving to vpn_clients.
// ServerMonitoring $stats['speed_up_kbps'] is Kbps.
// Bytes/s = Kbps * 1000 / 8.
$speedUpBytes = (int) ($stats['speed_up_kbps'] * 1000 / 8);
$speedDownBytes = (int) ($stats['speed_down_kbps'] * 1000 / 8);
$totalSpeedBytes = $speedUpBytes + $speedDownBytes;
$stmt->execute([
$stats['bytes_sent'],
$stats['bytes_received'],
$speedUpBytes,
$speedDownBytes,
$totalSpeedBytes,
$clientId
]);
} }
/** /**
* Get server metrics for last 24 hours * Get server metrics for last 24 hours
*/ */
public static function getServerMetrics(int $serverId, int $hours = 24): array public static function getServerMetrics(int $serverId, int $hours = 24): array
{ {
$db = DB::conn(); $db = DB::conn();
$stmt = $db->prepare(" $stmt = $db->prepare("
SELECT * SELECT *
FROM server_metrics FROM server_metrics
@@ -292,19 +328,19 @@ class ServerMonitoring
AND collected_at >= DATE_SUB(NOW(), INTERVAL ? HOUR) AND collected_at >= DATE_SUB(NOW(), INTERVAL ? HOUR)
ORDER BY collected_at ASC ORDER BY collected_at ASC
"); ");
$stmt->execute([$serverId, $hours]); $stmt->execute([$serverId, $hours]);
return $stmt->fetchAll(PDO::FETCH_ASSOC); return $stmt->fetchAll(PDO::FETCH_ASSOC);
} }
/** /**
* Get client metrics for last 24 hours * Get client metrics for last 24 hours
*/ */
public static function getClientMetrics(int $clientId, int $hours = 24): array public static function getClientMetrics(int $clientId, int $hours = 24): array
{ {
$db = DB::conn(); $db = DB::conn();
$stmt = $db->prepare(" $stmt = $db->prepare("
SELECT * SELECT *
FROM client_metrics FROM client_metrics
@@ -312,26 +348,26 @@ class ServerMonitoring
AND collected_at >= DATE_SUB(NOW(), INTERVAL ? HOUR) AND collected_at >= DATE_SUB(NOW(), INTERVAL ? HOUR)
ORDER BY collected_at ASC ORDER BY collected_at ASC
"); ");
$stmt->execute([$clientId, $hours]); $stmt->execute([$clientId, $hours]);
return $stmt->fetchAll(PDO::FETCH_ASSOC); return $stmt->fetchAll(PDO::FETCH_ASSOC);
} }
/** /**
* Clean old metrics (older than 24 hours) * Clean old metrics (older than 24 hours)
*/ */
public static function cleanOldMetrics(): void public static function cleanOldMetrics(): void
{ {
$db = DB::conn(); $db = DB::conn();
// Clean server metrics // Clean server metrics
$db->exec("DELETE FROM server_metrics WHERE collected_at < DATE_SUB(NOW(), INTERVAL 24 HOUR)"); $db->exec("DELETE FROM server_metrics WHERE collected_at < DATE_SUB(NOW(), INTERVAL 24 HOUR)");
// Clean client metrics // Clean client metrics
$db->exec("DELETE FROM client_metrics WHERE collected_at < DATE_SUB(NOW(), INTERVAL 24 HOUR)"); $db->exec("DELETE FROM client_metrics WHERE collected_at < DATE_SUB(NOW(), INTERVAL 24 HOUR)");
} }
/** /**
* Execute SSH command on server * Execute SSH command on server
*/ */
@@ -341,7 +377,7 @@ class ServerMonitoring
$port = $this->serverData['port']; $port = $this->serverData['port'];
$username = $this->serverData['username']; $username = $this->serverData['username'];
$password = $this->serverData['password']; $password = $this->serverData['password'];
$sshCmd = sprintf( $sshCmd = sprintf(
'sshpass -p %s ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p %d %s@%s %s 2>/dev/null', 'sshpass -p %s ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p %d %s@%s %s 2>/dev/null',
escapeshellarg($password), escapeshellarg($password),
@@ -350,9 +386,9 @@ class ServerMonitoring
escapeshellarg($host), escapeshellarg($host),
escapeshellarg($cmd) escapeshellarg($cmd)
); );
$output = shell_exec($sshCmd); $output = shell_exec($sshCmd);
return $output ?: null; return $output ?: null;
} }
} }