|
|
|
|
@ -1,6 +1,8 @@
|
|
|
|
|
export class Metrics {
|
|
|
|
|
private static counters = new Map<string, number>();
|
|
|
|
|
private static gauges = new Map<string, number>();
|
|
|
|
|
private static labeledCounters = new Map<string, Map<string, number>>();
|
|
|
|
|
private static labeledGauges = new Map<string, Map<string, number>>();
|
|
|
|
|
|
|
|
|
|
static enabled(): boolean {
|
|
|
|
|
if (typeof process !== 'undefined' && process.env) {
|
|
|
|
|
@ -13,14 +15,48 @@ export class Metrics {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static inc(name: string, value: number = 1): void {
|
|
|
|
|
private static serializeLabels(labels: Record<string, string> | undefined | null): string | null {
|
|
|
|
|
if (!labels) return null;
|
|
|
|
|
const keys = Object.keys(labels).sort();
|
|
|
|
|
const parts = keys.map(k => {
|
|
|
|
|
const val = String(labels[k] ?? '').replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n');
|
|
|
|
|
return `${k}="${val}"`;
|
|
|
|
|
});
|
|
|
|
|
return parts.join(',');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static ensureLabeledMap(map: Map<string, Map<string, number>>, name: string): Map<string, number> {
|
|
|
|
|
let inner = map.get(name);
|
|
|
|
|
if (!inner) {
|
|
|
|
|
inner = new Map<string, number>();
|
|
|
|
|
map.set(name, inner);
|
|
|
|
|
}
|
|
|
|
|
return inner;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static inc(name: string, value: number = 1, labels?: Record<string, string>): void {
|
|
|
|
|
if (!this.enabled()) return;
|
|
|
|
|
if (labels && Object.keys(labels).length > 0) {
|
|
|
|
|
const key = this.serializeLabels(labels);
|
|
|
|
|
if (!key) return;
|
|
|
|
|
const inner = this.ensureLabeledMap(this.labeledCounters, name);
|
|
|
|
|
const v = inner.get(key) || 0;
|
|
|
|
|
inner.set(key, v + value);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const v = this.counters.get(name) || 0;
|
|
|
|
|
this.counters.set(name, v + value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static set(name: string, value: number): void {
|
|
|
|
|
static set(name: string, value: number, labels?: Record<string, string>): void {
|
|
|
|
|
if (!this.enabled()) return;
|
|
|
|
|
if (labels && Object.keys(labels).length > 0) {
|
|
|
|
|
const key = this.serializeLabels(labels);
|
|
|
|
|
if (!key) return;
|
|
|
|
|
const inner = this.ensureLabeledMap(this.labeledGauges, name);
|
|
|
|
|
inner.set(key, value);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
this.gauges.set(name, value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -35,6 +71,12 @@ export class Metrics {
|
|
|
|
|
const json = {
|
|
|
|
|
counters: Object.fromEntries(this.counters.entries()),
|
|
|
|
|
gauges: Object.fromEntries(this.gauges.entries()),
|
|
|
|
|
labeledCounters: Object.fromEntries(
|
|
|
|
|
Array.from(this.labeledCounters.entries()).map(([name, inner]) => [name, Object.fromEntries(inner.entries())])
|
|
|
|
|
),
|
|
|
|
|
labeledGauges: Object.fromEntries(
|
|
|
|
|
Array.from(this.labeledGauges.entries()).map(([name, inner]) => [name, Object.fromEntries(inner.entries())])
|
|
|
|
|
)
|
|
|
|
|
};
|
|
|
|
|
return JSON.stringify(json);
|
|
|
|
|
}
|
|
|
|
|
@ -43,15 +85,29 @@ export class Metrics {
|
|
|
|
|
lines.push(`# TYPE ${k} counter`);
|
|
|
|
|
lines.push(`${k} ${v}`);
|
|
|
|
|
}
|
|
|
|
|
for (const [name, inner] of this.labeledCounters.entries()) {
|
|
|
|
|
lines.push(`# TYPE ${name} counter`);
|
|
|
|
|
for (const [labelKey, v] of inner.entries()) {
|
|
|
|
|
lines.push(`${name}{${labelKey}} ${v}`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for (const [k, v] of this.gauges.entries()) {
|
|
|
|
|
lines.push(`# TYPE ${k} gauge`);
|
|
|
|
|
lines.push(`${k} ${v}`);
|
|
|
|
|
}
|
|
|
|
|
for (const [name, inner] of this.labeledGauges.entries()) {
|
|
|
|
|
lines.push(`# TYPE ${name} gauge`);
|
|
|
|
|
for (const [labelKey, v] of inner.entries()) {
|
|
|
|
|
lines.push(`${name}{${labelKey}} ${v}`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return lines.join('\n') + '\n';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static reset(): void {
|
|
|
|
|
this.counters.clear();
|
|
|
|
|
this.gauges.clear();
|
|
|
|
|
this.labeledCounters.clear();
|
|
|
|
|
this.labeledGauges.clear();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|