/** * Lightweight process / RPC metrics for production observability. */ const startedAt = Date.now() const counters = { rpcTotal: 0, rpcErrors: 0, rpcDenied: 0, peersConnected: 0, peersDisconnected: 0, pushes: 0, } /** @type {Map} */ const methodCounts = new Map() /** @type {number[]} */ const latencies = [] const MAX_LATENCIES = 500 export function recordRpc(method, { ok = true, denied = false, latencyMs = 0 } = {}) { counters.rpcTotal += 1 if (!ok) counters.rpcErrors += 1 if (denied) counters.rpcDenied += 1 methodCounts.set(method, (methodCounts.get(method) || 0) + 1) if (latencyMs > 0) { latencies.push(latencyMs) if (latencies.length > MAX_LATENCIES) latencies.shift() } } export function recordPeerConnect() { counters.peersConnected += 1 } export function recordPeerDisconnect() { counters.peersDisconnected += 1 } export function recordPush() { counters.pushes += 1 } function percentile(arr, p) { if (!arr.length) return 0 const sorted = [...arr].sort((a, b) => a - b) const idx = Math.min(sorted.length - 1, Math.floor((p / 100) * sorted.length)) return sorted[idx] } /** * Snapshot for getMetrics RPC. * @param {{ peerCount?: number }} [extra] */ export function getMetricsSnapshot(extra = {}) { const mem = process.memoryUsage() return { success: true, type: 'metrics', uptimeMs: Date.now() - startedAt, startedAt: new Date(startedAt).toISOString(), process: { pid: process.pid, node: process.version, rss: mem.rss, heapUsed: mem.heapUsed, heapTotal: mem.heapTotal, external: mem.external, }, rpc: { total: counters.rpcTotal, errors: counters.rpcErrors, denied: counters.rpcDenied, byMethod: Object.fromEntries(methodCounts), latencyMs: { p50: percentile(latencies, 50), p95: percentile(latencies, 95), p99: percentile(latencies, 99), samples: latencies.length, }, }, peers: { connectEvents: counters.peersConnected, disconnectEvents: counters.peersDisconnected, live: extra.peerCount ?? null, }, pushes: counters.pushes, features: { swarm: (() => { const v = String(process.env.ENABLE_SWARM ?? '1').trim().toLowerCase() return !(v === '0' || v === 'false' || v === 'off' || v === 'no') })(), plugins: process.env.ENABLE_PLUGINS === '1' || process.env.ENABLE_PLUGINS === 'true', holesail: (() => { const v = String(process.env.ENABLE_HOLESAIL ?? '1').trim().toLowerCase() return !(v === '0' || v === 'false' || v === 'off' || v === 'no') })(), peerAllowlist: process.env.PEARDOCK_PEER_ALLOWLIST === '1' || process.env.PEARDOCK_PEER_ALLOWLIST === 'true', }, } }