Files
WRNexusJS/packages/observability/src/metrics.ts
T
2026-08-01 10:04:42 +05:30

82 lines
2.1 KiB
TypeScript

export type MetricLabels = Record<string, string | number | boolean>;
export interface MetricPoint {
name: string;
type: "counter" | "gauge" | "histogram";
value: number;
count?: number;
sum?: number;
min?: number;
max?: number;
labels: MetricLabels;
timestamp: number;
}
function labelKey(labels: MetricLabels): string {
return Object.entries(labels)
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
.map(([key, value]) => `${key}=${String(value)}`)
.join(",");
}
interface StoredMetric extends MetricPoint {
key: string;
}
export class MetricsRegistry {
private points = new Map<string, StoredMetric>();
constructor(private readonly clock: () => number = Date.now) {}
increment(name: string, value = 1, labels: MetricLabels = {}): void {
const key = `counter:${name}:${labelKey(labels)}`;
const current = this.points.get(key);
this.points.set(key, {
key,
name,
type: "counter",
value: (current?.value ?? 0) + value,
labels: { ...labels },
timestamp: this.clock(),
});
}
gauge(name: string, value: number, labels: MetricLabels = {}): void {
const key = `gauge:${name}:${labelKey(labels)}`;
this.points.set(key, {
key,
name,
type: "gauge",
value,
labels: { ...labels },
timestamp: this.clock(),
});
}
observe(name: string, value: number, labels: MetricLabels = {}): void {
const key = `histogram:${name}:${labelKey(labels)}`;
const current = this.points.get(key);
const count = (current?.count ?? 0) + 1;
const sum = (current?.sum ?? 0) + value;
this.points.set(key, {
key,
name,
type: "histogram",
value: sum / count,
count,
sum,
min: current?.min === undefined ? value : Math.min(current.min, value),
max: current?.max === undefined ? value : Math.max(current.max, value),
labels: { ...labels },
timestamp: this.clock(),
});
}
snapshot(): MetricPoint[] {
return [...this.points.values()].map(({ key: _key, ...point }) => ({ ...point }));
}
reset(): void {
this.points.clear();
}
}