Skip to content

Commit 15935f9

Browse files
Implement async exemplars for always_on filter
Allow asynchronous metric storage to use the configured exemplar filter so always_on can capture async exemplars, matching the clarified spec. Add coverage at both the storage and SDK integration levels. Refs #8355
1 parent 1b207c6 commit 15935f9

5 files changed

Lines changed: 64 additions & 4 deletions

File tree

sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/SdkMeter.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,7 @@ SdkObservableMeasurement registerObservableMeasurement(
318318
registeredView,
319319
meterProviderSharedState.getClock(),
320320
instrumentDescriptor,
321+
meterProviderSharedState.getExemplarFilter(),
321322
meterEnabled)));
322323
}
323324
}

sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/state/AsynchronousMetricStorage.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
1616
import io.opentelemetry.sdk.common.export.MemoryMode;
1717
import io.opentelemetry.sdk.common.internal.ThrottlingLogger;
18-
import io.opentelemetry.sdk.metrics.ExemplarFilter;
1918
import io.opentelemetry.sdk.metrics.View;
2019
import io.opentelemetry.sdk.metrics.data.AggregationTemporality;
2120
import io.opentelemetry.sdk.metrics.data.MetricData;
@@ -106,6 +105,7 @@ public static <T extends PointData> AsynchronousMetricStorage<T> create(
106105
RegisteredView registeredView,
107106
Clock clock,
108107
InstrumentDescriptor instrumentDescriptor,
108+
ExemplarFilterInternal exemplarFilter,
109109
boolean enabled) {
110110
View view = registeredView.getView();
111111
MetricDescriptor metricDescriptor =
@@ -118,7 +118,7 @@ public static <T extends PointData> AsynchronousMetricStorage<T> create(
118118
((AggregatorFactory) view.getAggregation())
119119
.createAggregator(
120120
instrumentDescriptor,
121-
ExemplarFilterInternal.asExemplarFilterInternal(ExemplarFilter.alwaysOff()),
121+
exemplarFilter,
122122
registeredReader.getReader().getMemoryMode());
123123
AttributesProcessor attributesProcessor = registeredView.getViewAttributesProcessor();
124124
int cardinalityLimit = registeredView.getCardinalityLimit();

sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/state/MeterProviderSharedState.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,6 @@ public static MeterProviderSharedState create(
3737
/** Returns the {@link Resource} to attach telemetry to. */
3838
public abstract Resource getResource();
3939

40-
/** Returns the {@link ExemplarFilterInternal} for remembering synchronous measurements. */
40+
/** Returns the {@link ExemplarFilterInternal} for remembering measurements. */
4141
public abstract ExemplarFilterInternal getExemplarFilter();
4242
}

sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/SdkMeterProviderTest.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -945,6 +945,36 @@ void collectAll_DropAggregator() {
945945
metric -> assertThat(metric).hasResource(RESOURCE).hasName("async-counter"));
946946
}
947947

948+
@Test
949+
void collectAll_AsyncInstrumentAlwaysOnExemplars() {
950+
InMemoryMetricReader collector = InMemoryMetricReader.create();
951+
Meter meter =
952+
sdkMeterProviderBuilder
953+
.setExemplarFilter(ExemplarFilter.alwaysOn())
954+
.registerMetricReader(collector)
955+
.build()
956+
.get("my-meter");
957+
958+
meter.counterBuilder("async-counter")
959+
.buildWithCallback(measurement -> measurement.record(1, Attributes.empty()));
960+
961+
assertThat(collector.collectAllMetrics())
962+
.satisfiesExactly(
963+
metric ->
964+
assertThat(metric)
965+
.hasResource(RESOURCE)
966+
.hasName("async-counter")
967+
.hasLongSumSatisfying(
968+
sum ->
969+
sum.hasPointsSatisfying(
970+
point ->
971+
point
972+
.hasValue(1)
973+
.hasAttributes(Attributes.empty())
974+
.hasExemplarsSatisfying(
975+
exemplar -> exemplar.hasValue(1)))));
976+
}
977+
948978
@Test
949979
void shutdown() {
950980
when(metricReader.getDefaultAggregation(any())).thenCallRealMethod();

sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/internal/state/AsynchronousMetricStorageTest.java

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
package io.opentelemetry.sdk.metrics.internal.state;
77

88
import static io.opentelemetry.sdk.common.export.MemoryMode.REUSABLE_DATA;
9+
import static io.opentelemetry.sdk.metrics.internal.exemplar.ExemplarFilterInternal.asExemplarFilterInternal;
910
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
1011
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.attributeEntry;
1112
import static org.mockito.ArgumentMatchers.any;
@@ -16,6 +17,7 @@
1617
import io.opentelemetry.internal.testing.slf4j.SuppressLogger;
1718
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
1819
import io.opentelemetry.sdk.common.export.MemoryMode;
20+
import io.opentelemetry.sdk.metrics.ExemplarFilter;
1921
import io.opentelemetry.sdk.metrics.InstrumentSelector;
2022
import io.opentelemetry.sdk.metrics.InstrumentType;
2123
import io.opentelemetry.sdk.metrics.InstrumentValueType;
@@ -76,10 +78,15 @@ class AsynchronousMetricStorageTest {
7678

7779
// Not using @BeforeEach since many methods require executing them for each MemoryMode
7880
void setup(MemoryMode memoryMode) {
79-
setup(memoryMode, AggregationTemporality.CUMULATIVE);
81+
setup(memoryMode, AggregationTemporality.CUMULATIVE, ExemplarFilter.alwaysOff());
8082
}
8183

8284
void setup(MemoryMode memoryMode, AggregationTemporality temporality) {
85+
setup(memoryMode, temporality, ExemplarFilter.alwaysOff());
86+
}
87+
88+
void setup(
89+
MemoryMode memoryMode, AggregationTemporality temporality, ExemplarFilter exemplarFilter) {
8390
when(reader.getAggregationTemporality(any())).thenReturn(temporality);
8491
when(reader.getMemoryMode()).thenReturn(memoryMode);
8592
registeredReader = RegisteredReader.create(reader, ViewRegistry.create());
@@ -96,6 +103,7 @@ void setup(MemoryMode memoryMode, AggregationTemporality temporality) {
96103
InstrumentType.COUNTER,
97104
InstrumentValueType.LONG,
98105
Advice.empty()),
106+
asExemplarFilterInternal(exemplarFilter),
99107
/* enabled= */ true);
100108
doubleCounterStorage =
101109
AsynchronousMetricStorage.create(
@@ -109,6 +117,7 @@ void setup(MemoryMode memoryMode, AggregationTemporality temporality) {
109117
InstrumentType.COUNTER,
110118
InstrumentValueType.DOUBLE,
111119
Advice.empty()),
120+
asExemplarFilterInternal(exemplarFilter),
112121
/* enabled= */ true);
113122
}
114123

@@ -186,6 +195,7 @@ void record_ProcessesAttributes(MemoryMode memoryMode) {
186195
InstrumentType.COUNTER,
187196
InstrumentValueType.LONG,
188197
Advice.empty()),
198+
asExemplarFilterInternal(ExemplarFilter.alwaysOff()),
189199
/* enabled= */ true);
190200

191201
storage.record(Attributes.builder().put("key1", "a").put("key2", "b").build(), 1);
@@ -421,6 +431,7 @@ void collect_DeltaComputesDiff(MemoryMode memoryMode) {
421431
InstrumentType.COUNTER,
422432
InstrumentValueType.LONG,
423433
Advice.empty()),
434+
asExemplarFilterInternal(ExemplarFilter.alwaysOff()),
424435
/* enabled= */ true);
425436

426437
// Record measurement and collect at time 10
@@ -482,6 +493,24 @@ void collect_DeltaComputesDiff(MemoryMode memoryMode) {
482493
.hasAttributes(Attributes.builder().put("key", "value2").build())));
483494
}
484495

496+
@ParameterizedTest
497+
@EnumSource(MemoryMode.class)
498+
void recordLong_AlwaysOnCollectsExemplars(MemoryMode memoryMode) {
499+
setup(memoryMode, AggregationTemporality.CUMULATIVE, ExemplarFilter.alwaysOn());
500+
501+
longCounterStorage.record(Attributes.empty(), 3);
502+
503+
assertThat(longCounterStorage.collect(resource, scope, testClock.now()))
504+
.hasLongSumSatisfying(
505+
sum ->
506+
sum.hasPointsSatisfying(
507+
point ->
508+
point
509+
.hasValue(3)
510+
.hasAttributes(Attributes.empty())
511+
.hasExemplarsSatisfying(exemplar -> exemplar.hasValue(3))));
512+
}
513+
485514
@Test
486515
void collect_reusableData_reusedObjectsAreReturnedOnSecondCall() {
487516
setup(REUSABLE_DATA);

0 commit comments

Comments
 (0)