All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.opentelemetry.sdk.trace.export.BatchSpanProcessor Maven / Gradle / Ivy

There is a newer version: 1.48.0
Show newest version
/*
 * Copyright The OpenTelemetry Authors
 * SPDX-License-Identifier: Apache-2.0
 */

package io.opentelemetry.sdk.trace.export;

import io.opentelemetry.api.common.Labels;
import io.opentelemetry.api.metrics.GlobalMetricsProvider;
import io.opentelemetry.api.metrics.LongCounter;
import io.opentelemetry.api.metrics.LongCounter.BoundLongCounter;
import io.opentelemetry.api.metrics.Meter;
import io.opentelemetry.context.Context;
import io.opentelemetry.sdk.common.CompletableResultCode;
import io.opentelemetry.sdk.internal.DaemonThreadFactory;
import io.opentelemetry.sdk.trace.ReadWriteSpan;
import io.opentelemetry.sdk.trace.ReadableSpan;
import io.opentelemetry.sdk.trace.SpanProcessor;
import io.opentelemetry.sdk.trace.data.SpanData;
import java.util.ArrayList;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Implementation of the {@link SpanProcessor} that batches spans exported by the SDK then pushes
 * them to the exporter pipeline.
 *
 * 

All spans reported by the SDK implementation are first added to a synchronized queue (with a * {@code maxQueueSize} maximum size, if queue is full spans are dropped). Spans are exported either * when there are {@code maxExportBatchSize} pending spans or {@code scheduleDelayNanos} has passed * since the last export finished. * *

This batch {@link SpanProcessor} can cause high contention in a very high traffic service. * TODO: Add a link to the SpanProcessor that uses Disruptor as alternative with low contention. * *

Configuration options for {@link BatchSpanProcessor} can be read from system properties, * environment variables, or {@link java.util.Properties} objects. * *

For system properties and {@link java.util.Properties} objects, {@link BatchSpanProcessor} * will look for the following names: * *

    *
  • {@code otel.bsp.schedule.delay}: sets the delay interval between two consecutive exports. *
  • {@code otel.bsp.max.queue}: sets the maximum queue size. *
  • {@code otel.bsp.max.export.batch}: sets the maximum batch size. *
  • {@code otel.bsp.export.timeout}: sets the maximum allowed time to export data. *
  • {@code otel.bsp.export.sampled}: sets whether only sampled spans should be exported. *
* *

For environment variables, {@link BatchSpanProcessor} will look for the following names: * *

    *
  • {@code OTEL_BSP_SCHEDULE_DELAY}: sets the delay interval between two consecutive exports. *
  • {@code OTEL_BSP_MAX_QUEUE}: sets the maximum queue size. *
  • {@code OTEL_BSP_MAX_EXPORT_BATCH}: sets the maximum batch size. *
  • {@code OTEL_BSP_EXPORT_TIMEOUT}: sets the maximum allowed time to export data. *
  • {@code OTEL_BSP_EXPORT_SAMPLED}: sets whether only sampled spans should be exported. *
