Repository navigation
Description
Since #143, esp_opentelemetry_tracing_setup() drops most spans on any app that ends more than a few spans a second. It also stalled the camera on dust-mite's car.
Symptom
dust-mite moved from bb5c90c to dd73720 (#143) and changed nothing else. Its car then streamed Camera frames over HTTP at about 18 spans a second (one camera.frame.send span per frame).
- Every build (base, all-features, profiling) logs
BatchSpanProcessor queue is full - dropping span.repeatedly, fromsdk/src/trace/batch_span_processor.cc:93. - After 15–30 s the camera stops delivering frames (
cam_hal: Failed to get frame: timeout), and dust-mite's smoke test fails on a 7 s gap with no frames.
The same car code on bb5c90c passes the smoke test, with no queue-full lines and no camera timeouts.
Cause
src/integration/esp_tracing.cpp sets max_queue_size (CONFIG_ESP_OPENTELEMETRY_BATCH_MAX_QUEUE_SIZE, default 64) and schedule_delay_millis. It leaves max_export_batch_size at the SDK default of 512. esp_logs.cpp does the same with CONFIG_ESP_OPENTELEMETRY_LOGS_BATCH_MAX_QUEUE_SIZE (128).
BatchSpanProcessorOptions documents that max_export_batch_size "must be smaller or equal to max_queue_size", and the OpenTelemetry SDK specification says the same. Our configuration has broken that rule all along, but it did no harm until #143 bumped opentelemetry-cpp to 7f93577b. That version includes open-telemetry/opentelemetry-cpp#4466 (a2739943, "BatchSpanProcessor: wait for a full batch instead of draining partial batches"):
- The worker's wait condition changed from
!buffer_.empty()tobuffer_.size() >= max_export_batch_size_. - With a queue of 64 and a batch of 512, that condition can never hold. The worker therefore exports only when
schedule_delay_millis(5000 ms) expires. - At 18 spans a second the queue fills in about 3.5 s. Spans are dropped, with a warning each, until the next scheduled export.
BatchLogRecordProcessor still drains partial batches, so logs are not affected yet. The same mismatch applies to them, though.
Verified fix
Adding only batch_options.max_export_batch_size = CONFIG_ESP_OPENTELEMETRY_BATCH_MAX_QUEUE_SIZE / 2; to esp_tracing.cpp on dd73720 makes dust-mite's smoke test pass on the car, with no queue-full lines and no camera timeouts.
Proposed change
- Add
CONFIG_ESP_OPENTELEMETRY_BATCH_MAX_EXPORT_BATCH_SIZEandCONFIG_ESP_OPENTELEMETRY_LOGS_BATCH_MAX_EXPORT_BATCH_SIZE, defaulting to half of each queue size. Pass them to the span and log processor options. - Reject a batch size larger than its queue at build time, with a
static_assertor a Kconfig range, rather than leaving it to the SDK. - Cover the case with a test that ends spans faster than
schedule_delay_millisallows and checks that none are dropped.
Upstream observations (not filed)
- opentelemetry-cpp does not validate or clamp
max_export_batch_sizeagainstmax_queue_size, so a bad configuration fails silently. The Java SDK throws and the Python SDK raisesValueError. - After #4466,
OnEndstill wakes the worker once the queue is half full (batch_span_processor.cc:100), but the new wait condition ignores that wake-up unless a full batch is ready. With a batch size equal to the queue size, which is allowed, nothing exports until the queue is completely full, and spans that arrive during the export are dropped.
Related: stale CMake cache after the bump
#143 renames opentelemetry-cpp's options to OTELCPP_*. An existing build directory still holds WITH_ETW:BOOL=OFF in CMakeCache.txt, and the reconfigure then fails with WITH_ETW is unavailable.OTELCPP_WITH_ETW is only supported on Windows (cmake/tools.cmake:450). Deleting CMakeCache.txt fixes it, and the compiled objects are still reused. Consider noting this in the release notes or README.
Activity
ltowarek commentedon Oct 21, 2026
Spec
This spec refines the issue above. Where it differs from the issue's "Proposed change" section, this spec takes precedence.
Problem Statement
An application that ends more spans per second than the queue can hold in one schedule delay loses most of its spans. The
BatchSpanProcessorexports only when a full batch is ready. The export batch size is left at the SDK default of 512, but the queue holds only 64, so a full batch never forms. Exports happen only when the 5 s schedule delay expires, and in the meantime the queue fills and every further span is dropped with a warning. On dust-mite's car, the dropping and warning at about 18 spans a second also stalled the camera.The log record processor has the same batch-larger-than-queue mismatch (512 against 128). It works today only because the logs SDK still drains partial batches.
Solution
The export batch size becomes a build-time setting for each batched signal, configured the same way as its queue size. It defaults to half of that signal's default queue size. Spans and log records then export as soon as a batch is ready, without waiting for the schedule delay, and nothing is dropped at rates the queue can absorb.
User Stories
BatchSpanProcessor queue is fullwarnings, so that my console isn't flooded and tracing doesn't slow my application down.Implementation Decisions
BatchSpanProcessoroptions asmax_export_batch_size. The logs setup does the same for theBatchLogRecordProcessor. The schedule delay settings don't change.static_assertnor a Kconfig range. Enforcement belongs in opentelemetry-cpp, which is separate work. A queue or batch size of 0 counts as a configuration error and is not guarded.SpanProcessororLogRecordProcessorare unaffected.Testing Decisions
signals_disabled,profiling_only,profiles_export_memory) and theirtest_qemu.shrunners in the CI matrix.Out of Scope
CMakeCache.txt/WITH_ETWreconfigure failure after the bump in Remove the instrument metadata validator workaround #143.Further Notes
dd73720: the smoke test passed, with no queue-full lines and no camera timeouts.ltowarek commentedon Oct 21, 2026
Spec: upstream opentelemetry-cpp clamps
max_export_batch_sizetomax_queue_sizeThis spec covers the upstream follow-up to the first item in "Upstream observations" above. It is separate from the fork fix that this issue proposes (Kconfig options, build-time check). The fork still needs that fix.
Problem Statement
In opentelemetry-cpp, a
BatchSpanProcessorconfigured with amax_export_batch_sizelarger than itsmax_queue_sizefails silently. The OpenTelemetry SDK specification states that the batch size must be less than or equal to the queue size, and the options' doc comments say the same, but the SDK neither validates nor corrects the value.Since open-telemetry/opentelemetry-cpp#4466, the worker waits for a full batch. If the batch is larger than the queue, a full batch can never be ready, so the worker exports only when
schedule_delay_millisexpires. Any application that ends spans faster than the queue size per schedule delay then drops spans, logging a "queue is full" warning for each. Nothing points at the configuration as the cause. On dust-mite's car, this dropped mostcamera.frame.sendspans and stalled the camera.Other SDKs do not accept this configuration:
BatchSpanProcessorBuilder.build()(L191-L197), and the same inBatchLogRecordProcessorBuilder.build()(L184-L189).ValueError("max_export_batch_size must be less than or equal to max_queue_size."):BatchSpanProcessor._validate_arguments(L287-L288), called from__init__(L183), and the same inBatchLogRecordProcessor._validate_arguments(L392-L393).opentelemetry-cpp must also work with exceptions disabled, so raising an error the way Python does is not an option.
Solution
When a
BatchSpanProcessoris constructed withmax_export_batch_sizelarger thanmax_queue_size, it usesmax_queue_sizeas the batch size. It also logs one internal warning that names both configured values and the value it uses instead. A valid configuration (batch size less than or equal to the queue size) is left unchanged and logs nothing.This matches the Java SDK, which clamps the batch size to the queue size and logs a warning (links above). It also follows a precedent within opentelemetry-cpp:
PeriodicExportingMetricReadercorrects an invalid timeout/interval pair at construction and logs a warning instead of failing.The value is clamped to
max_queue_size, not to some smaller value, because a batch size equal to the queue size is a valid configuration under the specification.User Stories
OTEL_BSP_*environment variables, I want the same correction, so that a bad environment cannot silently drop spans.main, so that the bug cannot come back unnoticed.Implementation Decisions
BatchSpanProcessor). The log processor is out of scope (see below).BatchSpanProcessorconstructors (options only, and options plus runtime options). Environment variables, the options struct and declarative configuration all go through these constructors, so no other place needs changing. The declarative-configuration parser and builders stay as they are.max_queue_size, and an internal warning is logged at construction. The SDK does not throw, abort or reset values to defaults. Resetting to defaults would raise the queue size and the memory it uses.const, so the clamp runs in each constructor's initializer list through a file-local helper. The helper returns the effective value and logs the warning. There is no new shared or public helper and no build-system change.[BatchSpanProcessor] max_export_batch_size (512) is larger than max_queue_size (64), using 64.max_export_batch_sizedoc comment on the span processor options, saying that larger values are clamped tomax_queue_sizewith a warning.mainwith no fetch. The PR description is drafted locally. Nothing is pushed or opened upstream until the maintainer decides to.Testing Decisions
frienddeclaration, and does not check the warning text.mainthis test fails, because nothing exports until the schedule delay expires.Out of Scope
BatchLogRecordProcessorhas the same mismatch, but it still exports partial batches ([SDK] BatchLogRecordProcessor still drains the queue in a tight loop, inconsistent with BatchSpanProcessor fix open-telemetry/opentelemetry-cpp#4498), so a clamp there can't be observed through its exporter yet. Follow up once #4498 lands.OnEndstill wakes the worker once the queue is half full, but the worker ignores that wake-up unless a full batch is ready. With a batch size equal to the queue size (valid, and the result of this clamp), the worker exports only when the queue is completely full. That leaves no headroom: spans that arrive while the worker is waking up are dropped. This is a behaviour change to #4466, so it needs its own issue and discussion.Further Notes
maxExportBatchSize"must be less than or equal to maxQueueSize".Bump third_party/opentelemetry-cpp to v1.30.0
Keep each export batch within its queue