Ich habe das UI Testweise nochmal erweitert und die Art wie wir die SNMP Daten abrufen und anzeigen nochmal überarbeitet. Es gibt jetzt eine kleine API die über Javascript in Intervallen die Daten aktualisiert. In der Datei Changelog sind nochmal die genauen Änderungen erklärt.
229 lines
9.2 KiB
PHP
229 lines
9.2 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Services\Snmp;
|
|
|
|
use RuntimeException;
|
|
|
|
/**
|
|
* Service zur Ermittlung des Serverstatus per SNMP.
|
|
*
|
|
* Features:
|
|
* - Robuste Fehlerbehandlung mit aussagekräftigen Exceptions
|
|
* - Intelligente Fallback-Logik bei fehlenden oder unerwarteten OID-Beschreibungen
|
|
* - Unterstützung für Windows (C:\) und Linux (/) Systeme
|
|
* - Detailliertes Logging über Exceptions
|
|
*
|
|
* Wird vom DashboardController beim initialen Laden aufgerufen.
|
|
* Das Live-Polling erfolgt über das API-Endpunkt (public/api/snmp_status.php).
|
|
*/
|
|
class SnmpServerStatusService
|
|
{
|
|
/** @var array<string, mixed> SNMP-Konfiguration (Host, Community, Timeout, OIDs, etc.) */
|
|
private array $config;
|
|
|
|
/**
|
|
* Erwartet den Teilbereich "snmp" aus der allgemeinen Konfiguration (config.php).
|
|
*
|
|
* @param array<string, mixed> $snmpConfig Konfiguration für die SNMP-Abfragen
|
|
*/
|
|
public function __construct(array $snmpConfig)
|
|
{
|
|
$this->config = $snmpConfig;
|
|
}
|
|
|
|
/**
|
|
* Liefert den aktuellen Serverstatus zurück.
|
|
*
|
|
* @return array<string, mixed> Assoziatives Array mit Statuswerten (Hostname, CPU%, RAM%, etc.)
|
|
*
|
|
* @throws RuntimeException wenn die SNMP-Konfiguration unvollständig ist oder Abfragen fehlschlagen
|
|
*/
|
|
public function getServerStatus(): array
|
|
{
|
|
// --- 1. Konfiguration auslesen ---
|
|
$host = (string)($this->config['host'] ?? '127.0.0.1');
|
|
$community = (string)($this->config['community'] ?? '');
|
|
$oids = $this->config['oids'] ?? [];
|
|
$timeout = (int)($this->config['timeout'] ?? 1) * 1_000_000; // in Mikrosekunden
|
|
$retries = (int)($this->config['retries'] ?? 1);
|
|
|
|
// --- Prüfungen für essentielle Konfig-Werte ---
|
|
if ($host === '') {
|
|
throw new RuntimeException('SNMP-Konfiguration ist unvollständig (host fehlt).');
|
|
}
|
|
if ($community === '') {
|
|
throw new RuntimeException('SNMP-Konfiguration ist unvollständig (community fehlt).');
|
|
}
|
|
if (empty($oids)) {
|
|
throw new RuntimeException('SNMP-Konfiguration ist unvollständig (oids fehlen).');
|
|
}
|
|
|
|
if (!function_exists('snmpget')) {
|
|
throw new RuntimeException('PHP-SNMP-Erweiterung ist nicht installiert.');
|
|
}
|
|
|
|
// Hilfsfunktion: SNMP-Werte bereinigen (z.B. "INTEGER: 123" -> 123)
|
|
$cleanSnmpValue = fn($v) => (int)filter_var($v, FILTER_SANITIZE_NUMBER_INT);
|
|
|
|
// --- 2. Uptime abfragen ---
|
|
$uptimeOid = $oids['uptime'] ?? '1.3.6.1.2.1.1.3.0';
|
|
$uptimeResult = @snmpget($host, $community, $uptimeOid, $timeout, $retries);
|
|
if ($uptimeResult === false) {
|
|
throw new RuntimeException("SNMP Uptime GET fehlgeschlagen.");
|
|
}
|
|
|
|
// Uptime aus TimeTicks (Hundertstel-Sekunden) in lesbar konvertieren
|
|
preg_match('/\((.*?)\)/', $uptimeResult, $matches);
|
|
$uptimeTicks = (int)($matches[1] ?? 0);
|
|
$uptimeSeconds = $uptimeTicks / 100;
|
|
$uptimeFormatted = sprintf(
|
|
'%d Tage, %02d:%02d:%02d',
|
|
(int)floor($uptimeSeconds / 86400),
|
|
(int)floor(($uptimeSeconds % 86400) / 3600),
|
|
(int)floor(($uptimeSeconds % 3600) / 60),
|
|
(int)($uptimeSeconds % 60)
|
|
);
|
|
|
|
// --- 3. CPU (Durchschnitt über alle Kerne) ---
|
|
$cpuTable = $oids['cpu_table'] ?? '1.3.6.1.2.1.25.3.3.1.2';
|
|
$cpuValues = @snmpwalk($host, $community, $cpuTable, $timeout, $retries);
|
|
|
|
if (!is_array($cpuValues) || empty($cpuValues)) {
|
|
throw new RuntimeException("SNMP CPU WALK fehlgeschlagen.");
|
|
}
|
|
|
|
$cpuValues = array_map($cleanSnmpValue, $cpuValues);
|
|
$cpuAvg = (int)round(array_sum($cpuValues) / count($cpuValues));
|
|
|
|
// --- 4. Storage-Tabellen (RAM + Disks) ---
|
|
$descrOid = $oids['storage_descr'] ?? '1.3.6.1.2.1.25.2.3.1.3';
|
|
$unitsOid = $oids['storage_units'] ?? '1.3.6.1.2.1.25.2.3.1.4';
|
|
$sizeOid = $oids['storage_size'] ?? '1.3.6.1.2.1.25.2.3.1.5';
|
|
$usedOid = $oids['storage_used'] ?? '1.3.6.1.2.1.25.2.3.1.6';
|
|
|
|
$descr = @snmpwalk($host, $community, $descrOid, $timeout, $retries);
|
|
$units = @snmpwalk($host, $community, $unitsOid, $timeout, $retries);
|
|
$size = @snmpwalk($host, $community, $sizeOid, $timeout, $retries);
|
|
$used = @snmpwalk($host, $community, $usedOid, $timeout, $retries);
|
|
|
|
if (!is_array($descr) || !is_array($units) || !is_array($size) || !is_array($used)) {
|
|
throw new RuntimeException("SNMP Storage WALK fehlgeschlagen.");
|
|
}
|
|
|
|
// Werte bereinigen
|
|
$descr = array_map(fn($v) => trim(str_ireplace('STRING:', '', $v), ' "'), $descr);
|
|
$units = array_map($cleanSnmpValue, $units);
|
|
$size = array_map($cleanSnmpValue, $size);
|
|
$used = array_map($cleanSnmpValue, $used);
|
|
|
|
// --- 5. RAM mit Fallback-Logik ---
|
|
$ramPercent = null;
|
|
$memTotalBytes = null;
|
|
|
|
// Heuristik 1: Suche nach "Physical Memory"
|
|
$ramIndex = array_search("Physical Memory", $descr);
|
|
if ($ramIndex !== false) {
|
|
$memTotalBytes = $units[$ramIndex] * $size[$ramIndex];
|
|
$ramUsedBytes = $units[$ramIndex] * $used[$ramIndex];
|
|
$ramPercent = ($memTotalBytes > 0) ? ($ramUsedBytes / $memTotalBytes) * 100 : 0;
|
|
}
|
|
|
|
// Fallback 1: Wenn nicht gefunden, suche nach ähnlichen Labels
|
|
if ($ramPercent === null) {
|
|
foreach ($descr as $index => $description) {
|
|
$lower = strtolower($description);
|
|
if (strpos($lower, 'physical') !== false || strpos($lower, 'memory') !== false || strpos($lower, 'ram') !== false) {
|
|
$memTotalBytes = $units[$index] * $size[$index];
|
|
$ramUsedBytes = $units[$index] * $used[$index];
|
|
$ramPercent = ($memTotalBytes > 0) ? ($ramUsedBytes / $memTotalBytes) * 100 : 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fallback 2: Wenn immer noch nicht gefunden, nimm den kleinsten Eintrag (i.d.R. RAM)
|
|
if ($ramPercent === null && count($descr) > 0) {
|
|
$minIndex = 0;
|
|
$minSize = PHP_INT_MAX;
|
|
foreach ($size as $index => $s) {
|
|
if ($s > 0 && $s < $minSize) {
|
|
$minSize = $s;
|
|
$minIndex = $index;
|
|
}
|
|
}
|
|
$memTotalBytes = $units[$minIndex] * $size[$minIndex];
|
|
$ramUsedBytes = $units[$minIndex] * $used[$minIndex];
|
|
$ramPercent = ($memTotalBytes > 0) ? ($ramUsedBytes / $memTotalBytes) * 100 : 0;
|
|
}
|
|
|
|
// Fallback 3: Wenn gar nichts geht, Exception
|
|
if ($ramPercent === null) {
|
|
throw new RuntimeException("Konnte 'Physical Memory' in der SNMP Storage-Tabelle nicht finden.");
|
|
}
|
|
|
|
// --- 6. Disk C: / Root mit Fallback-Logik ---
|
|
$diskCPercent = null;
|
|
|
|
// Heuristik 1: Suche nach C:\
|
|
foreach ($descr as $index => $description) {
|
|
if (str_starts_with($description, 'C:\\')) {
|
|
$cTotal = $units[$index] * $size[$index];
|
|
$cUsed = $units[$index] * $used[$index];
|
|
$diskCPercent = ($cTotal > 0) ? ($cUsed / $cTotal) * 100 : 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Fallback 1: Suche nach "C:" oder "root" oder "/"
|
|
if ($diskCPercent === null) {
|
|
foreach ($descr as $index => $description) {
|
|
$lower = strtolower($description);
|
|
if (strpos($lower, 'c:') !== false || $lower === '/' || strpos($lower, 'root') !== false) {
|
|
$cTotal = $units[$index] * $size[$index];
|
|
$cUsed = $units[$index] * $used[$index];
|
|
$diskCPercent = ($cTotal > 0) ? ($cUsed / $cTotal) * 100 : 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fallback 2: Nimm den größten Eintrag > 1GB (wahrscheinlich der Hauptdatenträger)
|
|
if ($diskCPercent === null) {
|
|
$maxIndex = 0;
|
|
$maxSize = 0;
|
|
foreach ($size as $index => $s) {
|
|
$sizeGB = ($s * $units[$index]) / (1024 ** 3);
|
|
if ($sizeGB > 1 && $s > $maxSize) {
|
|
$maxSize = $s;
|
|
$maxIndex = $index;
|
|
}
|
|
}
|
|
if ($maxSize > 0) {
|
|
$cTotal = $units[$maxIndex] * $size[$maxIndex];
|
|
$cUsed = $units[$maxIndex] * $used[$maxIndex];
|
|
$diskCPercent = ($cTotal > 0) ? ($cUsed / $cTotal) * 100 : 0;
|
|
}
|
|
}
|
|
|
|
// Fallback 3: Wenn immer noch nichts, Exception
|
|
if ($diskCPercent === null) {
|
|
throw new RuntimeException("Konnte Laufwerk 'C:\\' oder Root-Partition in der SNMP Storage-Tabelle nicht finden.");
|
|
}
|
|
|
|
// --- 7. Status-Array zusammenbauen ---
|
|
$status = [
|
|
'hostname' => $host,
|
|
'os' => 'Windows Server', // TODO: OS dynamisch per SNMP abfragen (OID 1.3.6.1.2.1.1.1.0)
|
|
'uptime' => $uptimeFormatted,
|
|
'cpu_usage' => $cpuAvg,
|
|
'memory_usage' => (int)round($ramPercent),
|
|
'disk_usage_c' => (int)round($diskCPercent),
|
|
'last_update' => date('d.m.Y H:i:s'),
|
|
];
|
|
|
|
return $status;
|
|
}
|
|
}
|