From c01ceabf6b38569c2606d5c98de78783f8f38e43 Mon Sep 17 00:00:00 2001 From: Mike Goldsmith Date: Mon, 16 Feb 2026 14:42:56 +0000 Subject: [PATCH 01/14] Add recordMinMax param to histogram aggregations per spec Supports disabling min/max recording to work around GCP min/max handling issues. Backward compatible - defaults to true. --- .../fileconfig/AggregationFactory.java | 13 +++++-- .../aggregator/HistogramAggregationParam.java | 6 ++-- .../sdk/metrics/Aggregation.java | 29 +++++++++++++++ ...leBase2ExponentialHistogramAggregator.java | 34 ++++++++++++------ ...ubleExplicitBucketHistogramAggregator.java | 36 ++++++++++++------- .../Base2ExponentialHistogramAggregation.java | 28 +++++++++++++-- .../ExplicitBucketHistogramAggregation.java | 14 ++++++-- ...se2ExponentialHistogramAggregatorTest.java | 16 ++++++--- ...ExplicitBucketHistogramAggregatorTest.java | 5 +-- 9 files changed, 140 insertions(+), 41 deletions(-) diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/AggregationFactory.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/AggregationFactory.java index ca2441f12f9..69e2c80dd4b 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/AggregationFactory.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/AggregationFactory.java @@ -10,6 +10,7 @@ import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Base2ExponentialBucketHistogramAggregationModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ExplicitBucketHistogramAggregationModel; import io.opentelemetry.sdk.metrics.Aggregation; +import io.opentelemetry.sdk.metrics.internal.aggregator.ExplicitBucketHistogramUtils; import java.util.List; final class AggregationFactory implements Factory { @@ -44,8 +45,10 @@ public Aggregation create(AggregationModel model, DeclarativeConfigContext conte if (maxSize == null) { maxSize = 160; } + Boolean recordMinMax = exponentialBucketHistogram.getRecordMinMax(); + boolean shouldRecordMinMax = recordMinMax != null ? recordMinMax : true; try { - return Aggregation.base2ExponentialBucketHistogram(maxSize, maxScale); + return Aggregation.base2ExponentialBucketHistogram(maxSize, maxScale, shouldRecordMinMax); } catch (IllegalArgumentException e) { throw new DeclarativeConfigException("Invalid exponential bucket histogram", e); } @@ -54,11 +57,15 @@ public Aggregation create(AggregationModel model, DeclarativeConfigContext conte model.getExplicitBucketHistogram(); if (explicitBucketHistogram != null) { List boundaries = explicitBucketHistogram.getBoundaries(); + Boolean recordMinMax = explicitBucketHistogram.getRecordMinMax(); + boolean shouldRecordMinMax = recordMinMax != null ? recordMinMax : true; if (boundaries == null) { - return Aggregation.explicitBucketHistogram(); + // Use default boundaries with recordMinMax parameter + return Aggregation.explicitBucketHistogram( + ExplicitBucketHistogramUtils.DEFAULT_HISTOGRAM_BUCKET_BOUNDARIES, shouldRecordMinMax); } try { - return Aggregation.explicitBucketHistogram(boundaries); + return Aggregation.explicitBucketHistogram(boundaries, shouldRecordMinMax); } catch (IllegalArgumentException e) { throw new DeclarativeConfigException("Invalid explicit bucket histogram", e); } diff --git a/sdk/metrics/src/jmh/java/io/opentelemetry/sdk/metrics/internal/aggregator/HistogramAggregationParam.java b/sdk/metrics/src/jmh/java/io/opentelemetry/sdk/metrics/internal/aggregator/HistogramAggregationParam.java index 71bf89803cc..30c78a0500e 100644 --- a/sdk/metrics/src/jmh/java/io/opentelemetry/sdk/metrics/internal/aggregator/HistogramAggregationParam.java +++ b/sdk/metrics/src/jmh/java/io/opentelemetry/sdk/metrics/internal/aggregator/HistogramAggregationParam.java @@ -17,19 +17,21 @@ public enum HistogramAggregationParam { new DoubleExplicitBucketHistogramAggregator( ExplicitBucketHistogramUtils.createBoundaryArray( ExplicitBucketHistogramUtils.DEFAULT_HISTOGRAM_BUCKET_BOUNDARIES), + /* recordMinMax= */ true, ExemplarReservoirFactory.noSamples(), IMMUTABLE_DATA)), EXPLICIT_SINGLE_BUCKET( new DoubleExplicitBucketHistogramAggregator( ExplicitBucketHistogramUtils.createBoundaryArray(Collections.emptyList()), + /* recordMinMax= */ true, ExemplarReservoirFactory.noSamples(), IMMUTABLE_DATA)), EXPONENTIAL_SMALL_CIRCULAR_BUFFER( new DoubleBase2ExponentialHistogramAggregator( - ExemplarReservoirFactory.noSamples(), 20, 0, IMMUTABLE_DATA)), + ExemplarReservoirFactory.noSamples(), 20, 0, /* recordMinMax= */ true, IMMUTABLE_DATA)), EXPONENTIAL_CIRCULAR_BUFFER( new DoubleBase2ExponentialHistogramAggregator( - ExemplarReservoirFactory.noSamples(), 160, 0, IMMUTABLE_DATA)); + ExemplarReservoirFactory.noSamples(), 160, 0, /* recordMinMax= */ true, IMMUTABLE_DATA)); private final Aggregator aggregator; diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/Aggregation.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/Aggregation.java index ba8470a8ed9..b1bd36136b0 100644 --- a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/Aggregation.java +++ b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/Aggregation.java @@ -69,6 +69,18 @@ static Aggregation explicitBucketHistogram(List bucketBoundaries) { return ExplicitBucketHistogramAggregation.create(bucketBoundaries); } + /** + * Aggregates measurements into an explicit bucket {@link MetricDataType#HISTOGRAM}. + * + * @param bucketBoundaries A list of (inclusive) upper bounds for the histogram. Should be in + * order from lowest to highest. + * @param recordMinMax whether to record min and max values + * @since 1.45.0 + */ + static Aggregation explicitBucketHistogram(List bucketBoundaries, boolean recordMinMax) { + return ExplicitBucketHistogramAggregation.create(bucketBoundaries, recordMinMax); + } + /** * Aggregates measurements into a base-2 {@link MetricDataType#EXPONENTIAL_HISTOGRAM} using the * default {@code maxBuckets} and {@code maxScale}. @@ -93,4 +105,21 @@ static Aggregation base2ExponentialBucketHistogram() { static Aggregation base2ExponentialBucketHistogram(int maxBuckets, int maxScale) { return Base2ExponentialHistogramAggregation.create(maxBuckets, maxScale); } + + /** + * Aggregates measurements into a base-2 {@link MetricDataType#EXPONENTIAL_HISTOGRAM}. + * + * @param maxBuckets the max number of positive buckets and negative buckets (max total buckets is + * 2 * {@code maxBuckets} + 1 zero bucket). + * @param maxScale the maximum and initial scale. If measurements can't fit in a particular scale + * given the {@code maxBuckets}, the scale is reduced until the measurements can be + * accommodated. Setting maxScale may reduce the number of downscales. Additionally, the + * performance of computing bucket index is improved when scale is {@code <= 0}. + * @param recordMinMax whether to record min and max values + * @since 1.45.0 + */ + static Aggregation base2ExponentialBucketHistogram( + int maxBuckets, int maxScale, boolean recordMinMax) { + return Base2ExponentialHistogramAggregation.create(maxBuckets, maxScale, recordMinMax); + } } diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/aggregator/DoubleBase2ExponentialHistogramAggregator.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/aggregator/DoubleBase2ExponentialHistogramAggregator.java index 28c18a78d46..072f79a3e1c 100644 --- a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/aggregator/DoubleBase2ExponentialHistogramAggregator.java +++ b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/aggregator/DoubleBase2ExponentialHistogramAggregator.java @@ -40,27 +40,34 @@ public final class DoubleBase2ExponentialHistogramAggregator private final ExemplarReservoirFactory reservoirFactory; private final int maxBuckets; private final int maxScale; + private final boolean recordMinMax; private final MemoryMode memoryMode; /** * Constructs an exponential histogram aggregator. * * @param reservoirFactory Supplier of exemplar reservoirs per-stream. + * @param maxBuckets maximum number of buckets in each of the positive and negative ranges + * @param maxScale maximum scale factor + * @param recordMinMax whether to record min and max values + * @param memoryMode The {@link MemoryMode} to use in this aggregator. */ public DoubleBase2ExponentialHistogramAggregator( ExemplarReservoirFactory reservoirFactory, int maxBuckets, int maxScale, + boolean recordMinMax, MemoryMode memoryMode) { this.reservoirFactory = reservoirFactory; this.maxBuckets = maxBuckets; this.maxScale = maxScale; + this.recordMinMax = recordMinMax; this.memoryMode = memoryMode; } @Override public AggregatorHandle createHandle() { - return new Handle(reservoirFactory, maxBuckets, maxScale, memoryMode); + return new Handle(reservoirFactory, maxBuckets, maxScale, recordMinMax, memoryMode); } @Override @@ -82,6 +89,7 @@ public MetricData toMetricData( static final class Handle extends AggregatorHandle { private final int maxBuckets; private final int maxScale; + private final boolean recordMinMax; @Nullable private DoubleBase2ExponentialHistogramBuckets positiveBuckets; @Nullable private DoubleBase2ExponentialHistogramBuckets negativeBuckets; private long zeroCount; @@ -99,10 +107,12 @@ static final class Handle extends AggregatorHandle 0, - this.min, - this.count > 0, - this.max, + recordMinMax && this.count > 0, + recordMinMax ? this.min : 0, + recordMinMax && this.count > 0, + recordMinMax ? this.max : 0, resolveBuckets( this.positiveBuckets, currentScale, reset, /* reusableBuckets= */ null), resolveBuckets( @@ -149,10 +159,10 @@ protected synchronized ExponentialHistogramPointData doAggregateThenMaybeResetDo currentScale, sum, zeroCount, - this.count > 0, - this.min, - this.count > 0, - this.max, + recordMinMax && this.count > 0, + recordMinMax ? this.min : 0, + recordMinMax && this.count > 0, + recordMinMax ? this.max : 0, resolveBuckets( this.positiveBuckets, currentScale, reset, reusablePoint.getPositiveBuckets()), resolveBuckets( @@ -222,8 +232,10 @@ protected synchronized void doRecordDouble(double value) { sum += value; - this.min = Math.min(this.min, value); - this.max = Math.max(this.max, value); + if (recordMinMax) { + this.min = Math.min(this.min, value); + this.max = Math.max(this.max, value); + } count++; int c = Double.compare(value, 0); diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/aggregator/DoubleExplicitBucketHistogramAggregator.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/aggregator/DoubleExplicitBucketHistogramAggregator.java index 0d3ce15c1bf..4124454f755 100644 --- a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/aggregator/DoubleExplicitBucketHistogramAggregator.java +++ b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/aggregator/DoubleExplicitBucketHistogramAggregator.java @@ -38,6 +38,7 @@ public final class DoubleExplicitBucketHistogramAggregator implements Aggregator { private final double[] boundaries; + private final boolean recordMinMax; private final MemoryMode memoryMode; // a cache for converting to MetricData @@ -49,12 +50,17 @@ public final class DoubleExplicitBucketHistogramAggregator * Constructs an explicit bucket histogram aggregator. * * @param boundaries Bucket boundaries, in-order. + * @param recordMinMax whether to record min and max values * @param reservoirFactory Supplier of exemplar reservoirs per-stream. * @param memoryMode The {@link MemoryMode} to use in this aggregator. */ public DoubleExplicitBucketHistogramAggregator( - double[] boundaries, ExemplarReservoirFactory reservoirFactory, MemoryMode memoryMode) { + double[] boundaries, + boolean recordMinMax, + ExemplarReservoirFactory reservoirFactory, + MemoryMode memoryMode) { this.boundaries = boundaries; + this.recordMinMax = recordMinMax; this.memoryMode = memoryMode; List boundaryList = new ArrayList<>(this.boundaries.length); @@ -67,7 +73,7 @@ public DoubleExplicitBucketHistogramAggregator( @Override public AggregatorHandle createHandle() { - return new Handle(boundaryList, boundaries, reservoirFactory, memoryMode); + return new Handle(boundaryList, boundaries, recordMinMax, reservoirFactory, memoryMode); } @Override @@ -91,6 +97,8 @@ static final class Handle extends AggregatorHandle { private final List boundaryList; // read-only private final double[] boundaries; + // read-only + private final boolean recordMinMax; private final Object lock = new Object(); @@ -115,11 +123,13 @@ static final class Handle extends AggregatorHandle { Handle( List boundaryList, double[] boundaries, + boolean recordMinMax, ExemplarReservoirFactory reservoirFactory, MemoryMode memoryMode) { super(reservoirFactory, /* isDoubleType= */ true); this.boundaryList = boundaryList; this.boundaries = boundaries; + this.recordMinMax = recordMinMax; this.counts = new long[this.boundaries.length + 1]; this.sum = 0; this.min = Double.MAX_VALUE; @@ -156,10 +166,10 @@ protected HistogramPointData doAggregateThenMaybeResetDoubles( epochNanos, attributes, sum, - this.count > 0, - this.min, - this.count > 0, - this.max, + recordMinMax && this.count > 0, + recordMinMax ? this.min : 0, + recordMinMax && this.count > 0, + recordMinMax ? this.max : 0, boundaryList, PrimitiveLongList.wrap(Arrays.copyOf(counts, counts.length)), exemplars); @@ -170,10 +180,10 @@ protected HistogramPointData doAggregateThenMaybeResetDoubles( epochNanos, attributes, sum, - this.count > 0, - this.min, - this.count > 0, - this.max, + recordMinMax && this.count > 0, + recordMinMax ? this.min : 0, + recordMinMax && this.count > 0, + recordMinMax ? this.max : 0, boundaryList, counts, exemplars); @@ -195,8 +205,10 @@ protected void doRecordDouble(double value) { synchronized (lock) { this.sum += value; - this.min = Math.min(this.min, value); - this.max = Math.max(this.max, value); + if (recordMinMax) { + this.min = Math.min(this.min, value); + this.max = Math.max(this.max, value); + } this.count++; this.counts[bucketIndex]++; } diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/view/Base2ExponentialHistogramAggregation.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/view/Base2ExponentialHistogramAggregation.java index 6e354d7069c..0f87eeda180 100644 --- a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/view/Base2ExponentialHistogramAggregation.java +++ b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/view/Base2ExponentialHistogramAggregation.java @@ -32,14 +32,17 @@ public final class Base2ExponentialHistogramAggregation implements Aggregation, private static final int DEFAULT_MAX_SCALE = 20; private static final Aggregation DEFAULT = - new Base2ExponentialHistogramAggregation(DEFAULT_MAX_BUCKETS, DEFAULT_MAX_SCALE); + new Base2ExponentialHistogramAggregation( + DEFAULT_MAX_BUCKETS, DEFAULT_MAX_SCALE, /* recordMinMax= */ true); private final int maxBuckets; private final int maxScale; + private final boolean recordMinMax; - private Base2ExponentialHistogramAggregation(int maxBuckets, int maxScale) { + private Base2ExponentialHistogramAggregation(int maxBuckets, int maxScale, boolean recordMinMax) { this.maxBuckets = maxBuckets; this.maxScale = maxScale; + this.recordMinMax = recordMinMax; } public static Aggregation getDefault() { @@ -60,7 +63,25 @@ public static Aggregation getDefault() { public static Aggregation create(int maxBuckets, int maxScale) { checkArgument(maxBuckets >= 2, "maxBuckets must be >= 2"); checkArgument(maxScale <= 20 && maxScale >= -10, "maxScale must be -10 <= x <= 20"); - return new Base2ExponentialHistogramAggregation(maxBuckets, maxScale); + return new Base2ExponentialHistogramAggregation(maxBuckets, maxScale, /* recordMinMax= */ true); + } + + /** + * Aggregations measurements into an {@link MetricDataType#EXPONENTIAL_HISTOGRAM}. + * + * @param maxBuckets the max number of positive buckets and negative buckets (max total buckets is + * 2 * {@code maxBuckets} + 1 zero bucket). + * @param maxScale the maximum and initial scale. If measurements can't fit in a particular scale + * given the {@code maxBuckets}, the scale is reduced until the measurements can be + * accommodated. Setting maxScale may reduce the number of downscales. Additionally, the + * performance of computing bucket index is improved when scale is <= 0. + * @param recordMinMax whether to record min and max values + * @return the aggregation + */ + public static Aggregation create(int maxBuckets, int maxScale, boolean recordMinMax) { + checkArgument(maxBuckets >= 2, "maxBuckets must be >= 2"); + checkArgument(maxScale <= 20 && maxScale >= -10, "maxScale must be -10 <= x <= 20"); + return new Base2ExponentialHistogramAggregation(maxBuckets, maxScale, recordMinMax); } @Override @@ -79,6 +100,7 @@ public Aggregator createAggregator( RandomSupplier.platformDefault())), maxBuckets, maxScale, + recordMinMax, memoryMode); } diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/view/ExplicitBucketHistogramAggregation.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/view/ExplicitBucketHistogramAggregation.java index d4af55fd0ad..ff127ecc594 100644 --- a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/view/ExplicitBucketHistogramAggregation.java +++ b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/view/ExplicitBucketHistogramAggregation.java @@ -28,23 +28,30 @@ public final class ExplicitBucketHistogramAggregation implements Aggregation, Ag private static final Aggregation DEFAULT = new ExplicitBucketHistogramAggregation( - ExplicitBucketHistogramUtils.DEFAULT_HISTOGRAM_BUCKET_BOUNDARIES); + ExplicitBucketHistogramUtils.DEFAULT_HISTOGRAM_BUCKET_BOUNDARIES, + /* recordMinMax= */ true); public static Aggregation getDefault() { return DEFAULT; } public static Aggregation create(List bucketBoundaries) { - return new ExplicitBucketHistogramAggregation(bucketBoundaries); + return new ExplicitBucketHistogramAggregation(bucketBoundaries, /* recordMinMax= */ true); + } + + public static Aggregation create(List bucketBoundaries, boolean recordMinMax) { + return new ExplicitBucketHistogramAggregation(bucketBoundaries, recordMinMax); } private final List bucketBoundaries; private final double[] bucketBoundaryArray; + private final boolean recordMinMax; - private ExplicitBucketHistogramAggregation(List bucketBoundaries) { + private ExplicitBucketHistogramAggregation(List bucketBoundaries, boolean recordMinMax) { this.bucketBoundaries = bucketBoundaries; // We need to fail here if our bucket boundaries are ill-configured. this.bucketBoundaryArray = ExplicitBucketHistogramUtils.createBoundaryArray(bucketBoundaries); + this.recordMinMax = recordMinMax; } @Override @@ -56,6 +63,7 @@ public Aggregator createAggregator( return (Aggregator) new DoubleExplicitBucketHistogramAggregator( bucketBoundaryArray, + recordMinMax, ExemplarReservoirFactory.filtered( exemplarFilter, ExemplarReservoirFactory.histogramBucketReservoir( diff --git a/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/internal/aggregator/DoubleBase2ExponentialHistogramAggregatorTest.java b/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/internal/aggregator/DoubleBase2ExponentialHistogramAggregatorTest.java index 763d36fbec6..62dbcccda2b 100644 --- a/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/internal/aggregator/DoubleBase2ExponentialHistogramAggregatorTest.java +++ b/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/internal/aggregator/DoubleBase2ExponentialHistogramAggregatorTest.java @@ -82,10 +82,14 @@ private static Stream provideAggregat for (MemoryMode memoryMode : MemoryMode.values()) { parameters.add( new DoubleBase2ExponentialHistogramAggregator( - ExemplarReservoirFactory.noSamples(), 160, 20, memoryMode)); + ExemplarReservoirFactory.noSamples(), 160, 20, /* recordMinMax= */ true, memoryMode)); parameters.add( new DoubleBase2ExponentialHistogramAggregator( - ExemplarReservoirFactory.noSamples(), 160, MAX_SCALE, memoryMode)); + ExemplarReservoirFactory.noSamples(), + 160, + MAX_SCALE, + /* recordMinMax= */ true, + memoryMode)); } return parameters.stream(); } @@ -98,7 +102,7 @@ private static int valueToIndex(int scale, double value) { private void initialize(MemoryMode memoryMode) { aggregator = new DoubleBase2ExponentialHistogramAggregator( - ExemplarReservoirFactory.noSamples(), 160, 20, memoryMode); + ExemplarReservoirFactory.noSamples(), 160, 20, /* recordMinMax= */ true, memoryMode); } @ParameterizedTest @@ -230,7 +234,8 @@ void testRecordingsAtLimits(DoubleBase2ExponentialHistogramAggregator aggregator @EnumSource(MemoryMode.class) void aggregateThenMaybeReset_WithExemplars(MemoryMode memoryMode) { DoubleBase2ExponentialHistogramAggregator agg = - new DoubleBase2ExponentialHistogramAggregator(reservoirFactory, 160, MAX_SCALE, memoryMode); + new DoubleBase2ExponentialHistogramAggregator( + reservoirFactory, 160, MAX_SCALE, /* recordMinMax= */ true, memoryMode); Attributes attributes = Attributes.builder().put("test", "value").build(); DoubleExemplarData exemplar = @@ -348,7 +353,8 @@ void testToMetricData(MemoryMode memoryMode) { .thenReturn(Collections.singletonList(exemplar)); DoubleBase2ExponentialHistogramAggregator cumulativeAggregator = - new DoubleBase2ExponentialHistogramAggregator(reservoirFactory, 160, MAX_SCALE, memoryMode); + new DoubleBase2ExponentialHistogramAggregator( + reservoirFactory, 160, MAX_SCALE, /* recordMinMax= */ true, memoryMode); AggregatorHandle aggregatorHandle = cumulativeAggregator.createHandle(); diff --git a/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/internal/aggregator/DoubleExplicitBucketHistogramAggregatorTest.java b/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/internal/aggregator/DoubleExplicitBucketHistogramAggregatorTest.java index 2e425e5cd0c..922951b4b82 100644 --- a/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/internal/aggregator/DoubleExplicitBucketHistogramAggregatorTest.java +++ b/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/internal/aggregator/DoubleExplicitBucketHistogramAggregatorTest.java @@ -70,7 +70,7 @@ public LongExemplarReservoir createLongExemplarReservoir() { private void init(MemoryMode memoryMode) { aggregator = new DoubleExplicitBucketHistogramAggregator( - boundaries, ExemplarReservoirFactory.noSamples(), memoryMode); + boundaries, /* recordMinMax= */ true, ExemplarReservoirFactory.noSamples(), memoryMode); } @ParameterizedTest @@ -123,7 +123,8 @@ void aggregateThenMaybeReset_WithExemplars(MemoryMode memoryMode) { List exemplars = Collections.singletonList(exemplar); Mockito.when(reservoir.collectAndResetDoubles(Attributes.empty())).thenReturn(exemplars); DoubleExplicitBucketHistogramAggregator aggregator = - new DoubleExplicitBucketHistogramAggregator(boundaries, reservoirFactory, memoryMode); + new DoubleExplicitBucketHistogramAggregator( + boundaries, /* recordMinMax= */ true, reservoirFactory, memoryMode); AggregatorHandle aggregatorHandle = aggregator.createHandle(); aggregatorHandle.recordDouble(0, attributes, Context.root()); assertThat( From 8f364a527d7f2cb5fa848ec36661508256630fce Mon Sep 17 00:00:00 2001 From: Mike Goldsmith Date: Mon, 16 Feb 2026 14:54:00 +0000 Subject: [PATCH 02/14] Add tests for recordMinMax=false behavior Verifies min/max not tracked when disabled. --- ...se2ExponentialHistogramAggregatorTest.java | 23 +++++++++++++++++++ ...ExplicitBucketHistogramAggregatorTest.java | 23 +++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/internal/aggregator/DoubleBase2ExponentialHistogramAggregatorTest.java b/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/internal/aggregator/DoubleBase2ExponentialHistogramAggregatorTest.java index 62dbcccda2b..47486fab69e 100644 --- a/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/internal/aggregator/DoubleBase2ExponentialHistogramAggregatorTest.java +++ b/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/internal/aggregator/DoubleBase2ExponentialHistogramAggregatorTest.java @@ -574,4 +574,27 @@ public void reusablePoint_emptyFirstThenRecordAndCheck() { assertThat(point.getPositiveBuckets().getBucketCounts()).isNotEmpty(); assertThat(point.getNegativeBuckets().getBucketCounts()).isNotEmpty(); } + + @ParameterizedTest + @EnumSource(MemoryMode.class) + void testRecordMinMaxDisabled(MemoryMode memoryMode) { + DoubleBase2ExponentialHistogramAggregator aggregator = + new DoubleBase2ExponentialHistogramAggregator( + ExemplarReservoirFactory.noSamples(), 160, 20, /* recordMinMax= */ false, memoryMode); + AggregatorHandle aggregatorHandle = aggregator.createHandle(); + aggregatorHandle.recordDouble(0.5, Attributes.empty(), Context.current()); + aggregatorHandle.recordDouble(1.0, Attributes.empty(), Context.current()); + aggregatorHandle.recordDouble(12.0, Attributes.empty(), Context.current()); + aggregatorHandle.recordDouble(15.213, Attributes.empty(), Context.current()); + aggregatorHandle.recordDouble(-13.2, Attributes.empty(), Context.current()); + aggregatorHandle.recordDouble(-2.01, Attributes.empty(), Context.current()); + + ExponentialHistogramPointData point = + aggregatorHandle.aggregateThenMaybeReset(0, 1, Attributes.empty(), /* reset= */ true); + assertThat(point).isNotNull(); + assertThat(point.hasMin()).isFalse(); + assertThat(point.hasMax()).isFalse(); + assertThat(point.getSum()).isCloseTo(13.503, Offset.offset(0.0001)); + assertThat(point.getCount()).isEqualTo(6); + } } diff --git a/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/internal/aggregator/DoubleExplicitBucketHistogramAggregatorTest.java b/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/internal/aggregator/DoubleExplicitBucketHistogramAggregatorTest.java index 922951b4b82..66fd7dd43b6 100644 --- a/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/internal/aggregator/DoubleExplicitBucketHistogramAggregatorTest.java +++ b/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/internal/aggregator/DoubleExplicitBucketHistogramAggregatorTest.java @@ -287,4 +287,27 @@ void testReusableDataMemoryMode() { // The point data instance should be reused assertThat(anotherPointData).isSameAs(pointData); } + + @ParameterizedTest + @EnumSource(MemoryMode.class) + void testRecordMinMaxDisabled(MemoryMode memoryMode) { + DoubleExplicitBucketHistogramAggregator aggregator = + new DoubleExplicitBucketHistogramAggregator( + boundaries, + /* recordMinMax= */ false, + ExemplarReservoirFactory.noSamples(), + memoryMode); + AggregatorHandle aggregatorHandle = aggregator.createHandle(); + aggregatorHandle.recordLong(20, Attributes.empty(), Context.current()); + aggregatorHandle.recordLong(5, Attributes.empty(), Context.current()); + aggregatorHandle.recordLong(150, Attributes.empty(), Context.current()); + aggregatorHandle.recordLong(2000, Attributes.empty(), Context.current()); + HistogramPointData point = + aggregatorHandle.aggregateThenMaybeReset(0, 1, Attributes.empty(), /* reset= */ true); + assertThat(point.getSum()).isEqualTo(2175); + assertThat(point.hasMin()).isFalse(); + assertThat(point.hasMax()).isFalse(); + assertThat(point.getBoundaries()).isEqualTo(boundariesList); + assertThat(point.getCounts()).isEqualTo(Arrays.asList(1L, 1L, 1L, 1L)); + } } From e584f3115428804f04798dcd76a6cca17a8a613e Mon Sep 17 00:00:00 2001 From: Mike Goldsmith Date: Mon, 16 Feb 2026 15:30:45 +0000 Subject: [PATCH 03/14] Update API diff for recordMinMax methods --- .../apidiffs/current_vs_latest/opentelemetry-sdk-metrics.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-metrics.txt b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-metrics.txt index d339d345d3c..16f6cb84754 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-metrics.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-metrics.txt @@ -1,2 +1,5 @@ Comparing source compatibility of opentelemetry-sdk-metrics-1.60.0-SNAPSHOT.jar against opentelemetry-sdk-metrics-1.59.0.jar -No changes. \ No newline at end of file +*** MODIFIED INTERFACE: PUBLIC ABSTRACT io.opentelemetry.sdk.metrics.Aggregation (not serializable) + === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 + +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.sdk.metrics.Aggregation base2ExponentialBucketHistogram(int, int, boolean) + +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.sdk.metrics.Aggregation explicitBucketHistogram(java.util.List, boolean) From 6e30bcbc260ffc758d98652b17680cbd5a352d35 Mon Sep 17 00:00:00 2001 From: Mike Goldsmith Date: Mon, 16 Feb 2026 16:07:13 +0000 Subject: [PATCH 04/14] Add tests for recordMinMax parameter in AggregationFactory --- .../fileconfig/AggregationFactoryTest.java | 45 ++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/AggregationFactoryTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/AggregationFactoryTest.java index 8cf4ccc6187..043d1d96797 100644 --- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/AggregationFactoryTest.java +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/AggregationFactoryTest.java @@ -62,6 +62,49 @@ private static Stream createTestCases() { .withExplicitBucketHistogram( new ExplicitBucketHistogramAggregationModel() .withBoundaries(Arrays.asList(1.0, 2.0))), - Aggregation.explicitBucketHistogram(Arrays.asList(1.0, 2.0)))); + Aggregation.explicitBucketHistogram(Arrays.asList(1.0, 2.0))), + // Test recordMinMax parameter for explicit bucket histogram + Arguments.of( + new AggregationModel() + .withExplicitBucketHistogram( + new ExplicitBucketHistogramAggregationModel() + .withBoundaries(Arrays.asList(1.0, 2.0)) + .withRecordMinMax(true)), + Aggregation.explicitBucketHistogram(Arrays.asList(1.0, 2.0), true)), + Arguments.of( + new AggregationModel() + .withExplicitBucketHistogram( + new ExplicitBucketHistogramAggregationModel() + .withBoundaries(Arrays.asList(1.0, 2.0)) + .withRecordMinMax(false)), + Aggregation.explicitBucketHistogram(Arrays.asList(1.0, 2.0), false)), + Arguments.of( + new AggregationModel() + .withExplicitBucketHistogram( + new ExplicitBucketHistogramAggregationModel() + .withBoundaries(null) + .withRecordMinMax(false)), + Aggregation.explicitBucketHistogram( + Arrays.asList( + 0.0, 5.0, 10.0, 25.0, 50.0, 75.0, 100.0, 250.0, 500.0, 750.0, 1000.0, 2500.0, + 5000.0, 7500.0, 10000.0), + false)), + // Test recordMinMax parameter for exponential bucket histogram + Arguments.of( + new AggregationModel() + .withBase2ExponentialBucketHistogram( + new Base2ExponentialBucketHistogramAggregationModel() + .withMaxSize(2) + .withMaxScale(2) + .withRecordMinMax(true)), + Aggregation.base2ExponentialBucketHistogram(2, 2, true)), + Arguments.of( + new AggregationModel() + .withBase2ExponentialBucketHistogram( + new Base2ExponentialBucketHistogramAggregationModel() + .withMaxSize(2) + .withMaxScale(2) + .withRecordMinMax(false)), + Aggregation.base2ExponentialBucketHistogram(2, 2, false))); } } From 849cc50b45761ddb2b4b1279b234a5de6e76ffb2 Mon Sep 17 00:00:00 2001 From: Mike Goldsmith Date: Fri, 20 Feb 2026 12:56:24 +0000 Subject: [PATCH 05/14] Address review feedback - Remove @since annotations (added during release flow) - Remove old create() overloads without recordMinMax, update callers - Remove redundant // read-only comment on final primitive field Claude claude-sonnet-4-6 assisted with this change. --- .../opentelemetry/sdk/metrics/Aggregation.java | 6 ++---- ...DoubleExplicitBucketHistogramAggregator.java | 1 - .../Base2ExponentialHistogramAggregation.java | 17 ----------------- .../internal/view/DefaultAggregation.java | 2 +- .../ExplicitBucketHistogramAggregation.java | 4 ---- ...ase2ExponentialHistogramAggregationTest.java | 10 +++++----- 6 files changed, 8 insertions(+), 32 deletions(-) diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/Aggregation.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/Aggregation.java index b1bd36136b0..3d6524f976a 100644 --- a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/Aggregation.java +++ b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/Aggregation.java @@ -66,7 +66,7 @@ static Aggregation explicitBucketHistogram() { * order from lowest to highest. */ static Aggregation explicitBucketHistogram(List bucketBoundaries) { - return ExplicitBucketHistogramAggregation.create(bucketBoundaries); + return ExplicitBucketHistogramAggregation.create(bucketBoundaries, /* recordMinMax= */ true); } /** @@ -75,7 +75,6 @@ static Aggregation explicitBucketHistogram(List bucketBoundaries) { * @param bucketBoundaries A list of (inclusive) upper bounds for the histogram. Should be in * order from lowest to highest. * @param recordMinMax whether to record min and max values - * @since 1.45.0 */ static Aggregation explicitBucketHistogram(List bucketBoundaries, boolean recordMinMax) { return ExplicitBucketHistogramAggregation.create(bucketBoundaries, recordMinMax); @@ -103,7 +102,7 @@ static Aggregation base2ExponentialBucketHistogram() { * @since 1.23.0 */ static Aggregation base2ExponentialBucketHistogram(int maxBuckets, int maxScale) { - return Base2ExponentialHistogramAggregation.create(maxBuckets, maxScale); + return Base2ExponentialHistogramAggregation.create(maxBuckets, maxScale, /* recordMinMax= */ true); } /** @@ -116,7 +115,6 @@ static Aggregation base2ExponentialBucketHistogram(int maxBuckets, int maxScale) * accommodated. Setting maxScale may reduce the number of downscales. Additionally, the * performance of computing bucket index is improved when scale is {@code <= 0}. * @param recordMinMax whether to record min and max values - * @since 1.45.0 */ static Aggregation base2ExponentialBucketHistogram( int maxBuckets, int maxScale, boolean recordMinMax) { diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/aggregator/DoubleExplicitBucketHistogramAggregator.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/aggregator/DoubleExplicitBucketHistogramAggregator.java index 4124454f755..3d7cf1ceb31 100644 --- a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/aggregator/DoubleExplicitBucketHistogramAggregator.java +++ b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/aggregator/DoubleExplicitBucketHistogramAggregator.java @@ -97,7 +97,6 @@ static final class Handle extends AggregatorHandle { private final List boundaryList; // read-only private final double[] boundaries; - // read-only private final boolean recordMinMax; private final Object lock = new Object(); diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/view/Base2ExponentialHistogramAggregation.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/view/Base2ExponentialHistogramAggregation.java index 0f87eeda180..2101f9bfb70 100644 --- a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/view/Base2ExponentialHistogramAggregation.java +++ b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/view/Base2ExponentialHistogramAggregation.java @@ -49,23 +49,6 @@ public static Aggregation getDefault() { return DEFAULT; } - /** - * Aggregations measurements into an {@link MetricDataType#EXPONENTIAL_HISTOGRAM}. - * - * @param maxBuckets the max number of positive buckets and negative buckets (max total buckets is - * 2 * {@code maxBuckets} + 1 zero bucket). - * @param maxScale the maximum and initial scale. If measurements can't fit in a particular scale - * given the {@code maxBuckets}, the scale is reduced until the measurements can be - * accommodated. Setting maxScale may reduce the number of downscales. Additionally, the - * performance of computing bucket index is improved when scale is <= 0. - * @return the aggregation - */ - public static Aggregation create(int maxBuckets, int maxScale) { - checkArgument(maxBuckets >= 2, "maxBuckets must be >= 2"); - checkArgument(maxScale <= 20 && maxScale >= -10, "maxScale must be -10 <= x <= 20"); - return new Base2ExponentialHistogramAggregation(maxBuckets, maxScale, /* recordMinMax= */ true); - } - /** * Aggregations measurements into an {@link MetricDataType#EXPONENTIAL_HISTOGRAM}. * diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/view/DefaultAggregation.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/view/DefaultAggregation.java index d44a48269bf..dac425543c2 100644 --- a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/view/DefaultAggregation.java +++ b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/view/DefaultAggregation.java @@ -45,7 +45,7 @@ private static Aggregation resolve(InstrumentDescriptor instrument, boolean with case HISTOGRAM: if (withAdvice && instrument.getAdvice().getExplicitBucketBoundaries() != null) { return ExplicitBucketHistogramAggregation.create( - instrument.getAdvice().getExplicitBucketBoundaries()); + instrument.getAdvice().getExplicitBucketBoundaries(), /* recordMinMax= */ true); } return ExplicitBucketHistogramAggregation.getDefault(); case OBSERVABLE_GAUGE: diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/view/ExplicitBucketHistogramAggregation.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/view/ExplicitBucketHistogramAggregation.java index ff127ecc594..239aaaf7b1f 100644 --- a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/view/ExplicitBucketHistogramAggregation.java +++ b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/view/ExplicitBucketHistogramAggregation.java @@ -35,10 +35,6 @@ public static Aggregation getDefault() { return DEFAULT; } - public static Aggregation create(List bucketBoundaries) { - return new ExplicitBucketHistogramAggregation(bucketBoundaries, /* recordMinMax= */ true); - } - public static Aggregation create(List bucketBoundaries, boolean recordMinMax) { return new ExplicitBucketHistogramAggregation(bucketBoundaries, recordMinMax); } diff --git a/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/internal/view/Base2ExponentialHistogramAggregationTest.java b/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/internal/view/Base2ExponentialHistogramAggregationTest.java index ee10e75d679..070f81c15fe 100644 --- a/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/internal/view/Base2ExponentialHistogramAggregationTest.java +++ b/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/internal/view/Base2ExponentialHistogramAggregationTest.java @@ -29,25 +29,25 @@ class Base2ExponentialHistogramAggregationTest { @Test void goodConfig() { assertThat(Base2ExponentialHistogramAggregation.getDefault()).isNotNull(); - assertThat(Base2ExponentialHistogramAggregation.create(10, 20)).isNotNull(); + assertThat(Base2ExponentialHistogramAggregation.create(10, 20, /* recordMinMax= */ true)).isNotNull(); } @Test void invalidConfig_Throws() { - assertThatThrownBy(() -> Base2ExponentialHistogramAggregation.create(0, 20)) + assertThatThrownBy(() -> Base2ExponentialHistogramAggregation.create(0, 20, /* recordMinMax= */ true)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("maxBuckets must be >= 2"); - assertThatThrownBy(() -> Base2ExponentialHistogramAggregation.create(2, 21)) + assertThatThrownBy(() -> Base2ExponentialHistogramAggregation.create(2, 21, /* recordMinMax= */ true)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("maxScale must be -10 <= x <= 20"); - assertThatThrownBy(() -> Base2ExponentialHistogramAggregation.create(2, -11)) + assertThatThrownBy(() -> Base2ExponentialHistogramAggregation.create(2, -11, /* recordMinMax= */ true)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("maxScale must be -10 <= x <= 20"); } @Test void minimumBucketsCanAccommodateMaxRange() { - Aggregation aggregation = Base2ExponentialHistogramAggregation.create(2, 20); + Aggregation aggregation = Base2ExponentialHistogramAggregation.create(2, 20, /* recordMinMax= */ true); Aggregator aggregator = ((AggregatorFactory) aggregation) .createAggregator( From 7aaab6815aa9d6e60104a073a31f1862a0ad98e3 Mon Sep 17 00:00:00 2001 From: Mike Goldsmith Date: Fri, 20 Feb 2026 15:32:42 +0000 Subject: [PATCH 06/14] Include recordMinMax in Aggregation toString() Declarative config tests compare aggregation equality via toString(), so recordMinMax must be included for the comparisons to be valid. Claude claude-sonnet-4-6 assisted with this change. --- .../internal/view/Base2ExponentialHistogramAggregation.java | 2 ++ .../internal/view/ExplicitBucketHistogramAggregation.java | 5 ++++- .../java/io/opentelemetry/sdk/metrics/AggregationTest.java | 4 ++-- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/view/Base2ExponentialHistogramAggregation.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/view/Base2ExponentialHistogramAggregation.java index 2101f9bfb70..c69edcbe2bd 100644 --- a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/view/Base2ExponentialHistogramAggregation.java +++ b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/view/Base2ExponentialHistogramAggregation.java @@ -104,6 +104,8 @@ public String toString() { + maxBuckets + ",maxScale=" + maxScale + + ",recordMinMax=" + + recordMinMax + "}"; } } diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/view/ExplicitBucketHistogramAggregation.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/view/ExplicitBucketHistogramAggregation.java index 239aaaf7b1f..97668ba7cc3 100644 --- a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/view/ExplicitBucketHistogramAggregation.java +++ b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/view/ExplicitBucketHistogramAggregation.java @@ -80,6 +80,9 @@ public boolean isCompatibleWithInstrument(InstrumentDescriptor instrumentDescrip @Override public String toString() { - return "ExplicitBucketHistogramAggregation(" + bucketBoundaries.toString() + ")"; + return "ExplicitBucketHistogramAggregation{" + + "bucketBoundaries=" + bucketBoundaries + + ",recordMinMax=" + recordMinMax + + "}"; } } diff --git a/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/AggregationTest.java b/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/AggregationTest.java index be2502ec054..60f8e31f79d 100644 --- a/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/AggregationTest.java +++ b/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/AggregationTest.java @@ -30,10 +30,10 @@ void haveToString() { .contains("ExplicitBucketHistogramAggregation"); assertThat(Aggregation.base2ExponentialBucketHistogram()) .asString() - .isEqualTo("Base2ExponentialHistogramAggregation{maxBuckets=160,maxScale=20}"); + .isEqualTo("Base2ExponentialHistogramAggregation{maxBuckets=160,maxScale=20,recordMinMax=true}"); assertThat(Aggregation.base2ExponentialBucketHistogram(2, 0)) .asString() - .isEqualTo("Base2ExponentialHistogramAggregation{maxBuckets=2,maxScale=0}"); + .isEqualTo("Base2ExponentialHistogramAggregation{maxBuckets=2,maxScale=0,recordMinMax=true}"); } @Test From ec038ac83f31c62d091edca8a38f7b80ccc6c39f Mon Sep 17 00:00:00 2001 From: Mike Goldsmith Date: Fri, 20 Feb 2026 15:43:10 +0000 Subject: [PATCH 07/14] Apply spotless formatting --- .../io/opentelemetry/sdk/metrics/Aggregation.java | 3 ++- .../view/ExplicitBucketHistogramAggregation.java | 6 ++++-- .../sdk/metrics/AggregationTest.java | 6 ++++-- .../Base2ExponentialHistogramAggregationTest.java | 15 ++++++++++----- 4 files changed, 20 insertions(+), 10 deletions(-) diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/Aggregation.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/Aggregation.java index 3d6524f976a..65aa97873b8 100644 --- a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/Aggregation.java +++ b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/Aggregation.java @@ -102,7 +102,8 @@ static Aggregation base2ExponentialBucketHistogram() { * @since 1.23.0 */ static Aggregation base2ExponentialBucketHistogram(int maxBuckets, int maxScale) { - return Base2ExponentialHistogramAggregation.create(maxBuckets, maxScale, /* recordMinMax= */ true); + return Base2ExponentialHistogramAggregation.create( + maxBuckets, maxScale, /* recordMinMax= */ true); } /** diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/view/ExplicitBucketHistogramAggregation.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/view/ExplicitBucketHistogramAggregation.java index 97668ba7cc3..0a9f13f2f1f 100644 --- a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/view/ExplicitBucketHistogramAggregation.java +++ b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/view/ExplicitBucketHistogramAggregation.java @@ -81,8 +81,10 @@ public boolean isCompatibleWithInstrument(InstrumentDescriptor instrumentDescrip @Override public String toString() { return "ExplicitBucketHistogramAggregation{" - + "bucketBoundaries=" + bucketBoundaries - + ",recordMinMax=" + recordMinMax + + "bucketBoundaries=" + + bucketBoundaries + + ",recordMinMax=" + + recordMinMax + "}"; } } diff --git a/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/AggregationTest.java b/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/AggregationTest.java index 60f8e31f79d..8a8fe924d1b 100644 --- a/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/AggregationTest.java +++ b/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/AggregationTest.java @@ -30,10 +30,12 @@ void haveToString() { .contains("ExplicitBucketHistogramAggregation"); assertThat(Aggregation.base2ExponentialBucketHistogram()) .asString() - .isEqualTo("Base2ExponentialHistogramAggregation{maxBuckets=160,maxScale=20,recordMinMax=true}"); + .isEqualTo( + "Base2ExponentialHistogramAggregation{maxBuckets=160,maxScale=20,recordMinMax=true}"); assertThat(Aggregation.base2ExponentialBucketHistogram(2, 0)) .asString() - .isEqualTo("Base2ExponentialHistogramAggregation{maxBuckets=2,maxScale=0,recordMinMax=true}"); + .isEqualTo( + "Base2ExponentialHistogramAggregation{maxBuckets=2,maxScale=0,recordMinMax=true}"); } @Test diff --git a/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/internal/view/Base2ExponentialHistogramAggregationTest.java b/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/internal/view/Base2ExponentialHistogramAggregationTest.java index 070f81c15fe..ff33359ddcb 100644 --- a/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/internal/view/Base2ExponentialHistogramAggregationTest.java +++ b/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/internal/view/Base2ExponentialHistogramAggregationTest.java @@ -29,25 +29,30 @@ class Base2ExponentialHistogramAggregationTest { @Test void goodConfig() { assertThat(Base2ExponentialHistogramAggregation.getDefault()).isNotNull(); - assertThat(Base2ExponentialHistogramAggregation.create(10, 20, /* recordMinMax= */ true)).isNotNull(); + assertThat(Base2ExponentialHistogramAggregation.create(10, 20, /* recordMinMax= */ true)) + .isNotNull(); } @Test void invalidConfig_Throws() { - assertThatThrownBy(() -> Base2ExponentialHistogramAggregation.create(0, 20, /* recordMinMax= */ true)) + assertThatThrownBy( + () -> Base2ExponentialHistogramAggregation.create(0, 20, /* recordMinMax= */ true)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("maxBuckets must be >= 2"); - assertThatThrownBy(() -> Base2ExponentialHistogramAggregation.create(2, 21, /* recordMinMax= */ true)) + assertThatThrownBy( + () -> Base2ExponentialHistogramAggregation.create(2, 21, /* recordMinMax= */ true)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("maxScale must be -10 <= x <= 20"); - assertThatThrownBy(() -> Base2ExponentialHistogramAggregation.create(2, -11, /* recordMinMax= */ true)) + assertThatThrownBy( + () -> Base2ExponentialHistogramAggregation.create(2, -11, /* recordMinMax= */ true)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("maxScale must be -10 <= x <= 20"); } @Test void minimumBucketsCanAccommodateMaxRange() { - Aggregation aggregation = Base2ExponentialHistogramAggregation.create(2, 20, /* recordMinMax= */ true); + Aggregation aggregation = + Base2ExponentialHistogramAggregation.create(2, 20, /* recordMinMax= */ true); Aggregator aggregator = ((AggregatorFactory) aggregation) .createAggregator( From 94da36b1139444385e0152bc3242e677837085cc Mon Sep 17 00:00:00 2001 From: Mike Goldsmith Date: Tue, 24 Feb 2026 11:42:21 +0000 Subject: [PATCH 08/14] Add HistogramOptions builder for histogram behavioral options Replace boolean recordMinMax params on Aggregation factory methods with a HistogramOptions builder, providing a single extensible place for future histogram options without requiring new method overloads. Claude claude-sonnet-4-6 assisted with this change. --- .../opentelemetry-sdk-metrics.txt | 15 ++++- .../fileconfig/AggregationFactory.java | 18 ++++-- .../fileconfig/AggregationFactoryTest.java | 16 +++-- .../sdk/metrics/Aggregation.java | 14 +++-- .../sdk/metrics/HistogramOptions.java | 59 +++++++++++++++++++ 5 files changed, 103 insertions(+), 19 deletions(-) create mode 100644 sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/HistogramOptions.java diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-metrics.txt b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-metrics.txt index 16f6cb84754..8d59c4e6bed 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-metrics.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-metrics.txt @@ -1,5 +1,16 @@ Comparing source compatibility of opentelemetry-sdk-metrics-1.60.0-SNAPSHOT.jar against opentelemetry-sdk-metrics-1.59.0.jar *** MODIFIED INTERFACE: PUBLIC ABSTRACT io.opentelemetry.sdk.metrics.Aggregation (not serializable) === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 - +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.sdk.metrics.Aggregation base2ExponentialBucketHistogram(int, int, boolean) - +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.sdk.metrics.Aggregation explicitBucketHistogram(java.util.List, boolean) + +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.sdk.metrics.Aggregation base2ExponentialBucketHistogram(int, int, io.opentelemetry.sdk.metrics.HistogramOptions) + +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.sdk.metrics.Aggregation explicitBucketHistogram(java.util.List, io.opentelemetry.sdk.metrics.HistogramOptions) ++++ NEW CLASS: PUBLIC(+) FINAL(+) io.opentelemetry.sdk.metrics.HistogramOptions (not serializable) + +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. + +++ NEW SUPERCLASS: java.lang.Object + +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.sdk.metrics.HistogramOptions$Builder builder() + +++ NEW METHOD: PUBLIC(+) boolean isRecordMinMax() + +++ NEW METHOD: PUBLIC(+) java.lang.String toString() ++++ NEW CLASS: PUBLIC(+) STATIC(+) FINAL(+) io.opentelemetry.sdk.metrics.HistogramOptions$Builder (not serializable) + +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. + +++ NEW SUPERCLASS: java.lang.Object + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.metrics.HistogramOptions build() + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.metrics.HistogramOptions$Builder setRecordMinMax(boolean) diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/AggregationFactory.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/AggregationFactory.java index 69e2c80dd4b..a16647851c7 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/AggregationFactory.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/AggregationFactory.java @@ -10,6 +10,7 @@ import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Base2ExponentialBucketHistogramAggregationModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ExplicitBucketHistogramAggregationModel; import io.opentelemetry.sdk.metrics.Aggregation; +import io.opentelemetry.sdk.metrics.HistogramOptions; import io.opentelemetry.sdk.metrics.internal.aggregator.ExplicitBucketHistogramUtils; import java.util.List; @@ -46,9 +47,12 @@ public Aggregation create(AggregationModel model, DeclarativeConfigContext conte maxSize = 160; } Boolean recordMinMax = exponentialBucketHistogram.getRecordMinMax(); - boolean shouldRecordMinMax = recordMinMax != null ? recordMinMax : true; + HistogramOptions options = + HistogramOptions.builder() + .setRecordMinMax(recordMinMax != null ? recordMinMax : true) + .build(); try { - return Aggregation.base2ExponentialBucketHistogram(maxSize, maxScale, shouldRecordMinMax); + return Aggregation.base2ExponentialBucketHistogram(maxSize, maxScale, options); } catch (IllegalArgumentException e) { throw new DeclarativeConfigException("Invalid exponential bucket histogram", e); } @@ -58,14 +62,16 @@ public Aggregation create(AggregationModel model, DeclarativeConfigContext conte if (explicitBucketHistogram != null) { List boundaries = explicitBucketHistogram.getBoundaries(); Boolean recordMinMax = explicitBucketHistogram.getRecordMinMax(); - boolean shouldRecordMinMax = recordMinMax != null ? recordMinMax : true; + HistogramOptions options = + HistogramOptions.builder() + .setRecordMinMax(recordMinMax != null ? recordMinMax : true) + .build(); if (boundaries == null) { - // Use default boundaries with recordMinMax parameter return Aggregation.explicitBucketHistogram( - ExplicitBucketHistogramUtils.DEFAULT_HISTOGRAM_BUCKET_BOUNDARIES, shouldRecordMinMax); + ExplicitBucketHistogramUtils.DEFAULT_HISTOGRAM_BUCKET_BOUNDARIES, options); } try { - return Aggregation.explicitBucketHistogram(boundaries, shouldRecordMinMax); + return Aggregation.explicitBucketHistogram(boundaries, options); } catch (IllegalArgumentException e) { throw new DeclarativeConfigException("Invalid explicit bucket histogram", e); } diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/AggregationFactoryTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/AggregationFactoryTest.java index 043d1d96797..f92ae59853a 100644 --- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/AggregationFactoryTest.java +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/AggregationFactoryTest.java @@ -15,6 +15,7 @@ import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.LastValueAggregationModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.SumAggregationModel; import io.opentelemetry.sdk.metrics.Aggregation; +import io.opentelemetry.sdk.metrics.HistogramOptions; import java.util.Arrays; import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; @@ -70,14 +71,17 @@ private static Stream createTestCases() { new ExplicitBucketHistogramAggregationModel() .withBoundaries(Arrays.asList(1.0, 2.0)) .withRecordMinMax(true)), - Aggregation.explicitBucketHistogram(Arrays.asList(1.0, 2.0), true)), + Aggregation.explicitBucketHistogram( + Arrays.asList(1.0, 2.0), HistogramOptions.builder().setRecordMinMax(true).build())), Arguments.of( new AggregationModel() .withExplicitBucketHistogram( new ExplicitBucketHistogramAggregationModel() .withBoundaries(Arrays.asList(1.0, 2.0)) .withRecordMinMax(false)), - Aggregation.explicitBucketHistogram(Arrays.asList(1.0, 2.0), false)), + Aggregation.explicitBucketHistogram( + Arrays.asList(1.0, 2.0), + HistogramOptions.builder().setRecordMinMax(false).build())), Arguments.of( new AggregationModel() .withExplicitBucketHistogram( @@ -88,7 +92,7 @@ private static Stream createTestCases() { Arrays.asList( 0.0, 5.0, 10.0, 25.0, 50.0, 75.0, 100.0, 250.0, 500.0, 750.0, 1000.0, 2500.0, 5000.0, 7500.0, 10000.0), - false)), + HistogramOptions.builder().setRecordMinMax(false).build())), // Test recordMinMax parameter for exponential bucket histogram Arguments.of( new AggregationModel() @@ -97,7 +101,8 @@ private static Stream createTestCases() { .withMaxSize(2) .withMaxScale(2) .withRecordMinMax(true)), - Aggregation.base2ExponentialBucketHistogram(2, 2, true)), + Aggregation.base2ExponentialBucketHistogram( + 2, 2, HistogramOptions.builder().setRecordMinMax(true).build())), Arguments.of( new AggregationModel() .withBase2ExponentialBucketHistogram( @@ -105,6 +110,7 @@ private static Stream createTestCases() { .withMaxSize(2) .withMaxScale(2) .withRecordMinMax(false)), - Aggregation.base2ExponentialBucketHistogram(2, 2, false))); + Aggregation.base2ExponentialBucketHistogram( + 2, 2, HistogramOptions.builder().setRecordMinMax(false).build()))); } } diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/Aggregation.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/Aggregation.java index 65aa97873b8..54e72abed26 100644 --- a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/Aggregation.java +++ b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/Aggregation.java @@ -74,10 +74,11 @@ static Aggregation explicitBucketHistogram(List bucketBoundaries) { * * @param bucketBoundaries A list of (inclusive) upper bounds for the histogram. Should be in * order from lowest to highest. - * @param recordMinMax whether to record min and max values + * @param options histogram options */ - static Aggregation explicitBucketHistogram(List bucketBoundaries, boolean recordMinMax) { - return ExplicitBucketHistogramAggregation.create(bucketBoundaries, recordMinMax); + static Aggregation explicitBucketHistogram( + List bucketBoundaries, HistogramOptions options) { + return ExplicitBucketHistogramAggregation.create(bucketBoundaries, options.isRecordMinMax()); } /** @@ -115,10 +116,11 @@ static Aggregation base2ExponentialBucketHistogram(int maxBuckets, int maxScale) * given the {@code maxBuckets}, the scale is reduced until the measurements can be * accommodated. Setting maxScale may reduce the number of downscales. Additionally, the * performance of computing bucket index is improved when scale is {@code <= 0}. - * @param recordMinMax whether to record min and max values + * @param options histogram options */ static Aggregation base2ExponentialBucketHistogram( - int maxBuckets, int maxScale, boolean recordMinMax) { - return Base2ExponentialHistogramAggregation.create(maxBuckets, maxScale, recordMinMax); + int maxBuckets, int maxScale, HistogramOptions options) { + return Base2ExponentialHistogramAggregation.create( + maxBuckets, maxScale, options.isRecordMinMax()); } } diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/HistogramOptions.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/HistogramOptions.java new file mode 100644 index 00000000000..8c07b8dbb11 --- /dev/null +++ b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/HistogramOptions.java @@ -0,0 +1,59 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.metrics; + +/** + * Options for configuring histogram aggregations. + * + * @see Aggregation#explicitBucketHistogram(java.util.List, HistogramOptions) + * @see Aggregation#base2ExponentialBucketHistogram(int, int, HistogramOptions) + */ +public final class HistogramOptions { + + private final boolean recordMinMax; + + private HistogramOptions(Builder builder) { + this.recordMinMax = builder.recordMinMax; + } + + /** Returns a new {@link Builder} for {@link HistogramOptions}. */ + public static Builder builder() { + return new Builder(); + } + + /** Returns whether min and max values should be recorded. Defaults to {@code true}. */ + public boolean isRecordMinMax() { + return recordMinMax; + } + + @Override + public String toString() { + return "HistogramOptions{recordMinMax=" + recordMinMax + "}"; + } + + /** Builder for {@link HistogramOptions}. */ + public static final class Builder { + + private boolean recordMinMax = true; + + private Builder() {} + + /** + * Sets whether min and max values should be recorded. + * + * @param recordMinMax whether to record min and max values + */ + public Builder setRecordMinMax(boolean recordMinMax) { + this.recordMinMax = recordMinMax; + return this; + } + + /** Returns a new {@link HistogramOptions} with the configuration of this builder. */ + public HistogramOptions build() { + return new HistogramOptions(this); + } + } +} From e7041871b982d7f4785f4ae9ca6f6e3665f8f35f Mon Sep 17 00:00:00 2001 From: Mike Goldsmith Date: Fri, 27 Feb 2026 14:11:40 +0000 Subject: [PATCH 09/14] Rename isRecordMinMax() to recordMinMax() Claude claude-sonnet-4-6 assisted with this change. --- .../main/java/io/opentelemetry/sdk/metrics/Aggregation.java | 4 ++-- .../java/io/opentelemetry/sdk/metrics/HistogramOptions.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/Aggregation.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/Aggregation.java index 54e72abed26..ce8ddcaf1c2 100644 --- a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/Aggregation.java +++ b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/Aggregation.java @@ -78,7 +78,7 @@ static Aggregation explicitBucketHistogram(List bucketBoundaries) { */ static Aggregation explicitBucketHistogram( List bucketBoundaries, HistogramOptions options) { - return ExplicitBucketHistogramAggregation.create(bucketBoundaries, options.isRecordMinMax()); + return ExplicitBucketHistogramAggregation.create(bucketBoundaries, options.recordMinMax()); } /** @@ -121,6 +121,6 @@ static Aggregation base2ExponentialBucketHistogram(int maxBuckets, int maxScale) static Aggregation base2ExponentialBucketHistogram( int maxBuckets, int maxScale, HistogramOptions options) { return Base2ExponentialHistogramAggregation.create( - maxBuckets, maxScale, options.isRecordMinMax()); + maxBuckets, maxScale, options.recordMinMax()); } } diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/HistogramOptions.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/HistogramOptions.java index 8c07b8dbb11..5c34e9cfb1f 100644 --- a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/HistogramOptions.java +++ b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/HistogramOptions.java @@ -25,7 +25,7 @@ public static Builder builder() { } /** Returns whether min and max values should be recorded. Defaults to {@code true}. */ - public boolean isRecordMinMax() { + public boolean recordMinMax() { return recordMinMax; } From 542cf4d1cbc2228ad551c71710e5dc7e8305ff15 Mon Sep 17 00:00:00 2001 From: Mike Goldsmith Date: Fri, 27 Feb 2026 14:28:40 +0000 Subject: [PATCH 10/14] Add ExplicitBucketHistogramOptions and Base2ExponentialHistogramOptions builders Replaces HistogramOptions with per-type options builders that include all histogram parameters (boundaries/scale/maxBuckets + recordMinMax). Deprecates positional-param overloads which now delegate to the options-based methods. Assisted-by: Claude Sonnet 4.6 --- .../opentelemetry-sdk-metrics.txt | 40 +++++-- .../fileconfig/AggregationFactory.java | 41 ++++--- .../metric/viewconfig/ViewConfig.java | 8 +- .../fileconfig/AggregationFactoryTest.java | 42 +++++-- .../incubator/fileconfig/ViewFactoryTest.java | 7 +- .../aggregator/HistogramCollectBenchmark.java | 4 +- .../sdk/metrics/Aggregation.java | 38 ++++--- .../Base2ExponentialHistogramOptions.java | 105 ++++++++++++++++++ .../ExplicitBucketHistogramOptions.java | 80 +++++++++++++ .../sdk/metrics/HistogramOptions.java | 59 ---------- .../sdk/metrics/AggregationTest.java | 10 +- .../ExplicitBucketBoundariesAdviceTest.java | 12 +- .../sdk/metrics/SdkDoubleHistogramTest.java | 7 +- .../sdk/metrics/SdkLongHistogramTest.java | 14 ++- .../sdk/metrics/SdkMeterProviderTest.java | 18 ++- ...xplicitBucketHistogramAggregationTest.java | 25 ++++- 16 files changed, 373 insertions(+), 137 deletions(-) create mode 100644 sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/Base2ExponentialHistogramOptions.java create mode 100644 sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/ExplicitBucketHistogramOptions.java delete mode 100644 sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/HistogramOptions.java diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-metrics.txt b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-metrics.txt index 8d59c4e6bed..dc6e9779373 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-metrics.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-metrics.txt @@ -1,16 +1,38 @@ Comparing source compatibility of opentelemetry-sdk-metrics-1.60.0-SNAPSHOT.jar against opentelemetry-sdk-metrics-1.59.0.jar *** MODIFIED INTERFACE: PUBLIC ABSTRACT io.opentelemetry.sdk.metrics.Aggregation (not serializable) === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 - +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.sdk.metrics.Aggregation base2ExponentialBucketHistogram(int, int, io.opentelemetry.sdk.metrics.HistogramOptions) - +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.sdk.metrics.Aggregation explicitBucketHistogram(java.util.List, io.opentelemetry.sdk.metrics.HistogramOptions) -+++ NEW CLASS: PUBLIC(+) FINAL(+) io.opentelemetry.sdk.metrics.HistogramOptions (not serializable) + === UNCHANGED METHOD: PUBLIC STATIC io.opentelemetry.sdk.metrics.Aggregation base2ExponentialBucketHistogram(int, int) + +++ NEW ANNOTATION: java.lang.Deprecated + +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.sdk.metrics.Aggregation base2ExponentialBucketHistogram(io.opentelemetry.sdk.metrics.Base2ExponentialHistogramOptions) + === UNCHANGED METHOD: PUBLIC STATIC io.opentelemetry.sdk.metrics.Aggregation explicitBucketHistogram(java.util.List) + +++ NEW ANNOTATION: java.lang.Deprecated + +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.sdk.metrics.Aggregation explicitBucketHistogram(io.opentelemetry.sdk.metrics.ExplicitBucketHistogramOptions) ++++ NEW CLASS: PUBLIC(+) FINAL(+) io.opentelemetry.sdk.metrics.Base2ExponentialHistogramOptions (not serializable) +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. +++ NEW SUPERCLASS: java.lang.Object - +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.sdk.metrics.HistogramOptions$Builder builder() - +++ NEW METHOD: PUBLIC(+) boolean isRecordMinMax() - +++ NEW METHOD: PUBLIC(+) java.lang.String toString() -+++ NEW CLASS: PUBLIC(+) STATIC(+) FINAL(+) io.opentelemetry.sdk.metrics.HistogramOptions$Builder (not serializable) + +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) int DEFAULT_MAX_BUCKETS + +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) int DEFAULT_MAX_SCALE + +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.sdk.metrics.Base2ExponentialHistogramOptions$Builder builder() + +++ NEW METHOD: PUBLIC(+) int maxBuckets() + +++ NEW METHOD: PUBLIC(+) int maxScale() + +++ NEW METHOD: PUBLIC(+) boolean recordMinMax() ++++ NEW CLASS: PUBLIC(+) STATIC(+) FINAL(+) io.opentelemetry.sdk.metrics.Base2ExponentialHistogramOptions$Builder (not serializable) +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. +++ NEW SUPERCLASS: java.lang.Object - +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.metrics.HistogramOptions build() - +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.metrics.HistogramOptions$Builder setRecordMinMax(boolean) + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.metrics.Base2ExponentialHistogramOptions build() + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.metrics.Base2ExponentialHistogramOptions$Builder setMaxBuckets(int) + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.metrics.Base2ExponentialHistogramOptions$Builder setMaxScale(int) + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.metrics.Base2ExponentialHistogramOptions$Builder setRecordMinMax(boolean) ++++ NEW CLASS: PUBLIC(+) FINAL(+) io.opentelemetry.sdk.metrics.ExplicitBucketHistogramOptions (not serializable) + +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. + +++ NEW SUPERCLASS: java.lang.Object + +++ NEW METHOD: PUBLIC(+) java.util.List bucketBoundaries() + +++ NEW ANNOTATION: javax.annotation.Nullable + +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.sdk.metrics.ExplicitBucketHistogramOptions$Builder builder() + +++ NEW METHOD: PUBLIC(+) boolean recordMinMax() ++++ NEW CLASS: PUBLIC(+) STATIC(+) FINAL(+) io.opentelemetry.sdk.metrics.ExplicitBucketHistogramOptions$Builder (not serializable) + +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. + +++ NEW SUPERCLASS: java.lang.Object + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.metrics.ExplicitBucketHistogramOptions build() + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.metrics.ExplicitBucketHistogramOptions$Builder setBucketBoundaries(java.util.List) + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.metrics.ExplicitBucketHistogramOptions$Builder setRecordMinMax(boolean) diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/AggregationFactory.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/AggregationFactory.java index a16647851c7..3d2059bc4bc 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/AggregationFactory.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/AggregationFactory.java @@ -10,8 +10,8 @@ import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Base2ExponentialBucketHistogramAggregationModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ExplicitBucketHistogramAggregationModel; import io.opentelemetry.sdk.metrics.Aggregation; -import io.opentelemetry.sdk.metrics.HistogramOptions; -import io.opentelemetry.sdk.metrics.internal.aggregator.ExplicitBucketHistogramUtils; +import io.opentelemetry.sdk.metrics.Base2ExponentialHistogramOptions; +import io.opentelemetry.sdk.metrics.ExplicitBucketHistogramOptions; import java.util.List; final class AggregationFactory implements Factory { @@ -39,20 +39,20 @@ public Aggregation create(AggregationModel model, DeclarativeConfigContext conte model.getBase2ExponentialBucketHistogram(); if (exponentialBucketHistogram != null) { Integer maxScale = exponentialBucketHistogram.getMaxScale(); - if (maxScale == null) { - maxScale = 20; - } Integer maxSize = exponentialBucketHistogram.getMaxSize(); - if (maxSize == null) { - maxSize = 160; - } Boolean recordMinMax = exponentialBucketHistogram.getRecordMinMax(); - HistogramOptions options = - HistogramOptions.builder() - .setRecordMinMax(recordMinMax != null ? recordMinMax : true) - .build(); + Base2ExponentialHistogramOptions.Builder builder = Base2ExponentialHistogramOptions.builder(); + if (maxScale != null) { + builder.setMaxScale(maxScale); + } + if (maxSize != null) { + builder.setMaxBuckets(maxSize); + } + if (recordMinMax != null) { + builder.setRecordMinMax(recordMinMax); + } try { - return Aggregation.base2ExponentialBucketHistogram(maxSize, maxScale, options); + return Aggregation.base2ExponentialBucketHistogram(builder.build()); } catch (IllegalArgumentException e) { throw new DeclarativeConfigException("Invalid exponential bucket histogram", e); } @@ -62,16 +62,15 @@ public Aggregation create(AggregationModel model, DeclarativeConfigContext conte if (explicitBucketHistogram != null) { List boundaries = explicitBucketHistogram.getBoundaries(); Boolean recordMinMax = explicitBucketHistogram.getRecordMinMax(); - HistogramOptions options = - HistogramOptions.builder() - .setRecordMinMax(recordMinMax != null ? recordMinMax : true) - .build(); - if (boundaries == null) { - return Aggregation.explicitBucketHistogram( - ExplicitBucketHistogramUtils.DEFAULT_HISTOGRAM_BUCKET_BOUNDARIES, options); + ExplicitBucketHistogramOptions.Builder builder = ExplicitBucketHistogramOptions.builder(); + if (boundaries != null) { + builder.setBucketBoundaries(boundaries); + } + if (recordMinMax != null) { + builder.setRecordMinMax(recordMinMax); } try { - return Aggregation.explicitBucketHistogram(boundaries, options); + return Aggregation.explicitBucketHistogram(builder.build()); } catch (IllegalArgumentException e) { throw new DeclarativeConfigException("Invalid explicit bucket histogram", e); } diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/metric/viewconfig/ViewConfig.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/metric/viewconfig/ViewConfig.java index 9c08770d072..67cc972ba1a 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/metric/viewconfig/ViewConfig.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/metric/viewconfig/ViewConfig.java @@ -10,6 +10,8 @@ import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; import io.opentelemetry.sdk.metrics.Aggregation; +import io.opentelemetry.sdk.metrics.Base2ExponentialHistogramOptions; +import io.opentelemetry.sdk.metrics.ExplicitBucketHistogramOptions; import io.opentelemetry.sdk.metrics.InstrumentSelector; import io.opentelemetry.sdk.metrics.InstrumentSelectorBuilder; import io.opentelemetry.sdk.metrics.InstrumentType; @@ -203,7 +205,8 @@ static Aggregation toAggregation(String aggregationName, Map agg if (Aggregation.explicitBucketHistogram().equals(aggregation)) { List bucketBoundaries = getBucketBoundaries(aggregationArgs); if (bucketBoundaries != null) { - return Aggregation.explicitBucketHistogram(bucketBoundaries); + return Aggregation.explicitBucketHistogram( + ExplicitBucketHistogramOptions.builder().setBucketBoundaries(bucketBoundaries).build()); } } if (Aggregation.base2ExponentialBucketHistogram().equals(aggregation)) { @@ -215,7 +218,8 @@ static Aggregation toAggregation(String aggregationName, Map agg } // TODO: support configuring max_scale if (maxBuckets != null) { - return Aggregation.base2ExponentialBucketHistogram(maxBuckets, 20); + return Aggregation.base2ExponentialBucketHistogram( + Base2ExponentialHistogramOptions.builder().setMaxBuckets(maxBuckets).build()); } } return aggregation; diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/AggregationFactoryTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/AggregationFactoryTest.java index f92ae59853a..9b043bf54a7 100644 --- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/AggregationFactoryTest.java +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/AggregationFactoryTest.java @@ -15,7 +15,8 @@ import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.LastValueAggregationModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.SumAggregationModel; import io.opentelemetry.sdk.metrics.Aggregation; -import io.opentelemetry.sdk.metrics.HistogramOptions; +import io.opentelemetry.sdk.metrics.Base2ExponentialHistogramOptions; +import io.opentelemetry.sdk.metrics.ExplicitBucketHistogramOptions; import java.util.Arrays; import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; @@ -52,7 +53,11 @@ private static Stream createTestCases() { new Base2ExponentialBucketHistogramAggregationModel() .withMaxSize(2) .withMaxScale(2)), - Aggregation.base2ExponentialBucketHistogram(2, 2)), + Aggregation.base2ExponentialBucketHistogram( + Base2ExponentialHistogramOptions.builder() + .setMaxBuckets(2) + .setMaxScale(2) + .build())), Arguments.of( new AggregationModel() .withExplicitBucketHistogram( @@ -63,7 +68,10 @@ private static Stream createTestCases() { .withExplicitBucketHistogram( new ExplicitBucketHistogramAggregationModel() .withBoundaries(Arrays.asList(1.0, 2.0))), - Aggregation.explicitBucketHistogram(Arrays.asList(1.0, 2.0))), + Aggregation.explicitBucketHistogram( + ExplicitBucketHistogramOptions.builder() + .setBucketBoundaries(Arrays.asList(1.0, 2.0)) + .build())), // Test recordMinMax parameter for explicit bucket histogram Arguments.of( new AggregationModel() @@ -72,7 +80,10 @@ private static Stream createTestCases() { .withBoundaries(Arrays.asList(1.0, 2.0)) .withRecordMinMax(true)), Aggregation.explicitBucketHistogram( - Arrays.asList(1.0, 2.0), HistogramOptions.builder().setRecordMinMax(true).build())), + ExplicitBucketHistogramOptions.builder() + .setBucketBoundaries(Arrays.asList(1.0, 2.0)) + .setRecordMinMax(true) + .build())), Arguments.of( new AggregationModel() .withExplicitBucketHistogram( @@ -80,8 +91,10 @@ private static Stream createTestCases() { .withBoundaries(Arrays.asList(1.0, 2.0)) .withRecordMinMax(false)), Aggregation.explicitBucketHistogram( - Arrays.asList(1.0, 2.0), - HistogramOptions.builder().setRecordMinMax(false).build())), + ExplicitBucketHistogramOptions.builder() + .setBucketBoundaries(Arrays.asList(1.0, 2.0)) + .setRecordMinMax(false) + .build())), Arguments.of( new AggregationModel() .withExplicitBucketHistogram( @@ -89,10 +102,7 @@ private static Stream createTestCases() { .withBoundaries(null) .withRecordMinMax(false)), Aggregation.explicitBucketHistogram( - Arrays.asList( - 0.0, 5.0, 10.0, 25.0, 50.0, 75.0, 100.0, 250.0, 500.0, 750.0, 1000.0, 2500.0, - 5000.0, 7500.0, 10000.0), - HistogramOptions.builder().setRecordMinMax(false).build())), + ExplicitBucketHistogramOptions.builder().setRecordMinMax(false).build())), // Test recordMinMax parameter for exponential bucket histogram Arguments.of( new AggregationModel() @@ -102,7 +112,11 @@ private static Stream createTestCases() { .withMaxScale(2) .withRecordMinMax(true)), Aggregation.base2ExponentialBucketHistogram( - 2, 2, HistogramOptions.builder().setRecordMinMax(true).build())), + Base2ExponentialHistogramOptions.builder() + .setMaxBuckets(2) + .setMaxScale(2) + .setRecordMinMax(true) + .build())), Arguments.of( new AggregationModel() .withBase2ExponentialBucketHistogram( @@ -111,6 +125,10 @@ private static Stream createTestCases() { .withMaxScale(2) .withRecordMinMax(false)), Aggregation.base2ExponentialBucketHistogram( - 2, 2, HistogramOptions.builder().setRecordMinMax(false).build()))); + Base2ExponentialHistogramOptions.builder() + .setMaxBuckets(2) + .setMaxScale(2) + .setRecordMinMax(false) + .build()))); } } diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ViewFactoryTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ViewFactoryTest.java index 0ce2834dbff..1911c8fbbac 100644 --- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ViewFactoryTest.java +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ViewFactoryTest.java @@ -14,6 +14,7 @@ import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.IncludeExcludeModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ViewStreamModel; import io.opentelemetry.sdk.metrics.Aggregation; +import io.opentelemetry.sdk.metrics.ExplicitBucketHistogramOptions; import io.opentelemetry.sdk.metrics.View; import java.util.Arrays; import java.util.Collections; @@ -43,7 +44,11 @@ void create() { .setAttributeFilter( IncludeExcludePredicate.createExactMatching( Arrays.asList("foo", "bar"), Collections.singletonList("baz"))) - .setAggregation(Aggregation.explicitBucketHistogram(Arrays.asList(1.0, 2.0))) + .setAggregation( + Aggregation.explicitBucketHistogram( + ExplicitBucketHistogramOptions.builder() + .setBucketBoundaries(Arrays.asList(1.0, 2.0)) + .build())) .build(); View view = diff --git a/sdk/metrics/src/jmh/java/io/opentelemetry/sdk/metrics/internal/aggregator/HistogramCollectBenchmark.java b/sdk/metrics/src/jmh/java/io/opentelemetry/sdk/metrics/internal/aggregator/HistogramCollectBenchmark.java index be316162e7b..0b44e1a6e92 100644 --- a/sdk/metrics/src/jmh/java/io/opentelemetry/sdk/metrics/internal/aggregator/HistogramCollectBenchmark.java +++ b/sdk/metrics/src/jmh/java/io/opentelemetry/sdk/metrics/internal/aggregator/HistogramCollectBenchmark.java @@ -9,6 +9,7 @@ import io.opentelemetry.api.metrics.DoubleHistogram; import io.opentelemetry.sdk.common.CompletableResultCode; import io.opentelemetry.sdk.metrics.Aggregation; +import io.opentelemetry.sdk.metrics.Base2ExponentialHistogramOptions; import io.opentelemetry.sdk.metrics.ExemplarFilter; import io.opentelemetry.sdk.metrics.InstrumentType; import io.opentelemetry.sdk.metrics.SdkMeterProvider; @@ -113,7 +114,8 @@ public enum AggregationGenerator { EXPLICIT_BUCKET_HISTOGRAM(Aggregation.explicitBucketHistogram()), DEFAULT_BASE2_EXPONENTIAL_BUCKET_HISTOGRAM(Aggregation.base2ExponentialBucketHistogram()), ZERO_MAX_SCALE_BASE2_EXPONENTIAL_BUCKET_HISTOGRAM( - Aggregation.base2ExponentialBucketHistogram(160, 0)); + Aggregation.base2ExponentialBucketHistogram( + Base2ExponentialHistogramOptions.builder().setMaxBuckets(160).setMaxScale(0).build())); private final Aggregation aggregation; diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/Aggregation.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/Aggregation.java index ce8ddcaf1c2..7c3bca9f09e 100644 --- a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/Aggregation.java +++ b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/Aggregation.java @@ -6,6 +6,7 @@ package io.opentelemetry.sdk.metrics; import io.opentelemetry.sdk.metrics.data.MetricDataType; +import io.opentelemetry.sdk.metrics.internal.aggregator.ExplicitBucketHistogramUtils; import io.opentelemetry.sdk.metrics.internal.view.Base2ExponentialHistogramAggregation; import io.opentelemetry.sdk.metrics.internal.view.DefaultAggregation; import io.opentelemetry.sdk.metrics.internal.view.DropAggregation; @@ -64,21 +65,25 @@ static Aggregation explicitBucketHistogram() { * * @param bucketBoundaries A list of (inclusive) upper bounds for the histogram. Should be in * order from lowest to highest. + * @deprecated Use {@link #explicitBucketHistogram(ExplicitBucketHistogramOptions)} instead. */ + @Deprecated static Aggregation explicitBucketHistogram(List bucketBoundaries) { - return ExplicitBucketHistogramAggregation.create(bucketBoundaries, /* recordMinMax= */ true); + return explicitBucketHistogram( + ExplicitBucketHistogramOptions.builder().setBucketBoundaries(bucketBoundaries).build()); } /** * Aggregates measurements into an explicit bucket {@link MetricDataType#HISTOGRAM}. * - * @param bucketBoundaries A list of (inclusive) upper bounds for the histogram. Should be in - * order from lowest to highest. * @param options histogram options */ - static Aggregation explicitBucketHistogram( - List bucketBoundaries, HistogramOptions options) { - return ExplicitBucketHistogramAggregation.create(bucketBoundaries, options.recordMinMax()); + static Aggregation explicitBucketHistogram(ExplicitBucketHistogramOptions options) { + List boundaries = options.bucketBoundaries(); + if (boundaries == null) { + boundaries = ExplicitBucketHistogramUtils.DEFAULT_HISTOGRAM_BUCKET_BOUNDARIES; + } + return ExplicitBucketHistogramAggregation.create(boundaries, options.recordMinMax()); } /** @@ -101,26 +106,25 @@ static Aggregation base2ExponentialBucketHistogram() { * accommodated. Setting maxScale may reduce the number of downscales. Additionally, the * performance of computing bucket index is improved when scale is {@code <= 0}. * @since 1.23.0 + * @deprecated Use {@link #base2ExponentialBucketHistogram(Base2ExponentialHistogramOptions)} + * instead. */ + @Deprecated static Aggregation base2ExponentialBucketHistogram(int maxBuckets, int maxScale) { - return Base2ExponentialHistogramAggregation.create( - maxBuckets, maxScale, /* recordMinMax= */ true); + return base2ExponentialBucketHistogram( + Base2ExponentialHistogramOptions.builder() + .setMaxBuckets(maxBuckets) + .setMaxScale(maxScale) + .build()); } /** * Aggregates measurements into a base-2 {@link MetricDataType#EXPONENTIAL_HISTOGRAM}. * - * @param maxBuckets the max number of positive buckets and negative buckets (max total buckets is - * 2 * {@code maxBuckets} + 1 zero bucket). - * @param maxScale the maximum and initial scale. If measurements can't fit in a particular scale - * given the {@code maxBuckets}, the scale is reduced until the measurements can be - * accommodated. Setting maxScale may reduce the number of downscales. Additionally, the - * performance of computing bucket index is improved when scale is {@code <= 0}. * @param options histogram options */ - static Aggregation base2ExponentialBucketHistogram( - int maxBuckets, int maxScale, HistogramOptions options) { + static Aggregation base2ExponentialBucketHistogram(Base2ExponentialHistogramOptions options) { return Base2ExponentialHistogramAggregation.create( - maxBuckets, maxScale, options.recordMinMax()); + options.maxBuckets(), options.maxScale(), options.recordMinMax()); } } diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/Base2ExponentialHistogramOptions.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/Base2ExponentialHistogramOptions.java new file mode 100644 index 00000000000..7dfe63e7a64 --- /dev/null +++ b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/Base2ExponentialHistogramOptions.java @@ -0,0 +1,105 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.metrics; + +/** + * Options for configuring base-2 exponential histogram aggregations. + * + * @see Aggregation#base2ExponentialBucketHistogram(Base2ExponentialHistogramOptions) + */ +public final class Base2ExponentialHistogramOptions { + + /** The default maximum number of buckets. */ + public static final int DEFAULT_MAX_BUCKETS = 160; + + /** The default maximum scale. */ + public static final int DEFAULT_MAX_SCALE = 20; + + private final int maxBuckets; + private final int maxScale; + private final boolean recordMinMax; + + private Base2ExponentialHistogramOptions(Builder builder) { + this.maxBuckets = builder.maxBuckets; + this.maxScale = builder.maxScale; + this.recordMinMax = builder.recordMinMax; + } + + /** Returns a new {@link Builder} for {@link Base2ExponentialHistogramOptions}. */ + public static Builder builder() { + return new Builder(); + } + + /** + * Returns the max number of positive and negative buckets. Defaults to {@value + * #DEFAULT_MAX_BUCKETS}. + */ + public int maxBuckets() { + return maxBuckets; + } + + /** Returns the maximum and initial scale. Defaults to {@value #DEFAULT_MAX_SCALE}. */ + public int maxScale() { + return maxScale; + } + + /** Returns whether min and max values should be recorded. Defaults to {@code true}. */ + public boolean recordMinMax() { + return recordMinMax; + } + + /** Builder for {@link Base2ExponentialHistogramOptions}. */ + public static final class Builder { + + private int maxBuckets = DEFAULT_MAX_BUCKETS; + private int maxScale = DEFAULT_MAX_SCALE; + private boolean recordMinMax = true; + + private Builder() {} + + /** + * Sets the max number of positive buckets and negative buckets (max total buckets is 2 * {@code + * maxBuckets} + 1 zero bucket). + * + * @param maxBuckets the maximum number of buckets + */ + public Builder setMaxBuckets(int maxBuckets) { + this.maxBuckets = maxBuckets; + return this; + } + + /** + * Sets the maximum and initial scale. If measurements can't fit in a particular scale given the + * {@code maxBuckets}, the scale is reduced until the measurements can be accommodated. Setting + * maxScale may reduce the number of downscales. Additionally, the performance of computing + * bucket index is improved when scale is {@code <= 0}. + * + * @param maxScale the maximum scale + */ + public Builder setMaxScale(int maxScale) { + this.maxScale = maxScale; + return this; + } + + /** + * Sets whether min and max values should be recorded. + * + * @param recordMinMax whether to record min and max values + */ + public Builder setRecordMinMax(boolean recordMinMax) { + this.recordMinMax = recordMinMax; + return this; + } + + /** + * Returns a new {@link Base2ExponentialHistogramOptions} with the configuration of this + * builder. + */ + public Base2ExponentialHistogramOptions build() { + return new Base2ExponentialHistogramOptions(this); + } + } +} diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/ExplicitBucketHistogramOptions.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/ExplicitBucketHistogramOptions.java new file mode 100644 index 00000000000..d6460ea15ba --- /dev/null +++ b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/ExplicitBucketHistogramOptions.java @@ -0,0 +1,80 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.metrics; + +import java.util.List; +import javax.annotation.Nullable; + +/** + * Options for configuring explicit bucket histogram aggregations. + * + * @see Aggregation#explicitBucketHistogram(ExplicitBucketHistogramOptions) + */ +public final class ExplicitBucketHistogramOptions { + + @Nullable private final List bucketBoundaries; + private final boolean recordMinMax; + + private ExplicitBucketHistogramOptions(Builder builder) { + this.bucketBoundaries = builder.bucketBoundaries; + this.recordMinMax = builder.recordMinMax; + } + + /** Returns a new {@link Builder} for {@link ExplicitBucketHistogramOptions}. */ + public static Builder builder() { + return new Builder(); + } + + /** + * Returns the bucket boundaries, or {@code null} if the default OTel bucket boundaries should be + * used. + */ + @Nullable + public List bucketBoundaries() { + return bucketBoundaries; + } + + /** Returns whether min and max values should be recorded. Defaults to {@code true}. */ + public boolean recordMinMax() { + return recordMinMax; + } + + /** Builder for {@link ExplicitBucketHistogramOptions}. */ + public static final class Builder { + + @Nullable private List bucketBoundaries = null; + private boolean recordMinMax = true; + + private Builder() {} + + /** + * Sets the bucket boundaries. If not set, the default OTel bucket boundaries are used. + * + * @param bucketBoundaries a list of (inclusive) upper bounds, in order from lowest to highest + */ + public Builder setBucketBoundaries(List bucketBoundaries) { + this.bucketBoundaries = bucketBoundaries; + return this; + } + + /** + * Sets whether min and max values should be recorded. + * + * @param recordMinMax whether to record min and max values + */ + public Builder setRecordMinMax(boolean recordMinMax) { + this.recordMinMax = recordMinMax; + return this; + } + + /** + * Returns a new {@link ExplicitBucketHistogramOptions} with the configuration of this builder. + */ + public ExplicitBucketHistogramOptions build() { + return new ExplicitBucketHistogramOptions(this); + } + } +} diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/HistogramOptions.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/HistogramOptions.java deleted file mode 100644 index 5c34e9cfb1f..00000000000 --- a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/HistogramOptions.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.sdk.metrics; - -/** - * Options for configuring histogram aggregations. - * - * @see Aggregation#explicitBucketHistogram(java.util.List, HistogramOptions) - * @see Aggregation#base2ExponentialBucketHistogram(int, int, HistogramOptions) - */ -public final class HistogramOptions { - - private final boolean recordMinMax; - - private HistogramOptions(Builder builder) { - this.recordMinMax = builder.recordMinMax; - } - - /** Returns a new {@link Builder} for {@link HistogramOptions}. */ - public static Builder builder() { - return new Builder(); - } - - /** Returns whether min and max values should be recorded. Defaults to {@code true}. */ - public boolean recordMinMax() { - return recordMinMax; - } - - @Override - public String toString() { - return "HistogramOptions{recordMinMax=" + recordMinMax + "}"; - } - - /** Builder for {@link HistogramOptions}. */ - public static final class Builder { - - private boolean recordMinMax = true; - - private Builder() {} - - /** - * Sets whether min and max values should be recorded. - * - * @param recordMinMax whether to record min and max values - */ - public Builder setRecordMinMax(boolean recordMinMax) { - this.recordMinMax = recordMinMax; - return this; - } - - /** Returns a new {@link HistogramOptions} with the configuration of this builder. */ - public HistogramOptions build() { - return new HistogramOptions(this); - } - } -} diff --git a/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/AggregationTest.java b/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/AggregationTest.java index 8a8fe924d1b..f907d090093 100644 --- a/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/AggregationTest.java +++ b/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/AggregationTest.java @@ -25,14 +25,20 @@ void haveToString() { assertThat(Aggregation.explicitBucketHistogram()) .asString() .contains("ExplicitBucketHistogramAggregation"); - assertThat(Aggregation.explicitBucketHistogram(Collections.singletonList(1.0d))) + assertThat( + Aggregation.explicitBucketHistogram( + ExplicitBucketHistogramOptions.builder() + .setBucketBoundaries(Collections.singletonList(1.0d)) + .build())) .asString() .contains("ExplicitBucketHistogramAggregation"); assertThat(Aggregation.base2ExponentialBucketHistogram()) .asString() .isEqualTo( "Base2ExponentialHistogramAggregation{maxBuckets=160,maxScale=20,recordMinMax=true}"); - assertThat(Aggregation.base2ExponentialBucketHistogram(2, 0)) + assertThat( + Aggregation.base2ExponentialBucketHistogram( + Base2ExponentialHistogramOptions.builder().setMaxBuckets(2).setMaxScale(0).build())) .asString() .isEqualTo( "Base2ExponentialHistogramAggregation{maxBuckets=2,maxScale=0,recordMinMax=true}"); diff --git a/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/ExplicitBucketBoundariesAdviceTest.java b/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/ExplicitBucketBoundariesAdviceTest.java index 16d4d05cac0..765d583e520 100644 --- a/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/ExplicitBucketBoundariesAdviceTest.java +++ b/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/ExplicitBucketBoundariesAdviceTest.java @@ -5,7 +5,6 @@ package io.opentelemetry.sdk.metrics; -import static io.opentelemetry.sdk.metrics.Aggregation.explicitBucketHistogram; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; import io.github.netmikey.logunit.api.LogCapturer; @@ -111,7 +110,11 @@ void histogramWithAdviceAndViews(Function> hist .registerView( InstrumentSelector.builder().setType(InstrumentType.HISTOGRAM).build(), View.builder() - .setAggregation(explicitBucketHistogram(Collections.singletonList(50.0))) + .setAggregation( + Aggregation.explicitBucketHistogram( + ExplicitBucketHistogramOptions.builder() + .setBucketBoundaries(Collections.singletonList(50.0)) + .build())) .build()) .build(); @@ -145,7 +148,10 @@ void histogramWithAdviceAndReaderAggregationPreference( DefaultAggregationSelector.getDefault() .with( InstrumentType.HISTOGRAM, - explicitBucketHistogram(Collections.singletonList(50.0)))); + Aggregation.explicitBucketHistogram( + ExplicitBucketHistogramOptions.builder() + .setBucketBoundaries(Collections.singletonList(50.0)) + .build()))); meterProvider = SdkMeterProvider.builder().registerMetricReader(reader).build(); Consumer histogramRecorder = histogramBuilder.apply(meterProvider); diff --git a/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/SdkDoubleHistogramTest.java b/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/SdkDoubleHistogramTest.java index 4da66aca7df..c06948acf88 100644 --- a/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/SdkDoubleHistogramTest.java +++ b/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/SdkDoubleHistogramTest.java @@ -186,7 +186,12 @@ void collectMetrics_ExponentialHistogramAggregation() { .registerView( InstrumentSelector.builder().setType(InstrumentType.HISTOGRAM).build(), View.builder() - .setAggregation(Aggregation.base2ExponentialBucketHistogram(5, 20)) + .setAggregation( + Aggregation.base2ExponentialBucketHistogram( + Base2ExponentialHistogramOptions.builder() + .setMaxBuckets(5) + .setMaxScale(20) + .build())) .build()) .build(); DoubleHistogram doubleHistogram = diff --git a/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/SdkLongHistogramTest.java b/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/SdkLongHistogramTest.java index 4ecde4c0c28..1f0321f55bf 100644 --- a/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/SdkLongHistogramTest.java +++ b/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/SdkLongHistogramTest.java @@ -192,7 +192,12 @@ void collectMetrics_ExponentialHistogramScaleResets() { .registerView( InstrumentSelector.builder().setType(InstrumentType.HISTOGRAM).build(), View.builder() - .setAggregation(Aggregation.base2ExponentialBucketHistogram(5, 20)) + .setAggregation( + Aggregation.base2ExponentialBucketHistogram( + Base2ExponentialHistogramOptions.builder() + .setMaxBuckets(5) + .setMaxScale(20) + .build())) .build()) .build(); @@ -353,7 +358,12 @@ void collectMetrics_ExponentialHistogramAggregation() { .registerView( InstrumentSelector.builder().setType(InstrumentType.HISTOGRAM).build(), View.builder() - .setAggregation(Aggregation.base2ExponentialBucketHistogram(5, 20)) + .setAggregation( + Aggregation.base2ExponentialBucketHistogram( + Base2ExponentialHistogramOptions.builder() + .setMaxBuckets(5) + .setMaxScale(20) + .build())) .build()) .build(); LongHistogram longHistogram = diff --git a/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/SdkMeterProviderTest.java b/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/SdkMeterProviderTest.java index 1b6dcafb29a..d8036a5f724 100644 --- a/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/SdkMeterProviderTest.java +++ b/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/SdkMeterProviderTest.java @@ -216,7 +216,11 @@ void collectAllSyncInstruments_OverwriteTemporality() { sdkMeterProviderBuilder.registerView( InstrumentSelector.builder().setType(InstrumentType.COUNTER).build(), View.builder() - .setAggregation(Aggregation.explicitBucketHistogram(Collections.emptyList())) + .setAggregation( + Aggregation.explicitBucketHistogram( + ExplicitBucketHistogramOptions.builder() + .setBucketBoundaries(Collections.emptyList()) + .build())) .build()); InMemoryMetricReader sdkMeterReader = InMemoryMetricReader.createDelta(); SdkMeterProvider sdkMeterProvider = @@ -271,7 +275,11 @@ void collectAllSyncInstruments_OverwriteTemporality() { @Test void collectAllSyncInstruments_DeltaHistogram() { registerViewForAllTypes( - sdkMeterProviderBuilder, Aggregation.explicitBucketHistogram(Collections.emptyList())); + sdkMeterProviderBuilder, + Aggregation.explicitBucketHistogram( + ExplicitBucketHistogramOptions.builder() + .setBucketBoundaries(Collections.emptyList()) + .build())); InMemoryMetricReader sdkMeterReader = InMemoryMetricReader.createDelta(); SdkMeterProvider sdkMeterProvider = sdkMeterProviderBuilder.registerMetricReader(sdkMeterReader).build(); @@ -628,7 +636,11 @@ void viewSdk_AllowMultipleViewsPerSynchronousInstrument() { View.builder() .setName("not_test") .setDescription("not_desc") - .setAggregation(Aggregation.explicitBucketHistogram(Collections.emptyList())) + .setAggregation( + Aggregation.explicitBucketHistogram( + ExplicitBucketHistogramOptions.builder() + .setBucketBoundaries(Collections.emptyList()) + .build())) .build()) .registerView( selector, diff --git a/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/internal/view/ExplicitBucketHistogramAggregationTest.java b/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/internal/view/ExplicitBucketHistogramAggregationTest.java index 0569a59f710..9299babeb9f 100644 --- a/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/internal/view/ExplicitBucketHistogramAggregationTest.java +++ b/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/internal/view/ExplicitBucketHistogramAggregationTest.java @@ -9,6 +9,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import io.opentelemetry.sdk.metrics.Aggregation; +import io.opentelemetry.sdk.metrics.ExplicitBucketHistogramOptions; import java.util.Arrays; import java.util.Collections; import org.junit.jupiter.api.Test; @@ -26,17 +27,33 @@ void badBuckets_throwArgumentException() { assertThatThrownBy( () -> Aggregation.explicitBucketHistogram( - Collections.singletonList(Double.NEGATIVE_INFINITY))) + ExplicitBucketHistogramOptions.builder() + .setBucketBoundaries(Collections.singletonList(Double.NEGATIVE_INFINITY)) + .build())) .isInstanceOf(IllegalArgumentException.class) .hasMessage("invalid bucket boundary: -Inf"); assertThatThrownBy( - () -> Aggregation.explicitBucketHistogram(Arrays.asList(1.0, Double.POSITIVE_INFINITY))) + () -> + Aggregation.explicitBucketHistogram( + ExplicitBucketHistogramOptions.builder() + .setBucketBoundaries(Arrays.asList(1.0, Double.POSITIVE_INFINITY)) + .build())) .isInstanceOf(IllegalArgumentException.class) .hasMessage("invalid bucket boundary: +Inf"); - assertThatThrownBy(() -> Aggregation.explicitBucketHistogram(Arrays.asList(1.0, Double.NaN))) + assertThatThrownBy( + () -> + Aggregation.explicitBucketHistogram( + ExplicitBucketHistogramOptions.builder() + .setBucketBoundaries(Arrays.asList(1.0, Double.NaN)) + .build())) .isInstanceOf(IllegalArgumentException.class) .hasMessage("invalid bucket boundary: NaN"); - assertThatThrownBy(() -> Aggregation.explicitBucketHistogram(Arrays.asList(2.0, 1.0, 3.0))) + assertThatThrownBy( + () -> + Aggregation.explicitBucketHistogram( + ExplicitBucketHistogramOptions.builder() + .setBucketBoundaries(Arrays.asList(2.0, 1.0, 3.0)) + .build())) .isInstanceOf(IllegalArgumentException.class) .hasMessage("Bucket boundaries must be in increasing order: 2.0 >= 1.0"); } From 78e2805eee5f3305ac89ea52dd17dd70a0ee43c7 Mon Sep 17 00:00:00 2001 From: Mike Goldsmith Date: Mon, 2 Mar 2026 10:54:15 +0000 Subject: [PATCH 11/14] Align histogram options with SpanLimits/LogLimits/RetryPolicy pattern - Use AutoValue + @AutoValue.Builder for ExplicitBucketHistogramOptions and Base2ExponentialHistogramOptions, providing generated hashCode/equals - Rename accessors to use get* prefix (getBucketBoundaries, getRecordMinMax, getMaxBuckets, getMaxScale) for consistency with other SDK config types - Add getDefault() and toBuilder() to both options types - Move maxBuckets/maxScale validation into Base2ExponentialHistogramOptions Builder.build() using the autoBuild() pattern, removing it from the internal aggregation class Assisted-by: Claude Sonnet 4.6 Made-with: Cursor --- .../opentelemetry-sdk-metrics.txt | 36 +++++---- .../sdk/metrics/Aggregation.java | 6 +- .../Base2ExponentialHistogramOptions.java | 78 ++++++++++--------- .../ExplicitBucketHistogramOptions.java | 52 ++++++------- .../Base2ExponentialHistogramAggregation.java | 4 - ...e2ExponentialHistogramAggregationTest.java | 10 +-- 6 files changed, 91 insertions(+), 95 deletions(-) diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-metrics.txt b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-metrics.txt index dc6e9779373..15f08ba6b00 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-metrics.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-metrics.txt @@ -7,32 +7,36 @@ Comparing source compatibility of opentelemetry-sdk-metrics-1.60.0-SNAPSHOT.jar === UNCHANGED METHOD: PUBLIC STATIC io.opentelemetry.sdk.metrics.Aggregation explicitBucketHistogram(java.util.List) +++ NEW ANNOTATION: java.lang.Deprecated +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.sdk.metrics.Aggregation explicitBucketHistogram(io.opentelemetry.sdk.metrics.ExplicitBucketHistogramOptions) -+++ NEW CLASS: PUBLIC(+) FINAL(+) io.opentelemetry.sdk.metrics.Base2ExponentialHistogramOptions (not serializable) ++++ NEW CLASS: PUBLIC(+) ABSTRACT(+) io.opentelemetry.sdk.metrics.Base2ExponentialHistogramOptions (not serializable) +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. +++ NEW SUPERCLASS: java.lang.Object +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) int DEFAULT_MAX_BUCKETS +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) int DEFAULT_MAX_SCALE +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.sdk.metrics.Base2ExponentialHistogramOptions$Builder builder() - +++ NEW METHOD: PUBLIC(+) int maxBuckets() - +++ NEW METHOD: PUBLIC(+) int maxScale() - +++ NEW METHOD: PUBLIC(+) boolean recordMinMax() -+++ NEW CLASS: PUBLIC(+) STATIC(+) FINAL(+) io.opentelemetry.sdk.metrics.Base2ExponentialHistogramOptions$Builder (not serializable) + +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.sdk.metrics.Base2ExponentialHistogramOptions getDefault() + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) int getMaxBuckets() + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) int getMaxScale() + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) boolean getRecordMinMax() + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.sdk.metrics.Base2ExponentialHistogramOptions$Builder toBuilder() ++++ NEW CLASS: PUBLIC(+) ABSTRACT(+) STATIC(+) io.opentelemetry.sdk.metrics.Base2ExponentialHistogramOptions$Builder (not serializable) +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. +++ NEW SUPERCLASS: java.lang.Object +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.metrics.Base2ExponentialHistogramOptions build() - +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.metrics.Base2ExponentialHistogramOptions$Builder setMaxBuckets(int) - +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.metrics.Base2ExponentialHistogramOptions$Builder setMaxScale(int) - +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.metrics.Base2ExponentialHistogramOptions$Builder setRecordMinMax(boolean) -+++ NEW CLASS: PUBLIC(+) FINAL(+) io.opentelemetry.sdk.metrics.ExplicitBucketHistogramOptions (not serializable) + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.sdk.metrics.Base2ExponentialHistogramOptions$Builder setMaxBuckets(int) + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.sdk.metrics.Base2ExponentialHistogramOptions$Builder setMaxScale(int) + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.sdk.metrics.Base2ExponentialHistogramOptions$Builder setRecordMinMax(boolean) ++++ NEW CLASS: PUBLIC(+) ABSTRACT(+) io.opentelemetry.sdk.metrics.ExplicitBucketHistogramOptions (not serializable) +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. +++ NEW SUPERCLASS: java.lang.Object - +++ NEW METHOD: PUBLIC(+) java.util.List bucketBoundaries() - +++ NEW ANNOTATION: javax.annotation.Nullable +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.sdk.metrics.ExplicitBucketHistogramOptions$Builder builder() - +++ NEW METHOD: PUBLIC(+) boolean recordMinMax() -+++ NEW CLASS: PUBLIC(+) STATIC(+) FINAL(+) io.opentelemetry.sdk.metrics.ExplicitBucketHistogramOptions$Builder (not serializable) + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) java.util.List getBucketBoundaries() + +++ NEW ANNOTATION: javax.annotation.Nullable + +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.sdk.metrics.ExplicitBucketHistogramOptions getDefault() + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) boolean getRecordMinMax() + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.sdk.metrics.ExplicitBucketHistogramOptions$Builder toBuilder() ++++ NEW CLASS: PUBLIC(+) ABSTRACT(+) STATIC(+) io.opentelemetry.sdk.metrics.ExplicitBucketHistogramOptions$Builder (not serializable) +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. +++ NEW SUPERCLASS: java.lang.Object - +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.metrics.ExplicitBucketHistogramOptions build() - +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.metrics.ExplicitBucketHistogramOptions$Builder setBucketBoundaries(java.util.List) - +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.metrics.ExplicitBucketHistogramOptions$Builder setRecordMinMax(boolean) + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.sdk.metrics.ExplicitBucketHistogramOptions build() + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.sdk.metrics.ExplicitBucketHistogramOptions$Builder setBucketBoundaries(java.util.List) + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.sdk.metrics.ExplicitBucketHistogramOptions$Builder setRecordMinMax(boolean) diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/Aggregation.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/Aggregation.java index 7c3bca9f09e..08be08b55b5 100644 --- a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/Aggregation.java +++ b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/Aggregation.java @@ -79,11 +79,11 @@ static Aggregation explicitBucketHistogram(List bucketBoundaries) { * @param options histogram options */ static Aggregation explicitBucketHistogram(ExplicitBucketHistogramOptions options) { - List boundaries = options.bucketBoundaries(); + List boundaries = options.getBucketBoundaries(); if (boundaries == null) { boundaries = ExplicitBucketHistogramUtils.DEFAULT_HISTOGRAM_BUCKET_BOUNDARIES; } - return ExplicitBucketHistogramAggregation.create(boundaries, options.recordMinMax()); + return ExplicitBucketHistogramAggregation.create(boundaries, options.getRecordMinMax()); } /** @@ -125,6 +125,6 @@ static Aggregation base2ExponentialBucketHistogram(int maxBuckets, int maxScale) */ static Aggregation base2ExponentialBucketHistogram(Base2ExponentialHistogramOptions options) { return Base2ExponentialHistogramAggregation.create( - options.maxBuckets(), options.maxScale(), options.recordMinMax()); + options.getMaxBuckets(), options.getMaxScale(), options.getRecordMinMax()); } } diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/Base2ExponentialHistogramOptions.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/Base2ExponentialHistogramOptions.java index 7dfe63e7a64..8d91fe8734c 100644 --- a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/Base2ExponentialHistogramOptions.java +++ b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/Base2ExponentialHistogramOptions.java @@ -5,12 +5,19 @@ package io.opentelemetry.sdk.metrics; +import static io.opentelemetry.api.internal.Utils.checkArgument; + +import com.google.auto.value.AutoValue; +import javax.annotation.concurrent.Immutable; + /** * Options for configuring base-2 exponential histogram aggregations. * * @see Aggregation#base2ExponentialBucketHistogram(Base2ExponentialHistogramOptions) */ -public final class Base2ExponentialHistogramOptions { +@AutoValue +@Immutable +public abstract class Base2ExponentialHistogramOptions { /** The default maximum number of buckets. */ public static final int DEFAULT_MAX_BUCKETS = 160; @@ -18,47 +25,43 @@ public final class Base2ExponentialHistogramOptions { /** The default maximum scale. */ public static final int DEFAULT_MAX_SCALE = 20; - private final int maxBuckets; - private final int maxScale; - private final boolean recordMinMax; + private static final Base2ExponentialHistogramOptions DEFAULT = builder().build(); - private Base2ExponentialHistogramOptions(Builder builder) { - this.maxBuckets = builder.maxBuckets; - this.maxScale = builder.maxScale; - this.recordMinMax = builder.recordMinMax; + Base2ExponentialHistogramOptions() {} + + /** Returns the default {@link Base2ExponentialHistogramOptions}. */ + public static Base2ExponentialHistogramOptions getDefault() { + return DEFAULT; } /** Returns a new {@link Builder} for {@link Base2ExponentialHistogramOptions}. */ public static Builder builder() { - return new Builder(); + return new AutoValue_Base2ExponentialHistogramOptions.Builder() + .setMaxBuckets(DEFAULT_MAX_BUCKETS) + .setMaxScale(DEFAULT_MAX_SCALE) + .setRecordMinMax(true); } + /** Returns a {@link Builder} initialized to the same property values as the current instance. */ + public abstract Builder toBuilder(); + /** * Returns the max number of positive and negative buckets. Defaults to {@value * #DEFAULT_MAX_BUCKETS}. */ - public int maxBuckets() { - return maxBuckets; - } + public abstract int getMaxBuckets(); /** Returns the maximum and initial scale. Defaults to {@value #DEFAULT_MAX_SCALE}. */ - public int maxScale() { - return maxScale; - } + public abstract int getMaxScale(); /** Returns whether min and max values should be recorded. Defaults to {@code true}. */ - public boolean recordMinMax() { - return recordMinMax; - } + public abstract boolean getRecordMinMax(); /** Builder for {@link Base2ExponentialHistogramOptions}. */ - public static final class Builder { + @AutoValue.Builder + public abstract static class Builder { - private int maxBuckets = DEFAULT_MAX_BUCKETS; - private int maxScale = DEFAULT_MAX_SCALE; - private boolean recordMinMax = true; - - private Builder() {} + Builder() {} /** * Sets the max number of positive buckets and negative buckets (max total buckets is 2 * {@code @@ -66,10 +69,7 @@ private Builder() {} * * @param maxBuckets the maximum number of buckets */ - public Builder setMaxBuckets(int maxBuckets) { - this.maxBuckets = maxBuckets; - return this; - } + public abstract Builder setMaxBuckets(int maxBuckets); /** * Sets the maximum and initial scale. If measurements can't fit in a particular scale given the @@ -79,27 +79,31 @@ public Builder setMaxBuckets(int maxBuckets) { * * @param maxScale the maximum scale */ - public Builder setMaxScale(int maxScale) { - this.maxScale = maxScale; - return this; - } + public abstract Builder setMaxScale(int maxScale); /** * Sets whether min and max values should be recorded. * * @param recordMinMax whether to record min and max values */ - public Builder setRecordMinMax(boolean recordMinMax) { - this.recordMinMax = recordMinMax; - return this; - } + public abstract Builder setRecordMinMax(boolean recordMinMax); + + abstract Base2ExponentialHistogramOptions autoBuild(); /** * Returns a new {@link Base2ExponentialHistogramOptions} with the configuration of this * builder. + * + * @throws IllegalArgumentException if {@code maxBuckets} is less than 2, or if {@code maxScale} + * is not between -10 and 20 (inclusive). */ public Base2ExponentialHistogramOptions build() { - return new Base2ExponentialHistogramOptions(this); + Base2ExponentialHistogramOptions options = autoBuild(); + checkArgument(options.getMaxBuckets() >= 2, "maxBuckets must be >= 2"); + checkArgument( + options.getMaxScale() >= -10 && options.getMaxScale() <= 20, + "maxScale must be -10 <= x <= 20"); + return options; } } } diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/ExplicitBucketHistogramOptions.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/ExplicitBucketHistogramOptions.java index d6460ea15ba..d8ca702c8ef 100644 --- a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/ExplicitBucketHistogramOptions.java +++ b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/ExplicitBucketHistogramOptions.java @@ -5,76 +5,70 @@ package io.opentelemetry.sdk.metrics; +import com.google.auto.value.AutoValue; import java.util.List; import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; /** * Options for configuring explicit bucket histogram aggregations. * * @see Aggregation#explicitBucketHistogram(ExplicitBucketHistogramOptions) */ -public final class ExplicitBucketHistogramOptions { +@AutoValue +@Immutable +public abstract class ExplicitBucketHistogramOptions { - @Nullable private final List bucketBoundaries; - private final boolean recordMinMax; + private static final ExplicitBucketHistogramOptions DEFAULT = builder().build(); - private ExplicitBucketHistogramOptions(Builder builder) { - this.bucketBoundaries = builder.bucketBoundaries; - this.recordMinMax = builder.recordMinMax; + ExplicitBucketHistogramOptions() {} + + /** Returns the default {@link ExplicitBucketHistogramOptions}. */ + public static ExplicitBucketHistogramOptions getDefault() { + return DEFAULT; } /** Returns a new {@link Builder} for {@link ExplicitBucketHistogramOptions}. */ public static Builder builder() { - return new Builder(); + return new AutoValue_ExplicitBucketHistogramOptions.Builder().setRecordMinMax(true); } + /** Returns a {@link Builder} initialized to the same property values as the current instance. */ + public abstract Builder toBuilder(); + /** * Returns the bucket boundaries, or {@code null} if the default OTel bucket boundaries should be * used. */ @Nullable - public List bucketBoundaries() { - return bucketBoundaries; - } + public abstract List getBucketBoundaries(); /** Returns whether min and max values should be recorded. Defaults to {@code true}. */ - public boolean recordMinMax() { - return recordMinMax; - } + public abstract boolean getRecordMinMax(); /** Builder for {@link ExplicitBucketHistogramOptions}. */ - public static final class Builder { - - @Nullable private List bucketBoundaries = null; - private boolean recordMinMax = true; + @AutoValue.Builder + public abstract static class Builder { - private Builder() {} + Builder() {} /** * Sets the bucket boundaries. If not set, the default OTel bucket boundaries are used. * * @param bucketBoundaries a list of (inclusive) upper bounds, in order from lowest to highest */ - public Builder setBucketBoundaries(List bucketBoundaries) { - this.bucketBoundaries = bucketBoundaries; - return this; - } + public abstract Builder setBucketBoundaries(@Nullable List bucketBoundaries); /** * Sets whether min and max values should be recorded. * * @param recordMinMax whether to record min and max values */ - public Builder setRecordMinMax(boolean recordMinMax) { - this.recordMinMax = recordMinMax; - return this; - } + public abstract Builder setRecordMinMax(boolean recordMinMax); /** * Returns a new {@link ExplicitBucketHistogramOptions} with the configuration of this builder. */ - public ExplicitBucketHistogramOptions build() { - return new ExplicitBucketHistogramOptions(this); - } + public abstract ExplicitBucketHistogramOptions build(); } } diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/view/Base2ExponentialHistogramAggregation.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/view/Base2ExponentialHistogramAggregation.java index c69edcbe2bd..41455b0b1ac 100644 --- a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/view/Base2ExponentialHistogramAggregation.java +++ b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/view/Base2ExponentialHistogramAggregation.java @@ -5,8 +5,6 @@ package io.opentelemetry.sdk.metrics.internal.view; -import static io.opentelemetry.api.internal.Utils.checkArgument; - import io.opentelemetry.sdk.common.Clock; import io.opentelemetry.sdk.common.export.MemoryMode; import io.opentelemetry.sdk.common.internal.RandomSupplier; @@ -62,8 +60,6 @@ public static Aggregation getDefault() { * @return the aggregation */ public static Aggregation create(int maxBuckets, int maxScale, boolean recordMinMax) { - checkArgument(maxBuckets >= 2, "maxBuckets must be >= 2"); - checkArgument(maxScale <= 20 && maxScale >= -10, "maxScale must be -10 <= x <= 20"); return new Base2ExponentialHistogramAggregation(maxBuckets, maxScale, recordMinMax); } diff --git a/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/internal/view/Base2ExponentialHistogramAggregationTest.java b/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/internal/view/Base2ExponentialHistogramAggregationTest.java index ff33359ddcb..54a4c36e841 100644 --- a/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/internal/view/Base2ExponentialHistogramAggregationTest.java +++ b/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/internal/view/Base2ExponentialHistogramAggregationTest.java @@ -13,6 +13,7 @@ import io.opentelemetry.context.Context; import io.opentelemetry.sdk.common.export.MemoryMode; import io.opentelemetry.sdk.metrics.Aggregation; +import io.opentelemetry.sdk.metrics.Base2ExponentialHistogramOptions; import io.opentelemetry.sdk.metrics.ExemplarFilter; import io.opentelemetry.sdk.metrics.InstrumentType; import io.opentelemetry.sdk.metrics.InstrumentValueType; @@ -35,16 +36,13 @@ void goodConfig() { @Test void invalidConfig_Throws() { - assertThatThrownBy( - () -> Base2ExponentialHistogramAggregation.create(0, 20, /* recordMinMax= */ true)) + assertThatThrownBy(() -> Base2ExponentialHistogramOptions.builder().setMaxBuckets(0).build()) .isInstanceOf(IllegalArgumentException.class) .hasMessage("maxBuckets must be >= 2"); - assertThatThrownBy( - () -> Base2ExponentialHistogramAggregation.create(2, 21, /* recordMinMax= */ true)) + assertThatThrownBy(() -> Base2ExponentialHistogramOptions.builder().setMaxScale(21).build()) .isInstanceOf(IllegalArgumentException.class) .hasMessage("maxScale must be -10 <= x <= 20"); - assertThatThrownBy( - () -> Base2ExponentialHistogramAggregation.create(2, -11, /* recordMinMax= */ true)) + assertThatThrownBy(() -> Base2ExponentialHistogramOptions.builder().setMaxScale(-11).build()) .isInstanceOf(IllegalArgumentException.class) .hasMessage("maxScale must be -10 <= x <= 20"); } From dae105ef57271ab97fb7bd3f94449a8d291021fb Mon Sep 17 00:00:00 2001 From: Mike Goldsmith Date: Mon, 2 Mar 2026 12:20:03 +0000 Subject: [PATCH 12/14] Fix deprecated API usage in PrometheusMetricReaderTest Update to use Base2ExponentialHistogramOptions builder instead of the deprecated base2ExponentialBucketHistogram(int, int) positional overload. Made-with: Cursor --- .../exporter/prometheus/PrometheusMetricReaderTest.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusMetricReaderTest.java b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusMetricReaderTest.java index de31edef6e4..5ac24663c4b 100644 --- a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusMetricReaderTest.java +++ b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusMetricReaderTest.java @@ -19,6 +19,7 @@ import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.context.Scope; import io.opentelemetry.sdk.metrics.Aggregation; +import io.opentelemetry.sdk.metrics.Base2ExponentialHistogramOptions; import io.opentelemetry.sdk.metrics.InstrumentSelector; import io.opentelemetry.sdk.metrics.SdkMeterProvider; import io.opentelemetry.sdk.metrics.View; @@ -782,7 +783,12 @@ void exponentialHistogramBucketConversion() { .registerView( InstrumentSelector.builder().setName("my.exponential.histogram").build(), View.builder() - .setAggregation(Aggregation.base2ExponentialBucketHistogram(160, otelScale)) + .setAggregation( + Aggregation.base2ExponentialBucketHistogram( + Base2ExponentialHistogramOptions.builder() + .setMaxBuckets(160) + .setMaxScale(otelScale) + .build())) .build()) .build() .meterBuilder("test") From d881addd31e431b944642e673d2a548ee38ffae6 Mon Sep 17 00:00:00 2001 From: Mike Goldsmith Date: Tue, 3 Mar 2026 10:44:57 +0000 Subject: [PATCH 13/14] Apply suggestions from code review Co-authored-by: Jack Berg <34418638+jack-berg@users.noreply.github.com> --- .../sdk/metrics/Base2ExponentialHistogramOptions.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/Base2ExponentialHistogramOptions.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/Base2ExponentialHistogramOptions.java index 8d91fe8734c..d260a24a875 100644 --- a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/Base2ExponentialHistogramOptions.java +++ b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/Base2ExponentialHistogramOptions.java @@ -20,10 +20,10 @@ public abstract class Base2ExponentialHistogramOptions { /** The default maximum number of buckets. */ - public static final int DEFAULT_MAX_BUCKETS = 160; + private static final int DEFAULT_MAX_BUCKETS = 160; /** The default maximum scale. */ - public static final int DEFAULT_MAX_SCALE = 20; + private static final int DEFAULT_MAX_SCALE = 20; private static final Base2ExponentialHistogramOptions DEFAULT = builder().build(); From 82cf7a94f16898e462f13bb874bb7ba79244865a Mon Sep 17 00:00:00 2001 From: Mike Goldsmith Date: Tue, 3 Mar 2026 11:50:15 +0000 Subject: [PATCH 14/14] regenerate docs --- docs/apidiffs/current_vs_latest/opentelemetry-sdk-metrics.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-metrics.txt b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-metrics.txt index 15f08ba6b00..ee7e96820cc 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-metrics.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-metrics.txt @@ -10,8 +10,6 @@ Comparing source compatibility of opentelemetry-sdk-metrics-1.60.0-SNAPSHOT.jar +++ NEW CLASS: PUBLIC(+) ABSTRACT(+) io.opentelemetry.sdk.metrics.Base2ExponentialHistogramOptions (not serializable) +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. +++ NEW SUPERCLASS: java.lang.Object - +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) int DEFAULT_MAX_BUCKETS - +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) int DEFAULT_MAX_SCALE +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.sdk.metrics.Base2ExponentialHistogramOptions$Builder builder() +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.sdk.metrics.Base2ExponentialHistogramOptions getDefault() +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) int getMaxBuckets()