82 lines
2.1 KiB
TypeScript
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();
|
|
}
|
|
}
|