extractHost($this->site->url); if (!$host) { Log::error('CheckSitePing: Invalid URL', [ 'site_id' => $this->site->id, 'url' => $this->site->url, ]); // Record failed check due to invalid URL UptimeCheck::create([ 'site_id' => $this->site->id, 'check_type' => 'ping', 'status_code' => null, 'response_time_ms' => null, 'is_online' => false, 'checked_at' => now(), ]); return; } $result = $this->ping($host); Log::info('CheckSitePing: Raw result', [ 'site_id' => $this->site->id, 'host' => $host, 'result' => $result, ]); UptimeCheck::create([ 'site_id' => $this->site->id, 'check_type' => 'ping', 'status_code' => null, // ICMP doesn't have HTTP status codes 'response_time_ms' => $result['avg_time'], 'is_online' => $result['is_online'], 'checked_at' => now(), ]); Log::info('CheckSitePing: Ping completed', [ 'site_id' => $this->site->id, 'host' => $host, 'is_online' => $result['is_online'], 'avg_time' => $result['avg_time'], ]); } catch (\Exception $e) { Log::error('CheckSitePing: Failed', [ 'site_id' => $this->site->id, 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString(), ]); // Record failed check UptimeCheck::create([ 'site_id' => $this->site->id, 'check_type' => 'ping', 'status_code' => null, 'response_time_ms' => null, 'is_online' => false, 'checked_at' => now(), ]); throw $e; } } /** * Extract host from URL. */ protected function extractHost(string $url): ?string { $parsed = parse_url($url); return $parsed['host'] ?? null; } /** * Execute ping command and parse results. */ protected function ping(string $host): array { $isWindows = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN'; // Check if ping command exists $pingPath = $this->findPingCommand($isWindows); if (!$pingPath) { // Ping command not found, use TCP connection as fallback Log::warning('CheckSitePing: ping command not found, using TCP fallback', [ 'site_id' => $this->site->id, 'host' => $host, ]); return $this->tcpCheck($host); } // Build ping command based on OS if ($isWindows) { $command = sprintf('%s -n 4 %s 2>&1', $pingPath, escapeshellarg($host)); } else { $command = sprintf('%s -c 4 -W 5 %s 2>&1', $pingPath, escapeshellarg($host)); } $output = []; $returnVar = 0; exec($command, $output, $returnVar); $isOnline = $returnVar === 0; $avgTime = null; // Parse average response time if ($isOnline) { $avgTime = $this->parseAverageTime($output, $isWindows); } return [ 'is_online' => $isOnline, 'avg_time' => $avgTime, 'output' => implode("\n", $output), 'return_code' => $returnVar, 'command' => $command, ]; } /** * Find ping command path. */ protected function findPingCommand(bool $isWindows): ?string { if ($isWindows) { return 'ping'; } // Check common Linux locations $locations = ['/bin/ping', '/usr/bin/ping', '/sbin/ping', '/usr/sbin/ping']; foreach ($locations as $location) { if (file_exists($location) && is_executable($location)) { return $location; } } // Try to find using which exec('which ping 2>&1', $whichOutput, $whichReturn); if ($whichReturn === 0 && !empty($whichOutput[0])) { return trim($whichOutput[0]); } return null; } /** * TCP connection check as fallback when ping is not available. */ protected function tcpCheck(string $host): array { $port = 80; $timeout = 5; $startTime = microtime(true); $fp = @fsockopen($host, $port, $errno, $errstr, $timeout); $endTime = microtime(true); $responseTime = (int) round(($endTime - $startTime) * 1000); $isOnline = $fp !== false; if ($fp) { fclose($fp); } return [ 'is_online' => $isOnline, 'avg_time' => $isOnline ? $responseTime : null, 'output' => $isOnline ? "TCP connection successful on port {$port}" : "TCP connection failed: {$errstr}", 'return_code' => $isOnline ? 0 : $errno, 'command' => "fsockopen({$host}, {$port}, {$timeout}s)", ]; } /** * Parse average response time from ping output. */ protected function parseAverageTime(array $output, bool $isWindows): ?int { $outputText = implode("\n", $output); if ($isWindows) { // Windows format: "Average = 23ms" if (preg_match('/Average\s*=\s*(\d+)ms/i', $outputText, $matches)) { return (int) $matches[1]; } } else { // Linux format: "rtt min/avg/max/mdev = 12.345/23.456/34.567/8.901 ms" if (preg_match('/rtt min\/avg\/max\/mdev = [\d.]+\/([\d.]+)\/[\d.]+\/[\d.]+ ms/i', $outputText, $matches)) { return (int) round((float) $matches[1]); } // Alternative Linux format: "round-trip min/avg/max = 12.345/23.456/34.567 ms" if (preg_match('/round-trip min\/avg\/max = [\d.]+\/([\d.]+)\/[\d.]+ ms/i', $outputText, $matches)) { return (int) round((float) $matches[1]); } } return null; } /** * Handle a job failure. */ public function failed(\Throwable $exception): void { Log::error('CheckSitePing: Job failed after all retries', [ 'site_id' => $this->site->id, 'exception' => $exception->getMessage(), ]); } }