Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | import { Injectable, Logger } from '@nestjs/common'; export interface MetricPoint { name: string; value: number; labels?: Record<string, string>; timestamp: Date; } @Injectable() export class MetricsService { private readonly logger = new Logger(MetricsService.name); private readonly counters = new Map<string, number>(); private readonly gauges = new Map<string, number>(); private readonly histograms = new Map<string, number[]>(); incrementCounter( name: string, labels?: Record<string, string>, amount = 1, ): void { const key = this.buildKey(name, labels); this.counters.set(key, (this.counters.get(key) ?? 0) + amount); } setGauge(name: string, value: number, labels?: Record<string, string>): void { const key = this.buildKey(name, labels); this.gauges.set(key, value); } recordHistogram( name: string, value: number, labels?: Record<string, string>, ): void { const key = this.buildKey(name, labels); const arr = this.histograms.get(key) ?? []; arr.push(value); if (arr.length > 10000) arr.shift(); // sliding window this.histograms.set(key, arr); } getCounter(name: string, labels?: Record<string, string>): number { return this.counters.get(this.buildKey(name, labels)) ?? 0; } getGauge(name: string, labels?: Record<string, string>): number { return this.gauges.get(this.buildKey(name, labels)) ?? 0; } getHistogramStats( name: string, labels?: Record<string, string>, ): { count: number; avg: number; p50: number; p95: number; p99: number; max: number; } | null { const arr = this.histograms.get(this.buildKey(name, labels)); if (!arr || arr.length === 0) return null; const sorted = [...arr].sort((a, b) => a - b); const count = sorted.length; const avg = sorted.reduce((a, b) => a + b, 0) / count; return { count, avg: Math.round(avg * 100) / 100, p50: sorted[Math.floor(count * 0.5)], p95: sorted[Math.floor(count * 0.95)], p99: sorted[Math.floor(count * 0.99)], max: sorted[count - 1], }; } getAllMetrics(): { counters: Record<string, number>; gauges: Record<string, number>; } { return { counters: Object.fromEntries(this.counters), gauges: Object.fromEntries(this.gauges), }; } private buildKey(name: string, labels?: Record<string, string>): string { if (!labels || Object.keys(labels).length === 0) return name; const labelStr = Object.entries(labels) .sort(([a], [b]) => a.localeCompare(b)) .map(([k, v]) => `${k}=${v}`) .join(','); return `${name}{${labelStr}}`; } } |