UNPKG

unleash-server

Version:

Unleash is an enterprise ready feature flag service. It provides different strategies for handling feature flags.

155 lines 5.43 kB
import { Counter, Gauge } from 'prom-client'; import { BatchHistogram } from './batch-histogram.js'; export class MetricsTranslator { constructor(registry) { this.registry = registry; } sanitizeName(name) { const regex = /[^a-zA-Z0-9_]/g; return name.replace(regex, '_'); } hasNewLabels(existingMetric, newLabelNames) { const existingLabelNames = existingMetric.labelNames || []; return newLabelNames.some((label) => !existingLabelNames.includes(label)); } hasNewBuckets(existingHistogram, newBuckets) { const existingBoundaries = existingHistogram.bucketBoundaries; if (existingBoundaries.size !== newBuckets.length) { return true; } for (const bucket of newBuckets) { if (!existingBoundaries.has(bucket.le)) { return true; } } return false; } buildLabels(sample, type) { const result = { origin: sample.labels?.origin || 'sdk', metric_type: type, }; if (sample.labels) { for (const [key, value] of Object.entries(sample.labels)) { const sanitized = this.sanitizeName(key); if (/^[a-zA-Z_]/.test(sanitized)) { result[sanitized] = value; } } } return result; } resolveCounter(name, help, labelNames) { const existing = this.registry.getSingleMetric(name); if (existing && existing instanceof Counter) { if (!this.hasNewLabels(existing, labelNames)) { return existing; } this.registry.removeSingleMetric(name); } return new Counter({ name, help, registers: [this.registry], labelNames, }); } resolveGauge(name, help, labelNames) { const existing = this.registry.getSingleMetric(name); if (existing && existing instanceof Gauge) { if (!this.hasNewLabels(existing, labelNames)) { return existing; } this.registry.removeSingleMetric(name); } return new Gauge({ name, help, registers: [this.registry], labelNames, }); } resolveHistogram(name, help, labelNames, samples) { const existing = this.registry.getSingleMetric(name); if (existing && existing instanceof BatchHistogram) { const firstSample = samples[0]; const needsRecreation = !firstSample?.buckets || this.hasNewLabels(existing, labelNames) || this.hasNewBuckets(existing, firstSample.buckets); if (!needsRecreation) { return existing; } this.registry.removeSingleMetric(name); } return new BatchHistogram({ name, help, registry: this.registry, labelNames, }); } collectLabelNames(metric) { const allPlain = new Set(['origin', 'metric_type']); for (const sample of metric.samples) { if (sample.labels) { for (const label of Object.keys(sample.labels)) { allPlain.add(label); } } } return Array.from(allPlain) .map((l) => this.sanitizeName(l)) .filter((l) => /^[a-zA-Z_]/.test(l)); } translateMetric(metric) { const sanitizedName = this.sanitizeName(metric.name); const validName = /^[a-zA-Z_]/.test(sanitizedName); const labelNames = this.collectLabelNames(metric); if (!validName) { return null; } if (metric.type === 'counter') { const counter = this.resolveCounter(sanitizedName, metric.help, labelNames); for (const sample of metric.samples) { counter.inc(this.buildLabels(sample, metric.type), sample.value); } return null; } else if (metric.type === 'gauge') { const gauge = this.resolveGauge(sanitizedName, metric.help, labelNames); for (const sample of metric.samples) { gauge.set(this.buildLabels(sample, metric.type), sample.value); } return null; } else if (metric.type === 'histogram') { if (!metric.samples || metric.samples.length === 0) { return null; } const histogram = this.resolveHistogram(sanitizedName, metric.help, labelNames, metric.samples); for (const sample of metric.samples) { histogram.recordBatch(this.buildLabels(sample, metric.type), { count: sample.count, sum: sample.sum, buckets: sample.buckets, }); } return null; } return null; } translateMetrics(metrics) { for (const metric of metrics) { this.translateMetric(metric); } return this.registry; } serializeMetrics() { return this.registry.metrics(); } translateAndSerializeMetrics(metrics) { this.translateMetrics(metrics); return this.serializeMetrics(); } } //# sourceMappingURL=metrics-translator.js.map