*/ public final class BatchSpanProcessor implements SpanProcessor { private static final String WORKER_THREAD_NAME = BatchSpanProcessor.class.getSimpleName() + "_WorkerThread"; private static final String SPAN_PROCESSOR_TYPE_LABEL = "spanProcessorType"; private static final String SPAN_PROCESSOR_TYPE_VALUE = BatchSpanProcessor.class.getSimpleName(); private final Worker worker; private final boolean sampled; private final AtomicBoolean isShutdown = new AtomicBoolean(false); /** * Returns a new Builder for {@link BatchSpanProcessor}. * * @param spanExporter the {@code SpanExporter} to where the Spans are pushed. * @return a new {@link BatchSpanProcessor}. * @throws NullPointerException if the {@code spanExporter} is {@code null}. */ public static BatchSpanProcessorBuilder builder(SpanExporter spanExporter) { return new BatchSpanProcessorBuilder(spanExporter); } BatchSpanProcessor( SpanExporter spanExporter, boolean sampled, long scheduleDelayNanos, int maxQueueSize, int maxExportBatchSize, long exporterTimeoutNanos) { this.worker = new Worker( spanExporter, scheduleDelayNanos, maxExportBatchSize, exporterTimeoutNanos, new ArrayBlockingQueue<>(maxQueueSize)); Thread workerThread = new DaemonThreadFactory(WORKER_THREAD_NAME).newThread(worker); workerThread.start(); this.sampled = sampled; } @Override public void onStart(Context parentContext, ReadWriteSpan span) {} @Override public boolean isStartRequired() { return false; } @Override public void onEnd(ReadableSpan span) { if (sampled && !span.getSpanContext().isSampled()) { return; } worker.addSpan(span); } @Override public boolean isEndRequired() { return true; } @Override public CompletableResultCode shutdown() { if (isShutdown.getAndSet(true)) { return CompletableResultCode.ofSuccess(); } return worker.shutdown(); } @Override public CompletableResultCode forceFlush() { return worker.forceFlush(); } // Worker is a thread that batches multiple spans and calls the registered SpanExporter to export // the data. private static final class Worker implements Runnable { private final BoundLongCounter droppedSpans; private final BoundLongCounter exportedSpans; private static final Logger logger = Logger.getLogger(Worker.class.getName()); private final SpanExporter spanExporter; private final long scheduleDelayNanos; private final int maxExportBatchSize; private final long exporterTimeoutNanos; private long nextExportTime; private final BlockingQueue queue; private final AtomicReference flushRequested = new AtomicReference<>(); private volatile boolean continueWork = true; private final ArrayList batch; private Worker( SpanExporter spanExporter, long scheduleDelayNanos, int maxExportBatchSize, long exporterTimeoutNanos, BlockingQueue queue) { this.spanExporter = spanExporter; this.scheduleDelayNanos = scheduleDelayNanos; this.maxExportBatchSize = maxExportBatchSize; this.exporterTimeoutNanos = exporterTimeoutNanos; this.queue = queue; Meter meter = GlobalMetricsProvider.getMeter("io.opentelemetry.sdk.trace"); meter .longValueObserverBuilder("queueSize") .setDescription("The number of spans queued") .setUnit("1") .setUpdater( result -> result.observe( queue.size(), Labels.of(SPAN_PROCESSOR_TYPE_LABEL, SPAN_PROCESSOR_TYPE_VALUE))) .build(); LongCounter processedSpansCounter = meter .longCounterBuilder("processedSpans") .setUnit("1") .setDescription( "The number of spans processed by the BatchSpanProcessor. " + "[dropped=true if they were dropped due to high throughput]") .build(); droppedSpans = processedSpansCounter.bind( Labels.of(SPAN_PROCESSOR_TYPE_LABEL, SPAN_PROCESSOR_TYPE_VALUE, "dropped", "true")); exportedSpans = processedSpansCounter.bind( Labels.of(SPAN_PROCESSOR_TYPE_LABEL, SPAN_PROCESSOR_TYPE_VALUE, "dropped", "false")); this.batch = new ArrayList<>(this.maxExportBatchSize); } private void addSpan(ReadableSpan span) { if (!queue.offer(span)) { droppedSpans.add(1); } } @Override public void run() { updateNextExportTime(); while (continueWork) { if (flushRequested.get() != null) { flush(); } try { ReadableSpan lastElement = queue.poll(100, TimeUnit.MILLISECONDS); if (lastElement != null) { batch.add(lastElement.toSpanData()); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); return; } if (batch.size() >= maxExportBatchSize || System.nanoTime() >= nextExportTime) { exportCurrentBatch(); updateNextExportTime(); } } } private void flush() { int spansToFlush = queue.size(); while (spansToFlush > 0) { ReadableSpan span = queue.poll(); assert span != null; batch.add(span.toSpanData()); spansToFlush--; if (batch.size() >= maxExportBatchSize) { exportCurrentBatch(); } } exportCurrentBatch(); flushRequested.get().succeed(); flushRequested.set(null); } private void updateNextExportTime() { nextExportTime = System.nanoTime() + scheduleDelayNanos; } private CompletableResultCode shutdown() { final CompletableResultCode result = new CompletableResultCode(); final CompletableResultCode flushResult = forceFlush(); flushResult.whenComplete( () -> { continueWork = false; final CompletableResultCode shutdownResult = spanExporter.shutdown(); shutdownResult.whenComplete( () -> { if (!flushResult.isSuccess() || !shutdownResult.isSuccess()) { result.fail(); } else { result.succeed(); } }); }); return result; } private CompletableResultCode forceFlush() { CompletableResultCode flushResult = new CompletableResultCode(); this.flushRequested.compareAndSet(null, flushResult); return this.flushRequested.get(); } private void exportCurrentBatch() { if (batch.isEmpty()) { return; } try { final CompletableResultCode result = spanExporter.export(batch); result.join(exporterTimeoutNanos, TimeUnit.NANOSECONDS); if (result.isSuccess()) { exportedSpans.add(batch.size()); } else { logger.log(Level.FINE, "Exporter failed"); } } catch (Exception e) { logger.log(Level.WARNING, "Exporter threw an Exception", e); } finally { batch.clear(); } } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy