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
+30
View File
@@ -1,5 +1,6 @@
import type { GatewayRequest, OutboundMessage } from '../protocol.js';
import { makeResponse, makeError, ErrorCode } from '../protocol.js';
import type { MetricsSnapshot, EventEntry, ActiveRequestInfo } from '../metrics.js';
/** Per-session token usage report returned by system.tokenUsage. */
export interface TokenUsageEntry {
@@ -21,6 +22,12 @@ export interface SystemHandlerDeps {
getUsage?: () => { totalSessions: number; activeConnections: number };
/** Optional callback to retrieve per-session token usage data. */
getTokenUsage?: () => TokenUsageEntry[];
/** Optional callback to retrieve aggregated metrics snapshot. */
getMetrics?: () => MetricsSnapshot;
/** Optional callback to retrieve recent events. */
getEvents?: (opts?: { level?: string; limit?: number }) => EventEntry[];
/** Optional callback to retrieve active requests. */
getActiveRequests?: () => ActiveRequestInfo[];
}
export function createSystemHandlers(deps: SystemHandlerDeps) {
@@ -75,5 +82,28 @@ export function createSystemHandlers(deps: SystemHandlerDeps) {
const sessions = deps.getTokenUsage?.() ?? [];
return makeResponse(request.id, { sessions });
},
'system.metrics': async (request: GatewayRequest): Promise<OutboundMessage> => {
if (!deps.getMetrics) {
return makeResponse(request.id, {});
}
return makeResponse(request.id, deps.getMetrics());
},
'system.events': async (request: GatewayRequest): Promise<OutboundMessage> => {
if (!deps.getEvents) {
return makeResponse(request.id, { events: [] });
}
const params = request.params as { level?: string; limit?: number } | undefined;
const events = deps.getEvents({ level: params?.level, limit: params?.limit });
return makeResponse(request.id, { events });
},
'system.activeRequests': async (request: GatewayRequest): Promise<OutboundMessage> => {
if (!deps.getActiveRequests) {
return makeResponse(request.id, { requests: [] });
}
return makeResponse(request.id, { requests: deps.getActiveRequests() });
},
};
}