From 968ca7b5268d885913544b6fa5cb909d133a17ff Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Fri, 5 Dec 2025 13:37:38 +0900 Subject: [PATCH 01/10] Implement SDK metrics for trace --- .../opentelemetry-sdk-trace.txt | 24 +- .../TracerProviderConfiguration.java | 7 +- .../fileconfig/SpanProcessorFactory.java | 9 +- ...OpenTelemetryConfigurationFactoryTest.java | 3 +- .../sdk/trace/ExtendedSdkSpanBuilder.java | 5 +- .../sdk/trace/ExtendedSdkTracer.java | 5 +- .../sdk/trace/IncubatingUtil.java | 11 +- .../io/opentelemetry/sdk/trace/SdkSpan.java | 15 +- .../sdk/trace/SdkSpanBuilder.java | 11 +- .../io/opentelemetry/sdk/trace/SdkTracer.java | 25 +- .../sdk/trace/SdkTracerMetrics.java | 88 ++ .../sdk/trace/SdkTracerProvider.java | 8 +- .../sdk/trace/SdkTracerProviderBuilder.java | 19 +- .../sdk/trace/export/BatchSpanProcessor.java | 73 +- .../export/BatchSpanProcessorBuilder.java | 11 + .../sdk/trace/export/SimpleSpanProcessor.java | 14 +- .../export/SimpleSpanProcessorBuilder.java | 21 +- .../trace/export/SpanProcessorMetrics.java | 200 ++++ .../trace/internal/SdkTracerProviderUtil.java | 33 + .../opentelemetry/sdk/trace/SdkSpanTest.java | 12 +- .../trace/SdkTracerProviderMetricsTest.java | 988 ++++++++++++++++++ 21 files changed, 1501 insertions(+), 81 deletions(-) create mode 100644 sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerMetrics.java create mode 100644 sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SpanProcessorMetrics.java create mode 100644 sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkTracerProviderMetricsTest.java diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-trace.txt b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-trace.txt index 3436b94c676..1c1e8e9a7e2 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-trace.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-trace.txt @@ -1,2 +1,24 @@ Comparing source compatibility of opentelemetry-sdk-trace-1.57.0-SNAPSHOT.jar against opentelemetry-sdk-trace-1.56.0.jar -No changes. \ No newline at end of file +*** MODIFIED CLASS: PUBLIC FINAL io.opentelemetry.sdk.trace.export.BatchSpanProcessorBuilder (not serializable) + === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.trace.export.BatchSpanProcessorBuilder setInternalTelemetryVersion(io.opentelemetry.sdk.common.InternalTelemetryVersion) ++++ NEW CLASS: PUBLIC(+) STATIC(+) FINAL(+) io.opentelemetry.sdk.trace.export.SpanProcessorMetrics$LegacyProcessorMetrics (not serializable) + +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. + +++ NEW INTERFACE: io.opentelemetry.sdk.trace.export.SpanProcessorMetrics + +++ NEW SUPERCLASS: java.lang.Object + +++ NEW METHOD: PUBLIC(+) void buildQueueCapacityMetric(long) + +++ NEW METHOD: PUBLIC(+) void buildQueueSizeMetric(io.opentelemetry.sdk.trace.export.SpanProcessorMetrics$LongCallable) + +++ NEW METHOD: PUBLIC(+) void dropSpans(int) + +++ NEW METHOD: PUBLIC(+) void finishSpans(int, java.lang.String) ++++ NEW INTERFACE: PUBLIC(+) ABSTRACT(+) STATIC(+) io.opentelemetry.sdk.trace.export.SpanProcessorMetrics$LongCallable (not serializable) + +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. + +++ NEW SUPERCLASS: java.lang.Object + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) long get() ++++ NEW CLASS: PUBLIC(+) STATIC(+) FINAL(+) io.opentelemetry.sdk.trace.export.SpanProcessorMetrics$SemConvSpanProcessorMetrics (not serializable) + +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. + +++ NEW INTERFACE: io.opentelemetry.sdk.trace.export.SpanProcessorMetrics + +++ NEW SUPERCLASS: java.lang.Object + +++ NEW METHOD: PUBLIC(+) void buildQueueCapacityMetric(long) + +++ NEW METHOD: PUBLIC(+) void buildQueueSizeMetric(io.opentelemetry.sdk.trace.export.SpanProcessorMetrics$LongCallable) + +++ NEW METHOD: PUBLIC(+) void dropSpans(int) + +++ NEW METHOD: PUBLIC(+) void finishSpans(int, java.lang.String) diff --git a/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/TracerProviderConfiguration.java b/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/TracerProviderConfiguration.java index ee6d75d4c70..1265893dd93 100644 --- a/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/TracerProviderConfiguration.java +++ b/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/TracerProviderConfiguration.java @@ -19,6 +19,7 @@ import io.opentelemetry.sdk.trace.export.BatchSpanProcessorBuilder; import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; import io.opentelemetry.sdk.trace.export.SpanExporter; +import io.opentelemetry.sdk.trace.internal.SdkTracerProviderUtil; import io.opentelemetry.sdk.trace.samplers.Sampler; import java.io.Closeable; import java.time.Duration; @@ -49,6 +50,7 @@ static void configureTracerProvider( List closeables) { tracerProviderBuilder.setSpanLimits(configureSpanLimits(config)); + SdkTracerProviderUtil.setMeterProvider(tracerProviderBuilder, meterProvider); String sampler = config.getString("otel.traces.sampler", PARENTBASED_ALWAYS_ON); tracerProviderBuilder.setSampler( @@ -80,7 +82,10 @@ static List configureSpanProcessors( for (String simpleProcessorExporterNames : simpleProcessorExporterNames) { SpanExporter exporter = exportersByNameCopy.remove(simpleProcessorExporterNames); if (exporter != null) { - SpanProcessor spanProcessor = SimpleSpanProcessor.create(exporter); + SpanProcessor spanProcessor = + SdkTracerProviderUtil.setMeterProvider( + SimpleSpanProcessor.builder(exporter), meterProvider) + .build(); closeables.add(spanProcessor); spanProcessors.add(spanProcessor); } diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/SpanProcessorFactory.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/SpanProcessorFactory.java index d5b2c3970f6..8a6574eab27 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/SpanProcessorFactory.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/SpanProcessorFactory.java @@ -14,7 +14,9 @@ import io.opentelemetry.sdk.trace.export.BatchSpanProcessor; import io.opentelemetry.sdk.trace.export.BatchSpanProcessorBuilder; import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; +import io.opentelemetry.sdk.trace.export.SimpleSpanProcessorBuilder; import io.opentelemetry.sdk.trace.export.SpanExporter; +import io.opentelemetry.sdk.trace.internal.SdkTracerProviderUtil; import java.time.Duration; import java.util.Map; @@ -62,7 +64,12 @@ public SpanProcessor create(SpanProcessorModel model, DeclarativeConfigContext c FileConfigUtil.requireNonNull( simpleModel.getExporter(), "simple span processor exporter"); SpanExporter spanExporter = SpanExporterFactory.getInstance().create(exporterModel, context); - return context.addCloseable(SimpleSpanProcessor.create(spanExporter)); + SimpleSpanProcessorBuilder builder = SimpleSpanProcessor.builder(spanExporter); + MeterProvider meterProvider = context.getMeterProvider(); + if (meterProvider != null) { + SdkTracerProviderUtil.setMeterProvider(builder, meterProvider); + } + return context.addCloseable(builder.build()); } Map.Entry keyValue = diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactoryTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactoryTest.java index d06e9164356..675dfa8872f 100644 --- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactoryTest.java +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactoryTest.java @@ -343,7 +343,8 @@ void create_Configured() throws NoSuchFieldException, IllegalAccessException { .extracting("sharedState") .extracting("activeSpanProcessor") .extracting("worker") - .extracting("processedSpansCounter") + .extracting("spanProcessorMetrics") + .extracting("processedSpans") .extracting("sdkMeter") .extracting("meterProviderSharedState") .isEqualTo(sharedState); diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/ExtendedSdkSpanBuilder.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/ExtendedSdkSpanBuilder.java index b68030202ce..4491e581977 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/ExtendedSdkSpanBuilder.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/ExtendedSdkSpanBuilder.java @@ -30,8 +30,9 @@ final class ExtendedSdkSpanBuilder extends SdkSpanBuilder implements ExtendedSpa String spanName, InstrumentationScopeInfo instrumentationScopeInfo, TracerSharedState tracerSharedState, - SpanLimits spanLimits) { - super(spanName, instrumentationScopeInfo, tracerSharedState, spanLimits); + SpanLimits spanLimits, + SdkTracerMetrics tracerProviderMetrics) { + super(spanName, instrumentationScopeInfo, tracerSharedState, spanLimits, tracerProviderMetrics); } @Override diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/ExtendedSdkTracer.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/ExtendedSdkTracer.java index 0c1d1c8e8b9..ae0dd654578 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/ExtendedSdkTracer.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/ExtendedSdkTracer.java @@ -16,8 +16,9 @@ final class ExtendedSdkTracer extends SdkTracer implements ExtendedTracer { ExtendedSdkTracer( TracerSharedState sharedState, InstrumentationScopeInfo instrumentationScopeInfo, - TracerConfig tracerConfig) { - super(sharedState, instrumentationScopeInfo, tracerConfig); + TracerConfig tracerConfig, + SdkTracerMetrics tracerProviderMetrics) { + super(sharedState, instrumentationScopeInfo, tracerConfig, tracerProviderMetrics); } @Override diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/IncubatingUtil.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/IncubatingUtil.java index c7ab15e9c2e..af0ea505c7c 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/IncubatingUtil.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/IncubatingUtil.java @@ -20,16 +20,19 @@ private IncubatingUtil() {} static SdkTracer createExtendedTracer( TracerSharedState sharedState, InstrumentationScopeInfo instrumentationScopeInfo, - TracerConfig tracerConfig) { - return new ExtendedSdkTracer(sharedState, instrumentationScopeInfo, tracerConfig); + TracerConfig tracerConfig, + SdkTracerMetrics tracerProviderMetrics) { + return new ExtendedSdkTracer( + sharedState, instrumentationScopeInfo, tracerConfig, tracerProviderMetrics); } static SdkSpanBuilder createExtendedSpanBuilder( String spanName, InstrumentationScopeInfo instrumentationScopeInfo, TracerSharedState tracerSharedState, - SpanLimits spanLimits) { + SpanLimits spanLimits, + SdkTracerMetrics tracerProviderMetrics) { return new ExtendedSdkSpanBuilder( - spanName, instrumentationScopeInfo, tracerSharedState, spanLimits); + spanName, instrumentationScopeInfo, tracerSharedState, spanLimits, tracerProviderMetrics); } } diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkSpan.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkSpan.java index 37deab7ffc8..ce9a46cc8b5 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkSpan.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkSpan.java @@ -61,6 +61,9 @@ final class SdkSpan implements ReadWriteSpan { private final InstrumentationScopeInfo instrumentationScopeInfo; // The start time of the span. private final long startEpochNanos; + // Callback to run when span ends. + private final Runnable onEnd; + // Lock used to internally guard the mutable state of this instance private final Object lock = new Object(); @@ -132,7 +135,8 @@ private SdkSpan( @Nullable AttributesMap attributes, @Nullable List links, int totalRecordedLinks, - long startEpochNanos) { + long startEpochNanos, + Runnable onEnd) { this.context = context; this.instrumentationScopeInfo = instrumentationScopeInfo; this.parentSpanContext = parentSpanContext; @@ -148,6 +152,7 @@ private SdkSpan( this.startEpochNanos = startEpochNanos; this.attributes = attributes; this.spanLimits = spanLimits; + this.onEnd = onEnd; } /** @@ -163,6 +168,7 @@ private SdkSpan( * @param resource the resource associated with this span. * @param attributes the attributes set during span creation. * @param links the links set during span creation, may be truncated. The list MUST be immutable. + * @param onEnd a {@link Runnable} to run when the span is ended. * @return a new and started span. */ static SdkSpan startSpan( @@ -180,7 +186,8 @@ static SdkSpan startSpan( @Nullable AttributesMap attributes, @Nullable List links, int totalRecordedLinks, - long userStartEpochNanos) { + long userStartEpochNanos, + Runnable onEnd) { boolean createdAnchoredClock; AnchoredClock clock; if (parentSpan instanceof SdkSpan) { @@ -219,7 +226,8 @@ static SdkSpan startSpan( attributes, links, totalRecordedLinks, - startEpochNanos); + startEpochNanos, + onEnd); // Call onStart here instead of calling in the constructor to make sure the span is completely // initialized. if (spanProcessor.isStartRequired()) { @@ -557,6 +565,7 @@ private void endInternal(long endEpochNanos) { spanEndingThread = Thread.currentThread(); hasEnded = EndState.ENDING; } + onEnd.run(); if (spanProcessor instanceof ExtendedSpanProcessor) { ExtendedSpanProcessor extendedSpanProcessor = (ExtendedSpanProcessor) spanProcessor; if (extendedSpanProcessor.isOnEndingRequired()) { diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkSpanBuilder.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkSpanBuilder.java index c0f872265ec..9e5db4fb27a 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkSpanBuilder.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkSpanBuilder.java @@ -39,6 +39,7 @@ class SdkSpanBuilder implements SpanBuilder { private final InstrumentationScopeInfo instrumentationScopeInfo; private final TracerSharedState tracerSharedState; private final SpanLimits spanLimits; + private final SdkTracerMetrics tracerProviderMetrics; @Nullable private Context parent; // null means: Use current context. private SpanKind spanKind = SpanKind.INTERNAL; @@ -51,11 +52,13 @@ class SdkSpanBuilder implements SpanBuilder { String spanName, InstrumentationScopeInfo instrumentationScopeInfo, TracerSharedState tracerSharedState, - SpanLimits spanLimits) { + SpanLimits spanLimits, + SdkTracerMetrics tracerProviderMetrics) { this.spanName = spanName; this.instrumentationScopeInfo = instrumentationScopeInfo; this.tracerSharedState = tracerSharedState; this.spanLimits = spanLimits; + this.tracerProviderMetrics = tracerProviderMetrics; } @Override @@ -204,6 +207,9 @@ public Span startSpan() { /* remote= */ false, tracerSharedState.isIdGeneratorSafeToSkipIdValidation()); + Runnable recordEndSpanMetrics = + tracerProviderMetrics.startSpan(parentSpanContext, samplingDecision); + if (!isRecording(samplingDecision)) { return Span.wrap(spanContext); } @@ -232,7 +238,8 @@ public Span startSpan() { recordedAttributes, currentLinks, totalNumberOfLinksAdded, - startEpochNanos); + startEpochNanos, + recordEndSpanMetrics); } private AttributesMap attributes() { diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracer.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracer.java index d90f4d8cb01..d6542f929ae 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracer.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracer.java @@ -30,25 +30,30 @@ class SdkTracer implements Tracer { private final TracerSharedState sharedState; private final InstrumentationScopeInfo instrumentationScopeInfo; + private final SdkTracerMetrics tracerProviderMetrics; protected volatile boolean tracerEnabled; SdkTracer( TracerSharedState sharedState, InstrumentationScopeInfo instrumentationScopeInfo, - TracerConfig tracerConfig) { + TracerConfig tracerConfig, + SdkTracerMetrics tracerProviderMetrics) { this.sharedState = sharedState; this.instrumentationScopeInfo = instrumentationScopeInfo; this.tracerEnabled = tracerConfig.isEnabled(); + this.tracerProviderMetrics = tracerProviderMetrics; } static SdkTracer create( TracerSharedState sharedState, InstrumentationScopeInfo instrumentationScopeInfo, - TracerConfig tracerConfig) { + TracerConfig tracerConfig, + SdkTracerMetrics tracerProviderMetrics) { return INCUBATOR_AVAILABLE - ? IncubatingUtil.createExtendedTracer(sharedState, instrumentationScopeInfo, tracerConfig) - : new SdkTracer(sharedState, instrumentationScopeInfo, tracerConfig); + ? IncubatingUtil.createExtendedTracer( + sharedState, instrumentationScopeInfo, tracerConfig, tracerProviderMetrics) + : new SdkTracer(sharedState, instrumentationScopeInfo, tracerConfig, tracerProviderMetrics); } /** @@ -68,9 +73,17 @@ public SpanBuilder spanBuilder(String spanName) { } return INCUBATOR_AVAILABLE ? IncubatingUtil.createExtendedSpanBuilder( - spanName, instrumentationScopeInfo, sharedState, sharedState.getSpanLimits()) + spanName, + instrumentationScopeInfo, + sharedState, + sharedState.getSpanLimits(), + tracerProviderMetrics) : new SdkSpanBuilder( - spanName, instrumentationScopeInfo, sharedState, sharedState.getSpanLimits()); + spanName, + instrumentationScopeInfo, + sharedState, + sharedState.getSpanLimits(), + tracerProviderMetrics); } // Visible for testing diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerMetrics.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerMetrics.java new file mode 100644 index 00000000000..058e7b8d6c1 --- /dev/null +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerMetrics.java @@ -0,0 +1,88 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.trace; + +import static io.opentelemetry.api.common.AttributeKey.stringKey; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.LongCounter; +import io.opentelemetry.api.metrics.LongUpDownCounter; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.api.metrics.MeterProvider; +import io.opentelemetry.api.trace.SpanContext; +import io.opentelemetry.sdk.trace.samplers.SamplingDecision; + +/** + * SDK metrics exported for started and ended spans as defined in the semantic + * conventions. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +final class SdkTracerMetrics { + + // Visible for testing + static final AttributeKey OTEL_SPAN_PARENT_ORIGIN = stringKey("otel.span.parent.origin"); + // Visible for testing + static final AttributeKey OTEL_SPAN_SAMPLING_RESULT = + stringKey("otel.span.sampling_result"); + + private final LongCounter startedSpans; + private final LongUpDownCounter liveSpans; + + SdkTracerMetrics(MeterProvider meterProvider) { + Meter meter = meterProvider.get("io.opentelemetry.sdk.trace"); + + startedSpans = + meter + .counterBuilder("otel.sdk.span.started") + .setUnit("{span}") + .setDescription("The number of created spans.") + .build(); + liveSpans = + meter + .upDownCounterBuilder("otel.sdk.span.live") + .setUnit("{span}") + .setDescription( + "The number of created spans with recording=true for which the end operation has not been called yet.") + .build(); + } + + /** + * Records metrics for when a span starts and returns a {@link Runnable} to execute when ending + * the span. + */ + Runnable startSpan(SpanContext parentSpanContext, SamplingDecision samplingDecision) { + startedSpans.add( + 1, + Attributes.of( + OTEL_SPAN_PARENT_ORIGIN, + parentOrigin(parentSpanContext), + OTEL_SPAN_SAMPLING_RESULT, + samplingDecision.name())); + + if (samplingDecision == SamplingDecision.DROP) { + return () -> {}; + } + + Attributes liveSpansAttributes = + Attributes.of(OTEL_SPAN_SAMPLING_RESULT, samplingDecision.name()); + liveSpans.add(1, liveSpansAttributes); + return () -> liveSpans.add(-1, liveSpansAttributes); + } + + private static String parentOrigin(SpanContext parentSpanContext) { + if (!parentSpanContext.isValid()) { + return "none"; + } + if (parentSpanContext.isRemote()) { + return "remote"; + } + return "local"; + } +} diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProvider.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProvider.java index f39ce565731..beccc2f90bc 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProvider.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProvider.java @@ -5,6 +5,7 @@ package io.opentelemetry.sdk.trace; +import io.opentelemetry.api.metrics.MeterProvider; import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.api.trace.TracerBuilder; import io.opentelemetry.api.trace.TracerProvider; @@ -54,7 +55,9 @@ public static SdkTracerProviderBuilder builder() { Sampler sampler, List spanProcessors, ScopeConfigurator tracerConfigurator, - ExceptionAttributeResolver exceptionAttributeResolver) { + ExceptionAttributeResolver exceptionAttributeResolver, + MeterProvider meterProvider) { + SdkTracerMetrics tracerProviderMetrics = new SdkTracerMetrics(meterProvider); this.sharedState = new TracerSharedState( clock, @@ -70,7 +73,8 @@ public static SdkTracerProviderBuilder builder() { SdkTracer.create( sharedState, instrumentationScopeInfo, - getTracerConfig(instrumentationScopeInfo))); + getTracerConfig(instrumentationScopeInfo), + tracerProviderMetrics)); this.tracerConfigurator = tracerConfigurator; } diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProviderBuilder.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProviderBuilder.java index f480fe37272..056e12bd1a3 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProviderBuilder.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProviderBuilder.java @@ -7,6 +7,7 @@ import static java.util.Objects.requireNonNull; +import io.opentelemetry.api.metrics.MeterProvider; import io.opentelemetry.api.trace.Span; import io.opentelemetry.sdk.common.Clock; import io.opentelemetry.sdk.common.InstrumentationScopeInfo; @@ -38,6 +39,7 @@ public final class SdkTracerProviderBuilder { TracerConfig.configuratorBuilder(); private ExceptionAttributeResolver exceptionAttributeResolver = ExceptionAttributeResolver.getDefault(); + private MeterProvider meterProvider = MeterProvider.noop(); /** * Assign a {@link Clock}. {@link Clock} will be used each time a {@link Span} is started, ended @@ -232,6 +234,20 @@ SdkTracerProviderBuilder setExceptionAttributeResolver( return this; } + /** + * Sets the {@link MeterProvider} to use to generate SDK Span + * Metrics. + * + *

This method is experimental so not public. You may reflectively call it using {@link + * SdkTracerProviderUtil#setMeterProvider(SdkTracerProviderBuilder, MeterProvider)}. + */ + SdkTracerProviderBuilder setMeterProvider(MeterProvider meterProvider) { + requireNonNull(meterProvider, "meterProvider"); + this.meterProvider = meterProvider; + return this; + } + /** * Create a new {@link SdkTracerProvider} instance with the configuration. * @@ -246,7 +262,8 @@ public SdkTracerProvider build() { sampler, spanProcessors, tracerConfiguratorBuilder.build(), - exceptionAttributeResolver); + exceptionAttributeResolver, + meterProvider); } SdkTracerProviderBuilder() {} diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessor.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessor.java index cdedb5abb1c..0912782cc52 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessor.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessor.java @@ -5,13 +5,11 @@ package io.opentelemetry.sdk.trace.export; -import io.opentelemetry.api.common.AttributeKey; -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.metrics.LongCounter; -import io.opentelemetry.api.metrics.Meter; import io.opentelemetry.api.metrics.MeterProvider; import io.opentelemetry.context.Context; import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.common.InternalTelemetryVersion; +import io.opentelemetry.sdk.internal.ComponentId; import io.opentelemetry.sdk.internal.DaemonThreadFactory; import io.opentelemetry.sdk.internal.ThrowableUtil; import io.opentelemetry.sdk.trace.ReadWriteSpan; @@ -43,15 +41,13 @@ */ public final class BatchSpanProcessor implements SpanProcessor { + private static final ComponentId COMPONENT_ID = + ComponentId.generateLazy("batching_span_processor"); + private static final Logger logger = Logger.getLogger(BatchSpanProcessor.class.getName()); private static final String WORKER_THREAD_NAME = BatchSpanProcessor.class.getSimpleName() + "_WorkerThread"; - private static final AttributeKey SPAN_PROCESSOR_TYPE_LABEL = - AttributeKey.stringKey("processorType"); - private static final AttributeKey SPAN_PROCESSOR_DROPPED_LABEL = - AttributeKey.booleanKey("dropped"); - private static final String SPAN_PROCESSOR_TYPE_VALUE = BatchSpanProcessor.class.getSimpleName(); private final boolean exportUnsampledSpans; private final Worker worker; @@ -72,6 +68,7 @@ public static BatchSpanProcessorBuilder builder(SpanExporter spanExporter) { SpanExporter spanExporter, boolean exportUnsampledSpans, MeterProvider meterProvider, + InternalTelemetryVersion telemetryVersion, long scheduleDelayNanos, int maxQueueSize, int maxExportBatchSize, @@ -81,10 +78,12 @@ public static BatchSpanProcessorBuilder builder(SpanExporter spanExporter) { new Worker( spanExporter, meterProvider, + telemetryVersion, scheduleDelayNanos, maxExportBatchSize, exporterTimeoutNanos, - JcTools.newFixedSizeQueue(maxQueueSize)); + JcTools.newFixedSizeQueue(maxQueueSize), + maxQueueSize); Thread workerThread = new DaemonThreadFactory(WORKER_THREAD_NAME).newThread(worker); workerThread.start(); } @@ -161,9 +160,7 @@ public String toString() { // the data. private static final class Worker implements Runnable { - private final LongCounter processedSpansCounter; - private final Attributes droppedAttrs; - private final Attributes exportedAttrs; + private final SpanProcessorMetrics spanProcessorMetrics; private final SpanExporter spanExporter; private final long scheduleDelayNanos; @@ -189,54 +186,30 @@ private static final class Worker implements Runnable { private Worker( SpanExporter spanExporter, MeterProvider meterProvider, + InternalTelemetryVersion telemetryVersion, long scheduleDelayNanos, int maxExportBatchSize, long exporterTimeoutNanos, - Queue queue) { + Queue queue, + long maxQueueSize) { this.spanExporter = spanExporter; this.scheduleDelayNanos = scheduleDelayNanos; this.maxExportBatchSize = maxExportBatchSize; this.exporterTimeoutNanos = exporterTimeoutNanos; this.queue = queue; this.signal = new ArrayBlockingQueue<>(1); - Meter meter = meterProvider.meterBuilder("io.opentelemetry.sdk.trace").build(); - meter - .gaugeBuilder("queueSize") - .ofLongs() - .setDescription("The number of items queued") - .setUnit("1") - .buildWithCallback( - result -> - result.record( - queue.size(), - Attributes.of(SPAN_PROCESSOR_TYPE_LABEL, SPAN_PROCESSOR_TYPE_VALUE))); - processedSpansCounter = - meter - .counterBuilder("processedSpans") - .setUnit("1") - .setDescription( - "The number of spans processed by the BatchSpanProcessor. " - + "[dropped=true if they were dropped due to high throughput]") - .build(); - droppedAttrs = - Attributes.of( - SPAN_PROCESSOR_TYPE_LABEL, - SPAN_PROCESSOR_TYPE_VALUE, - SPAN_PROCESSOR_DROPPED_LABEL, - true); - exportedAttrs = - Attributes.of( - SPAN_PROCESSOR_TYPE_LABEL, - SPAN_PROCESSOR_TYPE_VALUE, - SPAN_PROCESSOR_DROPPED_LABEL, - false); + + spanProcessorMetrics = + SpanProcessorMetrics.get(telemetryVersion, COMPONENT_ID, meterProvider); + spanProcessorMetrics.buildQueueCapacityMetric(maxQueueSize); + spanProcessorMetrics.buildQueueSizeMetric(queue::size); this.batch = new ArrayList<>(this.maxExportBatchSize); } private void addSpan(ReadableSpan span) { if (!queue.offer(span)) { - processedSpansCounter.add(1, droppedAttrs); + spanProcessorMetrics.dropSpans(1); } else { if (queueSize.incrementAndGet() >= spansNeeded.get()) { signal.offer(true); @@ -340,18 +313,20 @@ private void exportCurrentBatch() { return; } + String error = null; try { CompletableResultCode result = spanExporter.export(Collections.unmodifiableList(batch)); result.join(exporterTimeoutNanos, TimeUnit.NANOSECONDS); - if (result.isSuccess()) { - processedSpansCounter.add(batch.size(), exportedAttrs); - } else { + if (!result.isSuccess()) { logger.log(Level.FINE, "Exporter failed"); + error = "export_failed"; } } catch (Throwable t) { ThrowableUtil.propagateIfFatal(t); logger.log(Level.WARNING, "Exporter threw an Exception", t); + error = t.getClass().getName(); } finally { + spanProcessorMetrics.finishSpans(batch.size(), error); batch.clear(); } } diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessorBuilder.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessorBuilder.java index d89083ded06..fd325b6d126 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessorBuilder.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessorBuilder.java @@ -9,6 +9,7 @@ import static java.util.Objects.requireNonNull; import io.opentelemetry.api.metrics.MeterProvider; +import io.opentelemetry.sdk.common.InternalTelemetryVersion; import java.time.Duration; import java.util.concurrent.TimeUnit; import java.util.logging.Level; @@ -34,6 +35,7 @@ public final class BatchSpanProcessorBuilder { private int maxExportBatchSize = DEFAULT_MAX_EXPORT_BATCH_SIZE; private long exporterTimeoutNanos = TimeUnit.MILLISECONDS.toNanos(DEFAULT_EXPORT_TIMEOUT_MILLIS); private MeterProvider meterProvider = MeterProvider.noop(); + private InternalTelemetryVersion telemetryVersion = InternalTelemetryVersion.LEGACY; BatchSpanProcessorBuilder(SpanExporter spanExporter) { this.spanExporter = requireNonNull(spanExporter, "spanExporter"); @@ -149,6 +151,14 @@ public BatchSpanProcessorBuilder setMeterProvider(MeterProvider meterProvider) { return this; } + /** Sets the {@link InternalTelemetryVersion} defining which metrics this processor records. */ + public BatchSpanProcessorBuilder setInternalTelemetryVersion( + InternalTelemetryVersion telemetryVersion) { + requireNonNull(telemetryVersion, "telemetryVersion"); + this.telemetryVersion = telemetryVersion; + return this; + } + // Visible for testing int getMaxExportBatchSize() { return maxExportBatchSize; @@ -172,6 +182,7 @@ public BatchSpanProcessor build() { spanExporter, exportUnsampledSpans, meterProvider, + telemetryVersion, scheduleDelayNanos, maxQueueSize, maxExportBatchSize, diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SimpleSpanProcessor.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SimpleSpanProcessor.java index f543e25353e..7d71f2a04db 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SimpleSpanProcessor.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SimpleSpanProcessor.java @@ -7,8 +7,11 @@ import static java.util.Objects.requireNonNull; +import io.opentelemetry.api.metrics.MeterProvider; import io.opentelemetry.context.Context; import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.common.InternalTelemetryVersion; +import io.opentelemetry.sdk.internal.ComponentId; import io.opentelemetry.sdk.trace.ReadWriteSpan; import io.opentelemetry.sdk.trace.ReadableSpan; import io.opentelemetry.sdk.trace.SpanProcessor; @@ -33,6 +36,8 @@ */ public final class SimpleSpanProcessor implements SpanProcessor { + private static final ComponentId COMPONENT_ID = ComponentId.generateLazy("simple_span_processor"); + private static final Logger logger = Logger.getLogger(SimpleSpanProcessor.class.getName()); private final SpanExporter spanExporter; @@ -40,6 +45,7 @@ public final class SimpleSpanProcessor implements SpanProcessor { private final Set pendingExports = Collections.newSetFromMap(new ConcurrentHashMap<>()); private final AtomicBoolean isShutdown = new AtomicBoolean(false); + private final SpanProcessorMetrics spanProcessorMetrics; private final Object exporterLock = new Object(); @@ -68,9 +74,12 @@ public static SimpleSpanProcessorBuilder builder(SpanExporter exporter) { return new SimpleSpanProcessorBuilder(exporter); } - SimpleSpanProcessor(SpanExporter spanExporter, boolean exportUnsampledSpans) { + SimpleSpanProcessor( + SpanExporter spanExporter, boolean exportUnsampledSpans, MeterProvider meterProvider) { this.spanExporter = requireNonNull(spanExporter, "spanExporter"); this.exportUnsampledSpans = exportUnsampledSpans; + spanProcessorMetrics = + SpanProcessorMetrics.get(InternalTelemetryVersion.LATEST, COMPONENT_ID, meterProvider); } @Override @@ -98,9 +107,12 @@ public void onEnd(ReadableSpan span) { result.whenComplete( () -> { pendingExports.remove(result); + String error = null; if (!result.isSuccess()) { logger.log(Level.FINE, "Exporter failed"); + error = "export_failed"; } + spanProcessorMetrics.finishSpans(1, error); }); } catch (RuntimeException e) { logger.log(Level.WARNING, "Exporter threw an Exception", e); diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SimpleSpanProcessorBuilder.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SimpleSpanProcessorBuilder.java index de9f3f9152a..2681f0437d6 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SimpleSpanProcessorBuilder.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SimpleSpanProcessorBuilder.java @@ -7,6 +7,10 @@ import static java.util.Objects.requireNonNull; +import io.opentelemetry.api.metrics.MeterProvider; +import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder; +import io.opentelemetry.sdk.trace.internal.SdkTracerProviderUtil; + /** * Builder class for {@link SimpleSpanProcessor}. * @@ -14,6 +18,7 @@ */ public final class SimpleSpanProcessorBuilder { private final SpanExporter spanExporter; + private MeterProvider meterProvider = MeterProvider.noop(); private boolean exportUnsampledSpans = false; SimpleSpanProcessorBuilder(SpanExporter spanExporter) { @@ -29,12 +34,26 @@ public SimpleSpanProcessorBuilder setExportUnsampledSpans(boolean exportUnsample return this; } + /** + * Sets the {@link MeterProvider} to use to generate SDK Span + * Metrics. + * + *

This method is experimental so not public. You may reflectively call it using {@link + * SdkTracerProviderUtil#setMeterProvider(SdkTracerProviderBuilder, MeterProvider)}. + */ + SimpleSpanProcessorBuilder setMeterProvider(MeterProvider meterProvider) { + requireNonNull(meterProvider, "meterProvider"); + this.meterProvider = meterProvider; + return this; + } + /** * Returns a new {@link SimpleSpanProcessor} with the configuration of this builder. * * @return a new {@link SimpleSpanProcessor}. */ public SimpleSpanProcessor build() { - return new SimpleSpanProcessor(spanExporter, exportUnsampledSpans); + return new SimpleSpanProcessor(spanExporter, exportUnsampledSpans, meterProvider); } } diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SpanProcessorMetrics.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SpanProcessorMetrics.java new file mode 100644 index 00000000000..b798568fef1 --- /dev/null +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SpanProcessorMetrics.java @@ -0,0 +1,200 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.trace.export; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.LongCounter; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.api.metrics.MeterProvider; +import io.opentelemetry.sdk.common.InternalTelemetryVersion; +import io.opentelemetry.sdk.internal.ComponentId; +import io.opentelemetry.sdk.internal.SemConvAttributes; +import javax.annotation.Nullable; + +/** Metrics exported by span processors. */ +interface SpanProcessorMetrics { + + static SpanProcessorMetrics get( + InternalTelemetryVersion telemetryVersion, + ComponentId componentId, + MeterProvider meterProvider) { + switch (telemetryVersion) { + case LEGACY: + return new LegacyProcessorMetrics(meterProvider); + default: + return new SemConvSpanProcessorMetrics(componentId, meterProvider); + } + } + + /** Records metrics for spans dropped because a queue is full. */ + void dropSpans(int count); + + /** Record metrics for spans processed, possibly with an error. */ + void finishSpans(int count, @Nullable String error); + + /** Registers a metric for processor queue capacity. */ + void buildQueueCapacityMetric(long capacity); + + interface LongCallable { + long get(); + } + + /** Registers a metric for processor queue size. */ + void buildQueueSizeMetric(LongCallable queueSize); + + final class SemConvSpanProcessorMetrics implements SpanProcessorMetrics { + + private final Meter meter; + private final Attributes standardAttrs; + private final Attributes droppedAttrs; + + private final LongCounter processedSpans; + + SemConvSpanProcessorMetrics(ComponentId componentId, MeterProvider meterProvider) { + meter = meterProvider.get("io.opentelemetry.sdk.trace"); + + standardAttrs = + Attributes.of( + SemConvAttributes.OTEL_COMPONENT_TYPE, + componentId.getTypeName(), + SemConvAttributes.OTEL_COMPONENT_NAME, + componentId.getComponentName()); + droppedAttrs = + Attributes.of( + SemConvAttributes.OTEL_COMPONENT_TYPE, + componentId.getTypeName(), + SemConvAttributes.OTEL_COMPONENT_NAME, + componentId.getComponentName(), + SemConvAttributes.ERROR_TYPE, + "queue_full"); + + processedSpans = + meter + .counterBuilder("otel.sdk.processor.span.processed") + .setUnit("span") + .setDescription( + "The number of spans for which the processing has finished, either successful or failed.") + .build(); + } + + @Override + public void dropSpans(int count) { + processedSpans.add(count, droppedAttrs); + } + + /** Record metrics for spans processed, possibly with an error. */ + @Override + public void finishSpans(int count, @Nullable String error) { + if (error == null) { + processedSpans.add(count, standardAttrs); + return; + } + + Attributes attributes = + standardAttrs.toBuilder().put(SemConvAttributes.ERROR_TYPE, error).build(); + processedSpans.add(count, attributes); + } + + /** Registers a metric for processor queue capacity. */ + @Override + public void buildQueueCapacityMetric(long capacity) { + meter + .upDownCounterBuilder("otel.sdk.processor.span.queue.capacity") + .setUnit("span") + .setDescription( + "The maximum number of spans the queue of a given instance of an SDK span processor can hold. ") + .buildWithCallback(m -> m.record(capacity, standardAttrs)); + } + + /** Registers a metric for processor queue size. */ + @Override + public void buildQueueSizeMetric(LongCallable getSize) { + meter + .upDownCounterBuilder("otel.sdk.processor.span.queue.size") + .setUnit("span") + .setDescription( + "The number of spans in the queue of a given instance of an SDK span processor.") + .buildWithCallback(m -> m.record(getSize.get(), standardAttrs)); + } + } + + final class LegacyProcessorMetrics implements SpanProcessorMetrics { + private static final AttributeKey SPAN_PROCESSOR_TYPE_LABEL = + AttributeKey.stringKey("processorType"); + private static final AttributeKey SPAN_PROCESSOR_DROPPED_LABEL = + AttributeKey.booleanKey("dropped"); + // Legacy metrics are only created for batch span processor. + private static final String SPAN_PROCESSOR_TYPE_VALUE = + BatchSpanProcessor.class.getSimpleName(); + + private final Meter meter; + private final Attributes standardAttrs; + private final Attributes droppedAttrs; + + private final LongCounter processedSpans; + + LegacyProcessorMetrics(MeterProvider meterProvider) { + meter = meterProvider.get("io.opentelemetry.sdk.trace"); + + processedSpans = + meter + .counterBuilder("processedSpans") + .setUnit("1") + .setDescription( + "The number of spans processed by the BatchSpanProcessor. " + + "[dropped=true if they were dropped due to high throughput]") + .build(); + + standardAttrs = + Attributes.of( + SPAN_PROCESSOR_TYPE_LABEL, + SPAN_PROCESSOR_TYPE_VALUE, + SPAN_PROCESSOR_DROPPED_LABEL, + false); + droppedAttrs = + Attributes.of( + SPAN_PROCESSOR_TYPE_LABEL, + SPAN_PROCESSOR_TYPE_VALUE, + SPAN_PROCESSOR_DROPPED_LABEL, + true); + } + + /** Records metrics for spans dropped because a queue is full. */ + @Override + public void dropSpans(int count) { + processedSpans.add(count, droppedAttrs); + } + + @Override + public void finishSpans(int count, @Nullable String error) { + // Legacy metrics only record when no error. + if (error != null) { + processedSpans.add(count, standardAttrs); + } + } + + @Override + public void buildQueueCapacityMetric(long capacity) { + // No capacity metric when legacy. + } + + /** Registers a metric for processor queue size. */ + @Override + public void buildQueueSizeMetric(LongCallable queueSize) { + meter + .gaugeBuilder("queueSize") + .ofLongs() + .setDescription("The number of items queued") + .setUnit("1") + .buildWithCallback( + result -> + result.record( + queueSize.get(), + Attributes.of(SPAN_PROCESSOR_TYPE_LABEL, SPAN_PROCESSOR_TYPE_VALUE))); + } + } +} diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/SdkTracerProviderUtil.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/SdkTracerProviderUtil.java index f9ca8fafc56..1c2884fd442 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/SdkTracerProviderUtil.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/SdkTracerProviderUtil.java @@ -5,11 +5,13 @@ package io.opentelemetry.sdk.trace.internal; +import io.opentelemetry.api.metrics.MeterProvider; import io.opentelemetry.sdk.common.InstrumentationScopeInfo; import io.opentelemetry.sdk.internal.ExceptionAttributeResolver; import io.opentelemetry.sdk.internal.ScopeConfigurator; import io.opentelemetry.sdk.trace.SdkTracerProvider; import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder; +import io.opentelemetry.sdk.trace.export.SimpleSpanProcessorBuilder; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.function.Predicate; @@ -89,4 +91,35 @@ public static void setExceptionAttributeResolver( "Error calling setExceptionAttributeResolver on SdkTracerProviderBuilder", e); } } + + /** Reflectively set meter provider to the {@link SdkTracerProviderBuilder}. */ + public static SdkTracerProviderBuilder setMeterProvider( + SdkTracerProviderBuilder sdkTracerProviderBuilder, MeterProvider meterProvider) { + try { + Method method = + SdkTracerProviderBuilder.class.getDeclaredMethod("setMeterProvider", MeterProvider.class); + method.setAccessible(true); + method.invoke(sdkTracerProviderBuilder, meterProvider); + return sdkTracerProviderBuilder; + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + throw new IllegalStateException( + "Error calling setMeterProvider on SdkTracerProviderBuilder", e); + } + } + + /** Reflectively set meter provider to the {@link SdkTracerProviderBuilder}. */ + public static SimpleSpanProcessorBuilder setMeterProvider( + SimpleSpanProcessorBuilder simpleSpanProcessorBuilder, MeterProvider meterProvider) { + try { + Method method = + SimpleSpanProcessorBuilder.class.getDeclaredMethod( + "setMeterProvider", MeterProvider.class); + method.setAccessible(true); + method.invoke(simpleSpanProcessorBuilder, meterProvider); + return simpleSpanProcessorBuilder; + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + throw new IllegalStateException( + "Error calling setMeterProvider on SimpleSpanProcessorBuilder", e); + } + } } diff --git a/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkSpanTest.java b/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkSpanTest.java index c9d546f5486..262bd10a63c 100644 --- a/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkSpanTest.java +++ b/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkSpanTest.java @@ -1040,7 +1040,8 @@ void addLink_FaultIn() { null, null, // exercises the fault-in path 0, - 0); + 0, + () -> {}); SdkSpan linkedSpan = createTestSpan(SpanKind.INTERNAL); span.addLink(linkedSpan.getSpanContext()); @@ -1386,7 +1387,8 @@ void onStartOnEndNotRequired() { spanLimits.getMaxNumberOfAttributes(), spanLimits.getMaxAttributeValueLength()), Collections.emptyList(), 1, - 0); + 0, + () -> {}); verify(spanProcessor, never()).onStart(any(), any()); span.end(); @@ -1524,7 +1526,8 @@ private SdkSpan createTestSpan( attributes, linksCopy, linksCopy.size(), - 0); + 0, + () -> {}); Mockito.verify(spanProcessor, Mockito.times(1)).onStart(Context.root(), span); return span; } @@ -1612,7 +1615,8 @@ void testAsSpanData() { attributesWithCapacity, singletonList(link1), 1, - 0); + 0, + () -> {}); long startEpochNanos = clock.now(); clock.advance(Duration.ofMillis(4)); long firstEventEpochNanos = clock.now(); diff --git a/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkTracerProviderMetricsTest.java b/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkTracerProviderMetricsTest.java new file mode 100644 index 00000000000..843adb4780d --- /dev/null +++ b/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkTracerProviderMetricsTest.java @@ -0,0 +1,988 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.trace; + +import static io.opentelemetry.sdk.internal.SemConvAttributes.ERROR_TYPE; +import static io.opentelemetry.sdk.internal.SemConvAttributes.OTEL_COMPONENT_NAME; +import static io.opentelemetry.sdk.internal.SemConvAttributes.OTEL_COMPONENT_TYPE; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static io.opentelemetry.sdk.trace.SdkTracerMetrics.OTEL_SPAN_PARENT_ORIGIN; +import static io.opentelemetry.sdk.trace.SdkTracerMetrics.OTEL_SPAN_SAMPLING_RESULT; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.MeterProvider; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanContext; +import io.opentelemetry.api.trace.SpanId; +import io.opentelemetry.api.trace.TraceFlags; +import io.opentelemetry.api.trace.TraceId; +import io.opentelemetry.api.trace.TraceState; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.api.trace.TracerProvider; +import io.opentelemetry.context.Context; +import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.common.InternalTelemetryVersion; +import io.opentelemetry.sdk.metrics.SdkMeterProvider; +import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader; +import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter; +import io.opentelemetry.sdk.trace.export.BatchSpanProcessor; +import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; +import io.opentelemetry.sdk.trace.export.SpanExporter; +import io.opentelemetry.sdk.trace.internal.SdkTracerProviderUtil; +import io.opentelemetry.sdk.trace.samplers.Sampler; +import io.opentelemetry.sdk.trace.samplers.SamplingDecision; +import io.opentelemetry.sdk.trace.samplers.SamplingResult; +import java.time.Duration; +import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class SdkTracerProviderMetricsTest { + + @Mock private Sampler sampler; + @Mock private SpanExporter mockExporter; + + @Test + void simple() { + InMemoryMetricReader metricReader = InMemoryMetricReader.create(); + MeterProvider meterProvider = + SdkMeterProvider.builder().registerMetricReader(metricReader).build(); + + InMemorySpanExporter exporter = InMemorySpanExporter.create(); + TracerProvider tracerProvider = + SdkTracerProvider.builder() + .addSpanProcessor( + SdkTracerProviderUtil.setMeterProvider( + SimpleSpanProcessor.builder(exporter), meterProvider) + .build()) + .setMeterProvider(meterProvider) + .setSampler(sampler) + .build(); + + Tracer tracer = tracerProvider.get("test"); + + setSamplingDecision(SamplingDecision.RECORD_AND_SAMPLE); + Span span = tracer.spanBuilder("span").startSpan(); + assertThat(metricReader.collectAllMetrics()) + .satisfiesExactlyInAnyOrder( + m -> + assertThat(m) + .hasName("otel.sdk.span.started") + .hasLongSumSatisfying( + s -> + s.hasPointsSatisfying( + p -> + p.hasValue(1) + .hasAttributes( + Attributes.of( + OTEL_SPAN_PARENT_ORIGIN, + "none", + OTEL_SPAN_SAMPLING_RESULT, + "RECORD_AND_SAMPLE")))), + m -> + assertThat(m) + .hasName("otel.sdk.span.live") + .hasLongSumSatisfying( + s -> + s.hasPointsSatisfying( + p -> + p.hasValue(1) + .hasAttributes( + Attributes.of( + OTEL_SPAN_SAMPLING_RESULT, "RECORD_AND_SAMPLE"))))); + span.end(); + assertThat(metricReader.collectAllMetrics()) + .satisfiesExactlyInAnyOrder( + m -> + assertThat(m) + .hasName("otel.sdk.processor.span.processed") + .hasLongSumSatisfying( + s -> + s.hasPointsSatisfying( + p -> + p.hasValue(1) + .hasAttributes( + Attributes.of( + OTEL_COMPONENT_NAME, + "simple_span_processor/0", + OTEL_COMPONENT_TYPE, + "simple_span_processor")))), + m -> + assertThat(m) + .hasName("otel.sdk.span.started") + .hasLongSumSatisfying( + s -> + s.hasPointsSatisfying( + p -> + p.hasValue(1) + .hasAttributes( + Attributes.of( + OTEL_SPAN_PARENT_ORIGIN, + "none", + OTEL_SPAN_SAMPLING_RESULT, + "RECORD_AND_SAMPLE")))), + m -> + assertThat(m) + .hasName("otel.sdk.span.live") + .hasLongSumSatisfying( + s -> + s.hasPointsSatisfying( + p -> + p.hasValue(0) + .hasAttributes( + Attributes.of( + OTEL_SPAN_SAMPLING_RESULT, "RECORD_AND_SAMPLE"))))); + + setSamplingDecision(SamplingDecision.RECORD_ONLY); + span = tracer.spanBuilder("span").startSpan(); + assertThat(metricReader.collectAllMetrics()) + .satisfiesExactlyInAnyOrder( + m -> + assertThat(m) + .hasName("otel.sdk.processor.span.processed") + .hasLongSumSatisfying( + s -> + s.hasPointsSatisfying( + p -> + p.hasValue(1) + .hasAttributes( + Attributes.of( + OTEL_COMPONENT_NAME, + "simple_span_processor/0", + OTEL_COMPONENT_TYPE, + "simple_span_processor")))), + m -> + assertThat(m) + .hasName("otel.sdk.span.started") + .hasLongSumSatisfying( + s -> + s.hasPointsSatisfying( + p -> + p.hasValue(1) + .hasAttributes( + Attributes.of( + OTEL_SPAN_PARENT_ORIGIN, + "none", + OTEL_SPAN_SAMPLING_RESULT, + "RECORD_AND_SAMPLE")), + p -> + p.hasValue(1) + .hasAttributes( + Attributes.of( + OTEL_SPAN_PARENT_ORIGIN, + "none", + OTEL_SPAN_SAMPLING_RESULT, + "RECORD_ONLY")))), + m -> + assertThat(m) + .hasName("otel.sdk.span.live") + .hasLongSumSatisfying( + s -> + s.hasPointsSatisfying( + p -> + p.hasValue(0) + .hasAttributes( + Attributes.of( + OTEL_SPAN_SAMPLING_RESULT, "RECORD_AND_SAMPLE")), + p -> + p.hasValue(1) + .hasAttributes( + Attributes.of( + OTEL_SPAN_SAMPLING_RESULT, "RECORD_ONLY"))))); + span.end(); + assertThat(metricReader.collectAllMetrics()) + .satisfiesExactlyInAnyOrder( + m -> + assertThat(m) + .hasName("otel.sdk.processor.span.processed") + .hasLongSumSatisfying( + s -> + s.hasPointsSatisfying( + p -> + p.hasValue(1) + .hasAttributes( + Attributes.of( + OTEL_COMPONENT_NAME, + "simple_span_processor/0", + OTEL_COMPONENT_TYPE, + "simple_span_processor")))), + m -> + assertThat(m) + .hasName("otel.sdk.span.started") + .hasLongSumSatisfying( + s -> + s.hasPointsSatisfying( + p -> + p.hasValue(1) + .hasAttributes( + Attributes.of( + OTEL_SPAN_PARENT_ORIGIN, + "none", + OTEL_SPAN_SAMPLING_RESULT, + "RECORD_AND_SAMPLE")), + p -> + p.hasValue(1) + .hasAttributes( + Attributes.of( + OTEL_SPAN_PARENT_ORIGIN, + "none", + OTEL_SPAN_SAMPLING_RESULT, + "RECORD_ONLY")))), + m -> + assertThat(m) + .hasName("otel.sdk.span.live") + .hasLongSumSatisfying( + s -> + s.hasPointsSatisfying( + p -> + p.hasValue(0) + .hasAttributes( + Attributes.of( + OTEL_SPAN_SAMPLING_RESULT, "RECORD_AND_SAMPLE")), + p -> + p.hasValue(0) + .hasAttributes( + Attributes.of( + OTEL_SPAN_SAMPLING_RESULT, "RECORD_ONLY"))))); + + setSamplingDecision(SamplingDecision.DROP); + span = tracer.spanBuilder("span").startSpan(); + assertThat(metricReader.collectAllMetrics()) + .satisfiesExactlyInAnyOrder( + m -> + assertThat(m) + .hasName("otel.sdk.processor.span.processed") + .hasLongSumSatisfying( + s -> + s.hasPointsSatisfying( + p -> + p.hasValue(1) + .hasAttributes( + Attributes.of( + OTEL_COMPONENT_NAME, + "simple_span_processor/0", + OTEL_COMPONENT_TYPE, + "simple_span_processor")))), + m -> + assertThat(m) + .hasName("otel.sdk.span.started") + .hasLongSumSatisfying( + s -> + s.hasPointsSatisfying( + p -> + p.hasValue(1) + .hasAttributes( + Attributes.of( + OTEL_SPAN_PARENT_ORIGIN, + "none", + OTEL_SPAN_SAMPLING_RESULT, + "RECORD_AND_SAMPLE")), + p -> + p.hasValue(1) + .hasAttributes( + Attributes.of( + OTEL_SPAN_PARENT_ORIGIN, + "none", + OTEL_SPAN_SAMPLING_RESULT, + "RECORD_ONLY")), + p -> + p.hasValue(1) + .hasAttributes( + Attributes.of( + OTEL_SPAN_PARENT_ORIGIN, + "none", + OTEL_SPAN_SAMPLING_RESULT, + "DROP")))), + m -> + assertThat(m) + .hasName("otel.sdk.span.live") + .hasLongSumSatisfying( + s -> + s.hasPointsSatisfying( + p -> + p.hasValue(0) + .hasAttributes( + Attributes.of( + OTEL_SPAN_SAMPLING_RESULT, "RECORD_AND_SAMPLE")), + p -> + p.hasValue(0) + .hasAttributes( + Attributes.of( + OTEL_SPAN_SAMPLING_RESULT, "RECORD_ONLY"))))); + span.end(); + assertThat(metricReader.collectAllMetrics()) + .satisfiesExactlyInAnyOrder( + m -> + assertThat(m) + .hasName("otel.sdk.processor.span.processed") + .hasLongSumSatisfying( + s -> + s.hasPointsSatisfying( + p -> + p.hasValue(1) + .hasAttributes( + Attributes.of( + OTEL_COMPONENT_NAME, + "simple_span_processor/0", + OTEL_COMPONENT_TYPE, + "simple_span_processor")))), + m -> + assertThat(m) + .hasName("otel.sdk.span.started") + .hasLongSumSatisfying( + s -> + s.hasPointsSatisfying( + p -> + p.hasValue(1) + .hasAttributes( + Attributes.of( + OTEL_SPAN_PARENT_ORIGIN, + "none", + OTEL_SPAN_SAMPLING_RESULT, + "RECORD_AND_SAMPLE")), + p -> + p.hasValue(1) + .hasAttributes( + Attributes.of( + OTEL_SPAN_PARENT_ORIGIN, + "none", + OTEL_SPAN_SAMPLING_RESULT, + "RECORD_ONLY")), + p -> + p.hasValue(1) + .hasAttributes( + Attributes.of( + OTEL_SPAN_PARENT_ORIGIN, + "none", + OTEL_SPAN_SAMPLING_RESULT, + "DROP")))), + m -> + assertThat(m) + .hasName("otel.sdk.span.live") + .hasLongSumSatisfying( + s -> + s.hasPointsSatisfying( + p -> + p.hasValue(0) + .hasAttributes( + Attributes.of( + OTEL_SPAN_SAMPLING_RESULT, "RECORD_AND_SAMPLE")), + p -> + p.hasValue(0) + .hasAttributes( + Attributes.of( + OTEL_SPAN_SAMPLING_RESULT, "RECORD_ONLY"))))); + + span = + tracer + .spanBuilder("span") + .setParent( + Context.root() + .with( + Span.wrap( + SpanContext.create( + TraceId.fromLongs(1, 2), + SpanId.fromLong(3), + TraceFlags.getDefault(), + TraceState.getDefault())))) + .startSpan(); + assertThat(metricReader.collectAllMetrics()) + .satisfiesExactlyInAnyOrder( + m -> + assertThat(m) + .hasName("otel.sdk.processor.span.processed") + .hasLongSumSatisfying( + s -> + s.hasPointsSatisfying( + p -> + p.hasValue(1) + .hasAttributes( + Attributes.of( + OTEL_COMPONENT_NAME, + "simple_span_processor/0", + OTEL_COMPONENT_TYPE, + "simple_span_processor")))), + m -> + assertThat(m) + .hasName("otel.sdk.span.started") + .hasLongSumSatisfying( + s -> + s.hasPointsSatisfying( + p -> + p.hasValue(1) + .hasAttributes( + Attributes.of( + OTEL_SPAN_PARENT_ORIGIN, + "none", + OTEL_SPAN_SAMPLING_RESULT, + "RECORD_AND_SAMPLE")), + p -> + p.hasValue(1) + .hasAttributes( + Attributes.of( + OTEL_SPAN_PARENT_ORIGIN, + "none", + OTEL_SPAN_SAMPLING_RESULT, + "RECORD_ONLY")), + p -> + p.hasValue(1) + .hasAttributes( + Attributes.of( + OTEL_SPAN_PARENT_ORIGIN, + "none", + OTEL_SPAN_SAMPLING_RESULT, + "DROP")), + p -> + p.hasValue(1) + .hasAttributes( + Attributes.of( + OTEL_SPAN_PARENT_ORIGIN, + "local", + OTEL_SPAN_SAMPLING_RESULT, + "DROP")))), + m -> + assertThat(m) + .hasName("otel.sdk.span.live") + .hasLongSumSatisfying( + s -> + s.hasPointsSatisfying( + p -> + p.hasValue(0) + .hasAttributes( + Attributes.of( + OTEL_SPAN_SAMPLING_RESULT, "RECORD_AND_SAMPLE")), + p -> + p.hasValue(0) + .hasAttributes( + Attributes.of( + OTEL_SPAN_SAMPLING_RESULT, "RECORD_ONLY"))))); + span.end(); + assertThat(metricReader.collectAllMetrics()) + .satisfiesExactlyInAnyOrder( + m -> + assertThat(m) + .hasName("otel.sdk.processor.span.processed") + .hasLongSumSatisfying( + s -> + s.hasPointsSatisfying( + p -> + p.hasValue(1) + .hasAttributes( + Attributes.of( + OTEL_COMPONENT_NAME, + "simple_span_processor/0", + OTEL_COMPONENT_TYPE, + "simple_span_processor")))), + m -> + assertThat(m) + .hasName("otel.sdk.span.started") + .hasLongSumSatisfying( + s -> + s.hasPointsSatisfying( + p -> + p.hasValue(1) + .hasAttributes( + Attributes.of( + OTEL_SPAN_PARENT_ORIGIN, + "none", + OTEL_SPAN_SAMPLING_RESULT, + "RECORD_AND_SAMPLE")), + p -> + p.hasValue(1) + .hasAttributes( + Attributes.of( + OTEL_SPAN_PARENT_ORIGIN, + "none", + OTEL_SPAN_SAMPLING_RESULT, + "RECORD_ONLY")), + p -> + p.hasValue(1) + .hasAttributes( + Attributes.of( + OTEL_SPAN_PARENT_ORIGIN, + "none", + OTEL_SPAN_SAMPLING_RESULT, + "DROP")), + p -> + p.hasValue(1) + .hasAttributes( + Attributes.of( + OTEL_SPAN_PARENT_ORIGIN, + "local", + OTEL_SPAN_SAMPLING_RESULT, + "DROP")))), + m -> + assertThat(m) + .hasName("otel.sdk.span.live") + .hasLongSumSatisfying( + s -> + s.hasPointsSatisfying( + p -> + p.hasValue(0) + .hasAttributes( + Attributes.of( + OTEL_SPAN_SAMPLING_RESULT, "RECORD_AND_SAMPLE")), + p -> + p.hasValue(0) + .hasAttributes( + Attributes.of( + OTEL_SPAN_SAMPLING_RESULT, "RECORD_ONLY"))))); + + setSamplingDecision(SamplingDecision.RECORD_AND_SAMPLE); + span = + tracer + .spanBuilder("span") + .setParent( + Context.root() + .with( + Span.wrap( + SpanContext.createFromRemoteParent( + TraceId.fromLongs(1, 2), + SpanId.fromLong(3), + TraceFlags.getDefault(), + TraceState.getDefault())))) + .startSpan(); + assertThat(metricReader.collectAllMetrics()) + .satisfiesExactlyInAnyOrder( + m -> + assertThat(m) + .hasName("otel.sdk.processor.span.processed") + .hasLongSumSatisfying( + s -> + s.hasPointsSatisfying( + p -> + p.hasValue(1) + .hasAttributes( + Attributes.of( + OTEL_COMPONENT_NAME, + "simple_span_processor/0", + OTEL_COMPONENT_TYPE, + "simple_span_processor")))), + m -> + assertThat(m) + .hasName("otel.sdk.span.started") + .hasLongSumSatisfying( + s -> + s.hasPointsSatisfying( + p -> + p.hasValue(1) + .hasAttributes( + Attributes.of( + OTEL_SPAN_PARENT_ORIGIN, + "none", + OTEL_SPAN_SAMPLING_RESULT, + "RECORD_AND_SAMPLE")), + p -> + p.hasValue(1) + .hasAttributes( + Attributes.of( + OTEL_SPAN_PARENT_ORIGIN, + "remote", + OTEL_SPAN_SAMPLING_RESULT, + "RECORD_AND_SAMPLE")), + p -> + p.hasValue(1) + .hasAttributes( + Attributes.of( + OTEL_SPAN_PARENT_ORIGIN, + "none", + OTEL_SPAN_SAMPLING_RESULT, + "RECORD_ONLY")), + p -> + p.hasValue(1) + .hasAttributes( + Attributes.of( + OTEL_SPAN_PARENT_ORIGIN, + "none", + OTEL_SPAN_SAMPLING_RESULT, + "DROP")), + p -> + p.hasValue(1) + .hasAttributes( + Attributes.of( + OTEL_SPAN_PARENT_ORIGIN, + "local", + OTEL_SPAN_SAMPLING_RESULT, + "DROP")))), + m -> + assertThat(m) + .hasName("otel.sdk.span.live") + .hasLongSumSatisfying( + s -> + s.hasPointsSatisfying( + p -> + p.hasValue(1) + .hasAttributes( + Attributes.of( + OTEL_SPAN_SAMPLING_RESULT, "RECORD_AND_SAMPLE")), + p -> + p.hasValue(0) + .hasAttributes( + Attributes.of( + OTEL_SPAN_SAMPLING_RESULT, "RECORD_ONLY"))))); + span.end(); + assertThat(metricReader.collectAllMetrics()) + .satisfiesExactlyInAnyOrder( + m -> + assertThat(m) + .hasName("otel.sdk.processor.span.processed") + .hasLongSumSatisfying( + s -> + s.hasPointsSatisfying( + p -> + p.hasValue(2) + .hasAttributes( + Attributes.of( + OTEL_COMPONENT_NAME, + "simple_span_processor/0", + OTEL_COMPONENT_TYPE, + "simple_span_processor")))), + m -> + assertThat(m) + .hasName("otel.sdk.span.started") + .hasLongSumSatisfying( + s -> + s.hasPointsSatisfying( + p -> + p.hasValue(1) + .hasAttributes( + Attributes.of( + OTEL_SPAN_PARENT_ORIGIN, + "none", + OTEL_SPAN_SAMPLING_RESULT, + "RECORD_AND_SAMPLE")), + p -> + p.hasValue(1) + .hasAttributes( + Attributes.of( + OTEL_SPAN_PARENT_ORIGIN, + "remote", + OTEL_SPAN_SAMPLING_RESULT, + "RECORD_AND_SAMPLE")), + p -> + p.hasValue(1) + .hasAttributes( + Attributes.of( + OTEL_SPAN_PARENT_ORIGIN, + "none", + OTEL_SPAN_SAMPLING_RESULT, + "RECORD_ONLY")), + p -> + p.hasValue(1) + .hasAttributes( + Attributes.of( + OTEL_SPAN_PARENT_ORIGIN, + "none", + OTEL_SPAN_SAMPLING_RESULT, + "DROP")), + p -> + p.hasValue(1) + .hasAttributes( + Attributes.of( + OTEL_SPAN_PARENT_ORIGIN, + "local", + OTEL_SPAN_SAMPLING_RESULT, + "DROP")))), + m -> + assertThat(m) + .hasName("otel.sdk.span.live") + .hasLongSumSatisfying( + s -> + s.hasPointsSatisfying( + p -> + p.hasValue(0) + .hasAttributes( + Attributes.of( + OTEL_SPAN_SAMPLING_RESULT, "RECORD_AND_SAMPLE")), + p -> + p.hasValue(0) + .hasAttributes( + Attributes.of( + OTEL_SPAN_SAMPLING_RESULT, "RECORD_ONLY"))))); + } + + @Test + void batch() throws Exception { + InMemoryMetricReader metricReader = InMemoryMetricReader.create(); + MeterProvider meterProvider = + SdkMeterProvider.builder().registerMetricReader(metricReader).build(); + + BatchSpanProcessor processor = + BatchSpanProcessor.builder(mockExporter) + .setMaxQueueSize(1) + // Manually flush + .setScheduleDelay(Duration.ofDays(1)) + .setInternalTelemetryVersion(InternalTelemetryVersion.LATEST) + .setMeterProvider(meterProvider) + .build(); + TracerProvider tracerProvider = + SdkTracerProvider.builder() + .addSpanProcessor(processor) + .setMeterProvider(meterProvider) + .setSampler(Sampler.alwaysOn()) + .build(); + + Tracer tracer = tracerProvider.get("test"); + + CompletableResultCode result1 = new CompletableResultCode(); + CompletableResultCode result2 = new CompletableResultCode(); + when(mockExporter.export(any())).thenReturn(result1).thenReturn(result2); + + // Will immediately be processed. + tracer.spanBuilder("span").startSpan().end(); + Thread.sleep(500); // give time to start processing a batch of size 1 + // We haven't completed the export so this span is queued. + tracer.spanBuilder("span").startSpan().end(); + // Queue is full, this span is dropped. + tracer.spanBuilder("span").startSpan().end(); + + assertThat(metricReader.collectAllMetrics()) + .satisfiesExactlyInAnyOrder( + m -> + assertThat(m) + .hasName("otel.sdk.processor.span.queue.capacity") + .hasLongSumSatisfying( + s -> + s.hasPointsSatisfying( + p -> + p.hasValue(1) + .hasAttributes( + Attributes.of( + OTEL_COMPONENT_NAME, + "batching_span_processor/0", + OTEL_COMPONENT_TYPE, + "batching_span_processor")))), + m -> + assertThat(m) + .hasName("otel.sdk.processor.span.queue.size") + .hasLongSumSatisfying( + s -> + s.hasPointsSatisfying( + p -> + p.hasValue(1) + .hasAttributes( + Attributes.of( + OTEL_COMPONENT_NAME, + "batching_span_processor/0", + OTEL_COMPONENT_TYPE, + "batching_span_processor")))), + m -> + assertThat(m) + .hasName("otel.sdk.processor.span.processed") + .hasLongSumSatisfying( + s -> + s.hasPointsSatisfying( + p -> + p.hasValue(1) + .hasAttributes( + Attributes.of( + OTEL_COMPONENT_NAME, + "batching_span_processor/0", + OTEL_COMPONENT_TYPE, + "batching_span_processor", + ERROR_TYPE, + "queue_full")))), + m -> + assertThat(m) + .hasName("otel.sdk.span.started") + .hasLongSumSatisfying( + s -> + s.hasPointsSatisfying( + p -> + p.hasValue(3) + .hasAttributes( + Attributes.of( + OTEL_SPAN_PARENT_ORIGIN, + "none", + OTEL_SPAN_SAMPLING_RESULT, + "RECORD_AND_SAMPLE")))), + m -> + assertThat(m) + .hasName("otel.sdk.span.live") + .hasLongSumSatisfying( + s -> + s.hasPointsSatisfying( + p -> + p.hasValue(0) + .hasAttributes( + Attributes.of( + OTEL_SPAN_SAMPLING_RESULT, "RECORD_AND_SAMPLE"))))); + + result1.succeed(); + result2.fail(); + processor.forceFlush().join(1, TimeUnit.SECONDS); + + assertThat(metricReader.collectAllMetrics()) + .satisfiesExactlyInAnyOrder( + m -> + assertThat(m) + .hasName("otel.sdk.processor.span.queue.capacity") + .hasLongSumSatisfying( + s -> + s.hasPointsSatisfying( + p -> + p.hasValue(1) + .hasAttributes( + Attributes.of( + OTEL_COMPONENT_NAME, + "batching_span_processor/0", + OTEL_COMPONENT_TYPE, + "batching_span_processor")))), + m -> + assertThat(m) + .hasName("otel.sdk.processor.span.queue.size") + .hasLongSumSatisfying( + s -> + s.hasPointsSatisfying( + p -> + p.hasValue(0) + .hasAttributes( + Attributes.of( + OTEL_COMPONENT_NAME, + "batching_span_processor/0", + OTEL_COMPONENT_TYPE, + "batching_span_processor")))), + m -> + assertThat(m) + .hasName("otel.sdk.processor.span.processed") + .hasLongSumSatisfying( + s -> + s.hasPointsSatisfying( + p -> + p.hasValue(1) + .hasAttributes( + Attributes.of( + OTEL_COMPONENT_NAME, + "batching_span_processor/0", + OTEL_COMPONENT_TYPE, + "batching_span_processor")), + p -> + p.hasValue(1) + .hasAttributes( + Attributes.of( + OTEL_COMPONENT_NAME, + "batching_span_processor/0", + OTEL_COMPONENT_TYPE, + "batching_span_processor", + ERROR_TYPE, + "export_failed")), + p -> + p.hasValue(1) + .hasAttributes( + Attributes.of( + OTEL_COMPONENT_NAME, + "batching_span_processor/0", + OTEL_COMPONENT_TYPE, + "batching_span_processor", + ERROR_TYPE, + "queue_full")))), + m -> + assertThat(m) + .hasName("otel.sdk.span.started") + .hasLongSumSatisfying( + s -> + s.hasPointsSatisfying( + p -> + p.hasValue(3) + .hasAttributes( + Attributes.of( + OTEL_SPAN_PARENT_ORIGIN, + "none", + OTEL_SPAN_SAMPLING_RESULT, + "RECORD_AND_SAMPLE")))), + m -> + assertThat(m) + .hasName("otel.sdk.span.live") + .hasLongSumSatisfying( + s -> + s.hasPointsSatisfying( + p -> + p.hasValue(0) + .hasAttributes( + Attributes.of( + OTEL_SPAN_SAMPLING_RESULT, "RECORD_AND_SAMPLE"))))); + + when(mockExporter.shutdown()).thenReturn(CompletableResultCode.ofSuccess()); + processor.shutdown(); + } + + @Test + void simpleExportError() { + InMemoryMetricReader metricReader = InMemoryMetricReader.create(); + MeterProvider meterProvider = + SdkMeterProvider.builder().registerMetricReader(metricReader).build(); + + TracerProvider tracerProvider = + SdkTracerProvider.builder() + .addSpanProcessor( + SdkTracerProviderUtil.setMeterProvider( + SimpleSpanProcessor.builder(mockExporter), meterProvider) + .build()) + .setMeterProvider(meterProvider) + .setSampler(Sampler.alwaysOn()) + .build(); + + Tracer tracer = tracerProvider.get("test"); + + when(mockExporter.export(any())).thenReturn(CompletableResultCode.ofFailure()); + + tracer.spanBuilder("span").startSpan().end(); + + assertThat(metricReader.collectAllMetrics()) + .satisfiesExactlyInAnyOrder( + m -> + assertThat(m) + .hasName("otel.sdk.processor.span.processed") + .hasLongSumSatisfying( + s -> + s.hasPointsSatisfying( + p -> + p.hasValue(1) + .hasAttributes( + Attributes.of( + OTEL_COMPONENT_NAME, + "simple_span_processor/0", + OTEL_COMPONENT_TYPE, + "simple_span_processor", + ERROR_TYPE, + "export_failed")))), + m -> + assertThat(m) + .hasName("otel.sdk.span.started") + .hasLongSumSatisfying( + s -> + s.hasPointsSatisfying( + p -> + p.hasValue(1) + .hasAttributes( + Attributes.of( + OTEL_SPAN_PARENT_ORIGIN, + "none", + OTEL_SPAN_SAMPLING_RESULT, + "RECORD_AND_SAMPLE")))), + m -> + assertThat(m) + .hasName("otel.sdk.span.live") + .hasLongSumSatisfying( + s -> + s.hasPointsSatisfying( + p -> + p.hasValue(0) + .hasAttributes( + Attributes.of( + OTEL_SPAN_SAMPLING_RESULT, "RECORD_AND_SAMPLE"))))); + } + + private void setSamplingDecision(SamplingDecision decision) { + when(sampler.shouldSample(any(), any(), any(), any(), any(), any())) + .thenReturn(SamplingResult.create(decision)); + } +} From 6dc848c43d9d903f2d8032234b661f723b80e14f Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Fri, 5 Dec 2025 13:44:40 +0900 Subject: [PATCH 02/10] Non-public classes --- .../opentelemetry-sdk-trace.txt | 16 -- .../sdk/trace/SdkTracerMetrics.java | 3 - .../export/LegacySpanProcessorMetrics.java | 89 ++++++++++ .../export/SemConvSpanProcessorMetrics.java | 95 +++++++++++ .../trace/export/SpanProcessorMetrics.java | 159 +----------------- 5 files changed, 185 insertions(+), 177 deletions(-) create mode 100644 sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/LegacySpanProcessorMetrics.java create mode 100644 sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SemConvSpanProcessorMetrics.java diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-trace.txt b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-trace.txt index 1c1e8e9a7e2..f46fcbc2d8b 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-trace.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-trace.txt @@ -2,23 +2,7 @@ Comparing source compatibility of opentelemetry-sdk-trace-1.57.0-SNAPSHOT.jar ag *** MODIFIED CLASS: PUBLIC FINAL io.opentelemetry.sdk.trace.export.BatchSpanProcessorBuilder (not serializable) === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.trace.export.BatchSpanProcessorBuilder setInternalTelemetryVersion(io.opentelemetry.sdk.common.InternalTelemetryVersion) -+++ NEW CLASS: PUBLIC(+) STATIC(+) FINAL(+) io.opentelemetry.sdk.trace.export.SpanProcessorMetrics$LegacyProcessorMetrics (not serializable) - +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. - +++ NEW INTERFACE: io.opentelemetry.sdk.trace.export.SpanProcessorMetrics - +++ NEW SUPERCLASS: java.lang.Object - +++ NEW METHOD: PUBLIC(+) void buildQueueCapacityMetric(long) - +++ NEW METHOD: PUBLIC(+) void buildQueueSizeMetric(io.opentelemetry.sdk.trace.export.SpanProcessorMetrics$LongCallable) - +++ NEW METHOD: PUBLIC(+) void dropSpans(int) - +++ NEW METHOD: PUBLIC(+) void finishSpans(int, java.lang.String) +++ NEW INTERFACE: PUBLIC(+) ABSTRACT(+) STATIC(+) io.opentelemetry.sdk.trace.export.SpanProcessorMetrics$LongCallable (not serializable) +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. +++ NEW SUPERCLASS: java.lang.Object +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) long get() -+++ NEW CLASS: PUBLIC(+) STATIC(+) FINAL(+) io.opentelemetry.sdk.trace.export.SpanProcessorMetrics$SemConvSpanProcessorMetrics (not serializable) - +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. - +++ NEW INTERFACE: io.opentelemetry.sdk.trace.export.SpanProcessorMetrics - +++ NEW SUPERCLASS: java.lang.Object - +++ NEW METHOD: PUBLIC(+) void buildQueueCapacityMetric(long) - +++ NEW METHOD: PUBLIC(+) void buildQueueSizeMetric(io.opentelemetry.sdk.trace.export.SpanProcessorMetrics$LongCallable) - +++ NEW METHOD: PUBLIC(+) void dropSpans(int) - +++ NEW METHOD: PUBLIC(+) void finishSpans(int, java.lang.String) diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerMetrics.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerMetrics.java index 058e7b8d6c1..4fe65b63612 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerMetrics.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerMetrics.java @@ -20,9 +20,6 @@ * SDK metrics exported for started and ended spans as defined in the semantic * conventions. - * - *

This class is internal and is hence not for public use. Its APIs are unstable and can change - * at any time. */ final class SdkTracerMetrics { diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/LegacySpanProcessorMetrics.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/LegacySpanProcessorMetrics.java new file mode 100644 index 00000000000..a8395c37348 --- /dev/null +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/LegacySpanProcessorMetrics.java @@ -0,0 +1,89 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.trace.export; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.LongCounter; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.api.metrics.MeterProvider; +import javax.annotation.Nullable; + +/** Span processor metrics defined before they were standardized in semconv. */ +final class LegacySpanProcessorMetrics implements SpanProcessorMetrics { + private static final AttributeKey SPAN_PROCESSOR_TYPE_LABEL = + AttributeKey.stringKey("processorType"); + private static final AttributeKey SPAN_PROCESSOR_DROPPED_LABEL = + AttributeKey.booleanKey("dropped"); + // Legacy metrics are only created for batch span processor. + private static final String SPAN_PROCESSOR_TYPE_VALUE = BatchSpanProcessor.class.getSimpleName(); + + private final Meter meter; + private final Attributes standardAttrs; + private final Attributes droppedAttrs; + + private final LongCounter processedSpans; + + LegacySpanProcessorMetrics(MeterProvider meterProvider) { + meter = meterProvider.get("io.opentelemetry.sdk.trace"); + + processedSpans = + meter + .counterBuilder("processedSpans") + .setUnit("1") + .setDescription( + "The number of spans processed by the BatchSpanProcessor. " + + "[dropped=true if they were dropped due to high throughput]") + .build(); + + standardAttrs = + Attributes.of( + SPAN_PROCESSOR_TYPE_LABEL, + SPAN_PROCESSOR_TYPE_VALUE, + SPAN_PROCESSOR_DROPPED_LABEL, + false); + droppedAttrs = + Attributes.of( + SPAN_PROCESSOR_TYPE_LABEL, + SPAN_PROCESSOR_TYPE_VALUE, + SPAN_PROCESSOR_DROPPED_LABEL, + true); + } + + /** Records metrics for spans dropped because a queue is full. */ + @Override + public void dropSpans(int count) { + processedSpans.add(count, droppedAttrs); + } + + @Override + public void finishSpans(int count, @Nullable String error) { + // Legacy metrics only record when no error. + if (error != null) { + processedSpans.add(count, standardAttrs); + } + } + + @Override + public void buildQueueCapacityMetric(long capacity) { + // No capacity metric when legacy. + } + + /** Registers a metric for processor queue size. */ + @Override + public void buildQueueSizeMetric(LongCallable queueSize) { + meter + .gaugeBuilder("queueSize") + .ofLongs() + .setDescription("The number of items queued") + .setUnit("1") + .buildWithCallback( + result -> + result.record( + queueSize.get(), + Attributes.of(SPAN_PROCESSOR_TYPE_LABEL, SPAN_PROCESSOR_TYPE_VALUE))); + } +} diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SemConvSpanProcessorMetrics.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SemConvSpanProcessorMetrics.java new file mode 100644 index 00000000000..4f7748e2a3f --- /dev/null +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SemConvSpanProcessorMetrics.java @@ -0,0 +1,95 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.trace.export; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.LongCounter; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.api.metrics.MeterProvider; +import io.opentelemetry.sdk.internal.ComponentId; +import io.opentelemetry.sdk.internal.SemConvAttributes; +import javax.annotation.Nullable; + +/** + * SDK metrics exported for span processors as defined in the semantic + * conventions. + */ +final class SemConvSpanProcessorMetrics implements SpanProcessorMetrics { + + private final Meter meter; + private final Attributes standardAttrs; + private final Attributes droppedAttrs; + + private final LongCounter processedSpans; + + SemConvSpanProcessorMetrics(ComponentId componentId, MeterProvider meterProvider) { + meter = meterProvider.get("io.opentelemetry.sdk.trace"); + + standardAttrs = + Attributes.of( + SemConvAttributes.OTEL_COMPONENT_TYPE, + componentId.getTypeName(), + SemConvAttributes.OTEL_COMPONENT_NAME, + componentId.getComponentName()); + droppedAttrs = + Attributes.of( + SemConvAttributes.OTEL_COMPONENT_TYPE, + componentId.getTypeName(), + SemConvAttributes.OTEL_COMPONENT_NAME, + componentId.getComponentName(), + SemConvAttributes.ERROR_TYPE, + "queue_full"); + + processedSpans = + meter + .counterBuilder("otel.sdk.processor.span.processed") + .setUnit("span") + .setDescription( + "The number of spans for which the processing has finished, either successful or failed.") + .build(); + } + + @Override + public void dropSpans(int count) { + processedSpans.add(count, droppedAttrs); + } + + /** Record metrics for spans processed, possibly with an error. */ + @Override + public void finishSpans(int count, @Nullable String error) { + if (error == null) { + processedSpans.add(count, standardAttrs); + return; + } + + Attributes attributes = + standardAttrs.toBuilder().put(SemConvAttributes.ERROR_TYPE, error).build(); + processedSpans.add(count, attributes); + } + + /** Registers a metric for processor queue capacity. */ + @Override + public void buildQueueCapacityMetric(long capacity) { + meter + .upDownCounterBuilder("otel.sdk.processor.span.queue.capacity") + .setUnit("span") + .setDescription( + "The maximum number of spans the queue of a given instance of an SDK span processor can hold. ") + .buildWithCallback(m -> m.record(capacity, standardAttrs)); + } + + /** Registers a metric for processor queue size. */ + @Override + public void buildQueueSizeMetric(LongCallable getSize) { + meter + .upDownCounterBuilder("otel.sdk.processor.span.queue.size") + .setUnit("span") + .setDescription( + "The number of spans in the queue of a given instance of an SDK span processor.") + .buildWithCallback(m -> m.record(getSize.get(), standardAttrs)); + } +} diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SpanProcessorMetrics.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SpanProcessorMetrics.java index b798568fef1..94126fb5e99 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SpanProcessorMetrics.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SpanProcessorMetrics.java @@ -5,14 +5,9 @@ package io.opentelemetry.sdk.trace.export; -import io.opentelemetry.api.common.AttributeKey; -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.metrics.LongCounter; -import io.opentelemetry.api.metrics.Meter; import io.opentelemetry.api.metrics.MeterProvider; import io.opentelemetry.sdk.common.InternalTelemetryVersion; import io.opentelemetry.sdk.internal.ComponentId; -import io.opentelemetry.sdk.internal.SemConvAttributes; import javax.annotation.Nullable; /** Metrics exported by span processors. */ @@ -24,7 +19,7 @@ static SpanProcessorMetrics get( MeterProvider meterProvider) { switch (telemetryVersion) { case LEGACY: - return new LegacyProcessorMetrics(meterProvider); + return new LegacySpanProcessorMetrics(meterProvider); default: return new SemConvSpanProcessorMetrics(componentId, meterProvider); } @@ -45,156 +40,4 @@ interface LongCallable { /** Registers a metric for processor queue size. */ void buildQueueSizeMetric(LongCallable queueSize); - - final class SemConvSpanProcessorMetrics implements SpanProcessorMetrics { - - private final Meter meter; - private final Attributes standardAttrs; - private final Attributes droppedAttrs; - - private final LongCounter processedSpans; - - SemConvSpanProcessorMetrics(ComponentId componentId, MeterProvider meterProvider) { - meter = meterProvider.get("io.opentelemetry.sdk.trace"); - - standardAttrs = - Attributes.of( - SemConvAttributes.OTEL_COMPONENT_TYPE, - componentId.getTypeName(), - SemConvAttributes.OTEL_COMPONENT_NAME, - componentId.getComponentName()); - droppedAttrs = - Attributes.of( - SemConvAttributes.OTEL_COMPONENT_TYPE, - componentId.getTypeName(), - SemConvAttributes.OTEL_COMPONENT_NAME, - componentId.getComponentName(), - SemConvAttributes.ERROR_TYPE, - "queue_full"); - - processedSpans = - meter - .counterBuilder("otel.sdk.processor.span.processed") - .setUnit("span") - .setDescription( - "The number of spans for which the processing has finished, either successful or failed.") - .build(); - } - - @Override - public void dropSpans(int count) { - processedSpans.add(count, droppedAttrs); - } - - /** Record metrics for spans processed, possibly with an error. */ - @Override - public void finishSpans(int count, @Nullable String error) { - if (error == null) { - processedSpans.add(count, standardAttrs); - return; - } - - Attributes attributes = - standardAttrs.toBuilder().put(SemConvAttributes.ERROR_TYPE, error).build(); - processedSpans.add(count, attributes); - } - - /** Registers a metric for processor queue capacity. */ - @Override - public void buildQueueCapacityMetric(long capacity) { - meter - .upDownCounterBuilder("otel.sdk.processor.span.queue.capacity") - .setUnit("span") - .setDescription( - "The maximum number of spans the queue of a given instance of an SDK span processor can hold. ") - .buildWithCallback(m -> m.record(capacity, standardAttrs)); - } - - /** Registers a metric for processor queue size. */ - @Override - public void buildQueueSizeMetric(LongCallable getSize) { - meter - .upDownCounterBuilder("otel.sdk.processor.span.queue.size") - .setUnit("span") - .setDescription( - "The number of spans in the queue of a given instance of an SDK span processor.") - .buildWithCallback(m -> m.record(getSize.get(), standardAttrs)); - } - } - - final class LegacyProcessorMetrics implements SpanProcessorMetrics { - private static final AttributeKey SPAN_PROCESSOR_TYPE_LABEL = - AttributeKey.stringKey("processorType"); - private static final AttributeKey SPAN_PROCESSOR_DROPPED_LABEL = - AttributeKey.booleanKey("dropped"); - // Legacy metrics are only created for batch span processor. - private static final String SPAN_PROCESSOR_TYPE_VALUE = - BatchSpanProcessor.class.getSimpleName(); - - private final Meter meter; - private final Attributes standardAttrs; - private final Attributes droppedAttrs; - - private final LongCounter processedSpans; - - LegacyProcessorMetrics(MeterProvider meterProvider) { - meter = meterProvider.get("io.opentelemetry.sdk.trace"); - - processedSpans = - meter - .counterBuilder("processedSpans") - .setUnit("1") - .setDescription( - "The number of spans processed by the BatchSpanProcessor. " - + "[dropped=true if they were dropped due to high throughput]") - .build(); - - standardAttrs = - Attributes.of( - SPAN_PROCESSOR_TYPE_LABEL, - SPAN_PROCESSOR_TYPE_VALUE, - SPAN_PROCESSOR_DROPPED_LABEL, - false); - droppedAttrs = - Attributes.of( - SPAN_PROCESSOR_TYPE_LABEL, - SPAN_PROCESSOR_TYPE_VALUE, - SPAN_PROCESSOR_DROPPED_LABEL, - true); - } - - /** Records metrics for spans dropped because a queue is full. */ - @Override - public void dropSpans(int count) { - processedSpans.add(count, droppedAttrs); - } - - @Override - public void finishSpans(int count, @Nullable String error) { - // Legacy metrics only record when no error. - if (error != null) { - processedSpans.add(count, standardAttrs); - } - } - - @Override - public void buildQueueCapacityMetric(long capacity) { - // No capacity metric when legacy. - } - - /** Registers a metric for processor queue size. */ - @Override - public void buildQueueSizeMetric(LongCallable queueSize) { - meter - .gaugeBuilder("queueSize") - .ofLongs() - .setDescription("The number of items queued") - .setUnit("1") - .buildWithCallback( - result -> - result.record( - queueSize.get(), - Attributes.of(SPAN_PROCESSOR_TYPE_LABEL, SPAN_PROCESSOR_TYPE_VALUE))); - } - } } From a8e12abbd137b626114a6f3b9384842be26141f5 Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Thu, 11 Dec 2025 11:51:58 +0900 Subject: [PATCH 03/10] Rename variable --- .../main/java/io/opentelemetry/sdk/trace/SdkSpan.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkSpan.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkSpan.java index ce9a46cc8b5..ac55e01c8a6 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkSpan.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkSpan.java @@ -61,8 +61,8 @@ final class SdkSpan implements ReadWriteSpan { private final InstrumentationScopeInfo instrumentationScopeInfo; // The start time of the span. private final long startEpochNanos; - // Callback to run when span ends. - private final Runnable onEnd; + // Callback to run when span ends to record metrics. + private final Runnable recordEndMetrics; // Lock used to internally guard the mutable state of this instance private final Object lock = new Object(); @@ -136,7 +136,7 @@ private SdkSpan( @Nullable List links, int totalRecordedLinks, long startEpochNanos, - Runnable onEnd) { + Runnable recordEndMetrics) { this.context = context; this.instrumentationScopeInfo = instrumentationScopeInfo; this.parentSpanContext = parentSpanContext; @@ -152,7 +152,7 @@ private SdkSpan( this.startEpochNanos = startEpochNanos; this.attributes = attributes; this.spanLimits = spanLimits; - this.onEnd = onEnd; + this.recordEndMetrics = recordEndMetrics; } /** @@ -565,7 +565,7 @@ private void endInternal(long endEpochNanos) { spanEndingThread = Thread.currentThread(); hasEnded = EndState.ENDING; } - onEnd.run(); + recordEndMetrics.run(); if (spanProcessor instanceof ExtendedSpanProcessor) { ExtendedSpanProcessor extendedSpanProcessor = (ExtendedSpanProcessor) spanProcessor; if (extendedSpanProcessor.isOnEndingRequired()) { From abd98dfa742571077e569944465ed4afad9994df Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Thu, 11 Dec 2025 12:24:36 +0900 Subject: [PATCH 04/10] Fix merge --- .../apidiffs/current_vs_latest/opentelemetry-sdk-trace.txt | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-trace.txt b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-trace.txt index 93e40e1a0f5..e3518a3e492 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-trace.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-trace.txt @@ -1,5 +1,4 @@ -<<<<<<< HEAD -Comparing source compatibility of opentelemetry-sdk-trace-1.57.0-SNAPSHOT.jar against opentelemetry-sdk-trace-1.56.0.jar +Comparing source compatibility of opentelemetry-sdk-trace-1.58.0-SNAPSHOT.jar against opentelemetry-sdk-trace-1.57.0.jar *** MODIFIED CLASS: PUBLIC FINAL io.opentelemetry.sdk.trace.export.BatchSpanProcessorBuilder (not serializable) === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.trace.export.BatchSpanProcessorBuilder setInternalTelemetryVersion(io.opentelemetry.sdk.common.InternalTelemetryVersion) @@ -7,7 +6,3 @@ Comparing source compatibility of opentelemetry-sdk-trace-1.57.0-SNAPSHOT.jar ag +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. +++ NEW SUPERCLASS: java.lang.Object +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) long get() -======= -Comparing source compatibility of opentelemetry-sdk-trace-1.58.0-SNAPSHOT.jar against opentelemetry-sdk-trace-1.57.0.jar -No changes. ->>>>>>> 0e646e849045bc912c4ca79b7433233b756dafc0 From 3d6023132808eec84e5e1a63af902b28b947b618 Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Thu, 11 Dec 2025 12:26:12 +0900 Subject: [PATCH 05/10] Make shutdown mock optional --- .../opentelemetry/sdk/trace/SdkTracerProviderMetricsTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkTracerProviderMetricsTest.java b/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkTracerProviderMetricsTest.java index 843adb4780d..997728bb23e 100644 --- a/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkTracerProviderMetricsTest.java +++ b/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkTracerProviderMetricsTest.java @@ -12,6 +12,7 @@ import static io.opentelemetry.sdk.trace.SdkTracerMetrics.OTEL_SPAN_PARENT_ORIGIN; import static io.opentelemetry.sdk.trace.SdkTracerMetrics.OTEL_SPAN_SAMPLING_RESULT; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.when; import io.opentelemetry.api.common.Attributes; @@ -910,7 +911,7 @@ void batch() throws Exception { Attributes.of( OTEL_SPAN_SAMPLING_RESULT, "RECORD_AND_SAMPLE"))))); - when(mockExporter.shutdown()).thenReturn(CompletableResultCode.ofSuccess()); + lenient().when(mockExporter.shutdown()).thenReturn(CompletableResultCode.ofSuccess()); processor.shutdown(); } From 5c5e6ea4e5bdc5aa809b54f166cb9ac0d6d1a520 Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Fri, 12 Dec 2025 00:26:23 +0900 Subject: [PATCH 06/10] Overload --- .../src/main/java/io/opentelemetry/sdk/trace/SdkSpan.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkSpan.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkSpan.java index ac55e01c8a6..5c7acd36c6f 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkSpan.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkSpan.java @@ -168,7 +168,7 @@ private SdkSpan( * @param resource the resource associated with this span. * @param attributes the attributes set during span creation. * @param links the links set during span creation, may be truncated. The list MUST be immutable. - * @param onEnd a {@link Runnable} to run when the span is ended. + * @param recordEndMetrics a {@link Runnable} to run when the span is ended to record metrics. * @return a new and started span. */ static SdkSpan startSpan( @@ -187,7 +187,7 @@ static SdkSpan startSpan( @Nullable List links, int totalRecordedLinks, long userStartEpochNanos, - Runnable onEnd) { + Runnable recordEndMetrics) { boolean createdAnchoredClock; AnchoredClock clock; if (parentSpan instanceof SdkSpan) { @@ -227,7 +227,7 @@ static SdkSpan startSpan( links, totalRecordedLinks, startEpochNanos, - onEnd); + recordEndMetrics); // Call onStart here instead of calling in the constructor to make sure the span is completely // initialized. if (spanProcessor.isStartRequired()) { From af0de7afa95afa38d97f0ff7447dac5b94899970 Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Fri, 12 Dec 2025 11:45:00 +0900 Subject: [PATCH 07/10] Cleanups --- .../opentelemetry-sdk-trace.txt | 10 +- .../TracerProviderConfiguration.java | 7 +- .../fileconfig/SpanProcessorFactory.java | 3 +- .../sdk/trace/ExtendedSdkSpanBuilder.java | 5 +- .../sdk/trace/ExtendedSdkTracer.java | 5 +- .../sdk/trace/IncubatingUtil.java | 11 +- .../sdk/trace/SdkSpanBuilder.java | 7 +- .../io/opentelemetry/sdk/trace/SdkTracer.java | 25 +--- .../sdk/trace/SdkTracerMetrics.java | 133 +++++++++++++++--- .../sdk/trace/SdkTracerProvider.java | 7 +- .../sdk/trace/SdkTracerProviderBuilder.java | 5 +- .../sdk/trace/TracerSharedState.java | 9 +- .../sdk/trace/export/BatchSpanProcessor.java | 6 +- .../export/LegacySpanProcessorMetrics.java | 2 - .../sdk/trace/export/LongCallable.java | 13 ++ .../export/SimpleSpanProcessorBuilder.java | 7 +- .../trace/export/SpanProcessorMetrics.java | 4 - .../trace/internal/SdkTracerProviderUtil.java | 33 ----- .../trace/SdkTracerProviderMetricsTest.java | 9 +- 19 files changed, 169 insertions(+), 132 deletions(-) create mode 100644 sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/LongCallable.java diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-trace.txt b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-trace.txt index e3518a3e492..9a47d73b9c9 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-trace.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-trace.txt @@ -2,7 +2,9 @@ Comparing source compatibility of opentelemetry-sdk-trace-1.58.0-SNAPSHOT.jar ag *** MODIFIED CLASS: PUBLIC FINAL io.opentelemetry.sdk.trace.export.BatchSpanProcessorBuilder (not serializable) === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.trace.export.BatchSpanProcessorBuilder setInternalTelemetryVersion(io.opentelemetry.sdk.common.InternalTelemetryVersion) -+++ NEW INTERFACE: PUBLIC(+) ABSTRACT(+) STATIC(+) io.opentelemetry.sdk.trace.export.SpanProcessorMetrics$LongCallable (not serializable) - +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. - +++ NEW SUPERCLASS: java.lang.Object - +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) long get() +*** MODIFIED CLASS: PUBLIC FINAL io.opentelemetry.sdk.trace.export.SimpleSpanProcessorBuilder (not serializable) + === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.trace.export.SimpleSpanProcessorBuilder setMeterProvider(io.opentelemetry.api.metrics.MeterProvider) +*** MODIFIED CLASS: PUBLIC FINAL io.opentelemetry.sdk.trace.SdkTracerProviderBuilder (not serializable) + === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.trace.SdkTracerProviderBuilder setMeterProvider(io.opentelemetry.api.metrics.MeterProvider) diff --git a/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/TracerProviderConfiguration.java b/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/TracerProviderConfiguration.java index 1265893dd93..2e55307c8f0 100644 --- a/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/TracerProviderConfiguration.java +++ b/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/TracerProviderConfiguration.java @@ -19,7 +19,6 @@ import io.opentelemetry.sdk.trace.export.BatchSpanProcessorBuilder; import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; import io.opentelemetry.sdk.trace.export.SpanExporter; -import io.opentelemetry.sdk.trace.internal.SdkTracerProviderUtil; import io.opentelemetry.sdk.trace.samplers.Sampler; import java.io.Closeable; import java.time.Duration; @@ -50,7 +49,7 @@ static void configureTracerProvider( List closeables) { tracerProviderBuilder.setSpanLimits(configureSpanLimits(config)); - SdkTracerProviderUtil.setMeterProvider(tracerProviderBuilder, meterProvider); + tracerProviderBuilder.setMeterProvider(meterProvider); String sampler = config.getString("otel.traces.sampler", PARENTBASED_ALWAYS_ON); tracerProviderBuilder.setSampler( @@ -83,9 +82,7 @@ static List configureSpanProcessors( SpanExporter exporter = exportersByNameCopy.remove(simpleProcessorExporterNames); if (exporter != null) { SpanProcessor spanProcessor = - SdkTracerProviderUtil.setMeterProvider( - SimpleSpanProcessor.builder(exporter), meterProvider) - .build(); + SimpleSpanProcessor.builder(exporter).setMeterProvider(meterProvider).build(); closeables.add(spanProcessor); spanProcessors.add(spanProcessor); } diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/SpanProcessorFactory.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/SpanProcessorFactory.java index 8a6574eab27..e3aed899b5a 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/SpanProcessorFactory.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/SpanProcessorFactory.java @@ -16,7 +16,6 @@ import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; import io.opentelemetry.sdk.trace.export.SimpleSpanProcessorBuilder; import io.opentelemetry.sdk.trace.export.SpanExporter; -import io.opentelemetry.sdk.trace.internal.SdkTracerProviderUtil; import java.time.Duration; import java.util.Map; @@ -67,7 +66,7 @@ public SpanProcessor create(SpanProcessorModel model, DeclarativeConfigContext c SimpleSpanProcessorBuilder builder = SimpleSpanProcessor.builder(spanExporter); MeterProvider meterProvider = context.getMeterProvider(); if (meterProvider != null) { - SdkTracerProviderUtil.setMeterProvider(builder, meterProvider); + builder.setMeterProvider(meterProvider); } return context.addCloseable(builder.build()); } diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/ExtendedSdkSpanBuilder.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/ExtendedSdkSpanBuilder.java index 4491e581977..b68030202ce 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/ExtendedSdkSpanBuilder.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/ExtendedSdkSpanBuilder.java @@ -30,9 +30,8 @@ final class ExtendedSdkSpanBuilder extends SdkSpanBuilder implements ExtendedSpa String spanName, InstrumentationScopeInfo instrumentationScopeInfo, TracerSharedState tracerSharedState, - SpanLimits spanLimits, - SdkTracerMetrics tracerProviderMetrics) { - super(spanName, instrumentationScopeInfo, tracerSharedState, spanLimits, tracerProviderMetrics); + SpanLimits spanLimits) { + super(spanName, instrumentationScopeInfo, tracerSharedState, spanLimits); } @Override diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/ExtendedSdkTracer.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/ExtendedSdkTracer.java index ae0dd654578..0c1d1c8e8b9 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/ExtendedSdkTracer.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/ExtendedSdkTracer.java @@ -16,9 +16,8 @@ final class ExtendedSdkTracer extends SdkTracer implements ExtendedTracer { ExtendedSdkTracer( TracerSharedState sharedState, InstrumentationScopeInfo instrumentationScopeInfo, - TracerConfig tracerConfig, - SdkTracerMetrics tracerProviderMetrics) { - super(sharedState, instrumentationScopeInfo, tracerConfig, tracerProviderMetrics); + TracerConfig tracerConfig) { + super(sharedState, instrumentationScopeInfo, tracerConfig); } @Override diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/IncubatingUtil.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/IncubatingUtil.java index af0ea505c7c..c7ab15e9c2e 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/IncubatingUtil.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/IncubatingUtil.java @@ -20,19 +20,16 @@ private IncubatingUtil() {} static SdkTracer createExtendedTracer( TracerSharedState sharedState, InstrumentationScopeInfo instrumentationScopeInfo, - TracerConfig tracerConfig, - SdkTracerMetrics tracerProviderMetrics) { - return new ExtendedSdkTracer( - sharedState, instrumentationScopeInfo, tracerConfig, tracerProviderMetrics); + TracerConfig tracerConfig) { + return new ExtendedSdkTracer(sharedState, instrumentationScopeInfo, tracerConfig); } static SdkSpanBuilder createExtendedSpanBuilder( String spanName, InstrumentationScopeInfo instrumentationScopeInfo, TracerSharedState tracerSharedState, - SpanLimits spanLimits, - SdkTracerMetrics tracerProviderMetrics) { + SpanLimits spanLimits) { return new ExtendedSdkSpanBuilder( - spanName, instrumentationScopeInfo, tracerSharedState, spanLimits, tracerProviderMetrics); + spanName, instrumentationScopeInfo, tracerSharedState, spanLimits); } } diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkSpanBuilder.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkSpanBuilder.java index 9e5db4fb27a..6edbfc24db4 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkSpanBuilder.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkSpanBuilder.java @@ -39,7 +39,6 @@ class SdkSpanBuilder implements SpanBuilder { private final InstrumentationScopeInfo instrumentationScopeInfo; private final TracerSharedState tracerSharedState; private final SpanLimits spanLimits; - private final SdkTracerMetrics tracerProviderMetrics; @Nullable private Context parent; // null means: Use current context. private SpanKind spanKind = SpanKind.INTERNAL; @@ -52,13 +51,11 @@ class SdkSpanBuilder implements SpanBuilder { String spanName, InstrumentationScopeInfo instrumentationScopeInfo, TracerSharedState tracerSharedState, - SpanLimits spanLimits, - SdkTracerMetrics tracerProviderMetrics) { + SpanLimits spanLimits) { this.spanName = spanName; this.instrumentationScopeInfo = instrumentationScopeInfo; this.tracerSharedState = tracerSharedState; this.spanLimits = spanLimits; - this.tracerProviderMetrics = tracerProviderMetrics; } @Override @@ -208,7 +205,7 @@ public Span startSpan() { tracerSharedState.isIdGeneratorSafeToSkipIdValidation()); Runnable recordEndSpanMetrics = - tracerProviderMetrics.startSpan(parentSpanContext, samplingDecision); + tracerSharedState.getTracerMetrics().startSpan(parentSpanContext, samplingDecision); if (!isRecording(samplingDecision)) { return Span.wrap(spanContext); diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracer.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracer.java index d6542f929ae..d90f4d8cb01 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracer.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracer.java @@ -30,30 +30,25 @@ class SdkTracer implements Tracer { private final TracerSharedState sharedState; private final InstrumentationScopeInfo instrumentationScopeInfo; - private final SdkTracerMetrics tracerProviderMetrics; protected volatile boolean tracerEnabled; SdkTracer( TracerSharedState sharedState, InstrumentationScopeInfo instrumentationScopeInfo, - TracerConfig tracerConfig, - SdkTracerMetrics tracerProviderMetrics) { + TracerConfig tracerConfig) { this.sharedState = sharedState; this.instrumentationScopeInfo = instrumentationScopeInfo; this.tracerEnabled = tracerConfig.isEnabled(); - this.tracerProviderMetrics = tracerProviderMetrics; } static SdkTracer create( TracerSharedState sharedState, InstrumentationScopeInfo instrumentationScopeInfo, - TracerConfig tracerConfig, - SdkTracerMetrics tracerProviderMetrics) { + TracerConfig tracerConfig) { return INCUBATOR_AVAILABLE - ? IncubatingUtil.createExtendedTracer( - sharedState, instrumentationScopeInfo, tracerConfig, tracerProviderMetrics) - : new SdkTracer(sharedState, instrumentationScopeInfo, tracerConfig, tracerProviderMetrics); + ? IncubatingUtil.createExtendedTracer(sharedState, instrumentationScopeInfo, tracerConfig) + : new SdkTracer(sharedState, instrumentationScopeInfo, tracerConfig); } /** @@ -73,17 +68,9 @@ public SpanBuilder spanBuilder(String spanName) { } return INCUBATOR_AVAILABLE ? IncubatingUtil.createExtendedSpanBuilder( - spanName, - instrumentationScopeInfo, - sharedState, - sharedState.getSpanLimits(), - tracerProviderMetrics) + spanName, instrumentationScopeInfo, sharedState, sharedState.getSpanLimits()) : new SdkSpanBuilder( - spanName, - instrumentationScopeInfo, - sharedState, - sharedState.getSpanLimits(), - tracerProviderMetrics); + spanName, instrumentationScopeInfo, sharedState, sharedState.getSpanLimits()); } // Visible for testing diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerMetrics.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerMetrics.java index 4fe65b63612..95e875bddb6 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerMetrics.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerMetrics.java @@ -29,6 +29,65 @@ final class SdkTracerMetrics { static final AttributeKey OTEL_SPAN_SAMPLING_RESULT = stringKey("otel.span.sampling_result"); + private static final Attributes noParentDrop = + Attributes.of( + OTEL_SPAN_PARENT_ORIGIN, "none", OTEL_SPAN_SAMPLING_RESULT, SamplingDecision.DROP.name()); + private static final Attributes noParentRecordOnly = + Attributes.of( + OTEL_SPAN_PARENT_ORIGIN, + "none", + OTEL_SPAN_SAMPLING_RESULT, + SamplingDecision.RECORD_ONLY.name()); + private static final Attributes noParentRecordAndSample = + Attributes.of( + OTEL_SPAN_PARENT_ORIGIN, + "none", + OTEL_SPAN_SAMPLING_RESULT, + SamplingDecision.RECORD_AND_SAMPLE.name()); + + private static final Attributes remoteParentDrop = + Attributes.of( + OTEL_SPAN_PARENT_ORIGIN, + "remote", + OTEL_SPAN_SAMPLING_RESULT, + SamplingDecision.DROP.name()); + private static final Attributes remoteParentRecordOnly = + Attributes.of( + OTEL_SPAN_PARENT_ORIGIN, + "remote", + OTEL_SPAN_SAMPLING_RESULT, + SamplingDecision.RECORD_ONLY.name()); + private static final Attributes remoteParentRecordAndSample = + Attributes.of( + OTEL_SPAN_PARENT_ORIGIN, + "remote", + OTEL_SPAN_SAMPLING_RESULT, + SamplingDecision.RECORD_AND_SAMPLE.name()); + + private static final Attributes localParentDrop = + Attributes.of( + OTEL_SPAN_PARENT_ORIGIN, + "local", + OTEL_SPAN_SAMPLING_RESULT, + SamplingDecision.DROP.name()); + private static final Attributes localParentRecordOnly = + Attributes.of( + OTEL_SPAN_PARENT_ORIGIN, + "local", + OTEL_SPAN_SAMPLING_RESULT, + SamplingDecision.RECORD_ONLY.name()); + private static final Attributes localParentRecordAndSample = + Attributes.of( + OTEL_SPAN_PARENT_ORIGIN, + "local", + OTEL_SPAN_SAMPLING_RESULT, + SamplingDecision.RECORD_AND_SAMPLE.name()); + + private static final Attributes recordOnly = + Attributes.of(OTEL_SPAN_SAMPLING_RESULT, SamplingDecision.RECORD_ONLY.name()); + private static final Attributes recordAndSample = + Attributes.of(OTEL_SPAN_SAMPLING_RESULT, SamplingDecision.RECORD_AND_SAMPLE.name()); + private final LongCounter startedSpans; private final LongUpDownCounter liveSpans; @@ -55,31 +114,61 @@ final class SdkTracerMetrics { * the span. */ Runnable startSpan(SpanContext parentSpanContext, SamplingDecision samplingDecision) { - startedSpans.add( - 1, - Attributes.of( - OTEL_SPAN_PARENT_ORIGIN, - parentOrigin(parentSpanContext), - OTEL_SPAN_SAMPLING_RESULT, - samplingDecision.name())); - - if (samplingDecision == SamplingDecision.DROP) { - return () -> {}; + if (!parentSpanContext.isValid()) { + switch (samplingDecision) { + case DROP: + startedSpans.add(1, noParentDrop); + return SdkTracerMetrics::noop; + case RECORD_ONLY: + startedSpans.add(1, noParentRecordOnly); + liveSpans.add(1, recordOnly); + return this::decrementRecordOnly; + case RECORD_AND_SAMPLE: + startedSpans.add(1, noParentRecordAndSample); + liveSpans.add(1, recordAndSample); + return this::decrementRecordAndSample; + } + throw new IllegalArgumentException("Unrecognized sampling decision: " + samplingDecision); + } else if (parentSpanContext.isRemote()) { + switch (samplingDecision) { + case DROP: + startedSpans.add(1, remoteParentDrop); + return SdkTracerMetrics::noop; + case RECORD_ONLY: + startedSpans.add(1, remoteParentRecordOnly); + liveSpans.add(1, recordOnly); + return this::decrementRecordOnly; + case RECORD_AND_SAMPLE: + startedSpans.add(1, remoteParentRecordAndSample); + liveSpans.add(1, recordAndSample); + return this::decrementRecordAndSample; + } + throw new IllegalArgumentException("Unrecognized sampling decision: " + samplingDecision); + } + // local parent + switch (samplingDecision) { + case DROP: + startedSpans.add(1, localParentDrop); + return SdkTracerMetrics::noop; + case RECORD_ONLY: + startedSpans.add(1, localParentRecordOnly); + liveSpans.add(1, recordOnly); + return this::decrementRecordOnly; + case RECORD_AND_SAMPLE: + startedSpans.add(1, localParentRecordAndSample); + liveSpans.add(1, recordAndSample); + return this::decrementRecordAndSample; } + throw new IllegalArgumentException("Unrecognized sampling decision: " + samplingDecision); + } + + private static void noop() {} - Attributes liveSpansAttributes = - Attributes.of(OTEL_SPAN_SAMPLING_RESULT, samplingDecision.name()); - liveSpans.add(1, liveSpansAttributes); - return () -> liveSpans.add(-1, liveSpansAttributes); + private void decrementRecordOnly() { + liveSpans.add(-1, recordOnly); } - private static String parentOrigin(SpanContext parentSpanContext) { - if (!parentSpanContext.isValid()) { - return "none"; - } - if (parentSpanContext.isRemote()) { - return "remote"; - } - return "local"; + private void decrementRecordAndSample() { + liveSpans.add(-1, recordAndSample); } } diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProvider.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProvider.java index beccc2f90bc..12a0ad0d2a1 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProvider.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProvider.java @@ -57,7 +57,6 @@ public static SdkTracerProviderBuilder builder() { ScopeConfigurator tracerConfigurator, ExceptionAttributeResolver exceptionAttributeResolver, MeterProvider meterProvider) { - SdkTracerMetrics tracerProviderMetrics = new SdkTracerMetrics(meterProvider); this.sharedState = new TracerSharedState( clock, @@ -66,15 +65,15 @@ public static SdkTracerProviderBuilder builder() { spanLimitsSupplier, sampler, spanProcessors, - exceptionAttributeResolver); + exceptionAttributeResolver, + new SdkTracerMetrics(meterProvider)); this.tracerSdkComponentRegistry = new ComponentRegistry<>( instrumentationScopeInfo -> SdkTracer.create( sharedState, instrumentationScopeInfo, - getTracerConfig(instrumentationScopeInfo), - tracerProviderMetrics)); + getTracerConfig(instrumentationScopeInfo))); this.tracerConfigurator = tracerConfigurator; } diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProviderBuilder.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProviderBuilder.java index 056e12bd1a3..bb2fbfdeda5 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProviderBuilder.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProviderBuilder.java @@ -238,11 +238,8 @@ SdkTracerProviderBuilder setExceptionAttributeResolver( * Sets the {@link MeterProvider} to use to generate SDK Span * Metrics. - * - *

This method is experimental so not public. You may reflectively call it using {@link - * SdkTracerProviderUtil#setMeterProvider(SdkTracerProviderBuilder, MeterProvider)}. */ - SdkTracerProviderBuilder setMeterProvider(MeterProvider meterProvider) { + public SdkTracerProviderBuilder setMeterProvider(MeterProvider meterProvider) { requireNonNull(meterProvider, "meterProvider"); this.meterProvider = meterProvider; return this; diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/TracerSharedState.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/TracerSharedState.java index 74d43076b97..b425664aaba 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/TracerSharedState.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/TracerSharedState.java @@ -28,6 +28,7 @@ final class TracerSharedState { private final Sampler sampler; private final SpanProcessor activeSpanProcessor; private final ExceptionAttributeResolver exceptionAttributeResolver; + private final SdkTracerMetrics tracerMetrics; @Nullable private volatile CompletableResultCode shutdownResult = null; @@ -38,7 +39,8 @@ final class TracerSharedState { Supplier spanLimitsSupplier, Sampler sampler, List spanProcessors, - ExceptionAttributeResolver exceptionAttributeResolver) { + ExceptionAttributeResolver exceptionAttributeResolver, + SdkTracerMetrics tracerMetrics) { this.clock = clock; this.idGenerator = idGenerator; this.idGeneratorSafeToSkipIdValidation = idGenerator instanceof RandomIdGenerator; @@ -47,6 +49,7 @@ final class TracerSharedState { this.sampler = sampler; this.activeSpanProcessor = SpanProcessor.composite(spanProcessors); this.exceptionAttributeResolver = exceptionAttributeResolver; + this.tracerMetrics = tracerMetrics; } Clock getClock() { @@ -98,6 +101,10 @@ ExceptionAttributeResolver getExceptionAttributesResolver() { return exceptionAttributeResolver; } + SdkTracerMetrics getTracerMetrics() { + return tracerMetrics; + } + /** * Stops tracing, including shutting down processors and set to {@code true} {@link * #hasBeenShutdown()}. diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessor.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessor.java index 0912782cc52..7713002531a 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessor.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessor.java @@ -319,7 +319,11 @@ private void exportCurrentBatch() { result.join(exporterTimeoutNanos, TimeUnit.NANOSECONDS); if (!result.isSuccess()) { logger.log(Level.FINE, "Exporter failed"); - error = "export_failed"; + if (result.getFailureThrowable() != null) { + error = result.getFailureThrowable().getClass().getName(); + } else { + error = "export_failed"; + } } } catch (Throwable t) { ThrowableUtil.propagateIfFatal(t); diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/LegacySpanProcessorMetrics.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/LegacySpanProcessorMetrics.java index a8395c37348..641bbfd5584 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/LegacySpanProcessorMetrics.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/LegacySpanProcessorMetrics.java @@ -53,7 +53,6 @@ final class LegacySpanProcessorMetrics implements SpanProcessorMetrics { true); } - /** Records metrics for spans dropped because a queue is full. */ @Override public void dropSpans(int count) { processedSpans.add(count, droppedAttrs); @@ -72,7 +71,6 @@ public void buildQueueCapacityMetric(long capacity) { // No capacity metric when legacy. } - /** Registers a metric for processor queue size. */ @Override public void buildQueueSizeMetric(LongCallable queueSize) { meter diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/LongCallable.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/LongCallable.java new file mode 100644 index 00000000000..2f7e03d40b4 --- /dev/null +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/LongCallable.java @@ -0,0 +1,13 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.trace.export; + +/** A Callable returning a primitive long value. */ +@FunctionalInterface +interface LongCallable { + /** Returns the value. */ + long get(); +} diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SimpleSpanProcessorBuilder.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SimpleSpanProcessorBuilder.java index 2681f0437d6..7ad3ecf07ac 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SimpleSpanProcessorBuilder.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SimpleSpanProcessorBuilder.java @@ -8,8 +8,6 @@ import static java.util.Objects.requireNonNull; import io.opentelemetry.api.metrics.MeterProvider; -import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder; -import io.opentelemetry.sdk.trace.internal.SdkTracerProviderUtil; /** * Builder class for {@link SimpleSpanProcessor}. @@ -38,11 +36,8 @@ public SimpleSpanProcessorBuilder setExportUnsampledSpans(boolean exportUnsample * Sets the {@link MeterProvider} to use to generate SDK Span * Metrics. - * - *

This method is experimental so not public. You may reflectively call it using {@link - * SdkTracerProviderUtil#setMeterProvider(SdkTracerProviderBuilder, MeterProvider)}. */ - SimpleSpanProcessorBuilder setMeterProvider(MeterProvider meterProvider) { + public SimpleSpanProcessorBuilder setMeterProvider(MeterProvider meterProvider) { requireNonNull(meterProvider, "meterProvider"); this.meterProvider = meterProvider; return this; diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SpanProcessorMetrics.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SpanProcessorMetrics.java index 94126fb5e99..b62b0b1b683 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SpanProcessorMetrics.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SpanProcessorMetrics.java @@ -34,10 +34,6 @@ static SpanProcessorMetrics get( /** Registers a metric for processor queue capacity. */ void buildQueueCapacityMetric(long capacity); - interface LongCallable { - long get(); - } - /** Registers a metric for processor queue size. */ void buildQueueSizeMetric(LongCallable queueSize); } diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/SdkTracerProviderUtil.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/SdkTracerProviderUtil.java index 1c2884fd442..f9ca8fafc56 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/SdkTracerProviderUtil.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/SdkTracerProviderUtil.java @@ -5,13 +5,11 @@ package io.opentelemetry.sdk.trace.internal; -import io.opentelemetry.api.metrics.MeterProvider; import io.opentelemetry.sdk.common.InstrumentationScopeInfo; import io.opentelemetry.sdk.internal.ExceptionAttributeResolver; import io.opentelemetry.sdk.internal.ScopeConfigurator; import io.opentelemetry.sdk.trace.SdkTracerProvider; import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder; -import io.opentelemetry.sdk.trace.export.SimpleSpanProcessorBuilder; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.function.Predicate; @@ -91,35 +89,4 @@ public static void setExceptionAttributeResolver( "Error calling setExceptionAttributeResolver on SdkTracerProviderBuilder", e); } } - - /** Reflectively set meter provider to the {@link SdkTracerProviderBuilder}. */ - public static SdkTracerProviderBuilder setMeterProvider( - SdkTracerProviderBuilder sdkTracerProviderBuilder, MeterProvider meterProvider) { - try { - Method method = - SdkTracerProviderBuilder.class.getDeclaredMethod("setMeterProvider", MeterProvider.class); - method.setAccessible(true); - method.invoke(sdkTracerProviderBuilder, meterProvider); - return sdkTracerProviderBuilder; - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { - throw new IllegalStateException( - "Error calling setMeterProvider on SdkTracerProviderBuilder", e); - } - } - - /** Reflectively set meter provider to the {@link SdkTracerProviderBuilder}. */ - public static SimpleSpanProcessorBuilder setMeterProvider( - SimpleSpanProcessorBuilder simpleSpanProcessorBuilder, MeterProvider meterProvider) { - try { - Method method = - SimpleSpanProcessorBuilder.class.getDeclaredMethod( - "setMeterProvider", MeterProvider.class); - method.setAccessible(true); - method.invoke(simpleSpanProcessorBuilder, meterProvider); - return simpleSpanProcessorBuilder; - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { - throw new IllegalStateException( - "Error calling setMeterProvider on SimpleSpanProcessorBuilder", e); - } - } } diff --git a/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkTracerProviderMetricsTest.java b/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkTracerProviderMetricsTest.java index 997728bb23e..3ca9333c553 100644 --- a/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkTracerProviderMetricsTest.java +++ b/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkTracerProviderMetricsTest.java @@ -34,7 +34,6 @@ import io.opentelemetry.sdk.trace.export.BatchSpanProcessor; import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; import io.opentelemetry.sdk.trace.export.SpanExporter; -import io.opentelemetry.sdk.trace.internal.SdkTracerProviderUtil; import io.opentelemetry.sdk.trace.samplers.Sampler; import io.opentelemetry.sdk.trace.samplers.SamplingDecision; import io.opentelemetry.sdk.trace.samplers.SamplingResult; @@ -61,9 +60,7 @@ void simple() { TracerProvider tracerProvider = SdkTracerProvider.builder() .addSpanProcessor( - SdkTracerProviderUtil.setMeterProvider( - SimpleSpanProcessor.builder(exporter), meterProvider) - .build()) + SimpleSpanProcessor.builder(exporter).setMeterProvider(meterProvider).build()) .setMeterProvider(meterProvider) .setSampler(sampler) .build(); @@ -924,9 +921,7 @@ void simpleExportError() { TracerProvider tracerProvider = SdkTracerProvider.builder() .addSpanProcessor( - SdkTracerProviderUtil.setMeterProvider( - SimpleSpanProcessor.builder(mockExporter), meterProvider) - .build()) + SimpleSpanProcessor.builder(mockExporter).setMeterProvider(meterProvider).build()) .setMeterProvider(meterProvider) .setSampler(Sampler.alwaysOn()) .build(); From 052587d55cdea385153f08f59f7728dc7f41bc7c Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Thu, 18 Dec 2025 12:35:45 +0900 Subject: [PATCH 08/10] Lazy metrics --- .../opentelemetry-sdk-trace.txt | 5 +- .../TracerProviderConfiguration.java | 6 +- .../fileconfig/SpanProcessorFactory.java | 4 +- ...OpenTelemetryConfigurationFactoryTest.java | 5 +- .../sdk/trace/SdkTracerMetrics.java | 112 ++++++++++++------ .../sdk/trace/SdkTracerProvider.java | 2 +- .../sdk/trace/SdkTracerProviderBuilder.java | 4 +- .../sdk/trace/export/BatchSpanProcessor.java | 10 +- .../export/BatchSpanProcessorBuilder.java | 13 +- .../export/LegacySpanProcessorMetrics.java | 71 +++++++---- .../export/SemConvSpanProcessorMetrics.java | 72 +++++++---- .../sdk/trace/export/SimpleSpanProcessor.java | 11 +- .../export/SimpleSpanProcessorBuilder.java | 7 +- .../trace/export/SpanProcessorMetrics.java | 10 +- .../trace/SdkTracerProviderMetricsTest.java | 14 ++- 15 files changed, 228 insertions(+), 118 deletions(-) diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-trace.txt b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-trace.txt index 9a47d73b9c9..ab607b7bb6d 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-trace.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-trace.txt @@ -2,9 +2,10 @@ Comparing source compatibility of opentelemetry-sdk-trace-1.58.0-SNAPSHOT.jar ag *** MODIFIED CLASS: PUBLIC FINAL io.opentelemetry.sdk.trace.export.BatchSpanProcessorBuilder (not serializable) === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.trace.export.BatchSpanProcessorBuilder setInternalTelemetryVersion(io.opentelemetry.sdk.common.InternalTelemetryVersion) + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.trace.export.BatchSpanProcessorBuilder setMeterProvider(java.util.function.Supplier) *** MODIFIED CLASS: PUBLIC FINAL io.opentelemetry.sdk.trace.export.SimpleSpanProcessorBuilder (not serializable) === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 - +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.trace.export.SimpleSpanProcessorBuilder setMeterProvider(io.opentelemetry.api.metrics.MeterProvider) + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.trace.export.SimpleSpanProcessorBuilder setMeterProvider(java.util.function.Supplier) *** MODIFIED CLASS: PUBLIC FINAL io.opentelemetry.sdk.trace.SdkTracerProviderBuilder (not serializable) === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 - +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.trace.SdkTracerProviderBuilder setMeterProvider(io.opentelemetry.api.metrics.MeterProvider) + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.trace.SdkTracerProviderBuilder setMeterProvider(java.util.function.Supplier) diff --git a/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/TracerProviderConfiguration.java b/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/TracerProviderConfiguration.java index 2e55307c8f0..7ecc048ff3d 100644 --- a/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/TracerProviderConfiguration.java +++ b/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/TracerProviderConfiguration.java @@ -49,7 +49,7 @@ static void configureTracerProvider( List closeables) { tracerProviderBuilder.setSpanLimits(configureSpanLimits(config)); - tracerProviderBuilder.setMeterProvider(meterProvider); + tracerProviderBuilder.setMeterProvider(() -> meterProvider); String sampler = config.getString("otel.traces.sampler", PARENTBASED_ALWAYS_ON); tracerProviderBuilder.setSampler( @@ -82,7 +82,7 @@ static List configureSpanProcessors( SpanExporter exporter = exportersByNameCopy.remove(simpleProcessorExporterNames); if (exporter != null) { SpanProcessor spanProcessor = - SimpleSpanProcessor.builder(exporter).setMeterProvider(meterProvider).build(); + SimpleSpanProcessor.builder(exporter).setMeterProvider(() -> meterProvider).build(); closeables.add(spanProcessor); spanProcessors.add(spanProcessor); } @@ -103,7 +103,7 @@ static List configureSpanProcessors( static BatchSpanProcessor configureBatchSpanProcessor( ConfigProperties config, SpanExporter exporter, MeterProvider meterProvider) { BatchSpanProcessorBuilder builder = - BatchSpanProcessor.builder(exporter).setMeterProvider(meterProvider); + BatchSpanProcessor.builder(exporter).setMeterProvider(() -> meterProvider); Duration scheduleDelay = config.getDuration("otel.bsp.schedule.delay"); if (scheduleDelay != null) { diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/SpanProcessorFactory.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/SpanProcessorFactory.java index e3aed899b5a..1f760cfe20c 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/SpanProcessorFactory.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/SpanProcessorFactory.java @@ -51,7 +51,7 @@ public SpanProcessor create(SpanProcessorModel model, DeclarativeConfigContext c } MeterProvider meterProvider = context.getMeterProvider(); if (meterProvider != null) { - builder.setMeterProvider(meterProvider); + builder.setMeterProvider(() -> meterProvider); } return context.addCloseable(builder.build()); @@ -66,7 +66,7 @@ public SpanProcessor create(SpanProcessorModel model, DeclarativeConfigContext c SimpleSpanProcessorBuilder builder = SimpleSpanProcessor.builder(spanExporter); MeterProvider meterProvider = context.getMeterProvider(); if (meterProvider != null) { - builder.setMeterProvider(meterProvider); + builder.setMeterProvider(() -> meterProvider); } return context.addCloseable(builder.build()); } diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactoryTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactoryTest.java index 675dfa8872f..49f6fbaf5e2 100644 --- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactoryTest.java +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactoryTest.java @@ -337,6 +337,7 @@ void create_Configured() throws NoSuchFieldException, IllegalAccessException { .extracting("meterProviderSharedState") .isEqualTo(sharedState); + // Lazily initialized assertThat(sdk) .extracting("tracerProvider") .extracting("delegate") @@ -345,8 +346,6 @@ void create_Configured() throws NoSuchFieldException, IllegalAccessException { .extracting("worker") .extracting("spanProcessorMetrics") .extracting("processedSpans") - .extracting("sdkMeter") - .extracting("meterProviderSharedState") - .isEqualTo(sharedState); + .isNull(); } } diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerMetrics.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerMetrics.java index 95e875bddb6..916a249975a 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerMetrics.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerMetrics.java @@ -15,6 +15,8 @@ import io.opentelemetry.api.metrics.MeterProvider; import io.opentelemetry.api.trace.SpanContext; import io.opentelemetry.sdk.trace.samplers.SamplingDecision; +import java.util.function.Supplier; +import javax.annotation.Nullable; /** * SDK metrics exported for started and ended spans as defined in the meterProvider; + + @Nullable private Meter meter; + @Nullable private volatile LongCounter startedSpans; + @Nullable private volatile LongUpDownCounter liveSpans; + + SdkTracerMetrics(Supplier meterProvider) { + this.meterProvider = meterProvider; } /** @@ -117,30 +110,30 @@ Runnable startSpan(SpanContext parentSpanContext, SamplingDecision samplingDecis if (!parentSpanContext.isValid()) { switch (samplingDecision) { case DROP: - startedSpans.add(1, noParentDrop); + startedSpans().add(1, noParentDrop); return SdkTracerMetrics::noop; case RECORD_ONLY: - startedSpans.add(1, noParentRecordOnly); - liveSpans.add(1, recordOnly); + startedSpans().add(1, noParentRecordOnly); + liveSpans().add(1, recordOnly); return this::decrementRecordOnly; case RECORD_AND_SAMPLE: - startedSpans.add(1, noParentRecordAndSample); - liveSpans.add(1, recordAndSample); + startedSpans().add(1, noParentRecordAndSample); + liveSpans().add(1, recordAndSample); return this::decrementRecordAndSample; } throw new IllegalArgumentException("Unrecognized sampling decision: " + samplingDecision); } else if (parentSpanContext.isRemote()) { switch (samplingDecision) { case DROP: - startedSpans.add(1, remoteParentDrop); + startedSpans().add(1, remoteParentDrop); return SdkTracerMetrics::noop; case RECORD_ONLY: - startedSpans.add(1, remoteParentRecordOnly); - liveSpans.add(1, recordOnly); + startedSpans().add(1, remoteParentRecordOnly); + liveSpans().add(1, recordOnly); return this::decrementRecordOnly; case RECORD_AND_SAMPLE: - startedSpans.add(1, remoteParentRecordAndSample); - liveSpans.add(1, recordAndSample); + startedSpans().add(1, remoteParentRecordAndSample); + liveSpans().add(1, recordAndSample); return this::decrementRecordAndSample; } throw new IllegalArgumentException("Unrecognized sampling decision: " + samplingDecision); @@ -148,15 +141,15 @@ Runnable startSpan(SpanContext parentSpanContext, SamplingDecision samplingDecis // local parent switch (samplingDecision) { case DROP: - startedSpans.add(1, localParentDrop); + startedSpans().add(1, localParentDrop); return SdkTracerMetrics::noop; case RECORD_ONLY: - startedSpans.add(1, localParentRecordOnly); - liveSpans.add(1, recordOnly); + startedSpans().add(1, localParentRecordOnly); + liveSpans().add(1, recordOnly); return this::decrementRecordOnly; case RECORD_AND_SAMPLE: - startedSpans.add(1, localParentRecordAndSample); - liveSpans.add(1, recordAndSample); + startedSpans().add(1, localParentRecordAndSample); + liveSpans().add(1, recordAndSample); return this::decrementRecordAndSample; } throw new IllegalArgumentException("Unrecognized sampling decision: " + samplingDecision); @@ -165,10 +158,57 @@ Runnable startSpan(SpanContext parentSpanContext, SamplingDecision samplingDecis private static void noop() {} private void decrementRecordOnly() { - liveSpans.add(-1, recordOnly); + liveSpans().add(-1, recordOnly); } private void decrementRecordAndSample() { - liveSpans.add(-1, recordAndSample); + liveSpans().add(-1, recordAndSample); + } + + private LongCounter startedSpans() { + LongCounter startedSpans = this.startedSpans; + if (startedSpans == null) { + synchronized (lock) { + startedSpans = this.startedSpans; + if (startedSpans == null) { + startedSpans = + meter() + .counterBuilder("otel.sdk.span.started") + .setUnit("{span}") + .setDescription("The number of created spans.") + .build(); + this.startedSpans = startedSpans; + } + } + } + return startedSpans; + } + + private LongUpDownCounter liveSpans() { + LongUpDownCounter liveSpans = this.liveSpans; + if (liveSpans == null) { + synchronized (lock) { + liveSpans = this.liveSpans; + if (liveSpans == null) { + liveSpans = + meter() + .upDownCounterBuilder("otel.sdk.span.live") + .setUnit("{span}") + .setDescription( + "The number of created spans with recording=true for which the end operation has not been called yet.") + .build(); + this.liveSpans = liveSpans; + } + } + } + return liveSpans; + } + + private Meter meter() { + if (meter == null) { + // Safe to call from multiple threads. + meter = meterProvider.get().get("io.opentelemetry.sdk.trace"); + } + return meter; } } diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProvider.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProvider.java index 12a0ad0d2a1..ad4af9a8b06 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProvider.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProvider.java @@ -56,7 +56,7 @@ public static SdkTracerProviderBuilder builder() { List spanProcessors, ScopeConfigurator tracerConfigurator, ExceptionAttributeResolver exceptionAttributeResolver, - MeterProvider meterProvider) { + Supplier meterProvider) { this.sharedState = new TracerSharedState( clock, diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProviderBuilder.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProviderBuilder.java index bb2fbfdeda5..54b9a3cb864 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProviderBuilder.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProviderBuilder.java @@ -39,7 +39,7 @@ public final class SdkTracerProviderBuilder { TracerConfig.configuratorBuilder(); private ExceptionAttributeResolver exceptionAttributeResolver = ExceptionAttributeResolver.getDefault(); - private MeterProvider meterProvider = MeterProvider.noop(); + private Supplier meterProvider = MeterProvider::noop; /** * Assign a {@link Clock}. {@link Clock} will be used each time a {@link Span} is started, ended @@ -239,7 +239,7 @@ SdkTracerProviderBuilder setExceptionAttributeResolver( * href="https://opentelemetry.io/docs/specs/semconv/otel/sdk-metrics/#span-metrics">SDK Span * Metrics. */ - public SdkTracerProviderBuilder setMeterProvider(MeterProvider meterProvider) { + public SdkTracerProviderBuilder setMeterProvider(Supplier meterProvider) { requireNonNull(meterProvider, "meterProvider"); this.meterProvider = meterProvider; return this; diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessor.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessor.java index 7713002531a..5e8603350e9 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessor.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessor.java @@ -27,6 +27,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; import java.util.logging.Level; import java.util.logging.Logger; @@ -67,7 +68,7 @@ public static BatchSpanProcessorBuilder builder(SpanExporter spanExporter) { BatchSpanProcessor( SpanExporter spanExporter, boolean exportUnsampledSpans, - MeterProvider meterProvider, + Supplier meterProvider, InternalTelemetryVersion telemetryVersion, long scheduleDelayNanos, int maxQueueSize, @@ -182,10 +183,11 @@ private static final class Worker implements Runnable { private final AtomicReference flushRequested = new AtomicReference<>(); private volatile boolean continueWork = true; private final ArrayList batch; + private final long maxQueueSize; private Worker( SpanExporter spanExporter, - MeterProvider meterProvider, + Supplier meterProvider, InternalTelemetryVersion telemetryVersion, long scheduleDelayNanos, int maxExportBatchSize, @@ -201,13 +203,13 @@ private Worker( spanProcessorMetrics = SpanProcessorMetrics.get(telemetryVersion, COMPONENT_ID, meterProvider); - spanProcessorMetrics.buildQueueCapacityMetric(maxQueueSize); - spanProcessorMetrics.buildQueueSizeMetric(queue::size); + this.maxQueueSize = maxQueueSize; this.batch = new ArrayList<>(this.maxExportBatchSize); } private void addSpan(ReadableSpan span) { + spanProcessorMetrics.buildQueueMetricsOnce(maxQueueSize, queue::size); if (!queue.offer(span)) { spanProcessorMetrics.dropSpans(1); } else { diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessorBuilder.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessorBuilder.java index fd325b6d126..e584d727153 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessorBuilder.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessorBuilder.java @@ -12,6 +12,7 @@ import io.opentelemetry.sdk.common.InternalTelemetryVersion; import java.time.Duration; import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; import java.util.logging.Level; import java.util.logging.Logger; @@ -34,7 +35,7 @@ public final class BatchSpanProcessorBuilder { private int maxQueueSize = DEFAULT_MAX_QUEUE_SIZE; private int maxExportBatchSize = DEFAULT_MAX_EXPORT_BATCH_SIZE; private long exporterTimeoutNanos = TimeUnit.MILLISECONDS.toNanos(DEFAULT_EXPORT_TIMEOUT_MILLIS); - private MeterProvider meterProvider = MeterProvider.noop(); + private Supplier meterProvider = MeterProvider::noop; private InternalTelemetryVersion telemetryVersion = InternalTelemetryVersion.LEGACY; BatchSpanProcessorBuilder(SpanExporter spanExporter) { @@ -146,6 +147,16 @@ public BatchSpanProcessorBuilder setMaxExportBatchSize(int maxExportBatchSize) { * metrics will not be collected. */ public BatchSpanProcessorBuilder setMeterProvider(MeterProvider meterProvider) { + requireNonNull(meterProvider, "meterProvider"); + this.meterProvider = () -> meterProvider; + return this; + } + + /** + * Sets the {@link MeterProvider} to use to collect metrics related to batch export. If not set, + * metrics will not be collected. + */ + public BatchSpanProcessorBuilder setMeterProvider(Supplier meterProvider) { requireNonNull(meterProvider, "meterProvider"); this.meterProvider = meterProvider; return this; diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/LegacySpanProcessorMetrics.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/LegacySpanProcessorMetrics.java index 641bbfd5584..e5b251cee5a 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/LegacySpanProcessorMetrics.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/LegacySpanProcessorMetrics.java @@ -10,6 +10,8 @@ import io.opentelemetry.api.metrics.LongCounter; import io.opentelemetry.api.metrics.Meter; import io.opentelemetry.api.metrics.MeterProvider; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Supplier; import javax.annotation.Nullable; /** Span processor metrics defined before they were standardized in semconv. */ @@ -21,23 +23,18 @@ final class LegacySpanProcessorMetrics implements SpanProcessorMetrics { // Legacy metrics are only created for batch span processor. private static final String SPAN_PROCESSOR_TYPE_VALUE = BatchSpanProcessor.class.getSimpleName(); - private final Meter meter; + private final Object lock = new Object(); + private final AtomicBoolean builtQueueMetrics = new AtomicBoolean(false); + + private final Supplier meterProvider; private final Attributes standardAttrs; private final Attributes droppedAttrs; - private final LongCounter processedSpans; - - LegacySpanProcessorMetrics(MeterProvider meterProvider) { - meter = meterProvider.get("io.opentelemetry.sdk.trace"); + @Nullable private Meter meter; + @Nullable private volatile LongCounter processedSpans; - processedSpans = - meter - .counterBuilder("processedSpans") - .setUnit("1") - .setDescription( - "The number of spans processed by the BatchSpanProcessor. " - + "[dropped=true if they were dropped due to high throughput]") - .build(); + LegacySpanProcessorMetrics(Supplier meterProvider) { + this.meterProvider = meterProvider; standardAttrs = Attributes.of( @@ -55,25 +52,23 @@ final class LegacySpanProcessorMetrics implements SpanProcessorMetrics { @Override public void dropSpans(int count) { - processedSpans.add(count, droppedAttrs); + processedSpans().add(count, droppedAttrs); } @Override public void finishSpans(int count, @Nullable String error) { // Legacy metrics only record when no error. if (error != null) { - processedSpans.add(count, standardAttrs); + processedSpans().add(count, standardAttrs); } } @Override - public void buildQueueCapacityMetric(long capacity) { - // No capacity metric when legacy. - } - - @Override - public void buildQueueSizeMetric(LongCallable queueSize) { - meter + public void buildQueueMetricsOnce(long unusedCapacity, LongCallable getSize) { + if (!builtQueueMetrics.compareAndSet(false, true)) { + return; + } + meter() .gaugeBuilder("queueSize") .ofLongs() .setDescription("The number of items queued") @@ -81,7 +76,37 @@ public void buildQueueSizeMetric(LongCallable queueSize) { .buildWithCallback( result -> result.record( - queueSize.get(), + getSize.get(), Attributes.of(SPAN_PROCESSOR_TYPE_LABEL, SPAN_PROCESSOR_TYPE_VALUE))); + // No capacity metric when legacy. + } + + private LongCounter processedSpans() { + LongCounter processedSpans = this.processedSpans; + if (processedSpans == null) { + synchronized (lock) { + processedSpans = this.processedSpans; + if (processedSpans == null) { + processedSpans = + meter() + .counterBuilder("processedSpans") + .setUnit("1") + .setDescription( + "The number of spans processed by the BatchSpanProcessor. " + + "[dropped=true if they were dropped due to high throughput]") + .build(); + this.processedSpans = processedSpans; + } + } + } + return processedSpans; + } + + private Meter meter() { + if (meter == null) { + // Safe to call from multiple threads. + meter = meterProvider.get().get("io.opentelemetry.sdk.trace"); + } + return meter; } } diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SemConvSpanProcessorMetrics.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SemConvSpanProcessorMetrics.java index 4f7748e2a3f..ea561e49a97 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SemConvSpanProcessorMetrics.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SemConvSpanProcessorMetrics.java @@ -11,6 +11,8 @@ import io.opentelemetry.api.metrics.MeterProvider; import io.opentelemetry.sdk.internal.ComponentId; import io.opentelemetry.sdk.internal.SemConvAttributes; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Supplier; import javax.annotation.Nullable; /** @@ -20,14 +22,18 @@ */ final class SemConvSpanProcessorMetrics implements SpanProcessorMetrics { - private final Meter meter; + private final Object lock = new Object(); + private final AtomicBoolean builtQueueMetrics = new AtomicBoolean(false); + + private final Supplier meterProvider; private final Attributes standardAttrs; private final Attributes droppedAttrs; - private final LongCounter processedSpans; + @Nullable private Meter meter; + @Nullable private volatile LongCounter processedSpans; - SemConvSpanProcessorMetrics(ComponentId componentId, MeterProvider meterProvider) { - meter = meterProvider.get("io.opentelemetry.sdk.trace"); + SemConvSpanProcessorMetrics(ComponentId componentId, Supplier meterProvider) { + this.meterProvider = meterProvider; standardAttrs = Attributes.of( @@ -43,53 +49,69 @@ final class SemConvSpanProcessorMetrics implements SpanProcessorMetrics { componentId.getComponentName(), SemConvAttributes.ERROR_TYPE, "queue_full"); - - processedSpans = - meter - .counterBuilder("otel.sdk.processor.span.processed") - .setUnit("span") - .setDescription( - "The number of spans for which the processing has finished, either successful or failed.") - .build(); } @Override public void dropSpans(int count) { - processedSpans.add(count, droppedAttrs); + processedSpans().add(count, droppedAttrs); } - /** Record metrics for spans processed, possibly with an error. */ @Override public void finishSpans(int count, @Nullable String error) { if (error == null) { - processedSpans.add(count, standardAttrs); + processedSpans().add(count, standardAttrs); return; } Attributes attributes = standardAttrs.toBuilder().put(SemConvAttributes.ERROR_TYPE, error).build(); - processedSpans.add(count, attributes); + processedSpans().add(count, attributes); } - /** Registers a metric for processor queue capacity. */ @Override - public void buildQueueCapacityMetric(long capacity) { - meter + public void buildQueueMetricsOnce(long capacity, LongCallable getSize) { + if (!builtQueueMetrics.compareAndSet(false, true)) { + return; + } + meter() .upDownCounterBuilder("otel.sdk.processor.span.queue.capacity") .setUnit("span") .setDescription( "The maximum number of spans the queue of a given instance of an SDK span processor can hold. ") .buildWithCallback(m -> m.record(capacity, standardAttrs)); - } - - /** Registers a metric for processor queue size. */ - @Override - public void buildQueueSizeMetric(LongCallable getSize) { - meter + meter() .upDownCounterBuilder("otel.sdk.processor.span.queue.size") .setUnit("span") .setDescription( "The number of spans in the queue of a given instance of an SDK span processor.") .buildWithCallback(m -> m.record(getSize.get(), standardAttrs)); } + + private LongCounter processedSpans() { + LongCounter processedSpans = this.processedSpans; + if (processedSpans == null) { + synchronized (lock) { + processedSpans = this.processedSpans; + if (processedSpans == null) { + processedSpans = + meter() + .counterBuilder("otel.sdk.processor.span.processed") + .setUnit("span") + .setDescription( + "The number of spans for which the processing has finished, either successful or failed.") + .build(); + this.processedSpans = processedSpans; + } + } + } + return processedSpans; + } + + private Meter meter() { + if (meter == null) { + // Safe to call from multiple threads. + meter = meterProvider.get().get("io.opentelemetry.sdk.trace"); + } + return meter; + } } diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SimpleSpanProcessor.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SimpleSpanProcessor.java index 7d71f2a04db..da840263888 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SimpleSpanProcessor.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SimpleSpanProcessor.java @@ -21,6 +21,7 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Supplier; import java.util.logging.Level; import java.util.logging.Logger; @@ -75,7 +76,9 @@ public static SimpleSpanProcessorBuilder builder(SpanExporter exporter) { } SimpleSpanProcessor( - SpanExporter spanExporter, boolean exportUnsampledSpans, MeterProvider meterProvider) { + SpanExporter spanExporter, + boolean exportUnsampledSpans, + Supplier meterProvider) { this.spanExporter = requireNonNull(spanExporter, "spanExporter"); this.exportUnsampledSpans = exportUnsampledSpans; spanProcessorMetrics = @@ -110,7 +113,11 @@ public void onEnd(ReadableSpan span) { String error = null; if (!result.isSuccess()) { logger.log(Level.FINE, "Exporter failed"); - error = "export_failed"; + if (result.getFailureThrowable() != null) { + error = result.getFailureThrowable().getClass().getName(); + } else { + error = "export_failed"; + } } spanProcessorMetrics.finishSpans(1, error); }); diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SimpleSpanProcessorBuilder.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SimpleSpanProcessorBuilder.java index 7ad3ecf07ac..f98e3b032ba 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SimpleSpanProcessorBuilder.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SimpleSpanProcessorBuilder.java @@ -8,6 +8,7 @@ import static java.util.Objects.requireNonNull; import io.opentelemetry.api.metrics.MeterProvider; +import java.util.function.Supplier; /** * Builder class for {@link SimpleSpanProcessor}. @@ -16,7 +17,7 @@ */ public final class SimpleSpanProcessorBuilder { private final SpanExporter spanExporter; - private MeterProvider meterProvider = MeterProvider.noop(); + private Supplier meterProvider = MeterProvider::noop; private boolean exportUnsampledSpans = false; SimpleSpanProcessorBuilder(SpanExporter spanExporter) { @@ -37,12 +38,14 @@ public SimpleSpanProcessorBuilder setExportUnsampledSpans(boolean exportUnsample * href="https://opentelemetry.io/docs/specs/semconv/otel/sdk-metrics/#span-metrics">SDK Span * Metrics. */ - public SimpleSpanProcessorBuilder setMeterProvider(MeterProvider meterProvider) { + public SimpleSpanProcessorBuilder setMeterProvider(Supplier meterProvider) { requireNonNull(meterProvider, "meterProvider"); this.meterProvider = meterProvider; return this; } + // TODO: add `setInternalTelemetryVersion when we support more than one version + /** * Returns a new {@link SimpleSpanProcessor} with the configuration of this builder. * diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SpanProcessorMetrics.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SpanProcessorMetrics.java index b62b0b1b683..5a49621f5ed 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SpanProcessorMetrics.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SpanProcessorMetrics.java @@ -8,6 +8,7 @@ import io.opentelemetry.api.metrics.MeterProvider; import io.opentelemetry.sdk.common.InternalTelemetryVersion; import io.opentelemetry.sdk.internal.ComponentId; +import java.util.function.Supplier; import javax.annotation.Nullable; /** Metrics exported by span processors. */ @@ -16,7 +17,7 @@ interface SpanProcessorMetrics { static SpanProcessorMetrics get( InternalTelemetryVersion telemetryVersion, ComponentId componentId, - MeterProvider meterProvider) { + Supplier meterProvider) { switch (telemetryVersion) { case LEGACY: return new LegacySpanProcessorMetrics(meterProvider); @@ -31,9 +32,6 @@ static SpanProcessorMetrics get( /** Record metrics for spans processed, possibly with an error. */ void finishSpans(int count, @Nullable String error); - /** Registers a metric for processor queue capacity. */ - void buildQueueCapacityMetric(long capacity); - - /** Registers a metric for processor queue size. */ - void buildQueueSizeMetric(LongCallable queueSize); + /** Registers metrics for processor queue capacity and size. */ + void buildQueueMetricsOnce(long capacity, LongCallable getSize); } diff --git a/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkTracerProviderMetricsTest.java b/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkTracerProviderMetricsTest.java index 3ca9333c553..1e50f5d2716 100644 --- a/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkTracerProviderMetricsTest.java +++ b/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkTracerProviderMetricsTest.java @@ -60,8 +60,8 @@ void simple() { TracerProvider tracerProvider = SdkTracerProvider.builder() .addSpanProcessor( - SimpleSpanProcessor.builder(exporter).setMeterProvider(meterProvider).build()) - .setMeterProvider(meterProvider) + SimpleSpanProcessor.builder(exporter).setMeterProvider(() -> meterProvider).build()) + .setMeterProvider(() -> meterProvider) .setSampler(sampler) .build(); @@ -719,12 +719,12 @@ void batch() throws Exception { // Manually flush .setScheduleDelay(Duration.ofDays(1)) .setInternalTelemetryVersion(InternalTelemetryVersion.LATEST) - .setMeterProvider(meterProvider) + .setMeterProvider(() -> meterProvider) .build(); TracerProvider tracerProvider = SdkTracerProvider.builder() .addSpanProcessor(processor) - .setMeterProvider(meterProvider) + .setMeterProvider(() -> meterProvider) .setSampler(Sampler.alwaysOn()) .build(); @@ -921,8 +921,10 @@ void simpleExportError() { TracerProvider tracerProvider = SdkTracerProvider.builder() .addSpanProcessor( - SimpleSpanProcessor.builder(mockExporter).setMeterProvider(meterProvider).build()) - .setMeterProvider(meterProvider) + SimpleSpanProcessor.builder(mockExporter) + .setMeterProvider(() -> meterProvider) + .build()) + .setMeterProvider(() -> meterProvider) .setSampler(Sampler.alwaysOn()) .build(); From caf64d0e6f75ab83c86f6fee458b04ca006aaecb Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Thu, 18 Dec 2025 12:37:33 +0900 Subject: [PATCH 09/10] Rename --- .../sdk/trace/export/BatchSpanProcessor.java | 12 ++++++------ ....java => LegacySpanProcessorInstrumentation.java} | 4 ++-- ...java => SemConvSpanProcessorInstrumentation.java} | 5 +++-- .../sdk/trace/export/SimpleSpanProcessor.java | 9 +++++---- ...etrics.java => SpanProcessorInstrumentation.java} | 8 ++++---- 5 files changed, 20 insertions(+), 18 deletions(-) rename sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/{LegacySpanProcessorMetrics.java => LegacySpanProcessorInstrumentation.java} (95%) rename sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/{SemConvSpanProcessorMetrics.java => SemConvSpanProcessorInstrumentation.java} (94%) rename sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/{SpanProcessorMetrics.java => SpanProcessorInstrumentation.java} (80%) diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessor.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessor.java index 5e8603350e9..63d951a2eb5 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessor.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/BatchSpanProcessor.java @@ -161,7 +161,7 @@ public String toString() { // the data. private static final class Worker implements Runnable { - private final SpanProcessorMetrics spanProcessorMetrics; + private final SpanProcessorInstrumentation spanProcessorInstrumentation; private final SpanExporter spanExporter; private final long scheduleDelayNanos; @@ -201,17 +201,17 @@ private Worker( this.queue = queue; this.signal = new ArrayBlockingQueue<>(1); - spanProcessorMetrics = - SpanProcessorMetrics.get(telemetryVersion, COMPONENT_ID, meterProvider); + spanProcessorInstrumentation = + SpanProcessorInstrumentation.get(telemetryVersion, COMPONENT_ID, meterProvider); this.maxQueueSize = maxQueueSize; this.batch = new ArrayList<>(this.maxExportBatchSize); } private void addSpan(ReadableSpan span) { - spanProcessorMetrics.buildQueueMetricsOnce(maxQueueSize, queue::size); + spanProcessorInstrumentation.buildQueueMetricsOnce(maxQueueSize, queue::size); if (!queue.offer(span)) { - spanProcessorMetrics.dropSpans(1); + spanProcessorInstrumentation.dropSpans(1); } else { if (queueSize.incrementAndGet() >= spansNeeded.get()) { signal.offer(true); @@ -332,7 +332,7 @@ private void exportCurrentBatch() { logger.log(Level.WARNING, "Exporter threw an Exception", t); error = t.getClass().getName(); } finally { - spanProcessorMetrics.finishSpans(batch.size(), error); + spanProcessorInstrumentation.finishSpans(batch.size(), error); batch.clear(); } } diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/LegacySpanProcessorMetrics.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/LegacySpanProcessorInstrumentation.java similarity index 95% rename from sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/LegacySpanProcessorMetrics.java rename to sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/LegacySpanProcessorInstrumentation.java index e5b251cee5a..bdee2dedcaf 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/LegacySpanProcessorMetrics.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/LegacySpanProcessorInstrumentation.java @@ -15,7 +15,7 @@ import javax.annotation.Nullable; /** Span processor metrics defined before they were standardized in semconv. */ -final class LegacySpanProcessorMetrics implements SpanProcessorMetrics { +final class LegacySpanProcessorInstrumentation implements SpanProcessorInstrumentation { private static final AttributeKey SPAN_PROCESSOR_TYPE_LABEL = AttributeKey.stringKey("processorType"); private static final AttributeKey SPAN_PROCESSOR_DROPPED_LABEL = @@ -33,7 +33,7 @@ final class LegacySpanProcessorMetrics implements SpanProcessorMetrics { @Nullable private Meter meter; @Nullable private volatile LongCounter processedSpans; - LegacySpanProcessorMetrics(Supplier meterProvider) { + LegacySpanProcessorInstrumentation(Supplier meterProvider) { this.meterProvider = meterProvider; standardAttrs = diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SemConvSpanProcessorMetrics.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SemConvSpanProcessorInstrumentation.java similarity index 94% rename from sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SemConvSpanProcessorMetrics.java rename to sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SemConvSpanProcessorInstrumentation.java index ea561e49a97..a59ca5ff381 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SemConvSpanProcessorMetrics.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SemConvSpanProcessorInstrumentation.java @@ -20,7 +20,7 @@ * href="https://opentelemetry.io/docs/specs/semconv/otel/sdk-metrics/#span-metrics">semantic * conventions. */ -final class SemConvSpanProcessorMetrics implements SpanProcessorMetrics { +final class SemConvSpanProcessorInstrumentation implements SpanProcessorInstrumentation { private final Object lock = new Object(); private final AtomicBoolean builtQueueMetrics = new AtomicBoolean(false); @@ -32,7 +32,8 @@ final class SemConvSpanProcessorMetrics implements SpanProcessorMetrics { @Nullable private Meter meter; @Nullable private volatile LongCounter processedSpans; - SemConvSpanProcessorMetrics(ComponentId componentId, Supplier meterProvider) { + SemConvSpanProcessorInstrumentation( + ComponentId componentId, Supplier meterProvider) { this.meterProvider = meterProvider; standardAttrs = diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SimpleSpanProcessor.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SimpleSpanProcessor.java index da840263888..43f48b731c6 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SimpleSpanProcessor.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SimpleSpanProcessor.java @@ -46,7 +46,7 @@ public final class SimpleSpanProcessor implements SpanProcessor { private final Set pendingExports = Collections.newSetFromMap(new ConcurrentHashMap<>()); private final AtomicBoolean isShutdown = new AtomicBoolean(false); - private final SpanProcessorMetrics spanProcessorMetrics; + private final SpanProcessorInstrumentation spanProcessorInstrumentation; private final Object exporterLock = new Object(); @@ -81,8 +81,9 @@ public static SimpleSpanProcessorBuilder builder(SpanExporter exporter) { Supplier meterProvider) { this.spanExporter = requireNonNull(spanExporter, "spanExporter"); this.exportUnsampledSpans = exportUnsampledSpans; - spanProcessorMetrics = - SpanProcessorMetrics.get(InternalTelemetryVersion.LATEST, COMPONENT_ID, meterProvider); + spanProcessorInstrumentation = + SpanProcessorInstrumentation.get( + InternalTelemetryVersion.LATEST, COMPONENT_ID, meterProvider); } @Override @@ -119,7 +120,7 @@ public void onEnd(ReadableSpan span) { error = "export_failed"; } } - spanProcessorMetrics.finishSpans(1, error); + spanProcessorInstrumentation.finishSpans(1, error); }); } catch (RuntimeException e) { logger.log(Level.WARNING, "Exporter threw an Exception", e); diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SpanProcessorMetrics.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SpanProcessorInstrumentation.java similarity index 80% rename from sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SpanProcessorMetrics.java rename to sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SpanProcessorInstrumentation.java index 5a49621f5ed..5b9af2cc92a 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SpanProcessorMetrics.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SpanProcessorInstrumentation.java @@ -12,17 +12,17 @@ import javax.annotation.Nullable; /** Metrics exported by span processors. */ -interface SpanProcessorMetrics { +interface SpanProcessorInstrumentation { - static SpanProcessorMetrics get( + static SpanProcessorInstrumentation get( InternalTelemetryVersion telemetryVersion, ComponentId componentId, Supplier meterProvider) { switch (telemetryVersion) { case LEGACY: - return new LegacySpanProcessorMetrics(meterProvider); + return new LegacySpanProcessorInstrumentation(meterProvider); default: - return new SemConvSpanProcessorMetrics(componentId, meterProvider); + return new SemConvSpanProcessorInstrumentation(componentId, meterProvider); } } From 0012d26c42a2a074b12a0762b26f477a8b6836ff Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Thu, 18 Dec 2025 12:43:19 +0900 Subject: [PATCH 10/10] Cleanup --- .../OpenTelemetryConfigurationFactoryTest.java | 2 +- .../opentelemetry/sdk/internal/SemConvAttributes.java | 5 +++++ .../sdk/internal/SemConvAttributesTest.java | 5 +++++ .../io/opentelemetry/sdk/trace/SdkTracerMetrics.java | 10 ++-------- .../sdk/trace/SdkTracerProviderMetricsTest.java | 4 ++-- 5 files changed, 15 insertions(+), 11 deletions(-) diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactoryTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactoryTest.java index 49f6fbaf5e2..b71eafc1702 100644 --- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactoryTest.java +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactoryTest.java @@ -344,7 +344,7 @@ void create_Configured() throws NoSuchFieldException, IllegalAccessException { .extracting("sharedState") .extracting("activeSpanProcessor") .extracting("worker") - .extracting("spanProcessorMetrics") + .extracting("spanProcessorInstrumentation") .extracting("processedSpans") .isNull(); } diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/internal/SemConvAttributes.java b/sdk/common/src/main/java/io/opentelemetry/sdk/internal/SemConvAttributes.java index 1075d537604..cad85a0c341 100644 --- a/sdk/common/src/main/java/io/opentelemetry/sdk/internal/SemConvAttributes.java +++ b/sdk/common/src/main/java/io/opentelemetry/sdk/internal/SemConvAttributes.java @@ -33,4 +33,9 @@ private SemConvAttributes() {} AttributeKey.longKey("rpc.grpc.status_code"); public static final AttributeKey HTTP_RESPONSE_STATUS_CODE = AttributeKey.longKey("http.response.status_code"); + + public static final AttributeKey OTEL_SPAN_PARENT_ORIGIN = + AttributeKey.stringKey("otel.span.parent.origin"); + public static final AttributeKey OTEL_SPAN_SAMPLING_RESULT = + AttributeKey.stringKey("otel.span.sampling_result"); } diff --git a/sdk/common/src/test/java/io/opentelemetry/sdk/internal/SemConvAttributesTest.java b/sdk/common/src/test/java/io/opentelemetry/sdk/internal/SemConvAttributesTest.java index d4a5d5de1f5..d1d10007d3a 100644 --- a/sdk/common/src/test/java/io/opentelemetry/sdk/internal/SemConvAttributesTest.java +++ b/sdk/common/src/test/java/io/opentelemetry/sdk/internal/SemConvAttributesTest.java @@ -31,5 +31,10 @@ void testAttributeKeys() { .isEqualTo(RpcIncubatingAttributes.RPC_GRPC_STATUS_CODE); assertThat(SemConvAttributes.HTTP_RESPONSE_STATUS_CODE) .isEqualTo(HttpAttributes.HTTP_RESPONSE_STATUS_CODE); + + assertThat(SemConvAttributes.OTEL_SPAN_PARENT_ORIGIN) + .isEqualTo(OtelIncubatingAttributes.OTEL_SPAN_PARENT_ORIGIN); + assertThat(SemConvAttributes.OTEL_SPAN_SAMPLING_RESULT) + .isEqualTo(OtelIncubatingAttributes.OTEL_SPAN_SAMPLING_RESULT); } } diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerMetrics.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerMetrics.java index 916a249975a..1f4ce69a1c0 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerMetrics.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerMetrics.java @@ -5,9 +5,9 @@ package io.opentelemetry.sdk.trace; -import static io.opentelemetry.api.common.AttributeKey.stringKey; +import static io.opentelemetry.sdk.internal.SemConvAttributes.OTEL_SPAN_PARENT_ORIGIN; +import static io.opentelemetry.sdk.internal.SemConvAttributes.OTEL_SPAN_SAMPLING_RESULT; -import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.metrics.LongCounter; import io.opentelemetry.api.metrics.LongUpDownCounter; @@ -25,12 +25,6 @@ */ final class SdkTracerMetrics { - // Visible for testing - static final AttributeKey OTEL_SPAN_PARENT_ORIGIN = stringKey("otel.span.parent.origin"); - // Visible for testing - static final AttributeKey OTEL_SPAN_SAMPLING_RESULT = - stringKey("otel.span.sampling_result"); - private static final Attributes noParentDrop = Attributes.of( OTEL_SPAN_PARENT_ORIGIN, "none", OTEL_SPAN_SAMPLING_RESULT, SamplingDecision.DROP.name()); diff --git a/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkTracerProviderMetricsTest.java b/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkTracerProviderMetricsTest.java index 1e50f5d2716..db7175e5b34 100644 --- a/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkTracerProviderMetricsTest.java +++ b/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkTracerProviderMetricsTest.java @@ -8,9 +8,9 @@ import static io.opentelemetry.sdk.internal.SemConvAttributes.ERROR_TYPE; import static io.opentelemetry.sdk.internal.SemConvAttributes.OTEL_COMPONENT_NAME; import static io.opentelemetry.sdk.internal.SemConvAttributes.OTEL_COMPONENT_TYPE; +import static io.opentelemetry.sdk.internal.SemConvAttributes.OTEL_SPAN_PARENT_ORIGIN; +import static io.opentelemetry.sdk.internal.SemConvAttributes.OTEL_SPAN_SAMPLING_RESULT; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; -import static io.opentelemetry.sdk.trace.SdkTracerMetrics.OTEL_SPAN_PARENT_ORIGIN; -import static io.opentelemetry.sdk.trace.SdkTracerMetrics.OTEL_SPAN_SAMPLING_RESULT; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.when;