unleash-server
Version:
Unleash is an enterprise ready feature flag service. It provides different strategies for handling feature flags.
171 lines • 6.34 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;
}
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