Skip to content

Commit ce88588

Browse files
authored
Implement /metrics/otel endpoint for Java (#6049)
1 parent 98a9519 commit ce88588

14 files changed

+385
-2
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
package com.datadoghq.trace.opentelemetry.controller;
2+
3+
import static com.datadoghq.ApmTestClient.LOGGER;
4+
5+
import com.datadoghq.trace.opentelemetry.dto.*;
6+
import io.opentelemetry.api.GlobalOpenTelemetry;
7+
import io.opentelemetry.api.common.Attributes;
8+
import io.opentelemetry.api.common.AttributesBuilder;
9+
import io.opentelemetry.api.metrics.*;
10+
import java.util.Locale;
11+
import java.util.Map;
12+
import java.util.concurrent.ConcurrentHashMap;
13+
import java.util.function.Consumer;
14+
import org.springframework.web.bind.annotation.PostMapping;
15+
import org.springframework.web.bind.annotation.RequestBody;
16+
import org.springframework.web.bind.annotation.RequestMapping;
17+
import org.springframework.web.bind.annotation.RestController;
18+
19+
@RestController
20+
@RequestMapping(value = "/metrics/otel")
21+
public class OpenTelemetryMetricsController {
22+
private final MeterProvider meterProvider = GlobalOpenTelemetry.getMeterProvider();
23+
24+
/** Known meters, populated via getMeter requests. */
25+
private final Map<String, Meter> meters = new ConcurrentHashMap<>();
26+
27+
/** Previously created instruments of various types. */
28+
private final Map<String, Object> instruments = new ConcurrentHashMap<>();
29+
30+
@PostMapping("get_meter")
31+
public void getMeter(@RequestBody GetMeterArgs args) {
32+
LOGGER.info("Getting OTel meter: {}", args);
33+
String meterName = args.name();
34+
if (!meters.containsKey(meterName)) {
35+
meters.put(
36+
meterName,
37+
meterProvider
38+
.meterBuilder(meterName)
39+
.setInstrumentationVersion(args.version())
40+
.setSchemaUrl(args.schemaUrl())
41+
.build());
42+
}
43+
}
44+
45+
@PostMapping("create_counter")
46+
public void createCounter(@RequestBody CreateCounterArgs args) {
47+
LOGGER.info("Creating OTel counter: {}", args);
48+
String meterName = args.meterName();
49+
Meter meter = lookupMeter(meterName);
50+
String instrumentKey =
51+
instrumentKey(meterName, args.name(), "counter", args.unit(), args.description());
52+
LongCounter counter =
53+
meter
54+
.counterBuilder(args.name())
55+
.setUnit(args.unit())
56+
.setDescription(args.description())
57+
.build();
58+
instruments.put(instrumentKey, counter);
59+
}
60+
61+
@PostMapping("create_updowncounter")
62+
public void createUpDownCounter(@RequestBody CreateUpDownCounterArgs args) {
63+
LOGGER.info("Creating OTel up-down counter: {}", args);
64+
String meterName = args.meterName();
65+
Meter meter = lookupMeter(meterName);
66+
String instrumentKey =
67+
instrumentKey(meterName, args.name(), "updowncounter", args.unit(), args.description());
68+
LongUpDownCounter upDownCounter =
69+
meter
70+
.upDownCounterBuilder(args.name())
71+
.setUnit(args.unit())
72+
.setDescription(args.description())
73+
.build();
74+
instruments.put(instrumentKey, upDownCounter);
75+
}
76+
77+
@PostMapping("create_gauge")
78+
public void createGauge(@RequestBody CreateGaugeArgs args) {
79+
LOGGER.info("Creating OTel gauge: {}", args);
80+
String meterName = args.meterName();
81+
Meter meter = lookupMeter(meterName);
82+
String instrumentKey =
83+
instrumentKey(meterName, args.name(), "gauge", args.unit(), args.description());
84+
DoubleGauge gauge =
85+
meter
86+
.gaugeBuilder(args.name())
87+
.setUnit(args.unit())
88+
.setDescription(args.description())
89+
.build();
90+
instruments.put(instrumentKey, gauge);
91+
}
92+
93+
@PostMapping("counter_add")
94+
public void counterAdd(@RequestBody CounterAddArgs args) {
95+
LOGGER.info("Adding value to OTel counter : {}", args);
96+
String meterName = args.meterName();
97+
lookupMeter(meterName);
98+
String instrumentKey =
99+
instrumentKey(meterName, args.name(), "counter", args.unit(), args.description());
100+
LongCounter counter = lookupInstrument(instrumentKey, LongCounter.class);
101+
counter.add(args.value().longValue(), fromMap(args.attributes()));
102+
}
103+
104+
@PostMapping("updowncounter_add")
105+
public void upDownCounterAdd(@RequestBody UpDownCounterAddArgs args) {
106+
LOGGER.info("Adding value to OTel up-down counter: {}", args);
107+
String meterName = args.meterName();
108+
lookupMeter(meterName);
109+
String instrumentKey =
110+
instrumentKey(meterName, args.name(), "updowncounter", args.unit(), args.description());
111+
LongUpDownCounter upDownCounter = lookupInstrument(instrumentKey, LongUpDownCounter.class);
112+
upDownCounter.add(args.value().longValue(), fromMap(args.attributes()));
113+
}
114+
115+
@PostMapping("gauge_record")
116+
public void gaugeRecord(@RequestBody GaugeRecordArgs args) {
117+
LOGGER.info("Recording value to OTel gauge: {}", args);
118+
String meterName = args.meterName();
119+
lookupMeter(meterName);
120+
String instrumentKey =
121+
instrumentKey(meterName, args.name(), "gauge", args.unit(), args.description());
122+
DoubleGauge gauge = lookupInstrument(instrumentKey, DoubleGauge.class);
123+
gauge.set(args.value().longValue(), fromMap(args.attributes()));
124+
}
125+
126+
@PostMapping("create_asynchronous_counter")
127+
public void createAsynchronousCounter(@RequestBody CreateAsynchronousCounterArgs args) {
128+
LOGGER.info("Creating OTel asynchronous counter: {}", args);
129+
String meterName = args.meterName();
130+
Meter meter = lookupMeter(meterName);
131+
String instrumentKey =
132+
instrumentKey(
133+
meterName, args.name(), "observable_counter", args.unit(), args.description());
134+
Consumer<ObservableLongMeasurement> observeCallback =
135+
measurement -> measurement.record(args.value().longValue(), fromMap(args.attributes()));
136+
ObservableLongCounter observableCounter =
137+
meter
138+
.counterBuilder(args.name())
139+
.setUnit(args.unit())
140+
.setDescription(args.description())
141+
.buildWithCallback(observeCallback);
142+
instruments.put(instrumentKey, observableCounter);
143+
}
144+
145+
@PostMapping("create_asynchronous_updowncounter")
146+
public void createAsynchronousUpDownCounter(
147+
@RequestBody CreateAsynchronousUpDownCounterArgs args) {
148+
LOGGER.info("Creating OTel asynchronous up-down counter: {}", args);
149+
String meterName = args.meterName();
150+
Meter meter = lookupMeter(meterName);
151+
String instrumentKey =
152+
instrumentKey(
153+
meterName, args.name(), "observable_updowncounter", args.unit(), args.description());
154+
Consumer<ObservableLongMeasurement> observeCallback =
155+
measurement -> measurement.record(args.value().longValue(), fromMap(args.attributes()));
156+
ObservableLongUpDownCounter observableUpDownCounter =
157+
meter
158+
.upDownCounterBuilder(args.name())
159+
.setUnit(args.unit())
160+
.setDescription(args.description())
161+
.buildWithCallback(observeCallback);
162+
instruments.put(instrumentKey, observableUpDownCounter);
163+
}
164+
165+
@PostMapping("create_asynchronous_gauge")
166+
public void createAsynchronousGauge(@RequestBody CreateAsynchronousGaugeArgs args) {
167+
LOGGER.info("Creating OTel asynchronous gauge: {}", args);
168+
String meterName = args.meterName();
169+
Meter meter = lookupMeter(meterName);
170+
String instrumentKey =
171+
instrumentKey(meterName, args.name(), "observable_gauge", args.unit(), args.description());
172+
Consumer<ObservableDoubleMeasurement> observeCallback =
173+
measurement -> measurement.record(args.value().doubleValue(), fromMap(args.attributes()));
174+
ObservableDoubleGauge observableGauge =
175+
meter
176+
.gaugeBuilder(args.name())
177+
.setUnit(args.unit())
178+
.setDescription(args.description())
179+
.buildWithCallback(observeCallback);
180+
instruments.put(instrumentKey, observableGauge);
181+
}
182+
183+
@PostMapping("create_histogram")
184+
public void createHistogram(@RequestBody CreateHistogramArgs args) {
185+
LOGGER.info("Creating OTel histogram: {}", args);
186+
String meterName = args.meterName();
187+
Meter meter = lookupMeter(meterName);
188+
String instrumentKey =
189+
instrumentKey(meterName, args.name(), "histogram", args.unit(), args.description());
190+
DoubleHistogram histogram =
191+
meter
192+
.histogramBuilder(args.name())
193+
.setUnit(args.unit())
194+
.setDescription(args.description())
195+
.build();
196+
instruments.put(instrumentKey, histogram);
197+
}
198+
199+
@PostMapping("histogram_record")
200+
public void histogramRecord(@RequestBody HistogramRecordArgs args) {
201+
LOGGER.info("Recording value to OTel histogram: {}", args);
202+
String meterName = args.meterName();
203+
lookupMeter(meterName);
204+
String instrumentKey =
205+
instrumentKey(meterName, args.name(), "histogram", args.unit(), args.description());
206+
DoubleHistogram histogram = lookupInstrument(instrumentKey, DoubleHistogram.class);
207+
histogram.record(args.value().longValue(), fromMap(args.attributes()));
208+
}
209+
210+
@PostMapping("force_flush")
211+
public FlushResult forceFlush(@RequestBody FlushArgs args) {
212+
LOGGER.info("Flushing OTel metrics: {}", args);
213+
try {
214+
// TODO: call internal hook to flush metrics
215+
return new FlushResult(true);
216+
} catch (Exception e) {
217+
LOGGER.warn("Failed to flush OTel metrics", e);
218+
return new FlushResult(false);
219+
}
220+
}
221+
222+
/** Builds {@link Attributes} from a map of strings. */
223+
private static Attributes fromMap(Map<String, String> map) {
224+
AttributesBuilder builder = Attributes.builder();
225+
for (Map.Entry<String, String> entry : map.entrySet()) {
226+
builder.put(entry.getKey(), entry.getValue());
227+
}
228+
return builder.build();
229+
}
230+
231+
private Meter lookupMeter(String meterName) {
232+
Meter meter = meters.get(meterName);
233+
if (meter == null) {
234+
throw new IllegalStateException(
235+
"Meter " + meterName + " not found in registered meters " + meters.keySet());
236+
}
237+
return meter;
238+
}
239+
240+
private <T> T lookupInstrument(String instrumentKey, Class<T> instrumentType) {
241+
Object instrument = instruments.get(instrumentKey);
242+
if (instrument == null) {
243+
throw new IllegalStateException(
244+
"Instrument "
245+
+ instrumentKey
246+
+ " not found in registered instruments "
247+
+ instruments.keySet());
248+
}
249+
return instrumentType.cast(instrument);
250+
}
251+
252+
private static String instrumentKey(
253+
String meterName, String name, String kind, String unit, String description) {
254+
return "Meter="
255+
+ meterName
256+
+ ", Name="
257+
+ name.toLowerCase(Locale.ROOT)
258+
+ ", Kind="
259+
+ kind
260+
+ ", Unit="
261+
+ unit
262+
+ ", Description="
263+
+ description;
264+
}
265+
}

utils/build/docker/java/parametric/src/main/java/com/datadoghq/trace/opentelemetry/controller/OpenTelemetryController.java renamed to utils/build/docker/java/parametric/src/main/java/com/datadoghq/trace/opentelemetry/controller/OpenTelemetryTraceController.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,12 @@
2828

2929
@RestController
3030
@RequestMapping(value = "/trace/otel")
31-
public class OpenTelemetryController {
31+
public class OpenTelemetryTraceController {
3232
private final Tracer tracer;
3333
private final Map<Long, Span> spans;
3434
private Baggage baggage;
3535

36-
public OpenTelemetryController() {
36+
public OpenTelemetryTraceController() {
3737
this.tracer = GlobalOpenTelemetry.getTracer("java-client");
3838
this.spans = new HashMap<>();
3939
this.baggage = Baggage.empty();
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.datadoghq.trace.opentelemetry.dto;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import java.util.Map;
5+
6+
public record CounterAddArgs(
7+
@JsonProperty("meter_name") String meterName,
8+
String name,
9+
String description,
10+
String unit,
11+
Number value,
12+
Map<String, String> attributes) {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.datadoghq.trace.opentelemetry.dto;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import java.util.Map;
5+
6+
public record CreateAsynchronousCounterArgs(
7+
@JsonProperty("meter_name") String meterName,
8+
String name,
9+
String description,
10+
String unit,
11+
Number value,
12+
Map<String, String> attributes) {}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.datadoghq.trace.opentelemetry.dto;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import java.util.Map;
5+
6+
public record CreateAsynchronousGaugeArgs(
7+
@JsonProperty("meter_name") String meterName,
8+
String name,
9+
String description,
10+
String unit,
11+
Number value,
12+
Map<String, String> attributes) {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.datadoghq.trace.opentelemetry.dto;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import java.util.Map;
5+
6+
public record CreateAsynchronousUpDownCounterArgs(
7+
@JsonProperty("meter_name") String meterName,
8+
String name,
9+
String description,
10+
String unit,
11+
Number value,
12+
Map<String, String> attributes) {}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package com.datadoghq.trace.opentelemetry.dto;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
5+
public record CreateCounterArgs(
6+
@JsonProperty("meter_name") String meterName, String name, String description, String unit) {}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package com.datadoghq.trace.opentelemetry.dto;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
5+
public record CreateGaugeArgs(
6+
@JsonProperty("meter_name") String meterName, String name, String description, String unit) {}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package com.datadoghq.trace.opentelemetry.dto;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
5+
public record CreateHistogramArgs(
6+
@JsonProperty("meter_name") String meterName, String name, String description, String unit) {}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package com.datadoghq.trace.opentelemetry.dto;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
5+
public record CreateUpDownCounterArgs(
6+
@JsonProperty("meter_name") String meterName, String name, String description, String unit) {}

0 commit comments

Comments
 (0)