244 lines
8.0 KiB
PHP
244 lines
8.0 KiB
PHP
<?php
|
|
/**
|
|
* admin/api/kind_1_report.php — Markdown relay status report.
|
|
*
|
|
* Returns a markdown-formatted relay status report suitable for use as
|
|
* a kind 1 event content. Includes:
|
|
* - Relay name header
|
|
* - 1H ASCII chart (event rate)
|
|
* - Database overview
|
|
* - Top 10 event kinds
|
|
* - Time-based statistics
|
|
* - Top pubkeys
|
|
*
|
|
* Accessible at: /relay/admin/api/kind_1_report.php
|
|
*/
|
|
require_once __DIR__ . '/../lib/helpers.php';
|
|
require_once __DIR__ . '/../lib/ascii_chart.php';
|
|
|
|
header('Content-Type: text/plain; charset=utf-8');
|
|
|
|
$pdo = db();
|
|
|
|
// --- Relay info ---
|
|
$relay_name = 'C-Relay-PG';
|
|
$relay_version = '';
|
|
try {
|
|
$relay_name = $pdo->query("SELECT value FROM config WHERE key = 'relay_name'")->fetchColumn() ?: 'C-Relay-PG';
|
|
$relay_version = $pdo->query("SELECT value FROM config WHERE key = 'relay_version'")->fetchColumn() ?: '';
|
|
} catch (PDOException $e) {}
|
|
$display_name = $relay_version ? "$relay_name v$relay_version" : $relay_name;
|
|
|
|
// --- Total events ---
|
|
$total_events = 0;
|
|
try {
|
|
$total_events = intval($pdo->query("SELECT COUNT(*) FROM events")->fetchColumn());
|
|
} catch (PDOException $e) {}
|
|
|
|
// --- Database size ---
|
|
$db_size = '-';
|
|
try {
|
|
$bytes = intval($pdo->query("SELECT pg_database_size(current_database())")->fetchColumn());
|
|
if ($bytes < 1073741824) {
|
|
$db_size = round($bytes / 1048576, 1) . ' MB';
|
|
} else {
|
|
$db_size = round($bytes / 1073741824, 2) . ' GB';
|
|
}
|
|
} catch (PDOException $e) {}
|
|
|
|
// --- Process ID ---
|
|
$process_id = '-';
|
|
try {
|
|
$pid = $pdo->query("SELECT pg_backend_pid()")->fetchColumn();
|
|
$process_id = strval($pid);
|
|
} catch (PDOException $e) {}
|
|
|
|
// --- WebSocket connections (estimate from pg_class) ---
|
|
$ws_connections = 0;
|
|
try {
|
|
$est = intval($pdo->query("SELECT reltuples FROM pg_class WHERE relname = 'subscriptions'")->fetchColumn());
|
|
$ws_connections = max(0, intval($est * 0.2));
|
|
} catch (PDOException $e) {}
|
|
|
|
// --- Active subscriptions (same estimate) ---
|
|
$active_subscriptions = $ws_connections;
|
|
|
|
// --- Memory usage ---
|
|
$memory_usage = '-';
|
|
$mem_total = 0;
|
|
$mem_avail = 0;
|
|
if (is_readable('/proc/meminfo')) {
|
|
$mem = @parse_ini_file('/proc/meminfo');
|
|
$mem_total = intval($mem['MemTotal'] ?? 0) * 1024;
|
|
$mem_avail = intval($mem['MemAvailable'] ?? 0) * 1024;
|
|
} elseif (function_exists('shell_exec')) {
|
|
$free = @shell_exec('free -b 2>/dev/null');
|
|
if ($free) {
|
|
foreach (explode("\n", $free) as $line) {
|
|
if (preg_match('/^Mem:\s+(\d+)\s+(\d+)/', $line, $m)) {
|
|
$mem_total = intval($m[1]);
|
|
$mem_avail = intval($m[1]) - intval($m[2]);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if ($mem_total > 0) {
|
|
$used = $mem_total - $mem_avail;
|
|
if ($used < 1073741824) {
|
|
$memory_usage = round($used / 1048576, 1) . ' MB / ' . round($mem_total / 1073741824, 2) . ' GB';
|
|
} else {
|
|
$memory_usage = round($used / 1073741824, 2) . ' GB / ' . round($mem_total / 1073741824, 2) . ' GB';
|
|
}
|
|
}
|
|
|
|
// --- CPU ---
|
|
$cpu_usage = '-';
|
|
$cpu_core = '-';
|
|
if (is_readable('/proc/cpuinfo')) {
|
|
$cores = intval(@shell_exec('nproc 2>/dev/null') ?: 1);
|
|
$cpu_core = strval($cores) . ' cores';
|
|
}
|
|
$load = @file_get_contents('/proc/loadavg');
|
|
if ($load !== false) {
|
|
$cpu_usage = trim(explode(' ', $load)[0] ?? '-');
|
|
}
|
|
|
|
// --- Oldest / newest event ---
|
|
$oldest_event = '-';
|
|
$newest_event = '-';
|
|
try {
|
|
$row = $pdo->query("SELECT MIN(created_at) AS oldest, MAX(created_at) AS newest FROM events")->fetch();
|
|
if ($row && $row['oldest']) $oldest_event = date('Y-m-d H:i:s', intval($row['oldest']));
|
|
if ($row && $row['newest']) $newest_event = date('Y-m-d H:i:s', intval($row['newest']));
|
|
} catch (PDOException $e) {}
|
|
|
|
// --- Time-based stats ---
|
|
$now = time();
|
|
$events_24h = 0; $events_7d = 0; $events_30d = 0;
|
|
try {
|
|
$events_24h = intval($pdo->query("SELECT COUNT(*) FROM events WHERE created_at >= $now - 86400")->fetchColumn());
|
|
$events_7d = intval($pdo->query("SELECT COUNT(*) FROM events WHERE created_at >= $now - 604800")->fetchColumn());
|
|
$events_30d = intval($pdo->query("SELECT COUNT(*) FROM events WHERE created_at >= $now - 2592000")->fetchColumn());
|
|
} catch (PDOException $e) {}
|
|
|
|
// --- Kind distribution (top 10) ---
|
|
$kinds = [];
|
|
try {
|
|
$rows = $pdo->query("SELECT kind, COUNT(*) AS cnt FROM events GROUP BY kind ORDER BY cnt DESC LIMIT 10")->fetchAll();
|
|
foreach ($rows as $r) {
|
|
$kinds[] = ['kind' => intval($r['kind']), 'count' => intval($r['cnt']), 'pct' => $total_events > 0 ? round(intval($r['cnt']) / $total_events * 100, 1) : 0];
|
|
}
|
|
} catch (PDOException $e) {}
|
|
|
|
// --- Top pubkeys (top 20) ---
|
|
$top_pubkeys = [];
|
|
try {
|
|
$rows = $pdo->query("
|
|
SELECT pubkey, COUNT(*) AS cnt
|
|
FROM events
|
|
GROUP BY pubkey
|
|
ORDER BY cnt DESC LIMIT 20
|
|
")->fetchAll();
|
|
$pubkeys = array_column($rows, 'pubkey');
|
|
$pmap = profile_map($pubkeys);
|
|
foreach ($rows as $r) {
|
|
$pk = $r['pubkey'];
|
|
$prof = $pmap[$pk] ?? null;
|
|
$top_pubkeys[] = [
|
|
'pubkey' => $pk,
|
|
'name' => $prof ? $prof['best_name'] : '',
|
|
'count' => intval($r['cnt']),
|
|
'pct' => $total_events > 0 ? round(intval($r['cnt']) / $total_events * 100, 1) : 0,
|
|
];
|
|
}
|
|
} catch (PDOException $e) {}
|
|
|
|
// --- 1H ASCII chart (30 bins of 2 minutes each) ---
|
|
$chart = '';
|
|
try {
|
|
$span = 3600;
|
|
$bin_size = 120; // 2 minutes
|
|
$num_bins = 30;
|
|
$epoch = $now - $span;
|
|
$base_bin = (int)floor($epoch / $bin_size);
|
|
$sql = "SELECT FLOOR(created_at / {$bin_size})::BIGINT - {$base_bin} AS bin, COUNT(*) AS cnt
|
|
FROM events
|
|
WHERE created_at >= {$epoch}
|
|
GROUP BY bin
|
|
ORDER BY bin";
|
|
$rows = $pdo->query($sql)->fetchAll();
|
|
$bins = build_bin_array($rows, $num_bins);
|
|
$bins = array_reverse($bins);
|
|
$chart = render_ascii_chart($bins, [
|
|
'title' => 'Event Rate (Last Hour)',
|
|
'max_height' => 11,
|
|
'bin_duration' => $bin_size,
|
|
'label_interval' => 5, // label every 5 bins (10 min)
|
|
]);
|
|
} catch (PDOException $e) {
|
|
$chart = "Chart unavailable\n";
|
|
}
|
|
|
|
// ================================
|
|
// BUILD MARKDOWN OUTPUT
|
|
// ================================
|
|
|
|
$output = '';
|
|
|
|
// Chart
|
|
$output .= "## laantungir.net/relay\n\n";
|
|
$output .= "```\n$chart```\n";
|
|
|
|
// Database Overview
|
|
$output .= "## Database Overview\n\n";
|
|
$output .= "| Metric | Value |\n";
|
|
$output .= "|--------|-------|\n";
|
|
$output .= "| Database Size | $db_size |\n";
|
|
$output .= "| Total Events | " . number_format($total_events) . " |\n";
|
|
$output .= "| Process ID | $process_id |\n";
|
|
$output .= "| WebSocket Connections | " . number_format($ws_connections) . " |\n";
|
|
$output .= "| Active Subscriptions | " . number_format($active_subscriptions) . " |\n";
|
|
$output .= "| Memory Usage | $memory_usage |\n";
|
|
$output .= "| CPU Usage | $cpu_usage |\n";
|
|
$output .= "| CPU Core | $cpu_core |\n";
|
|
$output .= "| Oldest Event | $oldest_event |\n";
|
|
$output .= "| Newest Event | $newest_event |\n\n";
|
|
|
|
// Event Kinds (Top 10)
|
|
$output .= "## Event Kinds (Top 10)\n\n";
|
|
$output .= "| Kind | Count | % |\n";
|
|
$output .= "|------|-------|---|\n";
|
|
foreach ($kinds as $k) {
|
|
$output .= "| {$k['kind']} | " . number_format($k['count']) . " | {$k['pct']}% |\n";
|
|
}
|
|
$output .= "\n";
|
|
|
|
// Time-Based Statistics
|
|
$output .= "## Time-Based Statistics\n\n";
|
|
$output .= "| Period | Events |\n";
|
|
$output .= "|--------|-------|\n";
|
|
$output .= "| 24 Hours | " . number_format($events_24h) . " |\n";
|
|
$output .= "| 7 Days | " . number_format($events_7d) . " |\n";
|
|
$output .= "| 30 Days | " . number_format($events_30d) . " |\n\n";
|
|
|
|
// Top Pubkeys
|
|
$output .= "## Top Pubkeys\n\n";
|
|
$output .= "| Name | Events | % |\n";
|
|
$output .= "|------|-------|---|\n";
|
|
foreach ($top_pubkeys as $p) {
|
|
if ($p['name']) {
|
|
$name = $p['name'];
|
|
} else {
|
|
$name = substr($p['pubkey'], 0, 4) . '..' . substr($p['pubkey'], -4);
|
|
}
|
|
$output .= "| $name | " . number_format($p['count']) . " | {$p['pct']}% |\n";
|
|
}
|
|
$output .= "\n";
|
|
|
|
// Footer
|
|
$output .= "---\n";
|
|
$output .= "_Report generated: " . date('Y-m-d H:i:s T') . "_\n";
|
|
|
|
echo $output;
|