diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 5952e1ed81..2c1c8a1d12 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -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 diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/MetricsConfiguration.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/MetricsConfiguration.java index 9af16dfecf..90c5ff563e 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/MetricsConfiguration.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/MetricsConfiguration.java @@ -48,7 +48,10 @@ public class MetricsConfiguration extends ConfigurationOptionProvider implements private final ConfigurationOption> 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>() { diff --git a/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-embedded-metrics-sdk/src/main/java/co/elastic/apm/agent/embeddedotel/proxy/ProxyDoubleHistogramBuilder.java b/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-embedded-metrics-sdk/src/main/java/co/elastic/apm/agent/embeddedotel/proxy/ProxyDoubleHistogramBuilder.java index 1e3ee4087b..5d4b0034ec 100644 --- a/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-embedded-metrics-sdk/src/main/java/co/elastic/apm/agent/embeddedotel/proxy/ProxyDoubleHistogramBuilder.java +++ b/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-embedded-metrics-sdk/src/main/java/co/elastic/apm/agent/embeddedotel/proxy/ProxyDoubleHistogramBuilder.java @@ -18,9 +18,11 @@ */ 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 { @@ -28,6 +30,9 @@ public class ProxyDoubleHistogramBuilder { public ProxyDoubleHistogramBuilder(DoubleHistogramBuilder delegate) { this.delegate = delegate; + //apply default bucket boundaries + List boundaries = GlobalTracer.get().getConfig(MetricsConfiguration.class).getCustomMetricsHistogramBoundaries(); + delegate.setExplicitBucketBoundariesAdvice(boundaries); } public DoubleHistogramBuilder getDelegate() { @@ -52,4 +57,8 @@ public ProxyDoubleHistogramBuilder setDescription(String arg0) { return this; } -} \ No newline at end of file + public ProxyDoubleHistogramBuilder setExplicitBucketBoundariesAdvice(List bucketBoundaries) { + delegate.setExplicitBucketBoundariesAdvice(bucketBoundaries); + return this; + } +} diff --git a/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-embedded-metrics-sdk/src/main/java/co/elastic/apm/agent/embeddedotel/proxy/ProxyLongHistogramBuilder.java b/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-embedded-metrics-sdk/src/main/java/co/elastic/apm/agent/embeddedotel/proxy/ProxyLongHistogramBuilder.java index 4f3fb3e76e..4ff5bee219 100644 --- a/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-embedded-metrics-sdk/src/main/java/co/elastic/apm/agent/embeddedotel/proxy/ProxyLongHistogramBuilder.java +++ b/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-embedded-metrics-sdk/src/main/java/co/elastic/apm/agent/embeddedotel/proxy/ProxyLongHistogramBuilder.java @@ -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 boundaries = GlobalTracer.get().getConfig(MetricsConfiguration.class).getCustomMetricsHistogramBoundaries(); + delegate.setExplicitBucketBoundariesAdvice(convertToLongBoundaries(boundaries)); + } + + private List convertToLongBoundaries(List boundaries) { + List 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() { @@ -47,4 +66,8 @@ public ProxyLongHistogramBuilder setDescription(String arg0) { return this; } -} \ No newline at end of file + public ProxyLongHistogramBuilder setExplicitBucketBoundariesAdvice(List bucketBoundaries) { + delegate.setExplicitBucketBoundariesAdvice(bucketBoundaries); + return this; + } +} diff --git a/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-metrics-bridge-parent/apm-opentelemetry-metrics-bridge-latest/src/main/java/co/elastic/apm/agent/opentelemetry/metrics/bridge/BridgeFactoryLatest.java b/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-metrics-bridge-parent/apm-opentelemetry-metrics-bridge-latest/src/main/java/co/elastic/apm/agent/opentelemetry/metrics/bridge/BridgeFactoryLatest.java index c873de6e01..dfe5e4d746 100644 --- a/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-metrics-bridge-parent/apm-opentelemetry-metrics-bridge-latest/src/main/java/co/elastic/apm/agent/opentelemetry/metrics/bridge/BridgeFactoryLatest.java +++ b/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-metrics-bridge-parent/apm-opentelemetry-metrics-bridge-latest/src/main/java/co/elastic/apm/agent/opentelemetry/metrics/bridge/BridgeFactoryLatest.java @@ -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; @@ -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); + } } diff --git a/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-metrics-bridge-parent/apm-opentelemetry-metrics-bridge-latest/src/main/java/co/elastic/apm/agent/opentelemetry/metrics/bridge/latest/BridgeDoubleHistogramBuilder.java b/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-metrics-bridge-parent/apm-opentelemetry-metrics-bridge-latest/src/main/java/co/elastic/apm/agent/opentelemetry/metrics/bridge/latest/BridgeDoubleHistogramBuilder.java new file mode 100644 index 0000000000..fed2abdcea --- /dev/null +++ b/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-metrics-bridge-parent/apm-opentelemetry-metrics-bridge-latest/src/main/java/co/elastic/apm/agent/opentelemetry/metrics/bridge/latest/BridgeDoubleHistogramBuilder.java @@ -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 bucketBoundaries) { + delegate.setExplicitBucketBoundariesAdvice(bucketBoundaries); + return this; + } +} diff --git a/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-metrics-bridge-parent/apm-opentelemetry-metrics-bridge-latest/src/main/java/co/elastic/apm/agent/opentelemetry/metrics/bridge/latest/BridgeLongHistogramBuilder.java b/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-metrics-bridge-parent/apm-opentelemetry-metrics-bridge-latest/src/main/java/co/elastic/apm/agent/opentelemetry/metrics/bridge/latest/BridgeLongHistogramBuilder.java new file mode 100644 index 0000000000..7dbd44ef9b --- /dev/null +++ b/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-metrics-bridge-parent/apm-opentelemetry-metrics-bridge-latest/src/main/java/co/elastic/apm/agent/opentelemetry/metrics/bridge/latest/BridgeLongHistogramBuilder.java @@ -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 bucketBoundaries) { + delegate.setExplicitBucketBoundariesAdvice(bucketBoundaries); + return this; + } +} diff --git a/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-metricsdk-plugin/src/main/java/co/elastic/apm/agent/otelmetricsdk/ElasticOtelMetricsExporter.java b/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-metricsdk-plugin/src/main/java/co/elastic/apm/agent/otelmetricsdk/ElasticOtelMetricsExporter.java index 941cf14f05..950459f4c2 100644 --- a/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-metricsdk-plugin/src/main/java/co/elastic/apm/agent/otelmetricsdk/ElasticOtelMetricsExporter.java +++ b/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-metricsdk-plugin/src/main/java/co/elastic/apm/agent/otelmetricsdk/ElasticOtelMetricsExporter.java @@ -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; @@ -37,6 +38,7 @@ import java.time.Duration; import java.util.Collection; +import java.util.List; public class ElasticOtelMetricsExporter implements MetricExporter { @@ -44,6 +46,8 @@ public class ElasticOtelMetricsExporter implements MetricExporter { 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; @@ -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; + } + } } diff --git a/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-metricsdk-plugin/src/test/java/co/elastic/apm/agent/otelmetricsdk/AbstractOtelMetricsTest.java b/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-metricsdk-plugin/src/test/java/co/elastic/apm/agent/otelmetricsdk/AbstractOtelMetricsTest.java index 53c3b29819..185e47ad99 100644 --- a/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-metricsdk-plugin/src/test/java/co/elastic/apm/agent/otelmetricsdk/AbstractOtelMetricsTest.java +++ b/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-metricsdk-plugin/src/test/java/co/elastic/apm/agent/otelmetricsdk/AbstractOtelMetricsTest.java @@ -32,6 +32,7 @@ import io.opentelemetry.api.metrics.DoubleCounter; import io.opentelemetry.api.metrics.DoubleGaugeBuilder; import io.opentelemetry.api.metrics.DoubleHistogram; +import io.opentelemetry.api.metrics.DoubleHistogramBuilder; import io.opentelemetry.api.metrics.DoubleUpDownCounter; import io.opentelemetry.api.metrics.LongCounter; import io.opentelemetry.api.metrics.LongHistogram; @@ -588,44 +589,44 @@ public void testUpDownCounter() { @Test public void testHistogram() { MetricsConfiguration metricsConfig = config.getConfig(MetricsConfiguration.class); - doReturn(List.of(1.0, 5.0)).when(metricsConfig).getCustomMetricsHistogramBoundaries(); + doReturn(List.of(5d, 10d, 25d)).when(metricsConfig).getCustomMetricsHistogramBoundaries(); Meter testMeter = createMeter("test"); DoubleHistogram doubleHisto = testMeter.histogramBuilder("double_histo").build(); LongHistogram longHisto = testMeter.histogramBuilder("long_histo").ofLongs().build(); doubleHisto.record(0.5); - doubleHisto.record(1.5); - doubleHisto.record(1.5); - doubleHisto.record(5.5); - doubleHisto.record(5.5); - doubleHisto.record(5.5); + doubleHisto.record(6.5); + doubleHisto.record(6.5); + doubleHisto.record(10.5); + doubleHisto.record(10.5); + doubleHisto.record(10.5); - longHisto.record(0); - longHisto.record(2); - longHisto.record(2); - longHisto.record(6); + longHisto.record(1); longHisto.record(6); longHisto.record(6); + longHisto.record(11); + longHisto.record(11); + longHisto.record(11); resetReporterAndFlushMetrics(); assertThatMetricSets(reporter.getBytes()) .hasMetricsetCount(1) .first() - .containsHistogramMetric("double_histo", List.of(0.5, 3.0, 5.0), List.of(1L, 2L, 3L)) - .containsHistogramMetric("long_histo", List.of(0.5, 3.0, 5.0), List.of(1L, 2L, 3L)) + .containsHistogramMetric("double_histo", List.of(2.5, 7.5, 17.5), List.of(1L, 2L, 3L)) + .containsHistogramMetric("long_histo", List.of(2.5, 7.5, 17.5), List.of(1L, 2L, 3L)) .hasMetricsCount(2); //make sure only delta is reported and empty buckets are omitted - doubleHisto.record(1.5); - longHisto.record(2); + doubleHisto.record(6.5); + longHisto.record(6); resetReporterAndFlushMetrics(); assertThatMetricSets(reporter.getBytes()) .hasMetricsetCount(1) .first() - .containsHistogramMetric("double_histo", List.of(3.0), List.of(1L)) - .containsHistogramMetric("long_histo", List.of(3.0), List.of(1L)) + .containsHistogramMetric("double_histo", List.of(7.5), List.of(1L)) + .containsHistogramMetric("long_histo", List.of(7.5), List.of(1L)) .hasMetricsCount(2); //empty histograms must not be exported @@ -664,9 +665,56 @@ public void testDefaultHistogramBuckets() { .hasMetricsetCount(1) .first() .metricSatisfies("double_histo", - metric -> assertThat(metric.counts.stream().mapToLong(Long::longValue).sum()).isEqualTo(totalSumFinal)) + metric -> { + assertThat(metric.counts).hasSizeGreaterThan(20); + assertThat(metric.counts.stream().mapToLong(Long::longValue).sum()).isEqualTo(totalSumFinal); + }) .metricSatisfies("long_histo", - metric -> assertThat(metric.counts.stream().mapToLong(Long::longValue).sum()).isEqualTo(totalSumFinal)) + metric -> { + assertThat(metric.counts).hasSizeGreaterThan(20); + assertThat(metric.counts.stream().mapToLong(Long::longValue).sum()).isEqualTo(totalSumFinal); + }) + .hasMetricsCount(2); + } + + + @Test + public void testHistogramAdviceAPI() { + try { + DoubleHistogramBuilder.class.getMethod("setExplicitBucketBoundariesAdvice", List.class); + } catch (NoSuchMethodException expected) { + //we are in an integration test where .setExplicitBucketBoundariesAdvice() doesn't exist, skip this test + return; + } + + Meter testMeter = createMeter("test"); + DoubleHistogram doubleHisto = testMeter.histogramBuilder("double_histo") + .setExplicitBucketBoundariesAdvice(List.of(2.0 ,6.0)) + .build(); + LongHistogram longHisto = testMeter.histogramBuilder("long_histo").ofLongs() + .setExplicitBucketBoundariesAdvice(List.of(2L ,6L)) + .build(); + + doubleHisto.record(0.5); + doubleHisto.record(2.5); + doubleHisto.record(2.5); + doubleHisto.record(7.5); + doubleHisto.record(7.5); + doubleHisto.record(7.5); + + longHisto.record(0); + longHisto.record(3); + longHisto.record(3); + longHisto.record(7); + longHisto.record(7); + longHisto.record(7); + + resetReporterAndFlushMetrics(); + assertThatMetricSets(reporter.getBytes()) + .hasMetricsetCount(1) + .first() + .containsHistogramMetric("double_histo", List.of(1.0, 4.0, 6.0), List.of(1L, 2L, 3L)) + .containsHistogramMetric("long_histo", List.of(1.0, 4.0, 6.0), List.of(1L, 2L, 3L)) .hasMetricsCount(2); } diff --git a/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-metricsdk-plugin/src/test/java/co/elastic/apm/agent/otelmetricsdk/PrivateUserSdkOtelMetricsTest.java b/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-metricsdk-plugin/src/test/java/co/elastic/apm/agent/otelmetricsdk/PrivateUserSdkOtelMetricsTest.java index f31884653b..5bd9e711cf 100644 --- a/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-metricsdk-plugin/src/test/java/co/elastic/apm/agent/otelmetricsdk/PrivateUserSdkOtelMetricsTest.java +++ b/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-metricsdk-plugin/src/test/java/co/elastic/apm/agent/otelmetricsdk/PrivateUserSdkOtelMetricsTest.java @@ -20,6 +20,7 @@ import co.elastic.apm.agent.configuration.MetricsConfiguration; import io.opentelemetry.api.metrics.DoubleHistogram; +import io.opentelemetry.api.metrics.DoubleHistogramBuilder; import io.opentelemetry.api.metrics.Meter; import io.opentelemetry.api.metrics.MeterProvider; import io.opentelemetry.sdk.metrics.Aggregation; @@ -95,6 +96,23 @@ protected void invokeSdkForceFlush() { ((SdkMeterProvider) getMeterProvider()).forceFlush(); } + + @Test + @Override + public void testDefaultHistogramBuckets() { + // Unfortunately default histogram buckets don't work with user-provided SDKs of version 1.32.0 or newer + // The reason is that a default aggregation provided by the exporter would override + // bucket boundaries set via DoubleHistogramBuilder.setExplicitBucketBoundaries + // We decided to instead respect the bucket boundaries provided by the API + try { + DoubleHistogramBuilder.class.getMethod("setExplicitBucketBoundariesAdvice", List.class); + //Method exists, default bucket boundaries are not supported + //Don't execute test for that reason + } catch (NoSuchMethodException e) { + super.testDefaultHistogramBuckets(); + } + } + @Test public void testCustomHistogramView() { sdkCustomizer = builder -> builder.registerView( diff --git a/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-test/src/test/java/co/elastic/apm/agent/opentelemetry/OpenTelemetryVersionIT.java b/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-test/src/test/java/co/elastic/apm/agent/opentelemetry/OpenTelemetryVersionIT.java index d617ad5f0f..0081f65c9b 100644 --- a/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-test/src/test/java/co/elastic/apm/agent/opentelemetry/OpenTelemetryVersionIT.java +++ b/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-test/src/test/java/co/elastic/apm/agent/opentelemetry/OpenTelemetryVersionIT.java @@ -65,25 +65,18 @@ void testTracingVersion(String version) throws Exception { } @ParameterizedTest - @ValueSource(strings = { - "1.10.0", - //"1.11.0", - //"1.12.0", - //"1.13.0", - "1.14.0", - "1.15.0", - //"1.16.0", - //"1.17.0", - //"1.18.0", - //"1.19.0", - //"1.20.0", - "1.21.0" - }) - void testAgentProvidedMetricsSdkForApiVersion(String version) throws Exception { + @CsvSource(value = { + "1.10.0|io.opentelemetry:opentelemetry-semconv:1.10.0-alpha", + "1.14.0|io.opentelemetry:opentelemetry-semconv:1.14.0-alpha", + "1.15.0|io.opentelemetry:opentelemetry-semconv:1.15.0-alpha", + "1.21.0|io.opentelemetry:opentelemetry-semconv:1.21.0-alpha", + "1.31.0|io.opentelemetry.semconv:opentelemetry-semconv:1.22.0-alpha", + }, delimiterString = "|") + void testAgentProvidedMetricsSdkForApiVersion(String version, String semConvDep) throws Exception { List dependencies = List.of( "io.opentelemetry:opentelemetry-api:" + version, "io.opentelemetry:opentelemetry-context:" + version, - "io.opentelemetry:opentelemetry-semconv:" + version + "-alpha"); + semConvDep); TestClassWithDependencyRunner runner = new TestClassWithDependencyRunner(dependencies, "co.elastic.apm.agent.opentelemetry.metrics.AgentProvidedSdkOtelMetricsTest", "co.elastic.apm.agent.otelmetricsdk.AbstractOtelMetricsTest", @@ -93,21 +86,19 @@ void testAgentProvidedMetricsSdkForApiVersion(String version) throws Exception { } @ParameterizedTest - @ValueSource(strings = { - "1.16.0", - //"1.17.0", - //"1.18.0", - //"1.19.0", - //"1.20.0", - "1.21.0" - }) - void testUserProvidedMetricsSdkVersion(String version) throws Exception { + @CsvSource(value = { + "1.16.0|io.opentelemetry:opentelemetry-semconv:1.16.0-alpha", + "1.21.0|io.opentelemetry:opentelemetry-semconv:1.21.0-alpha", + "1.31.0|io.opentelemetry.semconv:opentelemetry-semconv:1.22.0-alpha", + }, delimiterString = "|") + void testUserProvidedMetricsSdkVersion(String version, String semConvDep) throws Exception { List dependencies = List.of( "io.opentelemetry:opentelemetry-api:" + version, "io.opentelemetry:opentelemetry-sdk-metrics:" + version, + "io.opentelemetry:opentelemetry-extension-incubator:" + version+"-alpha", "io.opentelemetry:opentelemetry-sdk-common:" + version, "io.opentelemetry:opentelemetry-context:" + version, - "io.opentelemetry:opentelemetry-semconv:" + version + "-alpha"); + semConvDep); TestClassWithDependencyRunner runner = new TestClassWithDependencyRunner(dependencies, "co.elastic.apm.agent.otelmetricsdk.PrivateUserSdkOtelMetricsTest", "co.elastic.apm.agent.otelmetricsdk.AbstractOtelMetricsTest", diff --git a/apm-agent-plugins/apm-opentelemetry/pom.xml b/apm-agent-plugins/apm-opentelemetry/pom.xml index da814ccac4..a9e2917f56 100644 --- a/apm-agent-plugins/apm-opentelemetry/pom.xml +++ b/apm-agent-plugins/apm-opentelemetry/pom.xml @@ -19,7 +19,7 @@ When updating the version, add the old version to OpenTelemetryVersionIT to make sure that in the future we stay compatible with the previous version. --> - 1.31.0 + 1.32.0 1.22.0-alpha 8 diff --git a/docs/api-opentelemetry.asciidoc b/docs/api-opentelemetry.asciidoc index 5812da6d7e..821ba08deb 100644 --- a/docs/api-opentelemetry.asciidoc +++ b/docs/api-opentelemetry.asciidoc @@ -149,6 +149,8 @@ In both cases the Elastic APM Agent will respect the <> setting to customize histogram bucket boundaries. Alternatively you can use OpenTelemetry `Views` to define histogram buckets on a per-metric basis when providing your own `MeterProvider`. +Note that `custom_metrics_histogram_boundaries` will only work for API Usages. If you bring your own `MeterProvider` and therefore your own OpenTelemetry SDK, +the setting will only work for SDK versions prior to `1.32.0`. [float] [[otel-metrics-api]] @@ -178,7 +180,7 @@ If you provide your own `MeterProvider` (see <>), the agent wi In some cases using just the <> might not be flexible enough. Some example use cases are: - * Using OpenTelemetry Views (e.g. to customize histogram buckets on a per-metric basis) + * Using OpenTelemetry Views * Exporting metrics to other tools in addition to Elastic APM (e.g. prometheus) For these use cases you can just setup you OpenTelemetry SDK `MeterProvider`. diff --git a/docs/configuration.asciidoc b/docs/configuration.asciidoc index 1b8bb856a8..1e260f579b 100644 --- a/docs/configuration.asciidoc +++ b/docs/configuration.asciidoc @@ -2547,6 +2547,8 @@ NOTE: This feature is currently experimental, which means it is disabled by defa Defines the default bucket boundaries to use for OpenTelemetry histograms. +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. + @@ -4618,6 +4620,8 @@ Example: `5ms`. # dedot_custom_metrics=true # Defines the default bucket boundaries to use for OpenTelemetry histograms. +# +# 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. # # This setting can not be changed at runtime. Changes require a restart of the application. # Type: comma separated list