155 lines
5.9 KiB
PHP
155 lines
5.9 KiB
PHP
<?php
|
|
/**
|
|
* admin2/api/stats.php — Statistics page data.
|
|
* Returns DB size, event counts, kind distribution, top pubkeys,
|
|
* time-based stats, and event-rate delta (for the chart).
|
|
*/
|
|
require_once __DIR__ . '/../lib/helpers.php';
|
|
|
|
$pdo = db();
|
|
|
|
// Database size
|
|
$db_size = '-';
|
|
try {
|
|
$bytes = $pdo->query("SELECT pg_database_size(current_database())")->fetchColumn();
|
|
$db_size = format_bytes(intval($bytes));
|
|
} catch (PDOException $e) {}
|
|
|
|
// Total events
|
|
$total_events = intval($pdo->query("SELECT COUNT(*) FROM events")->fetchColumn());
|
|
|
|
// Event rate: events with first_seen in last 10 seconds
|
|
$events_delta = 0;
|
|
try {
|
|
$events_delta = intval($pdo->query("SELECT COUNT(*) FROM events WHERE first_seen >= EXTRACT(EPOCH FROM NOW())::BIGINT - 10")->fetchColumn());
|
|
} catch (PDOException $e) {}
|
|
|
|
// Process info (from pg_stat_activity)
|
|
$process_id = '-';
|
|
$ws_connections = '-';
|
|
try {
|
|
$pid = $pdo->query("SELECT pg_backend_pid()")->fetchColumn();
|
|
$process_id = strval($pid);
|
|
$ws_connections = intval($pdo->query("SELECT count(*) FROM pg_stat_activity WHERE state = 'active' AND pid != pg_backend_pid()")->fetchColumn());
|
|
} catch (PDOException $e) {}
|
|
|
|
// Active subscriptions
|
|
$active_subscriptions = 0;
|
|
try {
|
|
$active_subscriptions = intval($pdo->query("SELECT count(*) FROM subscriptions WHERE active = true")->fetchColumn());
|
|
} catch (PDOException $e) {}
|
|
|
|
// Memory/CPU (from /proc on Linux)
|
|
$memory_usage = '-';
|
|
$cpu_usage = '-';
|
|
$cpu_core = '-';
|
|
if (is_readable('/proc/meminfo')) {
|
|
$mem = parse_ini_file('/proc/meminfo');
|
|
$total = intval($mem['MemTotal'] ?? 0) * 1024;
|
|
$avail = intval($mem['MemAvailable'] ?? 0) * 1024;
|
|
if ($total > 0) $memory_usage = format_bytes($total - $avail) . ' / ' . format_bytes($total);
|
|
}
|
|
if (is_readable('/proc/cpuinfo')) {
|
|
$cores = intval(shell_exec('nproc 2>/dev/null') ?: 1);
|
|
$cpu_core = strval($cores) . ' cores';
|
|
}
|
|
$cpu_usage = @file_get_contents('/proc/loadavg');
|
|
if ($cpu_usage !== false) $cpu_usage = trim(explode(' ', $cpu_usage)[0] ?? '-');
|
|
|
|
// Oldest / newest event
|
|
$oldest_event = '-';
|
|
$newest_event = '-';
|
|
try {
|
|
$oldest = $pdo->query("SELECT to_timestamp(MIN(created_at)) FROM events")->fetchColumn();
|
|
$newest = $pdo->query("SELECT to_timestamp(MAX(created_at)) FROM events")->fetchColumn();
|
|
if ($oldest) $oldest_event = substr($oldest, 0, 19);
|
|
if ($newest) $newest_event = substr($newest, 0, 19);
|
|
} 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
|
|
$kinds = [];
|
|
try {
|
|
$rows = $pdo->query("SELECT kind, COUNT(*) AS cnt FROM events GROUP BY kind ORDER BY cnt DESC LIMIT 20")->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 (with names from profiles cache)
|
|
$top_pubkeys = [];
|
|
try {
|
|
$rows = $pdo->query("
|
|
SELECT pubkey, COUNT(*) AS cnt
|
|
FROM events
|
|
GROUP BY pubkey
|
|
ORDER BY cnt DESC LIMIT 20
|
|
")->fetchAll();
|
|
// Batch-resolve profile names from the profiles cache table.
|
|
$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) {}
|
|
|
|
// Name-field usage stats (from profiles cache)
|
|
$name_field_usage = ['both' => 0, 'name_only' => 0, 'display_only' => 0, 'neither' => 0, 'both_differ' => 0, 'total' => 0];
|
|
try {
|
|
$row = $pdo->query("
|
|
SELECT count(*) FILTER (WHERE name <> '' AND display_name <> '') AS both,
|
|
count(*) FILTER (WHERE name <> '' AND display_name = '') AS name_only,
|
|
count(*) FILTER (WHERE name = '' AND display_name <> '') AS display_only,
|
|
count(*) FILTER (WHERE name = '' AND display_name = '') AS neither,
|
|
count(*) FILTER (WHERE name <> '' AND display_name <> '' AND name <> display_name) AS both_differ,
|
|
count(*) AS total
|
|
FROM profiles
|
|
")->fetch();
|
|
if ($row) {
|
|
$name_field_usage = array_map('intval', $row);
|
|
}
|
|
} catch (PDOException $e) {}
|
|
|
|
json_response([
|
|
'db_size' => $db_size,
|
|
'total_events' => $total_events,
|
|
'events_delta' => $events_delta,
|
|
'process_id' => $process_id,
|
|
'ws_connections' => $ws_connections,
|
|
'active_subscriptions' => $active_subscriptions,
|
|
'memory_usage' => $memory_usage,
|
|
'cpu_usage' => $cpu_usage,
|
|
'cpu_core' => $cpu_core,
|
|
'oldest_event' => $oldest_event,
|
|
'newest_event' => $newest_event,
|
|
'events_24h' => $events_24h,
|
|
'events_7d' => $events_7d,
|
|
'events_30d' => $events_30d,
|
|
'kinds' => $kinds,
|
|
'top_pubkeys' => $top_pubkeys,
|
|
'name_field_usage' => $name_field_usage,
|
|
]);
|
|
|
|
/** Format bytes as human-readable. */
|
|
function format_bytes(int $bytes): string {
|
|
if ($bytes < 1024) return $bytes . ' B';
|
|
if ($bytes < 1048576) return round($bytes / 1024, 1) . ' KB';
|
|
if ($bytes < 1073741824) return round($bytes / 1048576, 1) . ' MB';
|
|
return round($bytes / 1073741824, 2) . ' GB';
|
|
}
|