diff --git a/include/xrpl/telemetry/DeterministicIdGenerator.h b/include/xrpl/telemetry/DeterministicIdGenerator.h index ba351d233e..60d4aa5c4a 100644 --- a/include/xrpl/telemetry/DeterministicIdGenerator.h +++ b/include/xrpl/telemetry/DeterministicIdGenerator.h @@ -167,6 +167,22 @@ public: operator=(PendingTraceId&&) = delete; }; +/** + * Running count of deterministic trace_ids that were pinned by a + * PendingTraceId but never consumed by a forced-root span before the guard was + * destroyed. Bumped once per silent drop. + * + * ~PendingTraceId also asserts on this condition, but the assert is a no-op in + * release builds, so this monotonic counter is the only signal that a + * deterministic trace root was lost there. It is process-wide (summed across + * all threads) and intended for a future diagnostic metric or debugger + * inspection; reading it has no side effects. + * + * @return the total number of unconsumed deterministic trace_id drops so far. + */ +[[nodiscard]] std::uint64_t +unconsumedDeterministicIdDrops() noexcept; + } // namespace xrpl::telemetry #endif // XRPL_ENABLE_TELEMETRY diff --git a/include/xrpl/telemetry/SpanGuard.h b/include/xrpl/telemetry/SpanGuard.h index b8f4166ce5..63a8435cb7 100644 --- a/include/xrpl/telemetry/SpanGuard.h +++ b/include/xrpl/telemetry/SpanGuard.h @@ -329,10 +329,19 @@ public: // --- Child / linked span creation ---------------------------------- /** - * Create a child span parented to the current thread's active - * context. This is meaningful when this guard's span is active on - * the thread (e.g. wrapped in a ScopedSpanGuard); otherwise the - * child inherits whatever context is currently on the stack. + * Create a child span parented to the CURRENT ambient context of this + * store, not to this guard's own span. It reads the active context via the + * runtime context store, so it is meaningful only when a scope is active on + * THIS store — a ScopedSpanGuard, a ScopedActivation, or this guard's span + * activated some other way. With coroutine-aware storage the ambient + * context is the correct parent in scoped and coroutine contexts (a + * coroutine keeps its store across a resume on another worker). When no + * scope is active the child inherits whatever context happens to be on the + * store, which may be unrelated. + * + * For explicit, store-independent parenting to a specific span (e.g. + * across stores or threads), capture spanContext() and use + * childSpan(name, ctx) instead. * @param name Span name for the child. * @return A new guard, or null if this guard is inactive. */ diff --git a/src/libxrpl/telemetry/DeterministicIdGenerator.cpp b/src/libxrpl/telemetry/DeterministicIdGenerator.cpp index ff54307d1b..c21bdcee5e 100644 --- a/src/libxrpl/telemetry/DeterministicIdGenerator.cpp +++ b/src/libxrpl/telemetry/DeterministicIdGenerator.cpp @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -42,6 +43,14 @@ thread_local std::optional> gTlsPendingTraceId; */ thread_local bool gTlsPendingConsumed = false; +/** + * Process-wide count of deterministic trace_ids dropped without being consumed. + * Bumped in ~PendingTraceId when the pending id was never taken by a root span. + * Atomic because it is summed across all threads; relaxed ordering is enough + * for a diagnostic tally. Exposed via unconsumedDeterministicIdDrops(). + */ +std::atomic gUnconsumedDeterministicIdDrops{0}; + } // namespace DeterministicIdGenerator::DeterministicIdGenerator() @@ -84,10 +93,23 @@ PendingTraceId::~PendingTraceId() noexcept XRPL_ASSERT( gTlsPendingConsumed, "xrpl::telemetry::PendingTraceId : deterministic trace_id was not consumed"); + if (!gTlsPendingConsumed) + { + // The assert above is stripped in release, so bump a counter too: a + // dropped deterministic trace root stays observable via + // unconsumedDeterministicIdDrops(). Relaxed: this is a diagnostic tally. + gUnconsumedDeterministicIdDrops.fetch_add(1, std::memory_order_relaxed); + } gTlsPendingTraceId.reset(); // never leak, even in release gTlsPendingConsumed = false; } +std::uint64_t +unconsumedDeterministicIdDrops() noexcept +{ + return gUnconsumedDeterministicIdDrops.load(std::memory_order_relaxed); +} + } // namespace xrpl::telemetry #endif // XRPL_ENABLE_TELEMETRY