diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-trace.txt b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-trace.txt index 3436b94c676..f46fcbc2d8b 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-trace.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-trace.txt @@ -1,2 +1,8 @@ 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 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() 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..4fe65b63612 --- /dev/null +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerMetrics.java @@ -0,0 +1,85 @@ +/* + * 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. + */ +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/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/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..94126fb5e99 --- /dev/null +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/export/SpanProcessorMetrics.java @@ -0,0 +1,43 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.trace.export; + +import io.opentelemetry.api.metrics.MeterProvider; +import io.opentelemetry.sdk.common.InternalTelemetryVersion; +import io.opentelemetry.sdk.internal.ComponentId; +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 LegacySpanProcessorMetrics(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); +} 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)); + } +}