io.opentelemetry.opencensusshim.MetricAdapter Maven / Gradle / Ivy
The newest version!
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.opencensusshim;
import io.opencensus.common.Timestamp;
import io.opencensus.metrics.LabelKey;
import io.opencensus.metrics.LabelValue;
import io.opencensus.metrics.data.Exemplar;
import io.opencensus.metrics.export.Distribution;
import io.opencensus.metrics.export.Metric;
import io.opencensus.metrics.export.MetricDescriptor;
import io.opencensus.metrics.export.Point;
import io.opencensus.metrics.export.Summary;
import io.opencensus.metrics.export.TimeSeries;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.api.trace.SpanContext;
import io.opentelemetry.api.trace.TraceFlags;
import io.opentelemetry.api.trace.TraceState;
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
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.GaugeData;
import io.opentelemetry.sdk.metrics.data.HistogramData;
import io.opentelemetry.sdk.metrics.data.HistogramPointData;
import io.opentelemetry.sdk.metrics.data.LongPointData;
import io.opentelemetry.sdk.metrics.data.MetricData;
import io.opentelemetry.sdk.metrics.data.SumData;
import io.opentelemetry.sdk.metrics.data.SummaryData;
import io.opentelemetry.sdk.metrics.data.SummaryPointData;
import io.opentelemetry.sdk.metrics.data.ValueAtQuantile;
import io.opentelemetry.sdk.metrics.internal.data.ImmutableDoubleExemplarData;
import io.opentelemetry.sdk.metrics.internal.data.ImmutableDoublePointData;
import io.opentelemetry.sdk.metrics.internal.data.ImmutableGaugeData;
import io.opentelemetry.sdk.metrics.internal.data.ImmutableHistogramData;
import io.opentelemetry.sdk.metrics.internal.data.ImmutableHistogramPointData;
import io.opentelemetry.sdk.metrics.internal.data.ImmutableLongPointData;
import io.opentelemetry.sdk.metrics.internal.data.ImmutableMetricData;
import io.opentelemetry.sdk.metrics.internal.data.ImmutableSumData;
import io.opentelemetry.sdk.metrics.internal.data.ImmutableSummaryData;
import io.opentelemetry.sdk.metrics.internal.data.ImmutableSummaryPointData;
import io.opentelemetry.sdk.metrics.internal.data.ImmutableValueAtQuantile;
import io.opentelemetry.sdk.resources.Resource;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.regex.MatchResult;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Nullable;
/** Adapts an OpenCensus metric into the OpenTelemetry metric data API. */
final class MetricAdapter {
private MetricAdapter() {}
// All OpenCensus metrics come from this shim.
// VisibleForTesting.
static final InstrumentationScopeInfo INSTRUMENTATION_SCOPE_INFO =
InstrumentationScopeInfo.create("io.opentelemetry.opencensusshim");
// Parser for string value of `io.opencensus.contrib.exemplar.util.AttachmentValueSpanContext`
// // SpanContext{traceId=TraceId{traceId=(id))}, spanId=SpanId{spanId=(id), ...}
private static final Pattern OPENCENSUS_TRACE_ATTACHMENT_PATTERN =
Pattern.compile(
"SpanContext\\{traceId=TraceId\\{traceId=([0-9A-Ga-g]+)\\}, spanId=SpanId\\{spanId=([0-9A-Ga-g]+)\\},.*\\}");
/**
* Converts an open-census metric into the OTLP format.
*
* @param otelResource The resource associated with the opentelemetry SDK.
* @param censusMetric The OpenCensus metric to convert.
*/
static MetricData convert(Resource otelResource, Metric censusMetric) {
// Note: we can't just adapt interfaces, we need to do full copy because OTel data API uses
// auto-value vs. pure interfaces.
switch (censusMetric.getMetricDescriptor().getType()) {
case GAUGE_INT64:
return ImmutableMetricData.createLongGauge(
otelResource,
INSTRUMENTATION_SCOPE_INFO,
censusMetric.getMetricDescriptor().getName(),
censusMetric.getMetricDescriptor().getDescription(),
censusMetric.getMetricDescriptor().getUnit(),
convertLongGauge(censusMetric));
case GAUGE_DOUBLE:
return ImmutableMetricData.createDoubleGauge(
otelResource,
INSTRUMENTATION_SCOPE_INFO,
censusMetric.getMetricDescriptor().getName(),
censusMetric.getMetricDescriptor().getDescription(),
censusMetric.getMetricDescriptor().getUnit(),
convertDoubleGauge(censusMetric));
case CUMULATIVE_INT64:
return ImmutableMetricData.createLongSum(
otelResource,
INSTRUMENTATION_SCOPE_INFO,
censusMetric.getMetricDescriptor().getName(),
censusMetric.getMetricDescriptor().getDescription(),
censusMetric.getMetricDescriptor().getUnit(),
convertLongSum(censusMetric));
case CUMULATIVE_DOUBLE:
return ImmutableMetricData.createDoubleSum(
otelResource,
INSTRUMENTATION_SCOPE_INFO,
censusMetric.getMetricDescriptor().getName(),
censusMetric.getMetricDescriptor().getDescription(),
censusMetric.getMetricDescriptor().getUnit(),
convertDoubleSum(censusMetric));
case CUMULATIVE_DISTRIBUTION:
return ImmutableMetricData.createDoubleHistogram(
otelResource,
INSTRUMENTATION_SCOPE_INFO,
censusMetric.getMetricDescriptor().getName(),
censusMetric.getMetricDescriptor().getDescription(),
censusMetric.getMetricDescriptor().getUnit(),
convertHistogram(censusMetric));
case SUMMARY:
return ImmutableMetricData.createDoubleSummary(
otelResource,
INSTRUMENTATION_SCOPE_INFO,
censusMetric.getMetricDescriptor().getName(),
censusMetric.getMetricDescriptor().getDescription(),
censusMetric.getMetricDescriptor().getUnit(),
convertSummary(censusMetric));
case GAUGE_DISTRIBUTION:
return ImmutableMetricData.createDoubleHistogram(
otelResource,
INSTRUMENTATION_SCOPE_INFO,
censusMetric.getMetricDescriptor().getName(),
censusMetric.getMetricDescriptor().getDescription(),
censusMetric.getMetricDescriptor().getUnit(),
convertGaugeHistogram(censusMetric));
}
// Should be unreachable....
throw new IllegalArgumentException(
"Unknown OpenCensus metric type: " + censusMetric.getMetricDescriptor().getType());
}
static GaugeData convertLongGauge(Metric censusMetric) {
return ImmutableGaugeData.create(convertLongPoints(censusMetric));
}
static GaugeData convertDoubleGauge(Metric censusMetric) {
return ImmutableGaugeData.create(convertDoublePoints(censusMetric));
}
static SumData convertLongSum(Metric censusMetric) {
return ImmutableSumData.create(
true, AggregationTemporality.CUMULATIVE, convertLongPoints(censusMetric));
}
static SumData convertDoubleSum(Metric censusMetric) {
return ImmutableSumData.create(
true, AggregationTemporality.CUMULATIVE, convertDoublePoints(censusMetric));
}
static HistogramData convertHistogram(Metric censusMetric) {
return ImmutableHistogramData.create(
AggregationTemporality.CUMULATIVE, convertHistogramPoints(censusMetric));
}
static HistogramData convertGaugeHistogram(Metric censusMetric) {
return ImmutableHistogramData.create(
AggregationTemporality.DELTA, convertHistogramPoints(censusMetric));
}
static SummaryData convertSummary(Metric censusMetric) {
return ImmutableSummaryData.create(convertSummaryPoints(censusMetric));
}
static Collection convertLongPoints(Metric censusMetric) {
// TODO - preallocate array to correct size.
List result = new ArrayList<>();
for (TimeSeries ts : censusMetric.getTimeSeriesList()) {
long startTimestamp = mapTimestamp(ts.getStartTimestamp());
Attributes attributes =
mapAttributes(censusMetric.getMetricDescriptor().getLabelKeys(), ts.getLabelValues());
for (Point point : ts.getPoints()) {
result.add(
ImmutableLongPointData.create(
startTimestamp, mapTimestamp(point.getTimestamp()), attributes, longValue(point)));
}
}
return result;
}
static Collection convertDoublePoints(Metric censusMetric) {
// TODO - preallocate array to correct size.
List result = new ArrayList<>();
for (TimeSeries ts : censusMetric.getTimeSeriesList()) {
long startTimestamp = mapTimestamp(ts.getStartTimestamp());
Attributes attributes =
mapAttributes(censusMetric.getMetricDescriptor().getLabelKeys(), ts.getLabelValues());
for (Point point : ts.getPoints()) {
result.add(
ImmutableDoublePointData.create(
startTimestamp,
mapTimestamp(point.getTimestamp()),
attributes,
doubleValue(point)));
}
}
return result;
}
static Collection convertHistogramPoints(Metric censusMetric) {
boolean isGauge =
censusMetric.getMetricDescriptor().getType() == MetricDescriptor.Type.GAUGE_DISTRIBUTION;
// TODO - preallocate array to correct size.
List result = new ArrayList<>();
for (TimeSeries ts : censusMetric.getTimeSeriesList()) {
long startTimestamp = mapTimestamp(ts.getStartTimestamp());
Attributes attributes =
mapAttributes(censusMetric.getMetricDescriptor().getLabelKeys(), ts.getLabelValues());
for (Point point : ts.getPoints()) {
long endTimestamp = mapTimestamp(point.getTimestamp());
HistogramPointData otelPoint =
point
.getValue()
.match(
doubleValue -> null,
longValue -> null,
distribution ->
ImmutableHistogramPointData.create(
// Report Gauge histograms as DELTA with "instantaneous" time window.
isGauge ? endTimestamp : startTimestamp,
endTimestamp,
attributes,
distribution.getSum(),
/* hasMin= */ false,
0,
/* hasMax= */ false,
0,
mapBoundaries(distribution.getBucketOptions()),
mapCounts(distribution.getBuckets()),
mapExemplars(distribution.getBuckets())),
summary -> null,
defaultValue -> null);
if (otelPoint != null) {
result.add(otelPoint);
}
}
}
return result;
}
static Collection convertSummaryPoints(Metric censusMetric) {
List result = new ArrayList<>();
for (TimeSeries ts : censusMetric.getTimeSeriesList()) {
long startTimestamp = mapTimestamp(ts.getStartTimestamp());
Attributes attributes =
mapAttributes(censusMetric.getMetricDescriptor().getLabelKeys(), ts.getLabelValues());
for (Point point : ts.getPoints()) {
SummaryPointData otelPoint =
point
.getValue()
.match(
dv -> null,
lv -> null,
distribution -> null,
summary ->
ImmutableSummaryPointData.create(
startTimestamp,
mapTimestamp(point.getTimestamp()),
attributes,
summary.getCount(),
summary.getSum(),
mapValueAtPercentiles(summary.getSnapshot().getValueAtPercentiles())),
defaultValue -> null);
if (otelPoint != null) {
result.add(otelPoint);
}
}
}
return result;
}
static Attributes mapAttributes(List labels, List values) {
AttributesBuilder result = Attributes.builder();
for (int i = 0; i < labels.size(); i++) {
result.put(labels.get(i).getKey(), values.get(i).getValue());
}
return result.build();
}
static long longValue(Point point) {
return point
.getValue()
.match(
Double::longValue,
lv -> lv,
// Ignore these cases (logic error)
distribution -> 0,
summary -> 0,
defaultValue -> 0)
.longValue();
}
static double doubleValue(Point point) {
return point
.getValue()
.match(
d -> d,
Long::doubleValue,
// Ignore these cases (logic error)
distribution -> 0,
summary -> 0,
defaultValue -> 0)
.doubleValue();
}
static List mapBoundaries(Distribution.BucketOptions censusBuckets) {
return censusBuckets.match(
explicit -> explicit.getBucketBoundaries(), defaultOption -> Collections.emptyList());
}
static List mapCounts(List buckets) {
List result = new ArrayList<>(buckets.size());
for (Distribution.Bucket bucket : buckets) {
result.add(bucket.getCount());
}
return result;
}
static List mapExemplars(List buckets) {
List result = new ArrayList<>();
for (Distribution.Bucket bucket : buckets) {
Exemplar exemplar = bucket.getExemplar();
if (exemplar != null) {
result.add(mapExemplar(exemplar));
}
}
return result;
}
private static DoubleExemplarData mapExemplar(Exemplar exemplar) {
// Look for trace/span id.
SpanContext spanContext = SpanContext.getInvalid();
if (exemplar.getAttachments().containsKey("SpanContext")) {
// We need to use `io.opencensus.contrib.exemplar.util.AttachmentValueSpanContext`
// The `toString` will be the following:
// SpanContext{traceId=TraceId{traceId=(id))}, spanId=SpanId{spanId=(id), ...}
// We *attempt* parse it rather than pull in yet another dependency.
String spanContextToString = exemplar.getAttachments().get("SpanContext").getValue();
Matcher m = OPENCENSUS_TRACE_ATTACHMENT_PATTERN.matcher(spanContextToString);
if (m.matches()) {
MatchResult mr = m.toMatchResult();
String traceId = mr.group(1);
String spanId = mr.group(2);
spanContext =
SpanContext.create(traceId, spanId, TraceFlags.getDefault(), TraceState.getDefault());
}
}
return ImmutableDoubleExemplarData.create(
Attributes.empty(),
mapTimestamp(exemplar.getTimestamp()),
spanContext,
exemplar.getValue());
}
static long mapTimestamp(@Nullable Timestamp time) {
// Treat all empty timestamps as "0" (proto3)
if (time == null) {
return 0;
}
return TimeUnit.SECONDS.toNanos(time.getSeconds()) + time.getNanos();
}
private static List mapValueAtPercentiles(
List valueAtPercentiles) {
List result = new ArrayList<>(valueAtPercentiles.size());
for (Summary.Snapshot.ValueAtPercentile censusValue : valueAtPercentiles) {
result.add(
ImmutableValueAtQuantile.create(
censusValue.getPercentile() / 100.0, censusValue.getValue()));
}
return result;
}
}