Files
smartproxy/ts/proxies/smart-proxy/rust-metrics-adapter.ts

162 lines
4.6 KiB
TypeScript

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.
*
* Rust Metrics JSON fields (camelCase via serde):
* activeConnections, totalConnections, bytesIn, bytesOut,
* throughputInBytesPerSec, throughputOutBytesPerSec,
* routes: { [routeName]: { activeConnections, totalConnections, bytesIn, bytesOut, ... } }
*/
export class RustMetricsAdapter implements IMetrics {
private bridge: RustProxyBridge;
private cache: any = null;
private pollTimer: ReturnType<typeof setInterval> | null = null;
private pollIntervalMs: number;
constructor(bridge: RustProxyBridge, pollIntervalMs = 1000) {
this.bridge = bridge;
this.pollIntervalMs = pollIntervalMs;
}
/**
* Poll Rust for metrics once. Can be awaited to ensure cache is fresh.
*/
public async poll(): Promise<void> {
try {
this.cache = await this.bridge.getMetrics();
} catch {
// Ignore poll errors (bridge may be shutting down)
}
}
public startPolling(): void {
if (this.pollTimer) return;
// Immediate first poll so cache is populated ASAP
this.poll();
this.pollTimer = setInterval(() => {
this.poll();
}, 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 ?? 0;
},
total: (): number => {
return this.cache?.totalConnections ?? 0;
},
byRoute: (): Map<string, number> => {
const result = new Map<string, number>();
if (this.cache?.routes) {
for (const [name, rm] of Object.entries(this.cache.routes)) {
result.set(name, (rm as any).activeConnections ?? 0);
}
}
return result;
},
byIP: (): Map<string, number> => {
// Per-IP tracking not yet available from Rust
return new Map();
},
topIPs: (_limit?: number): Array<{ ip: string; count: number }> => {
// Per-IP tracking not yet available from Rust
return [];
},
};
public throughput = {
instant: (): IThroughputData => {
return {
in: this.cache?.throughputInBytesPerSec ?? 0,
out: this.cache?.throughputOutBytesPerSec ?? 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<IThroughputHistoryPoint> => {
// Throughput history not yet available from Rust
return [];
},
byRoute: (_windowSeconds?: number): Map<string, IThroughputData> => {
const result = new Map<string, IThroughputData>();
if (this.cache?.routes) {
for (const [name, rm] of Object.entries(this.cache.routes)) {
result.set(name, {
in: (rm as any).throughputInBytesPerSec ?? 0,
out: (rm as any).throughputOutBytesPerSec ?? 0,
});
}
}
return result;
},
byIP: (_windowSeconds?: number): Map<string, IThroughputData> => {
return new Map();
},
};
public requests = {
perSecond: (): number => {
// Rust tracks connections, not HTTP requests (TCP-level proxy)
return 0;
},
perMinute: (): number => {
return 0;
},
total: (): number => {
// Use total connections as a proxy for total requests
return this.cache?.totalConnections ?? 0;
},
};
public totals = {
bytesIn: (): number => {
return this.cache?.bytesIn ?? 0;
},
bytesOut: (): number => {
return this.cache?.bytesOut ?? 0;
},
connections: (): number => {
return this.cache?.totalConnections ?? 0;
},
};
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 },
};
},
};
}