Skip to content

Commit c3a7955

Browse files
committed
test(openfeature): verify cumulative temporality and count accumulation in FlagEvalMetrics
1 parent 6b33269 commit c3a7955

File tree

3 files changed

+65
-1
lines changed

3 files changed

+65
-1
lines changed

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

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

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
@@ -101,6 +101,14 @@ class FlagEvalMetrics implements Closeable {
101101
this.meterProvider = null;
102102
}
103103

104+
/** Package-private constructor for integration testing with an injected SdkMeterProvider. */
105+
FlagEvalMetrics(SdkMeterProvider sdkMeterProvider) {
106+
meterProvider = sdkMeterProvider;
107+
Meter meter = sdkMeterProvider.meterBuilder(METER_NAME).build();
108+
counter =
109+
meter.counterBuilder(METRIC_NAME).setUnit(METRIC_UNIT).setDescription(METRIC_DESC).build();
110+
}
111+
104112
void record(
105113
String flagKey, String variant, String reason, ErrorCode errorCode, String allocationKey) {
106114
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)