mirror of
https://github.com/complexcaresolutions/cms.c2sgmbh.git
synced 2026-03-17 17:24:12 +00:00
Next.js webpack build cannot resolve .js extensions for .ts files. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
96 lines
2.8 KiB
TypeScript
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)
|