129 lines
3.8 KiB
PHP
129 lines
3.8 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use Illuminate\Support\Facades\Http;
|
|
use Illuminate\Support\Facades\Redis;
|
|
|
|
/**
|
|
* Sammelt System-Stats aus /proc, Disk, Ollama API, Redis Queue.
|
|
* Ein einziger Producer (Artisan-Command) ruft das auf und broadcastet
|
|
* per Reverb an alle Browser.
|
|
*/
|
|
class SystemStatsService
|
|
{
|
|
/**
|
|
* @return array{cpu:int, memory: array, disk: array, load: array, ollama: array, queue: int}
|
|
*/
|
|
public function collect(): array
|
|
{
|
|
return [
|
|
'cpu' => $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;
|
|
}
|
|
}
|
|
}
|