Create a new SupportabilityMetrics class rather than using alpha Metrics
MODIFY
instrumentation-api/instrumentation-api.gradle
instrumentation-api/instrumentation-api.gradle
@@ -8,7 +8,6 @@ dependencies {
api deps.opentelemetryContext
api deps.opentelemetrySemConv
- implementation deps.opentelemetryApiMetrics
implementation deps.slf4j
compileOnly deps.autoValueAnnotations

MODIFY
instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/tracer/BaseTracer.java
instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/tracer/BaseTracer.java
@@ -7,10 +7,6 @@ package io.opentelemetry.instrumentation.api.tracer;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.OpenTelemetry;
-import io.opentelemetry.api.metrics.GlobalMetricsProvider;
-import io.opentelemetry.api.metrics.LongCounter;
-import io.opentelemetry.api.metrics.Meter;
-import io.opentelemetry.api.metrics.common.Labels;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanBuilder;
import io.opentelemetry.api.trace.SpanKind;
@@ -21,12 +17,12 @@ import io.opentelemetry.context.ContextKey;
import io.opentelemetry.context.propagation.ContextPropagators;
import io.opentelemetry.context.propagation.TextMapGetter;
import io.opentelemetry.instrumentation.api.InstrumentationVersion;
+import io.opentelemetry.instrumentation.api.config.Config;
import io.opentelemetry.instrumentation.api.context.ContextPropagationDebug;
+import io.opentelemetry.instrumentation.api.tracer.utils.SupportabilityMetrics;
import java.lang.reflect.Method;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
/**
* Base class for all instrumentation specific tracer implementations.
@@ -43,10 +39,9 @@ import org.slf4j.LoggerFactory;
* are able to see those attributes in the {@code onStart()} method and can freely read/modify them.
*/
public abstract class BaseTracer {
- private static final Meter meterProvider =
- GlobalMetricsProvider.getMeter("io.opentelemetry.instrumentation.api.tracer");
-
- private static final Logger log = LoggerFactory.getLogger(BaseTracer.class);
+ // should we make this injectable?
+ private static final SupportabilityMetrics supportability =
+ new SupportabilityMetrics(Config.get()).start();
// Keeps track of the server span for the current trace.
// TODO(anuraaga): Should probably be renamed to local root key since it could be a consumer span
@@ -60,12 +55,6 @@ public abstract class BaseTracer {
protected final Tracer tracer;
protected final ContextPropagators propagators;
- private final LongCounter suppressionCounter =
- meterProvider
- .longCounterBuilder("agent.suppressed.spans")
- .setDescription("The number of spans that have been suppressed by the instrumentation.")
- .setUnit("1")
- .build();
public BaseTracer() {
tracer = GlobalOpenTelemetry.getTracer(getInstrumentationName(), getVersion());
@@ -139,15 +128,7 @@ public abstract class BaseTracer {
break;
}
if (suppressed) {
- suppressionCounter.add(
- 1,
- // note: an optimization here could be to make sure to re-use the labels,
- // since the set of possible labels will be quite small in a given application.
- // We could consider lazily creating bound counters for each combination of label values.
- Labels.of(
- "span.kind", proposedKind.name(),
- "instrumentation.name", getInstrumentationName(),
- "instrumentation.version", getVersion()));
+ supportability.recordSuppressedSpan(proposedKind, getInstrumentationName());
}
return !suppressed;
}

ADD
instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/tracer/utils/SupportabilityMetrics.java
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.api.tracer.utils;
+
+import io.opentelemetry.api.trace.SpanKind;
+import io.opentelemetry.instrumentation.api.config.Config;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.LongAdder;
+import java.util.function.Consumer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class SupportabilityMetrics {
+ private static final Logger log = LoggerFactory.getLogger(SupportabilityMetrics.class);
+ private final boolean agentDebugEnabled;
+ private final Consumer<String> reporter;
+
+ private final ConcurrentMap<String, KindCounters> suppressionCounters = new ConcurrentHashMap<>();
+
+ public SupportabilityMetrics(Config config) {
+ this(config, log::debug);
+ }
+
+ // visible for testing
+ SupportabilityMetrics(Config config, Consumer<String> reporter) {
+ agentDebugEnabled = config.isAgentDebugEnabled();
+ this.reporter = reporter;
+ }
+
+ public void recordSuppressedSpan(SpanKind kind, String instrumentationName) {
+ if (!agentDebugEnabled) {
+ return;
+ }
+
+ suppressionCounters
+ .computeIfAbsent(instrumentationName, s -> new KindCounters())
+ .increment(kind);
+ }
+
+ // visible for testing
+ void report() {
+ suppressionCounters.forEach(
+ (instrumentationName, countsByKind) -> {
+ for (SpanKind kind : SpanKind.values()) {
+ long value = countsByKind.getAndReset(kind);
+ if (value > 0) {
+ reporter.accept(
+ "Suppressed Spans by '" + instrumentationName + "' (" + kind + ") : " + value);
+ }
+ }
+ });
+ }
+
+ public SupportabilityMetrics start() {
+ if (agentDebugEnabled) {
+ Executors.newScheduledThreadPool(
+ 1,
+ runnable -> {
+ Thread result = new Thread(runnable, "supportability_metrics_reporter");
+ result.setDaemon(true);
+ return result;
+ })
+ .scheduleAtFixedRate(this::report, 5, 5, TimeUnit.SECONDS);
+ }
+ return this;
+ }
+
+ // this class is threadsafe.
+ private static class KindCounters {
+ private final LongAdder server = new LongAdder();
+ private final LongAdder client = new LongAdder();
+ private final LongAdder internal = new LongAdder();
+ private final LongAdder consumer = new LongAdder();
+ private final LongAdder producer = new LongAdder();
+
+ void increment(SpanKind kind) {
+ switch (kind) {
+ case INTERNAL:
+ internal.increment();
+ break;
+ case SERVER:
+ server.increment();
+ break;
+ case CLIENT:
+ client.increment();
+ break;
+ case PRODUCER:
+ producer.increment();
+ break;
+ case CONSUMER:
+ consumer.increment();
+ break;
+ default:
+ // in case a new kind gets added, we don't want to fail.
+ break;
+ }
+ }
+
+ long getAndReset(SpanKind kind) {
+ switch (kind) {
+ case INTERNAL:
+ return internal.sumThenReset();
+ case SERVER:
+ return server.sumThenReset();
+ case CLIENT:
+ return client.sumThenReset();
+ case PRODUCER:
+ return producer.sumThenReset();
+ case CONSUMER:
+ return consumer.sumThenReset();
+ default:
+ // in case a new kind gets added, we don't want to fail.
+ return 0;
+ }
+ }
+ }
+}

ADD
instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/tracer/utils/SupportabilityMetricsTest.java
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.api.tracer.utils;
+
+import static java.util.Collections.singletonList;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import io.opentelemetry.api.trace.SpanKind;
+import io.opentelemetry.instrumentation.api.config.Config;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import org.junit.jupiter.api.Test;
+
+class SupportabilityMetricsTest {
+ @Test
+ void disabled() {
+ List<String> reports = new ArrayList<>();
+ SupportabilityMetrics metrics =
+ new SupportabilityMetrics(
+ Config.create(Collections.singletonMap("otel.javaagent.debug", "false")), reports::add);
+
+ metrics.recordSuppressedSpan(SpanKind.CLIENT, "favoriteInstrumentation");
+ metrics.recordSuppressedSpan(SpanKind.SERVER, "favoriteInstrumentation");
+ metrics.recordSuppressedSpan(SpanKind.CLIENT, "favoriteInstrumentation");
+ metrics.recordSuppressedSpan(SpanKind.INTERNAL, "otherInstrumentation");
+
+ metrics.report();
+
+ assertThat(reports).isEmpty();
+ }
+
+ @Test
+ void reportsMetrics() {
+ List<String> reports = new ArrayList<>();
+ SupportabilityMetrics metrics =
+ new SupportabilityMetrics(
+ Config.create(Collections.singletonMap("otel.javaagent.debug", "true")), reports::add);
+
+ metrics.recordSuppressedSpan(SpanKind.CLIENT, "favoriteInstrumentation");
+ metrics.recordSuppressedSpan(SpanKind.SERVER, "favoriteInstrumentation");
+ metrics.recordSuppressedSpan(SpanKind.CLIENT, "favoriteInstrumentation");
+ metrics.recordSuppressedSpan(SpanKind.INTERNAL, "otherInstrumentation");
+
+ metrics.report();
+
+ assertThat(reports)
+ .isNotEmpty()
+ .hasSize(3)
+ .hasSameElementsAs(
+ Arrays.asList(
+ "Suppressed Spans by 'favoriteInstrumentation' (CLIENT) : 2",
+ "Suppressed Spans by 'favoriteInstrumentation' (SERVER) : 1",
+ "Suppressed Spans by 'otherInstrumentation' (INTERNAL) : 1"));
+ }
+
+ @Test
+ void resetsCountsEachReport() {
+ List<String> reports = new ArrayList<>();
+ SupportabilityMetrics metrics =
+ new SupportabilityMetrics(
+ Config.create(Collections.singletonMap("otel.javaagent.debug", "true")), reports::add);
+
+ metrics.recordSuppressedSpan(SpanKind.CLIENT, "favoriteInstrumentation");
+
+ metrics.report();
+ metrics.report();
+
+ assertThat(reports)
+ .isNotEmpty()
+ .hasSize(1)
+ .hasSameElementsAs(
+ singletonList("Suppressed Spans by 'favoriteInstrumentation' (CLIENT) : 1"));
+ }
+}

MODIFY
javaagent-api/src/main/java/io/opentelemetry/javaagent/instrumentation/api/OpenTelemetrySdkAccess.java
javaagent-api/src/main/java/io/opentelemetry/javaagent/instrumentation/api/OpenTelemetrySdkAccess.java
@@ -17,7 +17,7 @@ import java.util.concurrent.TimeUnit;
public final class OpenTelemetrySdkAccess {
/**
- * Interface matching {@link io.opentelemetry.sdk.trace.SdkTracerManagement#forceFlush()} to allow
+ * Interface matching {@link io.opentelemetry.sdk.trace.SdkTracerProvider#forceFlush()} to allow
* holding a reference to it.
*/
public interface ForceFlusher {
@@ -27,7 +27,7 @@ public final class OpenTelemetrySdkAccess {
private static volatile ForceFlusher FORCE_FLUSH;
- /** Forces flush of pending spans and metrics. */
+ /** Forces flushing of pending spans. */
public static void forceFlush(int timeout, TimeUnit unit) {
FORCE_FLUSH.run(timeout, unit);
}

Create a new SupportabilityMetrics class rather than using alpha Metrics (#2353)