diff --git a/modules/resource_stats/stats_aggregate.php b/modules/resource_stats/stats_aggregate.php index 78252559..149ef6cb 100644 --- a/modules/resource_stats/stats_aggregate.php +++ b/modules/resource_stats/stats_aggregate.php @@ -1,5 +1,6 @@ 'panel_database' ]; $TABLE_PREFIX = 'gsp_'; -$DISCORD_WEBHOOK = 'https://discord.com/api/webhooks/REPLACE_ME'; -$ALERT_THRESHOLD = 80.0; +$AUTO_REFRESH_SECONDS = 30; // set 0 to disable auto-refresh /***************************************/ -$format = $_GET['format'] ?? 'json'; -$machine = $_GET['machine'] ?? null; - -if ($format === 'html') header('Content-Type: text/html'); else header('Content-Type: application/json'); - -$mysqli = new mysqli($db['host'], $db['user'], $db['pass'], $db['name']); +$mysqli = @new mysqli($db['host'], $db['user'], $db['pass'], $db['name']); if ($mysqli->connect_errno) { http_response_code(500); - echo json_encode(['error' => 'DB connect failed', 'detail' => $mysqli->connect_error]); exit; + echo "
DB connect failed: " . htmlspecialchars($mysqli->connect_error) . "
"; + exit; } $mysqli->set_charset("utf8mb4"); @@ -29,6 +25,7 @@ function q($mysqli, $sql, $params=[]) { $stmt = $mysqli->prepare($sql); if(!$stmt){ throw new Exception($mysqli->error); } if(!empty($params)) { + // infer types (all strings for simplicity) $types = str_repeat('s', count($params)); $stmt->bind_param($types, ...$params); } @@ -38,155 +35,241 @@ function q($mysqli, $sql, $params=[]) { $stmt->close(); return $rows; } -function send_discord($url, $content) { - $ch = curl_init($url); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']); - curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(['content' => $content])); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - $resp = curl_exec($ch); $err = curl_error($ch); curl_close($ch); - return [$resp, $err]; +function fmt_bytes($b) { + if ($b===null) return '—'; + $u = ['B','KB','MB','GB','TB','PB']; $i=0; + while ($b>=1024 && $i= (NOW() - INTERVAL {$hours} HOUR)"; +function pct_class($p) { + if ($p===null) return 'bar'; + if ($p>=80) return 'bar danger'; + if ($p>=60) return 'bar warn'; + return 'bar ok'; } +function pct($v) { return $v===null ? '—' : sprintf('%.1f%%', $v); } +function num0($v) { return $v===null ? '—' : number_format($v,0); } +$machine = isset($_GET['machine']) ? trim($_GET['machine']) : ''; +$windows = ['1h'=>1, '24h'=>24, '7d'=>168]; + +?> + + + +GSP Stats Dashboard<?= $machine ? " – ".htmlspecialchars($machine) : "" ?> +0): ?> + + + + + + +
+
+

GSP Stats Dashboard

+ 0 ? "auto-refresh {$AUTO_REFRESH_SECONDS}s" : "manual refresh" ?> + All machines + + Refresh + View raw JSON + +
+ + $rows]; - echo $format==='html' ? "
".htmlspecialchars(json_encode($payload, JSON_PRETTY_PRINT))."
" : json_encode($payload, JSON_PRETTY_PRINT); - exit; + if (!$machine) { + // list machines with last-ts + $rows = q($mysqli, "SELECT m.machine_id, m.hostname, + (SELECT MAX(ts) FROM {$TABLE_PREFIX}machine_samples s WHERE s.machine_id=m.machine_id) AS last_ts + FROM {$TABLE_PREFIX}machines m ORDER BY m.created_at DESC"); + echo '
'; + foreach ($rows as $r) { + echo '
'; + echo '

'.htmlspecialchars($r['machine_id']).'

