| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| 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, p50: 0, p95: 0, p99: 0, phaseBreakdown: {} }; |
| } |
|
|
| const totals = recent.map((h) => h.totalMs).sort((a, b) => a - b); |
|
|
| |
| 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, |
| p50: percentile(totals, 50), |
| p95: percentile(totals, 95), |
| p99: percentile(totals, 99), |
| phaseBreakdown, |
| }; |
| } |
|
|
| export { PHASES }; |
|
|