Skip to content

Commit fbed4e4

Browse files
committed
Introduce shared OtelMetricRegistry to track metrics by instrumentation scope.
This will allow metrics produced by drop-in support and the OTel API to coexist.
1 parent 6ee53ad commit fbed4e4

11 files changed

Lines changed: 134 additions & 73 deletions

File tree

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package datadog.trace.bootstrap.otel.metrics.data;
2+
3+
import datadog.trace.bootstrap.otel.common.OtelInstrumentationScope;
4+
import datadog.trace.bootstrap.otel.metrics.OtelInstrumentDescriptor;
5+
import datadog.trace.bootstrap.otel.metrics.export.OtelMetricsVisitor;
6+
import datadog.trace.bootstrap.otel.metrics.export.OtelScopedMetricsVisitor;
7+
import java.util.ArrayList;
8+
import java.util.List;
9+
import java.util.Map;
10+
import java.util.concurrent.ConcurrentHashMap;
11+
import java.util.function.Function;
12+
13+
/** Tracks metric storage and observable callbacks by instrumentation scope. */
14+
public final class OtelMetricRegistry {
15+
public static final OtelMetricRegistry INSTANCE = new OtelMetricRegistry();
16+
17+
private final Map<OtelInstrumentationScope, Map<OtelInstrumentDescriptor, OtelMetricStorage>>
18+
scopedStorage = new ConcurrentHashMap<>();
19+
20+
private final Map<OtelInstrumentationScope, List<OtelObservable>> scopedObservables =
21+
new ConcurrentHashMap<>();
22+
23+
public OtelMetricStorage registerStorage(
24+
OtelInstrumentationScope instrumentationScope,
25+
OtelInstrumentDescriptor descriptor,
26+
Function<OtelInstrumentDescriptor, OtelMetricStorage> storageFactory) {
27+
return scopedStorage
28+
.computeIfAbsent(instrumentationScope, unused -> new ConcurrentHashMap<>())
29+
.computeIfAbsent(descriptor, storageFactory);
30+
}
31+
32+
public void registerObservable(
33+
OtelInstrumentationScope instrumentationScope, OtelObservable observable) {
34+
List<OtelObservable> observables =
35+
scopedObservables.computeIfAbsent(instrumentationScope, unused -> new ArrayList<>());
36+
synchronized (observables) {
37+
observables.add(observable);
38+
}
39+
}
40+
41+
public boolean unregisterObservable(
42+
OtelInstrumentationScope instrumentationScope, OtelObservable observable) {
43+
List<OtelObservable> observables = scopedObservables.get(instrumentationScope);
44+
if (observables == null) {
45+
return false;
46+
}
47+
synchronized (observables) {
48+
return observables.remove(observable);
49+
}
50+
}
51+
52+
public void collectMetrics(OtelMetricsVisitor visitor) {
53+
scopedStorage.forEach(
54+
(scope, storage) ->
55+
collectScopedMetrics(scope, storage, visitor.visitScopedMetrics(scope)));
56+
}
57+
58+
private void collectScopedMetrics(
59+
OtelInstrumentationScope instrumentationScope,
60+
Map<OtelInstrumentDescriptor, OtelMetricStorage> storage,
61+
OtelScopedMetricsVisitor visitor) {
62+
List<OtelObservable> observables = scopedObservables.get(instrumentationScope);
63+
if (observables != null) {
64+
// take local snapshot of current observables
65+
List<OtelObservable> observablesCopy;
66+
synchronized (observables) {
67+
observablesCopy = new ArrayList<>(observables);
68+
}
69+
// must observe measurements outside of lock
70+
observablesCopy.forEach(OtelObservable::observeMeasurements);
71+
}
72+
storage.forEach((descriptor, s) -> s.collectMetric(visitor.visitMetric(descriptor)));
73+
}
74+
}

dd-java-agent/agent-otel/otel-bootstrap/src/main/java/datadog/trace/bootstrap/otel/metrics/data/OtelMetricStorage.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString;
66
import datadog.trace.bootstrap.otel.metrics.OtelInstrumentDescriptor;
77
import datadog.trace.bootstrap.otel.metrics.OtelInstrumentType;
8-
import datadog.trace.bootstrap.otel.metrics.export.OtelInstrumentVisitor;
8+
import datadog.trace.bootstrap.otel.metrics.export.OtelMetricVisitor;
99
import datadog.trace.relocate.api.RatelimitedLogger;
1010
import io.opentelemetry.api.common.Attributes;
1111
import java.util.List;
@@ -149,7 +149,7 @@ private OtelAggregator aggregator(Map<Object, OtelAggregator> aggregators, Objec
149149
return aggregators.computeIfAbsent(attributes, aggregatorSupplier);
150150
}
151151

