unleash-server
Version:
Unleash is an enterprise ready feature flag service. It provides different strategies for handling feature flags.
155 lines • 5.43 kB
JavaScript
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