spb/datacenterindex
Public
HTML 53.9%
TypeScript 44.5%
JavaScript 0.6%
SQL 0.5%
1/** Minimal Prometheus registry (no dependency): request counter, latency histogram, cache counters, gauges. */23const BUCKETS = [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10];45class Metrics {6 private requests = new Map<string, number>(); // "route|status" → count7 private histCounts = new Map<string, number[]>(); // route → bucket counts (+Inf last)8 private histSum = new Map<string, number>();9 private histCount = new Map<string, number>();10 private cacheHits: Record<string, number> = { memory: 0, redis: 0 };11 private cacheMisses = 0;12 private gauges = new Map<string, number>();13 readonly startedAt = Date.now();1415 observe(route: string, status: number, seconds: number): void {16 const k = `${route}|${status}`;17 this.requests.set(k, (this.requests.get(k) ?? 0) + 1);18 let b = this.histCounts.get(route);19 if (!b) { b = new Array(BUCKETS.length + 1).fill(0) as number[]; this.histCounts.set(route, b); }20 for (let i = 0; i < BUCKETS.length; i++) if (seconds <= BUCKETS[i]!) b[i] = (b[i] ?? 0) + 1;21 b[BUCKETS.length] = (b[BUCKETS.length] ?? 0) + 1;22 this.histSum.set(route, (this.histSum.get(route) ?? 0) + seconds);23 this.histCount.set(route, (this.histCount.get(route) ?? 0) + 1);24 }2526 cacheHit(layer: "memory" | "redis"): void { this.cacheHits[layer] = (this.cacheHits[layer] ?? 0) + 1; }27 cacheMiss(): void { this.cacheMisses++; }28 gauge(name: string, value: number): void { this.gauges.set(name, value); }2930 render(extra: Record<string, number> = {}): string {31 const esc = (s: string) => s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');32 const lines: string[] = [];33 lines.push("# HELP dci_api_requests_total HTTP requests by route and status.", "# TYPE dci_api_requests_total counter");34 for (const [k, v] of this.requests) { const [route, status] = k.split("|"); lines.push(`dci_api_requests_total{route="${esc(route ?? "")}",status="${status}"} ${v}`); }35 lines.push("# HELP dci_api_request_duration_seconds Request latency.", "# TYPE dci_api_request_duration_seconds histogram");36 for (const [route, b] of this.histCounts) {37 for (let i = 0; i < BUCKETS.length; i++) lines.push(`dci_api_request_duration_seconds_bucket{route="${esc(route)}",le="${BUCKETS[i]}"} ${b[i]}`);38 lines.push(`dci_api_request_duration_seconds_bucket{route="${esc(route)}",le="+Inf"} ${b[BUCKETS.length]}`);39 lines.push(`dci_api_request_duration_seconds_sum{route="${esc(route)}"} ${(this.histSum.get(route) ?? 0).toFixed(6)}`);40 lines.push(`dci_api_request_duration_seconds_count{route="${esc(route)}"} ${this.histCount.get(route) ?? 0}`);41 }42 lines.push("# HELP dci_api_cache_hits_total Response cache hits by layer.", "# TYPE dci_api_cache_hits_total counter");43 for (const [layer, v] of Object.entries(this.cacheHits)) lines.push(`dci_api_cache_hits_total{layer="${layer}"} ${v}`);44 lines.push("# HELP dci_api_cache_misses_total Response cache misses.", "# TYPE dci_api_cache_misses_total counter", `dci_api_cache_misses_total ${this.cacheMisses}`);45 lines.push("# HELP dci_api_uptime_seconds Process uptime.", "# TYPE dci_api_uptime_seconds gauge", `dci_api_uptime_seconds ${Math.round((Date.now() - this.startedAt) / 1000)}`);46 for (const [name, v] of [...this.gauges, ...Object.entries(extra)]) lines.push(`# TYPE ${name} gauge`, `${name} ${v}`);47 return lines.join("\n") + "\n";48 }49}5051export const metrics = new Metrics();52