162 lines
4.6 KiB
TypeScript
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 },
|
|
};
|
|
},
|
|
};
|
|
}
|