UNPKG

unleash-server

Version:

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

171 lines 6.34 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; } transformLabels(labels) { return Object.fromEntries(Object.entries(labels).map(([labelKey, value]) => [ `unleash_${this.sanitizeName(labelKey)}`, value, ])); } addOriginLabel(sample) { return { ...(sample.labels || {}), origin: sample.labels?.origin || 'sdk', }; } translateMetric(metric) { const sanitizedName = this.sanitizeName(metric.name); const prefixedName = `unleash_${metric.type}_${sanitizedName}`; const existingMetric = this.registry.getSingleMetric(prefixedName); const allLabelNames = new Set(); allLabelNames.add('unleash_origin'); for (const sample of metric.samples) { if (sample.labels) { Object.keys(sample.labels).forEach((label) => { allLabelNames.add(`unleash_${label}`); }); } } const labelNames = Array.from(allLabelNames).map((labelName) => this.sanitizeName(labelName)); if (metric.type === 'counter') { let counter; if (existingMetric && existingMetric instanceof Counter) { if (this.hasNewLabels(existingMetric, labelNames)) { this.registry.removeSingleMetric(prefixedName); counter = new Counter({ name: prefixedName, help: metric.help, registers: [this.registry], labelNames, }); } else { counter = existingMetric; } } else { counter = new Counter({ name: prefixedName, help: metric.help, registers: [this.registry], labelNames, }); } for (const sample of metric.samples) { counter.inc(this.transformLabels(this.addOriginLabel(sample)), sample.value); } return counter; } else if (metric.type === 'gauge') { let gauge; if (existingMetric && existingMetric instanceof Gauge) { if (this.hasNewLabels(existingMetric, labelNames)) { this.registry.removeSingleMetric(prefixedName); gauge = new Gauge({ name: prefixedName, help: metric.help, registers: [this.registry], labelNames, }); } else { gauge = existingMetric; } } else { gauge = new Gauge({ name: prefixedName, help: metric.help, registers: [this.registry], labelNames, }); } for (const sample of metric.samples) { gauge.set(this.transformLabels(this.addOriginLabel(sample)), sample.value); } return gauge; } else if (metric.type === 'histogram') { if (!metric.samples || metric.samples.length === 0) { return null; } let histogram; if (existingMetric && existingMetric instanceof BatchHistogram) { const firstSample = metric.samples[0]; // all samples should have same buckets const needsRecreation = !firstSample?.buckets || this.hasNewLabels(existingMetric, labelNames) || this.hasNewBuckets(existingMetric, firstSample.buckets); if (needsRecreation) { this.registry.removeSingleMetric(prefixedName); histogram = new BatchHistogram({ name: prefixedName, help: metric.help, registry: this.registry, labelNames, }); } else { histogram = existingMetric; } } else { histogram = new BatchHistogram({ name: prefixedName, help: metric.help, registry: this.registry, labelNames, }); } for (const sample of metric.samples) { const transformedLabels = this.transformLabels({ ...sample.labels, origin: sample.labels?.origin || 'sdk', }); histogram.recordBatch(transformedLabels, { count: sample.count, sum: sample.sum, buckets: sample.buckets, }); } return histogram; } 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