feat(03-01): create MetricsCollector and wire into gateway

- Add MetricsCollector class with counters, model call ring buffer, event ring buffer, and active request tracking
- Add system.metrics, system.events, system.activeRequests RPC handlers
- Add GET /health unauthenticated HTTP endpoint for Docker HEALTHCHECK
- Add totalPending() to LaneQueue for queue depth metrics
- Add 20 tests for MetricsCollector
This commit is contained in:
William Valentin
2026-02-09 21:28:05 -08:00
parent 7565d55551
commit bd1880a44c
6 changed files with 536 additions and 0 deletions
+31
View File
@@ -6,6 +6,7 @@ import { serveStatic } from './static.js';
import { SessionBridge } from './session-bridge.js';
import type { SessionBridgeConfig } from './session-bridge.js';
import { LaneQueue } from './lane-queue.js';
import { MetricsCollector } from './metrics.js';
import { authenticateRequest } from './auth.js';
import type { AuthConfig } from './auth.js';
import {
@@ -67,6 +68,7 @@ export class GatewayServer {
private router: Router;
private sessionBridge: SessionBridge;
private laneQueue: LaneQueue;
private metrics: MetricsCollector;
private connectionMap: Map<WebSocket, string> = new Map();
private config: GatewayServerConfig;
private startTime: number = Date.now();
@@ -83,6 +85,9 @@ export class GatewayServer {
});
this.laneQueue = new LaneQueue();
this.metrics = new MetricsCollector({
getQueueDepth: () => this.laneQueue.totalPending(),
});
this.router = new Router();
this.registerHandlers();
}
@@ -103,6 +108,9 @@ export class GatewayServer {
activeConnections: this.sessionBridge.connectionCount,
}),
getTokenUsage: this.config.getTokenUsage,
getMetrics: () => this.metrics.getSnapshot(),
getEvents: (opts) => this.metrics.getEvents(opts),
getActiveRequests: () => this.metrics.getActiveRequests(),
});
const sessionHandlers = createSessionHandlers({
@@ -118,6 +126,7 @@ export class GatewayServer {
const agentHandlers = createAgentHandlers({
sessionBridge: this.sessionBridge,
laneQueue: this.laneQueue,
metrics: this.metrics,
});
// Config handlers (only if config object is provided)
@@ -264,6 +273,23 @@ export class GatewayServer {
}
}
// Health endpoint — unauthenticated for Docker HEALTHCHECK / monitoring
if (req.method === 'GET' && req.url === '/health') {
const channelList = this.config.channelRegistry?.list().map(a => a.name) ?? [];
const body = JSON.stringify({
status: 'ok',
uptime: Math.floor((Date.now() - this.startTime) / 1000),
version: this.config.version ?? '0.1.0',
sessions: this.sessionBridge.listSessions().length,
connections: this.sessionBridge.connectionCount,
tools: this.config.toolRegistry.list().length,
channels: channelList,
});
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(body);
return;
}
// Gmail Pub/Sub push route — bypass gateway auth (Google sends push notifications directly)
if (this.config.gmailHandler && req.method === 'POST' && req.url?.startsWith('/gmail/push')) {
try {
@@ -350,6 +376,11 @@ export class GatewayServer {
return this.sessionBridge;
}
/** Get the metrics collector (for external wiring). */
getMetrics(): MetricsCollector {
return this.metrics;
}
/** Get list of registered methods. */
getMethods(): string[] {
return this.router.listMethods();