SPB Git forge
38commits 1branches 0releases
338.7 MBsize
maindefault branch
3 h agolast push
HTML 53.9% TypeScript 44.5% JavaScript 0.6% SQL 0.5%
3.4 KB · 52 lines typescript
Raw Blame History
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