cms.c2sgmbh/src/lib/monitoring/performance-tracker.ts
Martin Porwoll 884d33c0ae fix: remove .js extensions from monitoring module imports
Next.js webpack build cannot resolve .js extensions for .ts files.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 11:51:25 +00:00

96 lines
2.8 KiB
TypeScript

/**
* Performance Tracker
*
* Records HTTP request metrics in a fixed-size ring buffer and computes
* percentile-based performance statistics over configurable time windows.
* Used by the monitoring dashboard to display response time distributions,
* error rates, and throughput.
*/
import type { PerformanceEntry, PerformanceMetrics } from './types'
const PERIOD_MS: Record<string, number> = {
'1h': 3_600_000,
'6h': 21_600_000,
'24h': 86_400_000,
'7d': 604_800_000,
}
const EMPTY_METRICS: PerformanceMetrics = {
avgResponseTimeMs: 0,
p95ResponseTimeMs: 0,
p99ResponseTimeMs: 0,
errorRate: 0,
requestsPerMinute: 0,
}
export class PerformanceTracker {
private readonly buffer: PerformanceEntry[]
private pointer: number = 0
private count: number = 0
private readonly capacity: number
constructor(capacity: number = 10_000) {
this.capacity = capacity
this.buffer = new Array(capacity)
}
track(method: string, path: string, statusCode: number, durationMs: number): void {
this.buffer[this.pointer] = {
timestamp: Date.now(),
method,
path,
statusCode,
durationMs,
}
this.pointer = (this.pointer + 1) % this.capacity
if (this.count < this.capacity) {
this.count++
}
}
getMetrics(period: '1h' | '6h' | '24h' | '7d' = '1h'): PerformanceMetrics {
const cutoff = Date.now() - (PERIOD_MS[period] ?? PERIOD_MS['1h'])
const entries: PerformanceEntry[] = []
for (let i = 0; i < this.count; i++) {
const entry = this.buffer[i]
if (entry && entry.timestamp >= cutoff) {
entries.push(entry)
}
}
if (entries.length === 0) {
return { ...EMPTY_METRICS }
}
const durations = entries.map((e) => e.durationMs).sort((a, b) => a - b)
const avg = durations.reduce((sum, d) => sum + d, 0) / durations.length
const p95 = percentile(durations, 0.95)
const p99 = percentile(durations, 0.99)
const errorCount = entries.filter((e) => e.statusCode >= 500).length
const errorRate = errorCount / entries.length
const earliestTimestamp = Math.min(...entries.map((e) => e.timestamp))
const windowMinutes = Math.max((Date.now() - earliestTimestamp) / 60_000, 1)
const requestsPerMinute = entries.length / windowMinutes
return {
avgResponseTimeMs: Math.round(avg),
p95ResponseTimeMs: p95,
p99ResponseTimeMs: p99,
errorRate: Math.round(errorRate * 1000) / 1000,
requestsPerMinute: Math.round(requestsPerMinute * 10) / 10,
}
}
}
function percentile(sorted: number[], p: number): number {
const index = Math.floor(sorted.length * p)
return sorted[Math.min(index, sorted.length - 1)]
}
/** Singleton instance used across the application. */
export const performanceTracker = new PerformanceTracker(10_000)