152-
public void collect(OtelInstrumentVisitor visitor) {
152+
public void collectMetric(OtelMetricVisitor visitor) {
153153
if (resetOnCollect) {
154154
doCollectAndReset(visitor);
155155
} else {
@@ -158,7 +158,7 @@ public void collect(OtelInstrumentVisitor visitor) {
158158
}
159159

160160
/** Collect data for CUMULATIVE temporality, keeping aggregators for future writes. */
161-
private void doCollect(OtelInstrumentVisitor visitor) {
161+
private void doCollect(OtelMetricVisitor visitor) {
162162
// no need to hold writers back if we are not resetting metrics on collect
163163
currentRecording.aggregators.forEach(
164164
(attributes, aggregator) -> {
@@ -173,7 +173,7 @@ private void doCollect(OtelInstrumentVisitor visitor) {
173173
*
174174
* <p>Each collect request toggles between two groups of aggregators: current / previous.
175175
*/
176-
private void doCollectAndReset(OtelInstrumentVisitor visitor) {
176+
private void doCollectAndReset(OtelMetricVisitor visitor) {
177177

178178
// capture _current_ recording for collection, its aggregators will be reset at the end
179179
final Recording recording = currentRecording;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package datadog.trace.bootstrap.otel.metrics.data;
2+
3+
public abstract class OtelObservable {
4+
protected abstract void observeMeasurements();
5+
}

dd-java-agent/agent-otel/otel-bootstrap/src/main/java/datadog/trace/bootstrap/otel/metrics/export/OtelMeterVisitor.java

Lines changed: 0 additions & 8 deletions
This file was deleted.

dd-java-agent/agent-otel/otel-bootstrap/src/main/java/datadog/trace/bootstrap/otel/metrics/export/OtelInstrumentVisitor.java renamed to dd-java-agent/agent-otel/otel-bootstrap/src/main/java/datadog/trace/bootstrap/otel/metrics/export/OtelMetricVisitor.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33
import datadog.trace.bootstrap.otel.metrics.data.OtelPoint;
44

5-
public interface OtelInstrumentVisitor {
6-
/** Visits a data point collected by the instrument. */
5+
/** A visitor to visit a metric in an instrumentation scope. */
6+
public interface OtelMetricVisitor {
7+
/** Visits a data point in the metric. */
78
void visitPoint(Object attributes, OtelPoint point);
89
}

dd-java-agent/agent-otel/otel-bootstrap/src/main/java/datadog/trace/bootstrap/otel/metrics/export/OtelMetricsVisitor.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33
import datadog.trace.bootstrap.otel.common.OtelInstrumentationScope;
44

5+
/** A visitor to visit OpenTelemetry metrics. */
56
public interface OtelMetricsVisitor {
6-
/** Visits a meter created by the OpenTelemetry API. */
7-
OtelMeterVisitor visitMeter(OtelInstrumentationScope scope);
7+
/** Visits metrics produced by an instrumentation scope. */
8+
OtelScopedMetricsVisitor visitScopedMetrics(OtelInstrumentationScope scope);
89
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package datadog.trace.bootstrap.otel.metrics.export;
2+
3+
import datadog.trace.bootstrap.otel.metrics.OtelInstrumentDescriptor;
4+
5+
/** A visitor to visit metrics produced by an instrumentation scope. */
6+
public interface OtelScopedMetricsVisitor {
7+
/** Visits a metric in the instrumentation scope. */
8+
OtelMetricVisitor visitMetric(OtelInstrumentDescriptor descriptor);
9+
}

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

Lines changed: 7 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
import datadog.trace.bootstrap.otel.common.OtelInstrumentationScope;
88
import datadog.trace.bootstrap.otel.metrics.OtelInstrumentBuilder;
99
import datadog.trace.bootstrap.otel.metrics.OtelInstrumentDescriptor;
10+
import datadog.trace.bootstrap.otel.metrics.data.OtelMetricRegistry;
1011
import datadog.trace.bootstrap.otel.metrics.data.OtelMetricStorage;
11-
import datadog.trace.bootstrap.otel.metrics.export.OtelMeterVisitor;
1212
import io.opentelemetry.api.metrics.BatchCallback;
1313
import io.opentelemetry.api.metrics.DoubleGaugeBuilder;
1414
import io.opentelemetry.api.metrics.DoubleHistogramBuilder;
@@ -17,10 +17,7 @@
1717
import io.opentelemetry.api.metrics.Meter;
1818
import io.opentelemetry.api.metrics.MeterProvider;
1919
import io.opentelemetry.api.metrics.ObservableMeasurement;
20-
import java.util.ArrayList;
2120
import java.util.List;
22-
import java.util.Map;
23-
import java.util.concurrent.ConcurrentHashMap;
2421
import java.util.function.Consumer;
2522
import java.util.function.Function;
2623
import java.util.regex.Pattern;
@@ -42,11 +39,6 @@ final class OtelMeter implements Meter {
4239

4340
private final OtelInstrumentationScope instrumentationScope;
4441

45-
private final Map<OtelInstrumentDescriptor, OtelMetricStorage> storage =
46-
new ConcurrentHashMap<>();
47-
48-
private final List<OtelObservableCallback> observables = new ArrayList<>();
49-
5042
OtelMeter(OtelInstrumentationScope instrumentationScope) {
5143
this.instrumentationScope = instrumentationScope;
5244
}
@@ -104,14 +96,16 @@ public String toString() {
10496
OtelMetricStorage registerStorage(
10597
OtelInstrumentBuilder builder,
10698
Function<OtelInstrumentDescriptor, OtelMetricStorage> storageFactory) {
107-
return storage.computeIfAbsent(builder.descriptor(), storageFactory);
99+
return OtelMetricRegistry.INSTANCE.registerStorage(
100+
instrumentationScope, builder.descriptor(), storageFactory);
108101
}
109102

110103
OtelObservableMeasurement registerObservableStorage(
111104
OtelInstrumentBuilder builder,
112105
Function<OtelInstrumentDescriptor, OtelMetricStorage> storageFactory) {
113106
return new OtelObservableMeasurement(
114-
storage.computeIfAbsent(builder.observableDescriptor(), storageFactory));
107+
OtelMetricRegistry.INSTANCE.registerStorage(
108+
instrumentationScope, builder.observableDescriptor(), storageFactory));
115109
}
116110

117111
<M> OtelObservableCallback registerObservableCallback(Consumer<M> callback, M measurement) {
@@ -122,25 +116,12 @@ <M> OtelObservableCallback registerObservableCallback(Consumer<M> callback, M me
122116
OtelObservableCallback registerObservableCallback(
123117
Runnable callback, List<OtelObservableMeasurement> measurements) {
124118
OtelObservableCallback observable = new OtelObservableCallback(this, callback, measurements);
125-
synchronized (observables) {
126-
observables.add(observable);
127-
}
119+
OtelMetricRegistry.INSTANCE.registerObservable(instrumentationScope, observable);
128120
return observable;
129121
}
130122

131123
boolean unregisterObservableCallback(OtelObservableCallback observable) {
132-
synchronized (observables) {
133-
return observables.remove(observable);
134-
}
135-
}
136-
137-
void collect(OtelMeterVisitor visitor) {
138-
List<OtelObservableCallback> observablesCopy;
139-
synchronized (observables) {
140-
observablesCopy = new ArrayList<>(observables);
141-
}
142-
observablesCopy.forEach(OtelObservableCallback::observeMeasurements);
143-
storage.forEach((descriptor, storage) -> storage.collect(visitor.visitInstrument(descriptor)));
124+
return OtelMetricRegistry.INSTANCE.unregisterObservable(instrumentationScope, observable);
144125
}
145126

146127
private static boolean validInstrumentName(@Nullable String instrumentName) {

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

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package datadog.opentelemetry.shim.metrics;
22

33
import datadog.trace.bootstrap.otel.common.OtelInstrumentationScope;
4-
import datadog.trace.bootstrap.otel.metrics.export.OtelMetricsVisitor;
54
import datadog.trace.util.Strings;
65
import io.opentelemetry.api.metrics.Meter;
76
import io.opentelemetry.api.metrics.MeterBuilder;
@@ -33,10 +32,6 @@ public MeterBuilder meterBuilder(String instrumentationScopeName) {
3332
return new OtelMeterBuilder(this, instrumentationScopeName);
3433
}
3534

36-
public void collectMetrics(OtelMetricsVisitor visitor) {
37-
meters.forEach((scope, meter) -> meter.collect(visitor.visitMeter(scope)));
38-
}
39-
4035
OtelMeter getMeterShim(
4136
String instrumentationScopeName,
4237
@Nullable String instrumentationScopeVersion,

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package datadog.opentelemetry.shim.metrics;
22

3+
import datadog.trace.bootstrap.otel.metrics.data.OtelObservable;
34
import datadog.trace.relocate.api.RatelimitedLogger;
45
import io.opentelemetry.api.metrics.BatchCallback;
56
import io.opentelemetry.api.metrics.ObservableDoubleCounter;
@@ -13,7 +14,7 @@
1314
import org.slf4j.Logger;
1415
import org.slf4j.LoggerFactory;
1516

16-
final class OtelObservableCallback
17+
final class OtelObservableCallback extends OtelObservable
1718
implements ObservableDoubleCounter,
1819
ObservableLongCounter,
1920
ObservableDoubleGauge,
@@ -37,7 +38,8 @@ final class OtelObservableCallback
3738
this.measurements = measurements;
3839
}
3940

40-
void observeMeasurements() {
41+
@Override
42+
public void observeMeasurements() {
4143
measurements.forEach(OtelObservableMeasurement::activate);
4244
try {
4345
callback.run();

0 commit comments

Comments
 (0)