Reviewed-on: https://git.eckertplayground.de/taarly/PHP_AdminTool_Projekt/pulls/15
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;
|
|
}
|
|
}
|