'.htmlspecialchars($r['hostname']).'
'; + echo '
Last sample: '.htmlspecialchars($r['last_ts'] ?: '—').'
'; + echo '
Open
'; + echo '
'; + } + echo '
'; + echo '
'; exit; } - $out = ['machine' => $machine, 'windows' => []]; + // LAST sample + $last = q($mysqli, "SELECT * FROM {$TABLE_PREFIX}machine_samples WHERE machine_id=? ORDER BY ts DESC LIMIT 1", [$machine]); + $last = $last ? $last[0] : null; - $lastM = q($mysqli, - "SELECT * FROM {$TABLE_PREFIX}machine_samples WHERE machine_id=? ORDER BY ts DESC LIMIT 1", - [$machine] - ); - $lastTs = $lastM ? $lastM[0]['ts'] : null; - - $lastServers = q($mysqli, " - SELECT server_name, - SUM(cpu_pct) AS cpu_pct_sum, - AVG(mem_pct) AS mem_pct_avg, - MAX(folder_size_bytes) AS folder_size_bytes, - GROUP_CONCAT(DISTINCT pid ORDER BY pid) AS pids - FROM {$TABLE_PREFIX}process_samples - WHERE machine_id=? AND ts=(SELECT MAX(ts) FROM {$TABLE_PREFIX}process_samples WHERE machine_id=?) - GROUP BY server_name - ORDER BY server_name ASC - ", [$machine, $machine]); - - $out['last'] = [ - 'ts' => $lastTs, - 'machine' => $lastM ? [ - 'load1' => (float)$lastM[0]['load1'], - 'load5' => (float)$lastM[0]['load5'], - 'load15'=> (float)$lastM[0]['load15'], - 'cpu_pct'=> (float)$lastM[0]['cpu_pct'], - 'mem_used_pct'=> (float)$lastM[0]['mem_used_pct'], - 'disk_used_pct'=> (float)$lastM[0]['disk_used_pct'], - 'net_iface'=> $lastM[0]['net_iface'], - 'rx_bytes'=> (int)$lastM[0]['rx_bytes'], - 'tx_bytes'=> (int)$lastM[0]['tx_bytes'], - 'iface_speed_mbps'=> isset($lastM[0]['iface_speed_mbps']) ? (int)$lastM[0]['iface_speed_mbps'] : null - ] : null, - 'servers' => $lastServers - ]; - - $windows = [ '1h'=>1, '24h'=>24, '7d'=>24*7 ]; + // Windows + $agg = []; foreach($windows as $label=>$hours){ - $aggM = q($mysqli, " - SELECT COUNT(*) AS n, - AVG(cpu_pct) AS cpu_avg, - AVG(mem_used_pct) AS mem_avg, - AVG(disk_used_pct)AS disk_avg - FROM {$TABLE_PREFIX}machine_samples - WHERE machine_id=? AND ".window_clause($hours), - [$machine] - ); + $aggM = q($mysqli, "SELECT + COUNT(*) n, + AVG(cpu_pct) cpu_avg, + AVG(mem_used_pct) mem_avg, + AVG(disk_used_pct) disk_avg + FROM {$TABLE_PREFIX}machine_samples + WHERE machine_id=? AND ts >= (NOW() - INTERVAL {$hours} HOUR)", [$machine]); - $netRows = q($mysqli, " - SELECT ts, rx_bytes, tx_bytes, iface_speed_mbps - FROM {$TABLE_PREFIX}machine_samples - WHERE machine_id=? AND ".window_clause($hours)." - ORDER BY ts ASC - ", [$machine]); - - $net = null; - if(count($netRows) >= 2){ - $first = $netRows[0]; $last = $netRows[count($netRows)-1]; - $secs = max(1, strtotime($last['ts']) - strtotime($first['ts'])); - $rx_bps = ((int)$last['rx_bytes'] - (int)$first['rx_bytes']) / $secs; - $tx_bps = ((int)$last['tx_bytes'] - (int)$first['tx_bytes']) / $secs; - $speed_mbps = $last['iface_speed_mbps'] ? (int)$last['iface_speed_mbps'] : null; + $netRows = q($mysqli, "SELECT ts, rx_bytes, tx_bytes, iface_speed_mbps + FROM {$TABLE_PREFIX}machine_samples + WHERE machine_id=? AND ts >= (NOW() - INTERVAL {$hours} HOUR) + ORDER BY ts ASC", [$machine]); + $net = ['avg_rx_Bps'=>null,'avg_tx_Bps'=>null,'avg_total_Bps'=>null,'avg_util_pct'=>null]; + if (count($netRows)>=2) { + $first = $netRows[0]; $lastn = $netRows[count($netRows)-1]; + $secs = max(1, strtotime($lastn['ts']) - strtotime($first['ts'])); + $rx_bps = ((int)$lastn['rx_bytes'] - (int)$first['rx_bytes']) / $secs; + $tx_bps = ((int)$lastn['tx_bytes'] - (int)$first['tx_bytes']) / $secs; + $speed_mbps = $lastn['iface_speed_mbps'] ? (int)$lastn['iface_speed_mbps'] : null; $util_pct = null; - if($speed_mbps && $speed_mbps > 0){ + if ($speed_mbps && $speed_mbps>0) { $capacity_Bps = ($speed_mbps * 1000000) / 8.0; $util_pct = (($rx_bps + $tx_bps) / $capacity_Bps) * 100.0; } - $net = [ - 'avg_rx_Bps' => $rx_bps, - 'avg_tx_Bps' => $tx_bps, - 'avg_total_Bps' => $rx_bps + $tx_bps, - 'avg_util_pct' => $util_pct - ]; + $net = ['avg_rx_Bps'=>$rx_bps,'avg_tx_Bps'=>$tx_bps,'avg_total_Bps'=>$rx_bps+$tx_bps,'avg_util_pct'=>$util_pct]; } - $aggS = q($mysqli, " - SELECT server_name, - AVG(cpu_pct) AS cpu_avg, - AVG(mem_pct) AS mem_avg, - MAX(folder_size_bytes) AS folder_size_bytes - FROM {$TABLE_PREFIX}process_samples - WHERE machine_id=? AND ".window_clause($hours)." - GROUP BY server_name - ORDER BY server_name ASC - ", [$machine]); + $aggS = q($mysqli, "SELECT server_name, + AVG(cpu_pct) cpu_avg, + AVG(mem_pct) mem_avg, + MAX(folder_size_bytes) folder_size_bytes + FROM {$TABLE_PREFIX}process_samples + WHERE machine_id=? AND ts >= (NOW() - INTERVAL {$hours} HOUR) + GROUP BY server_name + ORDER BY server_name ASC", [$machine]); - $out['windows'][$label] = [ - 'machine' => [ - 'cpu_avg' => isset($aggM[0]['cpu_avg']) ? (float)$aggM[0]['cpu_avg'] : null, - 'mem_avg' => isset($aggM[0]['mem_avg']) ? (float)$aggM[0]['mem_avg'] : null, - 'disk_avg' => isset($aggM[0]['disk_avg']) ? (float)$aggM[0]['disk_avg'] : null, - 'net' => $net - ], - 'servers' => $aggS - ]; + $agg[$label] = ['machine'=>$aggM[0], 'net'=>$net, 'servers'=>$aggS]; } - $alerts = []; - foreach (['1h','24h'] as $w) { - $mw = $out['windows'][$w]['machine']; - if ($mw['cpu_avg'] !== null && $mw['cpu_avg'] >= $ALERT_THRESHOLD) $alerts[] = "Machine CPU avg {$w}: ".round($mw['cpu_avg'],1)."%"; - if ($mw['mem_avg'] !== null && $mw['mem_avg'] >= $ALERT_THRESHOLD) $alerts[] = "Machine MEM avg {$w}: ".round($mw['mem_avg'],1)."%"; - if ($mw['disk_avg'] !== null && $mw['disk_avg'] >= $ALERT_THRESHOLD) $alerts[] = "Machine DISK avg {$w}: ".round($mw['disk_avg'],1)."%"; - if (isset($mw['net']['avg_util_pct']) && $mw['net']['avg_util_pct'] !== null && $mw['net']['avg_util_pct'] >= $ALERT_THRESHOLD) { - $alerts[] = "Machine NET util avg {$w}: ".round($mw['net']['avg_util_pct'],1)."%"; - } - foreach ($out['windows'][$w]['servers'] as $s) { - if ($s['cpu_avg'] !== null && (float)$s['cpu_avg'] >= $ALERT_THRESHOLD) - $alerts[] = "Server '{$s['server_name']}' CPU avg {$w}: ".round($s['cpu_avg'],1)."%"; - if ($s['mem_avg'] !== null && (float)$s['mem_avg'] >= $ALERT_THRESHOLD) - $alerts[] = "Server '{$s['server_name']}' MEM avg {$w}: ".round($s['mem_avg'],1)."%"; - } - } - if (!empty($alerts) && !empty($DISCORD_WEBHOOK) && strpos($DISCORD_WEBHOOK, 'REPLACE_ME') === false) { - $msg = ":warning: **$machine** threshold(s) >= {$ALERT_THRESHOLD}%:\n- " . implode("\n- ", $alerts); - send_discord($DISCORD_WEBHOOK, $msg); - $out['alerts_sent'] = $alerts; - } else { - $out['alerts_sent'] = []; - } - - $json = json_encode($out, JSON_PRETTY_PRINT); - echo $format==='html' ? "
".htmlspecialchars($json)."
" : $json; - } catch (Throwable $e) { - http_response_code(500); - echo json_encode(['error' => 'Exception', 'detail' => $e->getMessage()]); -} finally { - $mysqli->close(); + echo '
Error: '.htmlspecialchars($e->getMessage()).'
'; exit; } +?> + +
+
+
+

+
Last sample: • IF:
+
+
+
+
CPU (last)
+
+
+
+
+
Memory used (last)
+
+
+
+
+
Disk used (last)
+
+
+
• used /
+
+
+
Net avg util (1h)
+ +
+
+
rx /s • tx /s
+
+
+
+ + $hours): $m=$agg[$label]['machine']; ?> +
+
+

Window:

+
+ AVG CPU +
+ + AVG MEM +
+ + AVG DISK +
+ +
+
+ +
+ +
+ + + + + + + + + + + + + '; + } else { + foreach ($rows as $s) { + $cpu = $s['cpu_avg']!==null ? (float)$s['cpu_avg'] : null; + $mem = $s['mem_avg']!==null ? (float)$s['mem_avg'] : null; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + } + } + ?> + +
ServerCPU avgMem avgFolder size
No server samples in this window.
'.htmlspecialchars($s['server_name']).''.pct($cpu).'
'.pct($mem).'
'.fmt_bytes($s['folder_size_bytes']).'
+
+
+ +
+ + + +