All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.opentelemetry.contrib.metrics.prometheus.clientbridge.MetricAdapter Maven / Gradle / Ivy

There is a newer version: 1.42.0-alpha
Show newest version
/*
 * Copyright The OpenTelemetry Authors
 * SPDX-License-Identifier: Apache-2.0
 */

package io.opentelemetry.contrib.metrics.prometheus.clientbridge;

import static io.prometheus.client.Collector.doubleToGoString;

import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.trace.SpanContext;
import io.opentelemetry.sdk.metrics.data.AggregationTemporality;
import io.opentelemetry.sdk.metrics.data.DoubleExemplarData;
import io.opentelemetry.sdk.metrics.data.DoublePointData;
import io.opentelemetry.sdk.metrics.data.ExemplarData;
import io.opentelemetry.sdk.metrics.data.HistogramPointData;
import io.opentelemetry.sdk.metrics.data.LongExemplarData;
import io.opentelemetry.sdk.metrics.data.LongPointData;
import io.opentelemetry.sdk.metrics.data.MetricData;
import io.opentelemetry.sdk.metrics.data.MetricDataType;
import io.opentelemetry.sdk.metrics.data.PointData;
import io.opentelemetry.sdk.metrics.data.SumData;
import io.opentelemetry.sdk.metrics.data.SummaryPointData;
import io.opentelemetry.sdk.metrics.data.ValueAtQuantile;
import io.prometheus.client.Collector;
import io.prometheus.client.Collector.MetricFamilySamples;
import io.prometheus.client.Collector.MetricFamilySamples.Sample;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import javax.annotation.Nullable;

