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:
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user