PHP_AdminTool_Projekt/app/Services/Snmp/SnmpServerStatusService.php
blaerf e82c2c041a snmp_update2 (#14)
Co-authored-by: Tom <165781231+GraegelTh@users.noreply.github.com>
Reviewed-on: https://git.eckertplayground.de/taarly/PHP_AdminTool_Projekt/pulls/14
2025-12-04 03:30:46 +00:00

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;
}
}