$this->cpuUsage(), 'memory' => $this->memoryUsage(), 'disk' => $this->diskUsage(), 'load' => $this->loadAverage(), 'ollama' => $this->ollamaStatus(), 'openai' => $this->openaiStatus(), 'queue' => $this->queueLength(), ]; } protected function cpuUsage(): int { $sample = function (): array { $line = explode("\n", (string) @file_get_contents('/proc/stat'))[0] ?? ''; $parts = preg_split('/\s+/', trim($line)); array_shift($parts); $parts = array_map('intval', $parts); return [ 'idle' => ($parts[3] ?? 0) + ($parts[4] ?? 0), 'total' => array_sum($parts), ]; }; $a = $sample(); usleep(100_000); $b = $sample(); $totalDiff = $b['total'] - $a['total']; $idleDiff = $b['idle'] - $a['idle']; return $totalDiff > 0 ? (int) round((1 - $idleDiff / $totalDiff) * 100) : 0; } protected function memoryUsage(): array { $info = (string) @file_get_contents('/proc/meminfo'); preg_match('/MemTotal:\s+(\d+)/', $info, $totalMatch); preg_match('/MemAvailable:\s+(\d+)/', $info, $availMatch); $total = (int) ($totalMatch[1] ?? 0); $available = (int) ($availMatch[1] ?? 0); $used = max(0, $total - $available); return [ 'percent' => $total > 0 ? (int) round(($used / $total) * 100) : 0, 'used_mb' => (int) round($used / 1024), 'total_mb' => (int) round($total / 1024), ]; } protected function diskUsage(): array { $total = (int) @disk_total_space('/var/www'); $free = (int) @disk_free_space('/var/www'); $used = max(0, $total - $free); return [ 'percent' => $total > 0 ? (int) round(($used / $total) * 100) : 0, 'used_gb' => round($used / 1024 / 1024 / 1024, 1), 'total_gb' => round($total / 1024 / 1024 / 1024, 1), ]; } protected function loadAverage(): array { $line = (string) @file_get_contents('/proc/loadavg'); $parts = preg_split('/\s+/', trim($line)); return [ '1m' => (float) ($parts[0] ?? 0), '5m' => (float) ($parts[1] ?? 0), '15m' => (float) ($parts[2] ?? 0), ]; } protected function ollamaStatus(): array { try { $r = Http::timeout(2)->get(rtrim((string) config('services.ollama.url'), '/').'/api/tags'); if ($r->successful()) { return ['online' => true, 'models' => count($r->json('models', []))]; } } catch (\Throwable) { // ignore } return ['online' => false, 'models' => 0]; } /** * OpenAI ist "online" wenn ein Key gesetzt ist - kein Live-Ping (kostet Geld). * Der echte Health-Check passiert beim ersten realen Request, im LLM-Fallback. */ protected function openaiStatus(): array { return ['online' => ! empty(config('services.openai.key'))]; } protected function queueLength(): int { try { return (int) Redis::connection()->llen(config('cache.prefix', '').'queues:default'); } catch (\Throwable) { return 0; } } }