Skip to content

Commit cffe9c6

Browse files
authored
Implement OpenTelemetry meter storage and aggregations (#10534)
Build instrument descriptors Fill in default arguments Disallow negative values in counters/histograms Validate and capture explicit bucket boundaries Update OTel metrics helper list Context is unused by our metric mapping Implement OTel metrics storage and aggregation Track metrics storage per-meter Introduce visitor API for exporting metrics Suppress warning about catching NPE in setExplicitBucketBoundariesAdvice - we do this to match OTel behaviour Spotless Cleanup javadoc Review feedback Co-authored-by: stuart.mcculloch <stuart.mcculloch@datadoghq.com>
1 parent 91a239a commit cffe9c6

31 files changed

Lines changed: 928 additions & 109 deletions

dd-java-agent/agent-otel/otel-shim/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@ dependencies {
77
// minimum OpenTelemetry API version this shim is compatible with
88
compileOnly group: 'io.opentelemetry', name: 'opentelemetry-api', version: '1.47.0'
99

10+
implementation project(':products:metrics:metrics-api')
1011
implementation project(':internal-api')
1112
}

dd-java-agent/agent-otel/otel-shim/src/main/java/datadog/opentelemetry/shim/OtelInstrumentationScope.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,20 @@ public OtelInstrumentationScope(
1717
this.schemaUrl = schemaUrl;
1818
}
1919

20+
public String getName() {
21+
return scopeName;
22+
}
23+
24+
@Nullable
25+
public String getVersion() {
26+
return scopeVersion;
27+
}
28+
29+
@Nullable
30+
public String getSchemaUrl() {
31+
return schemaUrl;
32+
}
33+
2034
@Override
2135
public boolean equals(Object o) {
2236
if (!(o instanceof OtelInstrumentationScope)) {
@@ -36,4 +50,15 @@ public int hashCode() {
3650
result = 31 * result + Objects.hashCode(schemaUrl);
3751
return result;
3852
}
53+
54+
@Override
55+
public String toString() {
56+
// use same property names as OTel in toString
57+
return "OtelInstrumentationScope{"
58+
+ "name='"
59+
+ scopeName
60+
+ (scopeVersion != null ? "', version='" + scopeVersion : "")
61+
+ (schemaUrl != null ? "', schemaUrl='" + schemaUrl : "")
62+
+ "'}";
63+
}
3964
}

dd-java-agent/agent-otel/otel-shim/src/main/java/datadog/opentelemetry/shim/metrics/OtelDoubleCounter.java

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,55 +5,76 @@
55
import static datadog.opentelemetry.shim.metrics.OtelMeter.NOOP_INSTRUMENT_NAME;
66
import static datadog.opentelemetry.shim.metrics.OtelMeter.NOOP_METER;
77

8+
import datadog.opentelemetry.shim.metrics.data.OtelMetricStorage;
9+
import datadog.trace.relocate.api.RatelimitedLogger;
810
import io.opentelemetry.api.common.Attributes;
911
import io.opentelemetry.api.metrics.DoubleCounter;
1012
import io.opentelemetry.api.metrics.DoubleCounterBuilder;
1113
import io.opentelemetry.api.metrics.ObservableDoubleCounter;
1214
import io.opentelemetry.api.metrics.ObservableDoubleMeasurement;
1315
import io.opentelemetry.context.Context;
16+
import java.util.concurrent.TimeUnit;
1417
import java.util.function.Consumer;
1518
import javax.annotation.ParametersAreNonnullByDefault;
19+
import org.slf4j.Logger;
20+
import org.slf4j.LoggerFactory;
1621

1722
@ParametersAreNonnullByDefault
18-
final class OtelDoubleCounter implements DoubleCounter {
23+
final class OtelDoubleCounter extends OtelInstrument implements DoubleCounter {
24+
private static final Logger LOGGER = LoggerFactory.getLogger(OtelDoubleCounter.class);
25+
private static final RatelimitedLogger RATELIMITED_LOGGER =
26+
new RatelimitedLogger(LOGGER, 5, TimeUnit.MINUTES);
27+
28+
OtelDoubleCounter(OtelMetricStorage storage) {
29+
super(storage);
30+
}
1931

2032
@Override
2133
public void add(double value) {
22-
// FIXME: implement recording
34+
add(value, Attributes.empty());
2335
}
2436

2537
@Override
2638
public void add(double value, Attributes attributes) {
27-
// FIXME: implement recording
39+
if (value < 0) {
40+
RATELIMITED_LOGGER.warn(
41+
"Counters can only increase. Instrument {} has recorded a negative value.",
42+
storage.getInstrumentName());
43+
} else {
44+
storage.recordDouble(value, attributes);
45+
}
2846
}
2947

3048
@Override
31-
public void add(double value, Attributes attributes, Context context) {
32-
// FIXME: implement recording
49+
public void add(double value, Attributes attributes, Context unused) {
50+
add(value, attributes);
3351
}
3452

3553
static final class Builder implements DoubleCounterBuilder {
36-
private final OtelInstrumentBuilder instrumentBuilder;
54+
private final OtelMeter meter;
55+
private final OtelInstrumentBuilder builder;
3756

38-
Builder(OtelInstrumentBuilder builder) {
39-
this.instrumentBuilder = ofDoubles(builder, COUNTER);
57+
Builder(OtelMeter meter, OtelInstrumentBuilder builder) {
58+
this.meter = meter;
59+
this.builder = ofDoubles(builder, COUNTER);
4060
}
4161

4262
@Override
4363
public DoubleCounterBuilder setDescription(String description) {
44-
instrumentBuilder.setDescription(description);
64+
builder.setDescription(description);
4565
return this;
4666
}
4767

4868
@Override
4969
public DoubleCounterBuilder setUnit(String unit) {
50-
instrumentBuilder.setUnit(unit);
70+
builder.setUnit(unit);
5171
return this;
5272
}
5373

5474
@Override
5575
public DoubleCounter build() {
56-
return new OtelDoubleCounter();
76+
return new OtelDoubleCounter(
77+
meter.registerStorage(builder.descriptor(), OtelMetricStorage::newDoubleSumStorage));
5778
}
5879

5980
@Override

dd-java-agent/agent-otel/otel-shim/src/main/java/datadog/opentelemetry/shim/metrics/OtelDoubleGauge.java

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import static datadog.opentelemetry.shim.metrics.OtelMeter.NOOP_INSTRUMENT_NAME;
66
import static datadog.opentelemetry.shim.metrics.OtelMeter.NOOP_METER;
77

8+
import datadog.opentelemetry.shim.metrics.data.OtelMetricStorage;
89
import io.opentelemetry.api.common.Attributes;
910
import io.opentelemetry.api.metrics.DoubleGauge;
1011
import io.opentelemetry.api.metrics.DoubleGaugeBuilder;
@@ -16,50 +17,56 @@
1617
import javax.annotation.ParametersAreNonnullByDefault;
1718

1819
@ParametersAreNonnullByDefault
19-
final class OtelDoubleGauge implements DoubleGauge {
20+
final class OtelDoubleGauge extends OtelInstrument implements DoubleGauge {
21+
OtelDoubleGauge(OtelMetricStorage storage) {
22+
super(storage);
23+
}
2024

2125
@Override
2226
public void set(double value) {
23-
// FIXME: implement recording
27+
set(value, Attributes.empty());
2428
}
2529

2630
@Override
2731
public void set(double value, Attributes attributes) {
28-
// FIXME: implement recording
32+
storage.recordDouble(value, attributes);
2933
}
3034

3135
@Override
32-
public void set(double value, Attributes attributes, Context context) {
33-
// FIXME: implement recording
36+
public void set(double value, Attributes attributes, Context unused) {
37+
set(value, attributes);
3438
}
3539

3640
static final class Builder implements DoubleGaugeBuilder {
37-
private final OtelInstrumentBuilder instrumentBuilder;
41+
private final OtelMeter meter;
42+
private final OtelInstrumentBuilder builder;
3843

3944
Builder(OtelMeter meter, String instrumentName) {
40-
this.instrumentBuilder = ofDoubles(meter, instrumentName, GAUGE);
45+
this.meter = meter;
46+
this.builder = ofDoubles(instrumentName, GAUGE);
4147
}
4248

4349
@Override
4450
public DoubleGaugeBuilder setDescription(String description) {
45-
instrumentBuilder.setDescription(description);
51+
builder.setDescription(description);
4652
return this;
4753
}
4854

4955
@Override
5056
public DoubleGaugeBuilder setUnit(String unit) {
51-
instrumentBuilder.setUnit(unit);
57+
builder.setUnit(unit);
5258
return this;
5359
}
5460

5561
@Override
5662
public LongGaugeBuilder ofLongs() {
57-
return new OtelLongGauge.Builder(instrumentBuilder);
63+
return new OtelLongGauge.Builder(meter, builder);
5864
}
5965

6066
@Override
6167
public DoubleGauge build() {
62-
return new OtelDoubleGauge();
68+
return new OtelDoubleGauge(
69+
meter.registerStorage(builder.descriptor(), OtelMetricStorage::newDoubleValueStorage));
6370
}
6471

6572
@Override

dd-java-agent/agent-otel/otel-shim/src/main/java/datadog/opentelemetry/shim/metrics/OtelDoubleHistogram.java

Lines changed: 81 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,66 +2,135 @@
22

33
import static datadog.opentelemetry.shim.metrics.OtelInstrumentBuilder.ofDoubles;
44
import static datadog.opentelemetry.shim.metrics.OtelInstrumentType.HISTOGRAM;
5+
import static datadog.opentelemetry.shim.metrics.data.OtelMetricStorage.newHistogramStorage;
6+
import static java.util.Arrays.asList;
7+
import static java.util.Collections.emptyList;
58

9+
import datadog.opentelemetry.shim.metrics.data.OtelMetricStorage;
10+
import datadog.trace.relocate.api.RatelimitedLogger;
11+
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
612
import io.opentelemetry.api.common.Attributes;
713
import io.opentelemetry.api.metrics.DoubleHistogram;
814
import io.opentelemetry.api.metrics.DoubleHistogramBuilder;
915
import io.opentelemetry.api.metrics.LongHistogramBuilder;
1016
import io.opentelemetry.context.Context;
17+
import java.util.ArrayList;
1118
import java.util.List;
19+
import java.util.Objects;
20+
import java.util.concurrent.TimeUnit;
1221
import javax.annotation.ParametersAreNonnullByDefault;
22+
import org.slf4j.Logger;
23+
import org.slf4j.LoggerFactory;
1324

1425
@ParametersAreNonnullByDefault
15-
final class OtelDoubleHistogram implements DoubleHistogram {
26+
final class OtelDoubleHistogram extends OtelInstrument implements DoubleHistogram {
27+
private static final Logger LOGGER = LoggerFactory.getLogger(OtelDoubleHistogram.class);
28+
private static final RatelimitedLogger RATELIMITED_LOGGER =
29+
new RatelimitedLogger(LOGGER, 5, TimeUnit.MINUTES);
30+
31+
OtelDoubleHistogram(OtelMetricStorage storage) {
32+
super(storage);
33+
}
1634

1735
@Override
1836
public void record(double value) {
19-
// FIXME: implement recording
37+
record(value, Attributes.empty());
2038
}
2139

2240
@Override
2341
public void record(double value, Attributes attributes) {
24-
// FIXME: implement recording
42+
if (value < 0) {
43+
RATELIMITED_LOGGER.warn(
44+
"Histograms can only record non-negative values. Instrument {} has recorded a negative value.",
45+
storage.getInstrumentName());
46+
} else {
47+
storage.recordDouble(value, attributes);
48+
}
2549
}
2650

2751
@Override
28-
public void record(double value, Attributes attributes, Context context) {
29-
// FIXME: implement recording
52+
public void record(double value, Attributes attributes, Context unused) {
53+
record(value, attributes);
3054
}
3155

3256
static final class Builder implements DoubleHistogramBuilder {
33-
private final OtelInstrumentBuilder instrumentBuilder;
57+
private static final List<Double> DEFAULT_BOUNDARIES =
58+
asList(
59+
0d, 5d, 10d, 25d, 50d, 75d, 100d, 250d, 500d, 750d, 1_000d, 2_500d, 5_000d, 7_500d,
60+
10_000d);
61+
62+
private final OtelMeter meter;
63+
private final OtelInstrumentBuilder builder;
64+
private List<Double> bucketBoundaries;
3465

3566
Builder(OtelMeter meter, String instrumentName) {
36-
this.instrumentBuilder = ofDoubles(meter, instrumentName, HISTOGRAM);
67+
this.meter = meter;
68+
this.builder = ofDoubles(instrumentName, HISTOGRAM);
69+
this.bucketBoundaries = DEFAULT_BOUNDARIES;
3770
}
3871

3972
@Override
4073
public DoubleHistogramBuilder setDescription(String description) {
41-
instrumentBuilder.setDescription(description);
74+
builder.setDescription(description);
4275
return this;
4376
}
4477

4578
@Override
4679
public DoubleHistogramBuilder setUnit(String unit) {
47-
instrumentBuilder.setUnit(unit);
80+
builder.setUnit(unit);
4881
return this;
4982
}
5083

5184
@Override
85+
@SuppressFBWarnings("DCN") // match OTel in catching and logging NPE
5286
public DoubleHistogramBuilder setExplicitBucketBoundariesAdvice(List<Double> bucketBoundaries) {
53-
// FIXME: implement boundary advice
87+
try {
88+
Objects.requireNonNull(bucketBoundaries, "bucketBoundaries must not be null");
89+
this.bucketBoundaries = validateBoundaries(new ArrayList<>(bucketBoundaries));
90+
} catch (IllegalArgumentException | NullPointerException e) {
91+
LOGGER.warn("Error setting explicit bucket boundaries advice: {}", e.getMessage());
92+
}
5493
return this;
5594
}
5695

5796
@Override
5897
public LongHistogramBuilder ofLongs() {
59-
return new OtelLongHistogram.Builder(instrumentBuilder);
98+
return new OtelLongHistogram.Builder(meter, builder, bucketBoundaries);
6099
}
61100

62101
@Override
63102
public DoubleHistogram build() {
64-
return new OtelDoubleHistogram();
103+
return new OtelDoubleHistogram(
104+
meter.registerStorage(
105+
builder.descriptor(),
106+
descriptor -> newHistogramStorage(descriptor, bucketBoundaries)));
107+
}
108+
109+
static List<Double> validateBoundaries(List<Double> boundaries) {
110+
if (boundaries.isEmpty()) {
111+
return emptyList();
112+
}
113+
if (boundaries.get(0) == Double.NEGATIVE_INFINITY) {
114+
throw new IllegalArgumentException("invalid bucket boundary: -Inf");
115+
}
116+
if (boundaries.get(boundaries.size() - 1) == Double.POSITIVE_INFINITY) {
117+
throw new IllegalArgumentException("invalid bucket boundary: +Inf");
118+
}
119+
Double previousBoundary = null;
120+
for (Double boundary : boundaries) {
121+
if (boundary.isNaN()) {
122+
throw new IllegalArgumentException("invalid bucket boundary: NaN");
123+
}
124+
if (previousBoundary != null && previousBoundary >= boundary) {
125+
throw new IllegalArgumentException(
126+
"Bucket boundaries must be in increasing order: "
127+
+ previousBoundary
128+
+ " >= "
129+
+ boundary);
130+
}
131+
previousBoundary = boundary;
132+
}
133+
return boundaries;
65134
}
66135
}
67136
}

0 commit comments

Comments
 (0)