Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package datadog.opentelemetry.shim.metrics;

import io.opentelemetry.api.metrics.BatchCallback;
import io.opentelemetry.api.metrics.DoubleGaugeBuilder;
import io.opentelemetry.api.metrics.DoubleHistogramBuilder;
import io.opentelemetry.api.metrics.LongCounterBuilder;
import io.opentelemetry.api.metrics.LongUpDownCounterBuilder;
import io.opentelemetry.api.metrics.Meter;
import io.opentelemetry.api.metrics.ObservableMeasurement;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

// https://www.javadoc.io/doc/io.opentelemetry/opentelemetry-api/1.47.0/io/opentelemetry/api/metrics/Meter.html
public class OtelMeter implements Meter {
private static final Logger LOGGER = LoggerFactory.getLogger(OtelMeter.class);

private final String instrumentationScopeName;
private final String schemaUrl;
private final String instrumentationVersion;

public OtelMeter(
String instrumentationScopeName, String schemaUrl, String instrumentationVersion) {
this.instrumentationScopeName = instrumentationScopeName;
this.schemaUrl = schemaUrl;
this.instrumentationVersion = instrumentationVersion;
}

public boolean match(
String instrumentationScopeName, String instrumentationVersion, String schemaUrl) {
return instrumentationScopeName.equals(this.instrumentationScopeName)
&& schemaUrl.equals(this.schemaUrl)
&& instrumentationVersion.equals(this.instrumentationVersion);
}

@Override
public LongCounterBuilder counterBuilder(String instrumentName) {
LOGGER.info("CounterBuilder is not yet supported");
return null;
}

@Override
public LongUpDownCounterBuilder upDownCounterBuilder(String instrumentName) {
LOGGER.info("upDownCounterBuilder is not yet supported");
return null;
}

@Override
public DoubleHistogramBuilder histogramBuilder(String instrumentName) {
LOGGER.info("histogramBuilder is not yet supported");
return null;
}

@Override
public DoubleGaugeBuilder gaugeBuilder(String instrumentName) {
LOGGER.info("gaugeBuilder is not yet supported");
return null;
}

@Override
public BatchCallback batchCallback(
Runnable callback,
ObservableMeasurement observableMeasurement,
ObservableMeasurement... additionalMeasurements) {
LOGGER.info("batchCallback is not yet supported");
return Meter.super.batchCallback(callback, observableMeasurement, additionalMeasurements);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package datadog.opentelemetry.shim.metrics;

import io.opentelemetry.api.metrics.Meter;
import io.opentelemetry.api.metrics.MeterBuilder;
import javax.annotation.ParametersAreNonnullByDefault;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class OtelMeterBuilder implements MeterBuilder {
private static final Logger LOGGER = LoggerFactory.getLogger(OtelMeterBuilder.class);
private final String instrumentationScopeName;
private String schemaUrl;
private String instrumentationVersion;

public OtelMeterBuilder(String instrumentationScopeName) {
this.instrumentationScopeName = instrumentationScopeName;
}

@Override
@ParametersAreNonnullByDefault
public MeterBuilder setSchemaUrl(String schemaUrl) {
this.schemaUrl = schemaUrl;
return this;
}

@Override
@ParametersAreNonnullByDefault
public MeterBuilder setInstrumentationVersion(String instrumentationVersion) {
this.instrumentationVersion = instrumentationVersion;
return this;
}

@Override
public Meter build() {
return new OtelMeter(instrumentationScopeName, instrumentationVersion, schemaUrl);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package datadog.opentelemetry.shim.metrics;

import io.opentelemetry.api.metrics.Meter;
import io.opentelemetry.api.metrics.MeterBuilder;
import io.opentelemetry.api.metrics.MeterProvider;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.ParametersAreNonnullByDefault;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class OtelMeterProvider implements MeterProvider {
private static final Logger LOGGER = LoggerFactory.getLogger(OtelMeterProvider.class);
private static final String DEFAULT_METER_NAME = "";
public static final MeterProvider INSTANCE = new OtelMeterProvider();
/** Meter instances, indexed by instrumentation scope name. */
private final Map<String, List<Meter>> meters;

public OtelMeterProvider() {
this.meters = new HashMap<>();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
private final Map<String, List<Meter>> meters;
public OtelMeterProvider() {
this.meters = new HashMap<>();
}
private final Map<String, List<Meter>> scopedMeters = new ConcurrentHashMap();

Users may call this from different threads, so the map needs to handle concurrent updates.

Also final fields can be assigned in the same line, avoiding the need to do that in the constructor.

This can mean you can drop the constructor completely, making the code more concise.

Lastly I would call this field scopedMeters because it holds meters indexed by scope.

(This helps make the code clearer later on when you query scopedMeters and store the list of meters for that scope in a variable called meters)


@Override
@ParametersAreNonnullByDefault
public Meter get(String instrumentationScopeName) {
return get(instrumentationScopeName, null);
}

public Meter get(String instrumentationScopeName, String instrumentationVersion) {
return get(instrumentationScopeName, instrumentationVersion, null);
}

public Meter get(
String instrumentationScopeName, String instrumentationVersion, String urlSchema) {
List<Meter> meters = this.meters.get(instrumentationScopeName);
if (meters != null) {
for (Meter meter : meters) {
if ((meter instanceof OtelMeter)
&& ((OtelMeter) meter)
.match(instrumentationScopeName, instrumentationVersion, urlSchema)) {
return meter;
}
}
}
Meter meter =
meterBuilder(instrumentationScopeName)
.setInstrumentationVersion(instrumentationVersion)
.setSchemaUrl(urlSchema)
.build();
this.meters.put(instrumentationScopeName, new ArrayList<>());
this.meters.get(instrumentationScopeName).add(meter);

return meter;
}

@Override
public MeterBuilder meterBuilder(String instrumentationScopeName) {
if (instrumentationScopeName.trim().isEmpty()) {
LOGGER.debug("Meter requested without instrumentation scope name.");
instrumentationScopeName = DEFAULT_METER_NAME;
}
return new OtelMeterBuilder(instrumentationScopeName);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
def openTelemetryVersion = '1.47.0'

muzzle {
pass {
module = 'opentelemetry-api'
group = 'io.opentelemetry'
versions = "[$openTelemetryVersion,)"
}
}

apply from: "$rootDir/gradle/java.gradle"

addTestSuiteForDir('latestDepTest', 'test')

dependencies {
compileOnly group: 'io.opentelemetry', name: 'opentelemetry-api', version: openTelemetryVersion
compileOnly group: 'com.google.auto.value', name: 'auto-value-annotations', version: '1.6.6'

implementation project(':dd-java-agent:agent-otel:otel-shim')
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package datadog.trace.instrumentation.opentelemetry147;

import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.implementsInterface;
import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.returns;
import static net.bytebuddy.matcher.ElementMatchers.takesNoArguments;

import com.google.auto.service.AutoService;
import datadog.opentelemetry.shim.metrics.OtelMeterProvider;
import datadog.trace.agent.tooling.Instrumenter;
import datadog.trace.agent.tooling.InstrumenterModule;
import io.opentelemetry.api.metrics.MeterProvider;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;

@AutoService(InstrumenterModule.class)
public class OpenTelemetryInstrumentation extends InstrumenterModule.Tracing
implements Instrumenter.CanShortcutTypeMatching, Instrumenter.HasMethodAdvice {

public OpenTelemetryInstrumentation() {
super("opentelemetry.metrics", "opentelemetry-147");
}

@Override
protected boolean defaultEnabled() {
// Not activated yet to prevent NPE
// return InstrumenterConfig.get().isMetricsOtelEnabled();
return false;
}

@Override
public String hierarchyMarkerType() {
return "io.opentelemetry.api.OpenTelemetry";
}

@Override
public ElementMatcher<TypeDescription> hierarchyMatcher() {
return implementsInterface(named(hierarchyMarkerType()));
}

@Override
public String[] knownMatchingTypes() {
return new String[] {
"io.opentelemetry.api.DefaultOpenTelemetry",
"io.opentelemetry.api.GlobalOpenTelemetry$ObfuscatedOpenTelemetry"
};
}

@Override
public boolean onlyMatchKnownTypes() {
return isShortcutMatchingEnabled(false);
}

@Override
public String[] helperClassNames() {
return new String[] {
"datadog.opentelemetry.shim.metrics.OtelMeter",
"datadog.opentelemetry.shim.metrics.OtelMeterBuilder",
"datadog.opentelemetry.shim.metrics.OtelMeterProvider",
};
}

@Override
public void methodAdvice(MethodTransformer transformer) {
// MeterProvider OpenTelemetry.getMeterProvider()
transformer.applyAdvice(
isMethod()
.and(named("getMeterProvider"))
.and(takesNoArguments())
.and(returns(named("io.opentelemetry.api.metrics.MeterProvider"))),
OpenTelemetryInstrumentation.class.getName() + "$MeterProviderAdvice");
}

public static class MeterProviderAdvice {
@Advice.OnMethodExit(suppress = Throwable.class)
public static void returnProvider(@Advice.Return(readOnly = false) MeterProvider result) {
result = OtelMeterProvider.INSTANCE;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import static datadog.trace.api.ConfigDefaults.DEFAULT_INTEGRATIONS_ENABLED;
import static datadog.trace.api.ConfigDefaults.DEFAULT_LLM_OBS_ENABLED;
import static datadog.trace.api.ConfigDefaults.DEFAULT_MEASURE_METHODS;
import static datadog.trace.api.ConfigDefaults.DEFAULT_METRICS_OTEL_ENABLED;
import static datadog.trace.api.ConfigDefaults.DEFAULT_RESOLVER_RESET_INTERVAL;
import static datadog.trace.api.ConfigDefaults.DEFAULT_RUM_ENABLED;
import static datadog.trace.api.ConfigDefaults.DEFAULT_RUNTIME_CONTEXT_FIELD_INJECTION;
Expand All @@ -29,6 +30,7 @@
import static datadog.trace.api.config.GeneralConfig.TRIAGE_REPORT_TRIGGER;
import static datadog.trace.api.config.IastConfig.IAST_ENABLED;
import static datadog.trace.api.config.LlmObsConfig.LLMOBS_ENABLED;
import static datadog.trace.api.config.OtlpConfig.METRICS_OTEL_ENABLED;
import static datadog.trace.api.config.ProfilingConfig.PROFILING_DIRECT_ALLOCATION_ENABLED;
import static datadog.trace.api.config.ProfilingConfig.PROFILING_DIRECT_ALLOCATION_ENABLED_DEFAULT;
import static datadog.trace.api.config.ProfilingConfig.PROFILING_ENABLED;
Expand Down Expand Up @@ -116,6 +118,7 @@ public class InstrumenterConfig {
private final boolean codeOriginEnabled;
private final boolean traceEnabled;
private final boolean traceOtelEnabled;
private final boolean metricsOtelEnabled;
private final ProfilingEnablement profilingEnabled;
private final boolean ciVisibilityEnabled;
private final ProductActivation appSecActivation;
Expand Down Expand Up @@ -209,6 +212,8 @@ private InstrumenterConfig() {
CODE_ORIGIN_FOR_SPANS_ENABLED, DEFAULT_CODE_ORIGIN_FOR_SPANS_ENABLED);
traceEnabled = configProvider.getBoolean(TRACE_ENABLED, DEFAULT_TRACE_ENABLED);
traceOtelEnabled = configProvider.getBoolean(TRACE_OTEL_ENABLED, DEFAULT_TRACE_OTEL_ENABLED);
metricsOtelEnabled =
configProvider.getBoolean(METRICS_OTEL_ENABLED, DEFAULT_METRICS_OTEL_ENABLED);

profilingEnabled =
ProfilingEnablement.of(
Expand Down Expand Up @@ -373,6 +378,10 @@ public boolean isTraceOtelEnabled() {
return traceOtelEnabled;
}

public boolean isMetricsOtelEnabled() {
return metricsOtelEnabled;
}

public boolean isProfilingEnabled() {
return profilingEnabled.isActive();
}
Expand Down Expand Up @@ -626,6 +635,8 @@ public String toString() {
+ traceEnabled
+ ", traceOtelEnabled="
+ traceOtelEnabled
+ ", metricsOtelEnabled="
+ metricsOtelEnabled
+ ", profilingEnabled="
+ profilingEnabled
+ ", ciVisibilityEnabled="
Expand Down
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,7 @@ include(
":dd-java-agent:instrumentation:opensearch:transport",
":dd-java-agent:instrumentation:opentelemetry:opentelemetry-0.3",
":dd-java-agent:instrumentation:opentelemetry:opentelemetry-1.4",
":dd-java-agent:instrumentation:opentelemetry:opentelemetry-1.47",
":dd-java-agent:instrumentation:opentelemetry:opentelemetry-annotations-1.20",
":dd-java-agent:instrumentation:opentelemetry:opentelemetry-annotations-1.26",
":dd-java-agent:instrumentation:opentracing",
Expand Down