import type { IMetrics, IThroughputData, IThroughputHistoryPoint } from './models/metrics-types.js'; import type { RustProxyBridge } from './rust-proxy-bridge.js'; /** * Adapts Rust JSON metrics to the IMetrics interface. * * Polls the Rust binary periodically via the bridge and caches the result. * All IMetrics getters read from the cache synchronously. * Fields not yet in Rust (percentiles, per-IP, history) return zero/empty. */ export class RustMetricsAdapter implements IMetrics { private bridge: RustProxyBridge; private cache: any = null; private pollTimer: ReturnType | null = null; private pollIntervalMs: number; // Cumulative totals tracked across polls private cumulativeBytesIn = 0; private cumulativeBytesOut = 0; private cumulativeConnections = 0; constructor(bridge: RustProxyBridge, pollIntervalMs = 1000) { this.bridge = bridge; this.pollIntervalMs = pollIntervalMs; } public startPolling(): void { if (this.pollTimer) return; this.pollTimer = setInterval(async () => { try { this.cache = await this.bridge.getMetrics(); // Update cumulative totals if (this.cache) { this.cumulativeBytesIn = this.cache.totalBytesIn ?? this.cache.total_bytes_in ?? 0; this.cumulativeBytesOut = this.cache.totalBytesOut ?? this.cache.total_bytes_out ?? 0; this.cumulativeConnections = this.cache.totalConnections ?? this.cache.total_connections ?? 0; } } catch { // Ignore poll errors (bridge may be shutting down) } }, this.pollIntervalMs); if (this.pollTimer.unref) { this.pollTimer.unref(); } } public stopPolling(): void { if (this.pollTimer) { clearInterval(this.pollTimer); this.pollTimer = null; } } // --- IMetrics implementation --- public connections = { active: (): number => { return this.cache?.activeConnections ?? this.cache?.active_connections ?? 0; }, total: (): number => { return this.cumulativeConnections; }, byRoute: (): Map => { return new Map(); }, byIP: (): Map => { return new Map(); }, topIPs: (_limit?: number): Array<{ ip: string; count: number }> => { return []; }, }; public throughput = { instant: (): IThroughputData => { return { in: this.cache?.bytesInPerSecond ?? 0, out: this.cache?.bytesOutPerSecond ?? 0 }; }, recent: (): IThroughputData => { return this.throughput.instant(); }, average: (): IThroughputData => { return this.throughput.instant(); }, custom: (_seconds: number): IThroughputData => { return this.throughput.instant(); }, history: (_seconds: number): Array => { return []; }, byRoute: (_windowSeconds?: number): Map => { return new Map(); }, byIP: (_windowSeconds?: number): Map => { return new Map(); }, }; public requests = { perSecond: (): number => { return this.cache?.requestsPerSecond ?? 0; }, perMinute: (): number => { return (this.cache?.requestsPerSecond ?? 0) * 60; }, total: (): number => { return this.cache?.totalRequests ?? this.cache?.total_requests ?? 0; }, }; public totals = { bytesIn: (): number => { return this.cumulativeBytesIn; }, bytesOut: (): number => { return this.cumulativeBytesOut; }, connections: (): number => { return this.cumulativeConnections; }, }; public percentiles = { connectionDuration: (): { p50: number; p95: number; p99: number } => { return { p50: 0, p95: 0, p99: 0 }; }, bytesTransferred: (): { in: { p50: number; p95: number; p99: number }; out: { p50: number; p95: number; p99: number }; } => { return { in: { p50: 0, p95: 0, p99: 0 }, out: { p50: 0, p95: 0, p99: 0 }, }; }, }; }