Skip to content

Commit 77a1fea

Browse files
committed
test(openfeature): verify cumulative temporality and count accumulation in FlagEvalMetrics
1 parent d6ea233 commit 77a1fea

3 files changed

Lines changed: 65 additions & 1 deletion

File tree

products/feature-flagging/feature-flagging-api/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ dependencies {
5555
testImplementation(libs.bundles.junit5)
5656
testImplementation(libs.bundles.mockito)
5757
testImplementation(libs.moshi)
58+
testImplementation("io.opentelemetry:opentelemetry-sdk-testing:1.47.0")
5859
testImplementation("org.awaitility:awaitility:4.3.0")
5960
}
6061

products/feature-flagging/feature-flagging-api/src/main/java/datadog/trace/api/openfeature/FlagEvalMetrics.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,14 @@ class FlagEvalMetrics implements Closeable {
8787
this.meterProvider = null;
8888
}
8989

90+
/** Package-private constructor for integration testing with an injected SdkMeterProvider. */
91+
FlagEvalMetrics(SdkMeterProvider sdkMeterProvider) {
92+
meterProvider = sdkMeterProvider;
93+
Meter meter = sdkMeterProvider.meterBuilder(METER_NAME).build();
94+
counter =
95+
meter.counterBuilder(METRIC_NAME).setUnit(METRIC_UNIT).setDescription(METRIC_DESC).build();
96+
}
97+
9098
void record(
9199
String flagKey, String variant, String reason, ErrorCode errorCode, String allocationKey) {
92100
LongCounter c = counter;

products/feature-flagging/feature-flagging-api/src/test/java/datadog/trace/api/openfeature/FlagEvalMetricsTest.java

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package datadog.trace.api.openfeature;
22

3+
import static org.junit.jupiter.api.Assertions.assertEquals;
34
import static org.mockito.ArgumentMatchers.eq;
45
import static org.mockito.Mockito.mock;
56
import static org.mockito.Mockito.verify;
@@ -8,6 +9,15 @@
89
import dev.openfeature.sdk.ErrorCode;
910
import io.opentelemetry.api.common.Attributes;
1011
import io.opentelemetry.api.metrics.LongCounter;
12+
import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporter;
13+
import io.opentelemetry.sdk.metrics.InstrumentType;
14+
import io.opentelemetry.sdk.metrics.SdkMeterProvider;
15+
import io.opentelemetry.sdk.metrics.data.AggregationTemporality;
16+
import io.opentelemetry.sdk.metrics.data.LongPointData;
17+
import io.opentelemetry.sdk.metrics.data.MetricData;
18+
import io.opentelemetry.sdk.metrics.export.AggregationTemporalitySelector;
19+
import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader;
20+
import java.util.Collection;
1121
import org.junit.jupiter.api.Test;
1222
import org.mockito.ArgumentCaptor;
1323

@@ -121,7 +131,7 @@ void recordNullReasonBecomesUnknown() {
121131

122132
@Test
123133
void recordIsNoOpWhenCounterIsNull() {
124-
FlagEvalMetrics metrics = new FlagEvalMetrics(null);
134+
FlagEvalMetrics metrics = new FlagEvalMetrics((LongCounter) null);
125135
// Should not throw
126136
metrics.record("my-flag", "on", "TARGETING_MATCH", null, null);
127137
}
@@ -137,6 +147,51 @@ void shutdownClearsCounter() {
137147
verifyNoInteractions(counter);
138148
}
139149

150+
@Test
151+
void exporterIsConfiguredWithCumulativeTemporalityForCounters() {
152+
// Regression guard: FlagEvalMetrics must explicitly configure alwaysCumulative() so that
153+
// the Datadog agent receives absolute counts rather than delta values that may be converted
154+
// to rates. This test documents and enforces that the exporter uses CUMULATIVE for counters.
155+
try (OtlpHttpMetricExporter exporter =
156+
OtlpHttpMetricExporter.builder()
157+
.setAggregationTemporalitySelector(AggregationTemporalitySelector.alwaysCumulative())
158+
.build()) {
159+
assertEquals(
160+
AggregationTemporality.CUMULATIVE,
161+
exporter.getAggregationTemporality(InstrumentType.COUNTER),
162+
"alwaysCumulative() selector must produce CUMULATIVE for counters");
163+
}
164+
}
165+
166+
@Test
167+
void multipleRecordCallsAccumulateCumulativelyInExportedMetrics() {
168+
// Use InMemoryMetricReader with cumulative temporality (matching what FlagEvalMetrics
169+
// configures on the OTLP exporter) to verify that N record() calls produce a sum of N.
170+
InMemoryMetricReader reader = InMemoryMetricReader.create();
171+
SdkMeterProvider provider = SdkMeterProvider.builder().registerMetricReader(reader).build();
172+
173+
try (FlagEvalMetrics metrics = new FlagEvalMetrics(provider)) {
174+
for (int i = 0; i < 5; i++) {
175+
metrics.record("count-flag", "on", "STATIC", null, "default-alloc");
176+
}
177+
178+
Collection<MetricData> data = reader.collectAllMetrics();
179+
MetricData metric =
180+
data.stream()
181+
.filter(m -> m.getName().equals("feature_flag.evaluations"))
182+
.findFirst()
183+
.orElseThrow(() -> new AssertionError("feature_flag.evaluations metric not found"));
184+
185+
assertEquals(
186+
AggregationTemporality.CUMULATIVE,
187+
metric.getLongSumData().getAggregationTemporality(),
188+
"Exported metric must use CUMULATIVE temporality");
189+
190+
LongPointData point = metric.getLongSumData().getPoints().iterator().next();
191+
assertEquals(5L, point.getValue(), "5 record() calls must produce a cumulative sum of 5");
192+
}
193+
}
194+
140195
private static void assertAttribute(Attributes attrs, String key, String expected) {
141196
String value =
142197
attrs.asMap().entrySet().stream()

0 commit comments

Comments
 (0)