| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
|
|
| interface PhaseTiming {
|
| phase: string;
|
| startMs: number;
|
| endMs: number;
|
| durationMs: number;
|
| [key: string]: unknown;
|
| }
|
|
|
| const PHASES = ["parse", "validate", "policy", "resolve", "connect", "stream", "finalize"] as const;
|
|
|
| interface TelemetrySummary {
|
| requestId: string;
|
| totalMs: number;
|
| phases: PhaseTiming[];
|
| recordedAt?: number;
|
| }
|
|
|
| export class RequestTelemetry {
|
| requestId: string;
|
| startTime: number;
|
| phases: PhaseTiming[];
|
| private _currentPhase: string | null;
|
| private _phaseStart: number | null;
|
|
|
| constructor(requestId: string) {
|
| this.requestId = requestId;
|
| this.startTime = Date.now();
|
| this.phases = [];
|
| this._currentPhase = null;
|
| this._phaseStart = null;
|
| }
|
|
|
| |
| |
| |
|
|
| startPhase(phase) {
|
| if (this._currentPhase) {
|
| this.endPhase();
|
| }
|
| this._currentPhase = phase;
|
| this._phaseStart = Date.now();
|
| }
|
|
|
| |
| |
| |
|
|
| endPhase(metadata = {}) {
|
| if (!this._currentPhase) return;
|
|
|
| const now = Date.now();
|
| this.phases.push({
|
| phase: this._currentPhase,
|
| startMs: this._phaseStart - this.startTime,
|
| endMs: now - this.startTime,
|
| durationMs: now - this._phaseStart,
|
| ...metadata,
|
| });
|
|
|
| this._currentPhase = null;
|
| this._phaseStart = null;
|
| }
|
|
|
| |
| |
| |
| |
| |
| |
|
|
| async measure(phase, fn) {
|
| this.startPhase(phase);
|
| try {
|
| const result = await fn();
|
| this.endPhase();
|
| return result;
|
| } catch (error) {
|
| this.endPhase({ error: error.message });
|
| throw error;
|
| }
|
| }
|
|
|
| |
| |
| |
|
|
| getSummary() {
|
|
|
| if (this._currentPhase) {
|
| this.endPhase();
|
| }
|
|
|
| return {
|
| requestId: this.requestId,
|
| totalMs: Date.now() - this.startTime,
|
| phases: [...this.phases],
|
| };
|
| }
|
| }
|
|
|
|
|
|
|
| const MAX_HISTORY = 1000;
|
|
|
| const history = [];
|
|
|
| |
| |
| |
|
|
| export function recordTelemetry(telemetry) {
|
| const summary = telemetry.getSummary();
|
| summary.recordedAt = Date.now();
|
| history.push(summary);
|
| while (history.length > MAX_HISTORY) {
|
| history.shift();
|
| }
|
| }
|
|
|
| |
| |
| |
| |
| |
|
|
| function percentile(sorted, p) {
|
| if (sorted.length === 0) return 0;
|
| const idx = Math.ceil((p / 100) * sorted.length) - 1;
|
| return sorted[Math.max(0, idx)];
|
| }
|
|
|
| |
| |
| |
| |
|
|
| export function getTelemetrySummary(windowMs = 300000) {
|
| const cutoff = Date.now() - windowMs;
|
| const recent = history.filter((h) => {
|
| return (h.recordedAt || 0) >= cutoff;
|
| });
|
|
|
| if (recent.length === 0) {
|
| return { count: 0, avg: 0, p50: 0, p95: 0, p99: 0, phaseBreakdown: {} };
|
| }
|
|
|
| const totals = recent.map((h) => h.totalMs).sort((a, b) => a - b);
|
| const avg = Math.round(totals.reduce((sum, value) => sum + value, 0) / totals.length);
|
|
|
|
|
| const phaseBreakdown = {};
|
| for (const phase of PHASES) {
|
| const durations = recent
|
| .flatMap((h) => h.phases.filter((p) => p.phase === phase).map((p) => p.durationMs))
|
| .sort((a, b) => a - b);
|
|
|
| if (durations.length > 0) {
|
| phaseBreakdown[phase] = {
|
| count: durations.length,
|
| p50: percentile(durations, 50),
|
| p95: percentile(durations, 95),
|
| avg: Math.round(durations.reduce((a, b) => a + b, 0) / durations.length),
|
| };
|
| }
|
| }
|
|
|
| return {
|
| count: recent.length,
|
| avg,
|
| p50: percentile(totals, 50),
|
| p95: percentile(totals, 95),
|
| p99: percentile(totals, 99),
|
| phaseBreakdown,
|
| };
|
| }
|
|
|
| export { PHASES };
|
|
|