/**
 * Util methods to convert OpenTelemetry Metrics data models to Prometheus data models.
 *
 * 

Each OpenTelemetry {@link MetricData} will be converted to a Prometheus {@link * MetricFamilySamples}, and each {@code Point} of the {@link MetricData} will be converted to * Prometheus {@link Sample}s. * *

{@code DoublePoint}, {@code LongPoint} will be converted to a single {@link Sample}. {@code * Summary} will be converted to two {@link Sample}s (sum and count) plus the number of Percentile * values {@code Sample}s * *

Please note that Prometheus Metric and Label name can only have alphanumeric characters and * underscore. All other characters will be sanitized by underscores. */ final class MetricAdapter { static final String SAMPLE_SUFFIX_COUNT = "_count"; static final String SAMPLE_SUFFIX_SUM = "_sum"; static final String SAMPLE_SUFFIX_BUCKET = "_bucket"; static final String LABEL_NAME_QUANTILE = "quantile"; static final String LABEL_NAME_LE = "le"; // Converts a MetricData to a Prometheus MetricFamilySamples. static MetricFamilySamples toMetricFamilySamples(MetricData metricData) { String cleanMetricName = cleanMetricName(metricData.getName()); Collector.Type type = toMetricFamilyType(metricData); return new MetricFamilySamples( cleanMetricName, type, metricData.getDescription(), toSamples(cleanMetricName, metricData.getType(), Serializer.getPoints(metricData))); } private static String cleanMetricName(String descriptorMetricName) { return Collector.sanitizeMetricName(descriptorMetricName); } static Collector.Type toMetricFamilyType(MetricData metricData) { switch (metricData.getType()) { case LONG_GAUGE: case DOUBLE_GAUGE: return Collector.Type.GAUGE; case LONG_SUM: SumData longSumData = metricData.getLongSumData(); if (longSumData.isMonotonic() && longSumData.getAggregationTemporality() == AggregationTemporality.CUMULATIVE) { return Collector.Type.COUNTER; } return Collector.Type.GAUGE; case DOUBLE_SUM: SumData doubleSumData = metricData.getDoubleSumData(); if (doubleSumData.isMonotonic() && doubleSumData.getAggregationTemporality() == AggregationTemporality.CUMULATIVE) { return Collector.Type.COUNTER; } return Collector.Type.GAUGE; case SUMMARY: return Collector.Type.SUMMARY; case HISTOGRAM: return Collector.Type.HISTOGRAM; case EXPONENTIAL_HISTOGRAM: return Collector.Type.UNKNOWN; // todo exporter for exponential histogram } return Collector.Type.UNKNOWN; } static final Function sanitizer = new NameSanitizer(); // Converts a list of points from MetricData to a list of Prometheus Samples. static List toSamples( String name, MetricDataType type, Collection points) { List samples = new ArrayList<>(estimateNumSamples(points.size(), type)); for (PointData pointData : points) { Attributes attributes = pointData.getAttributes(); List labelNames = new ArrayList<>(attributes.size()); List labelValues = new ArrayList<>(attributes.size()); attributes.forEach( (key, value) -> { String sanitizedLabelName = sanitizer.apply(key.getKey()); labelNames.add(sanitizedLabelName); // TODO: We want to create an error-log if there is overlap in toString of attribute // values for the same key name. labelValues.add(value == null ? "" : value.toString()); }); switch (type) { case DOUBLE_SUM: case DOUBLE_GAUGE: DoublePointData doublePoint = (DoublePointData) pointData; samples.add( createSample( name, labelNames, labelValues, doublePoint.getValue(), // Prometheus doesn't support exemplars on SUM/GAUGE null, doublePoint.getEpochNanos())); break; case LONG_SUM: case LONG_GAUGE: LongPointData longPoint = (LongPointData) pointData; samples.add( createSample( name, labelNames, labelValues, (double) longPoint.getValue(), // Prometheus doesn't support exemplars on SUM/GAUGE null, longPoint.getEpochNanos())); break; case SUMMARY: addSummarySamples((SummaryPointData) pointData, name, labelNames, labelValues, samples); break; case HISTOGRAM: addHistogramSamples( (HistogramPointData) pointData, name, labelNames, labelValues, samples); break; case EXPONENTIAL_HISTOGRAM: break; // todo } } return samples; } private static void addSummarySamples( SummaryPointData doubleSummaryPoint, String name, List labelNames, List labelValues, List samples) { samples.add( createSample( name + SAMPLE_SUFFIX_COUNT, labelNames, labelValues, (double) doubleSummaryPoint.getCount(), null, doubleSummaryPoint.getEpochNanos())); samples.add( createSample( name + SAMPLE_SUFFIX_SUM, labelNames, labelValues, doubleSummaryPoint.getSum(), null, doubleSummaryPoint.getEpochNanos())); List valueAtQuantiles = doubleSummaryPoint.getValues(); List labelNamesWithQuantile = new ArrayList<>(labelNames.size() + 1); labelNamesWithQuantile.addAll(labelNames); labelNamesWithQuantile.add(LABEL_NAME_QUANTILE); for (ValueAtQuantile valueAtQuantile : valueAtQuantiles) { List labelValuesWithQuantile = new ArrayList<>(labelValues.size() + 1); labelValuesWithQuantile.addAll(labelValues); labelValuesWithQuantile.add(doubleToGoString(valueAtQuantile.getQuantile())); samples.add( createSample( name, labelNamesWithQuantile, labelValuesWithQuantile, valueAtQuantile.getValue(), null, doubleSummaryPoint.getEpochNanos())); } } private static void addHistogramSamples( HistogramPointData histogramPointData, String name, List labelNames, List labelValues, List samples) { samples.add( createSample( name + SAMPLE_SUFFIX_COUNT, labelNames, labelValues, (double) histogramPointData.getCount(), null, histogramPointData.getEpochNanos())); samples.add( createSample( name + SAMPLE_SUFFIX_SUM, labelNames, labelValues, histogramPointData.getSum(), null, histogramPointData.getEpochNanos())); List labelNamesWithLe = new ArrayList<>(labelNames.size() + 1); labelNamesWithLe.addAll(labelNames); labelNamesWithLe.add(LABEL_NAME_LE); long cumulativeCount = 0; List counts = histogramPointData.getCounts(); for (int i = 0; i < counts.size(); i++) { List labelValuesWithLe = new ArrayList<>(labelValues.size() + 1); // This is the upper boundary (inclusive). I.e. all values should be < this value (LE - // Less-then-or-Equal). double boundary = Serializer.getBucketUpperBound(histogramPointData, i); labelValuesWithLe.addAll(labelValues); labelValuesWithLe.add(doubleToGoString(boundary)); cumulativeCount += counts.get(i); samples.add( createSample( name + SAMPLE_SUFFIX_BUCKET, labelNamesWithLe, labelValuesWithLe, (double) cumulativeCount, filterExemplars( histogramPointData.getExemplars(), Serializer.getBucketLowerBound(histogramPointData, i), boundary), histogramPointData.getEpochNanos())); } } @Nullable private static ExemplarData filterExemplars( Collection exemplars, double min, double max) { ExemplarData result = null; for (ExemplarData e : exemplars) { double value = getExemplarValue(e); if (value <= max && value > min) { result = e; } } return result; } private static int estimateNumSamples(int numPoints, MetricDataType type) { if (type == MetricDataType.SUMMARY) { // count + sum + estimated 2 percentiles return numPoints * 4; } return numPoints; } private static Sample createSample( String name, List labelNames, List labelValues, double value, @Nullable ExemplarData exemplar, long timestampNanos) { if (exemplar != null) { return new Sample( name, labelNames, labelValues, value, toPrometheusExemplar(exemplar), TimeUnit.MILLISECONDS.convert(timestampNanos, TimeUnit.NANOSECONDS)); } return new Sample( name, labelNames, labelValues, value, null, TimeUnit.MILLISECONDS.convert(timestampNanos, TimeUnit.NANOSECONDS)); } private static io.prometheus.client.exemplars.Exemplar toPrometheusExemplar( ExemplarData exemplar) { SpanContext spanContext = exemplar.getSpanContext(); if (spanContext.isValid()) { return new io.prometheus.client.exemplars.Exemplar( getExemplarValue(exemplar), // Convert to ms for prometheus, truncate nanosecond precision. TimeUnit.NANOSECONDS.toMillis(exemplar.getEpochNanos()), "trace_id", spanContext.getTraceId(), "span_id", spanContext.getSpanId()); } return new io.prometheus.client.exemplars.Exemplar(getExemplarValue(exemplar)); } private static double getExemplarValue(ExemplarData exemplar) { return exemplar instanceof DoubleExemplarData ? ((DoubleExemplarData) exemplar).getValue() : (double) ((LongExemplarData) exemplar).getValue(); } private MetricAdapter() {} }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy