feat(smart-proxy): add socket-handler relay, fast-path port-only forwarding, metrics and bridge improvements, and various TS/Rust integration fixes
This commit is contained in:
@@ -6,7 +6,11 @@ import type { RustProxyBridge } from './rust-proxy-bridge.js';
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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;
|
||||
@@ -14,30 +18,28 @@ export class RustMetricsAdapter implements IMetrics {
|
||||
private pollTimer: ReturnType<typeof setInterval> | 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
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)
|
||||
}
|
||||
// Immediate first poll so cache is populated ASAP
|
||||
this.poll();
|
||||
this.pollTimer = setInterval(() => {
|
||||
this.poll();
|
||||
}, this.pollIntervalMs);
|
||||
if (this.pollTimer.unref) {
|
||||
this.pollTimer.unref();
|
||||
@@ -55,25 +57,36 @@ export class RustMetricsAdapter implements IMetrics {
|
||||
|
||||
public connections = {
|
||||
active: (): number => {
|
||||
return this.cache?.activeConnections ?? this.cache?.active_connections ?? 0;
|
||||
return this.cache?.activeConnections ?? 0;
|
||||
},
|
||||
total: (): number => {
|
||||
return this.cumulativeConnections;
|
||||
return this.cache?.totalConnections ?? 0;
|
||||
},
|
||||
byRoute: (): Map<string, number> => {
|
||||
return new Map();
|
||||
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?.bytesInPerSecond ?? 0, out: this.cache?.bytesOutPerSecond ?? 0 };
|
||||
return {
|
||||
in: this.cache?.throughputInBytesPerSec ?? 0,
|
||||
out: this.cache?.throughputOutBytesPerSec ?? 0,
|
||||
};
|
||||
},
|
||||
recent: (): IThroughputData => {
|
||||
return this.throughput.instant();
|
||||
@@ -85,10 +98,20 @@ export class RustMetricsAdapter implements IMetrics {
|
||||
return this.throughput.instant();
|
||||
},
|
||||
history: (_seconds: number): Array<IThroughputHistoryPoint> => {
|
||||
// Throughput history not yet available from Rust
|
||||
return [];
|
||||
},
|
||||
byRoute: (_windowSeconds?: number): Map<string, IThroughputData> => {
|
||||
return new Map();
|
||||
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();
|
||||
@@ -97,25 +120,27 @@ export class RustMetricsAdapter implements IMetrics {
|
||||
|
||||
public requests = {
|
||||
perSecond: (): number => {
|
||||
return this.cache?.requestsPerSecond ?? 0;
|
||||
// Rust tracks connections, not HTTP requests (TCP-level proxy)
|
||||
return 0;
|
||||
},
|
||||
perMinute: (): number => {
|
||||
return (this.cache?.requestsPerSecond ?? 0) * 60;
|
||||
return 0;
|
||||
},
|
||||
total: (): number => {
|
||||
return this.cache?.totalRequests ?? this.cache?.total_requests ?? 0;
|
||||
// Use total connections as a proxy for total requests
|
||||
return this.cache?.totalConnections ?? 0;
|
||||
},
|
||||
};
|
||||
|
||||
public totals = {
|
||||
bytesIn: (): number => {
|
||||
return this.cumulativeBytesIn;
|
||||
return this.cache?.bytesIn ?? 0;
|
||||
},
|
||||
bytesOut: (): number => {
|
||||
return this.cumulativeBytesOut;
|
||||
return this.cache?.bytesOut ?? 0;
|
||||
},
|
||||
connections: (): number => {
|
||||
return this.cumulativeConnections;
|
||||
return this.cache?.totalConnections ?? 0;
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user