Skip to content

Commit

Permalink
Upgrade to OpenTelemetry 1.32.0, add support for bucket boundaries me…
Browse files Browse the repository at this point in the history
…trics API (#3447)
  • Loading branch information
JonasKunz committed Dec 6, 2023
1 parent 08bc842 commit 129a318
Show file tree
Hide file tree
Showing 14 changed files with 272 additions and 53 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.asciidoc
Expand Up @@ -38,6 +38,13 @@ Use subheadings with the "=====" level for adding notes for unreleased changes:
* Added support for Spring 6.1 / Spring-Boot 3.2 - {pull}3440[#3440]
* Add support for Apache HTTP client 5.x - {pull}3419[#3419]
[float]
===== Potentially breaking changes
* Added support for OpenTelemetry `1.32.0`. As a result, `custom_metrics_histogram_boundaries` will not
work when you bring your own `MeterProvider` from an SDK with version `1.32.0` or newer. As a workaround,
you should manually register a corresponding View in your `MeterProvider`. Note that this change will not
affect you, if you are using the OpenTelemetry API only and not the SDK. - {pull}3447[#3447]
[[release-notes-1.x]]
=== Java Agent version 1.x
Expand Down
Expand Up @@ -48,7 +48,10 @@ public class MetricsConfiguration extends ConfigurationOptionProvider implements
private final ConfigurationOption<List<Double>> customMetricsHistogramBoundaries = ConfigurationOption.builder(new ListValueConverter<>(DoubleValueConverter.INSTANCE), List.class)
.key("custom_metrics_histogram_boundaries")
.configurationCategory(METRICS_CATEGORY)
.description("Defines the default bucket boundaries to use for OpenTelemetry histograms.")
.description("Defines the default bucket boundaries to use for OpenTelemetry histograms.\n" +
"\n" +
"Note that for OpenTelemetry 1.32.0 or newer this setting will only work when using API only. " +
"The default buckets will not be applied when bringing your own SDK.")
.dynamic(false)
.tags("added[1.37.0]", "experimental")
.addValidator(new ConfigurationOption.Validator<List<Double>>() {
Expand Down
Expand Up @@ -18,16 +18,21 @@
*/
package co.elastic.apm.agent.embeddedotel.proxy;

import io.opentelemetry.api.metrics.DoubleHistogram;
import co.elastic.apm.agent.configuration.MetricsConfiguration;
import co.elastic.apm.agent.tracer.GlobalTracer;
import io.opentelemetry.api.metrics.DoubleHistogramBuilder;
import io.opentelemetry.api.metrics.LongHistogramBuilder;

import java.util.List;

public class ProxyDoubleHistogramBuilder {

private final DoubleHistogramBuilder delegate;

public ProxyDoubleHistogramBuilder(DoubleHistogramBuilder delegate) {
this.delegate = delegate;
//apply default bucket boundaries
List<Double> boundaries = GlobalTracer.get().getConfig(MetricsConfiguration.class).getCustomMetricsHistogramBoundaries();
delegate.setExplicitBucketBoundariesAdvice(boundaries);
}

public DoubleHistogramBuilder getDelegate() {
Expand All @@ -52,4 +57,8 @@ public ProxyDoubleHistogramBuilder setDescription(String arg0) {
return this;
}

}
public ProxyDoubleHistogramBuilder setExplicitBucketBoundariesAdvice(List<Double> bucketBoundaries) {
delegate.setExplicitBucketBoundariesAdvice(bucketBoundaries);
return this;
}
}
Expand Up @@ -18,15 +18,34 @@
*/
package co.elastic.apm.agent.embeddedotel.proxy;

import io.opentelemetry.api.metrics.LongHistogram;
import co.elastic.apm.agent.configuration.MetricsConfiguration;
import co.elastic.apm.agent.tracer.GlobalTracer;
import io.opentelemetry.api.metrics.LongHistogramBuilder;

import java.util.ArrayList;
import java.util.List;

public class ProxyLongHistogramBuilder {

private final LongHistogramBuilder delegate;

public ProxyLongHistogramBuilder(LongHistogramBuilder delegate) {
this.delegate = delegate;
//apply default bucket boundaries, they are guaranteed to be ordered
List<Double> boundaries = GlobalTracer.get().getConfig(MetricsConfiguration.class).getCustomMetricsHistogramBoundaries();
delegate.setExplicitBucketBoundariesAdvice(convertToLongBoundaries(boundaries));
}

private List<Long> convertToLongBoundaries(List<Double> boundaries) {
List<Long> result = new ArrayList<>();
for(double val : boundaries) {
long rounded = Math.round(val);
//Do not add the same boundary twice
if(rounded > 0 && (result.isEmpty() || result.get(result.size() - 1) != rounded)) {
result.add(rounded);
}
}
return result;
}

public LongHistogramBuilder getDelegate() {
Expand All @@ -47,4 +66,8 @@ public ProxyLongHistogramBuilder setDescription(String arg0) {
return this;
}

}
public ProxyLongHistogramBuilder setExplicitBucketBoundariesAdvice(List<Long> bucketBoundaries) {
delegate.setExplicitBucketBoundariesAdvice(bucketBoundaries);
return this;
}
}
Expand Up @@ -21,25 +21,31 @@
import co.elastic.apm.agent.embeddedotel.proxy.ProxyBatchCallback;
import co.elastic.apm.agent.embeddedotel.proxy.ProxyDoubleCounterBuilder;
import co.elastic.apm.agent.embeddedotel.proxy.ProxyDoubleGaugeBuilder;
import co.elastic.apm.agent.embeddedotel.proxy.ProxyDoubleHistogramBuilder;
import co.elastic.apm.agent.embeddedotel.proxy.ProxyDoubleUpDownCounterBuilder;
import co.elastic.apm.agent.embeddedotel.proxy.ProxyLongCounterBuilder;
import co.elastic.apm.agent.embeddedotel.proxy.ProxyLongGaugeBuilder;
import co.elastic.apm.agent.embeddedotel.proxy.ProxyLongHistogramBuilder;
import co.elastic.apm.agent.embeddedotel.proxy.ProxyLongUpDownCounterBuilder;
import co.elastic.apm.agent.embeddedotel.proxy.ProxyMeter;
import co.elastic.apm.agent.opentelemetry.metrics.bridge.latest.BridgeBatchCallback;
import co.elastic.apm.agent.opentelemetry.metrics.bridge.latest.BridgeDoubleCounterBuilder;
import co.elastic.apm.agent.opentelemetry.metrics.bridge.latest.BridgeDoubleGaugeBuilder;
import co.elastic.apm.agent.opentelemetry.metrics.bridge.latest.BridgeDoubleHistogramBuilder;
import co.elastic.apm.agent.opentelemetry.metrics.bridge.latest.BridgeDoubleUpDownCounterBuilder;
import co.elastic.apm.agent.opentelemetry.metrics.bridge.latest.BridgeLongCounterBuilder;
import co.elastic.apm.agent.opentelemetry.metrics.bridge.latest.BridgeLongGaugeBuilder;
import co.elastic.apm.agent.opentelemetry.metrics.bridge.latest.BridgeLongHistogramBuilder;
import co.elastic.apm.agent.opentelemetry.metrics.bridge.latest.BridgeLongUpDownCounterBuilder;
import co.elastic.apm.agent.opentelemetry.metrics.bridge.latest.BridgeMeter;
import io.opentelemetry.api.metrics.BatchCallback;
import io.opentelemetry.api.metrics.DoubleCounterBuilder;
import io.opentelemetry.api.metrics.DoubleGaugeBuilder;
import io.opentelemetry.api.metrics.DoubleHistogramBuilder;
import io.opentelemetry.api.metrics.DoubleUpDownCounterBuilder;
import io.opentelemetry.api.metrics.LongCounterBuilder;
import io.opentelemetry.api.metrics.LongGaugeBuilder;
import io.opentelemetry.api.metrics.LongHistogramBuilder;
import io.opentelemetry.api.metrics.LongUpDownCounterBuilder;
import io.opentelemetry.api.metrics.Meter;

Expand Down Expand Up @@ -99,4 +105,14 @@ public DoubleGaugeBuilder bridgeDoubleGaugeBuilder(ProxyDoubleGaugeBuilder deleg
public BatchCallback bridgeBatchCallback(ProxyBatchCallback delegate) {
return new BridgeBatchCallback(delegate);
}

@Override
public DoubleHistogramBuilder bridgeDoubleHistogramBuilder(ProxyDoubleHistogramBuilder delegate) {
return new BridgeDoubleHistogramBuilder(delegate);
}

@Override
public LongHistogramBuilder bridgeLongHistogramBuilder(ProxyLongHistogramBuilder delegate) {
return new BridgeLongHistogramBuilder(delegate);
}
}
@@ -0,0 +1,40 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package co.elastic.apm.agent.opentelemetry.metrics.bridge.latest;

import co.elastic.apm.agent.embeddedotel.proxy.ProxyDoubleHistogramBuilder;
import io.opentelemetry.api.metrics.DoubleHistogramBuilder;

import java.util.List;

public class BridgeDoubleHistogramBuilder extends co.elastic.apm.agent.opentelemetry.metrics.bridge.v1_14.BridgeDoubleHistogramBuilder {
public BridgeDoubleHistogramBuilder(ProxyDoubleHistogramBuilder delegate) {
super(delegate);
}

/**
* This method was added in 1.32.0, but is safe to have even if the provided API is older.
* This is safe because it doesn't reference any newly added API types.
*/
@Override
public DoubleHistogramBuilder setExplicitBucketBoundariesAdvice(List<Double> bucketBoundaries) {
delegate.setExplicitBucketBoundariesAdvice(bucketBoundaries);
return this;
}
}
@@ -0,0 +1,42 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package co.elastic.apm.agent.opentelemetry.metrics.bridge.latest;

import co.elastic.apm.agent.embeddedotel.proxy.ProxyDoubleHistogramBuilder;
import co.elastic.apm.agent.embeddedotel.proxy.ProxyLongHistogramBuilder;
import io.opentelemetry.api.metrics.DoubleHistogramBuilder;
import io.opentelemetry.api.metrics.LongHistogramBuilder;

import java.util.List;

public class BridgeLongHistogramBuilder extends co.elastic.apm.agent.opentelemetry.metrics.bridge.v1_14.BridgeLongHistogramBuilder {
public BridgeLongHistogramBuilder(ProxyLongHistogramBuilder delegate) {
super(delegate);
}

/**
* This method was added in 1.32.0, but is safe to have even if the provided API is older.
* This is safe because it doesn't reference any newly added API types.
*/
@Override
public LongHistogramBuilder setExplicitBucketBoundariesAdvice(List<Long> bucketBoundaries) {
delegate.setExplicitBucketBoundariesAdvice(bucketBoundaries);
return this;
}
}
Expand Up @@ -25,6 +25,7 @@
import co.elastic.apm.agent.sdk.internal.util.ExecutorUtils;
import co.elastic.apm.agent.tracer.configuration.MetricsConfiguration;
import co.elastic.apm.agent.tracer.configuration.ReporterConfiguration;
import io.opentelemetry.api.metrics.DoubleHistogramBuilder;
import io.opentelemetry.sdk.common.CompletableResultCode;
import io.opentelemetry.sdk.metrics.Aggregation;
import io.opentelemetry.sdk.metrics.InstrumentType;
Expand All @@ -37,13 +38,16 @@

import java.time.Duration;
import java.util.Collection;
import java.util.List;

public class ElasticOtelMetricsExporter implements MetricExporter {

private static final Logger logger = LoggerFactory.getLogger(ElasticOtelMetricsExporter.class);

private static final AggregationTemporalitySelector TEMPORALITY_SELECTOR = AggregationTemporalitySelector.deltaPreferred();

private static final boolean API_SUPPORTS_BUCKET_ADVICE = checkOtelApiSupportsHistogramBucketAdvice();

private final Aggregation defaultHistogramAggregation;

private final OtelMetricSerializer serializer;
Expand Down Expand Up @@ -103,10 +107,22 @@ public AggregationTemporality getAggregationTemporality(InstrumentType instrumen

@Override
public Aggregation getDefaultAggregation(InstrumentType instrumentType) {
if (instrumentType == InstrumentType.HISTOGRAM) {
// Unfortunately advices are not applied when a non-default aggregation is returned here
// When instrumenting API-usages, we now apply the default histogram boundaries via the
// ProxyLongHistogramBuilder and ProxyDoubleHistogramBuilder
if (instrumentType == InstrumentType.HISTOGRAM && !API_SUPPORTS_BUCKET_ADVICE) {
return defaultHistogramAggregation;
} else {
return Aggregation.defaultAggregation();
}
}

private static boolean checkOtelApiSupportsHistogramBucketAdvice() {
try {
DoubleHistogramBuilder.class.getMethod("setExplicitBucketBoundariesAdvice", List.class);
return true;
} catch (NoSuchMethodException | SecurityException e) {
return false;
}
}
}

0 comments on commit 129a318

Please sign in to comment.