Merge branch 'pratik/otel-phase2-rpc-tracing' into pratik/otel-phase3-tx-tracing

Brings M1 (dropped-deterministic-id counter) + M2 (childSpan doc) forward.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Pratik Mankawde
2026-07-22 23:14:41 +01:00
3 changed files with 51 additions and 4 deletions

View File

@@ -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

View File

@@ -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.
*/

View File

@@ -22,6 +22,7 @@
#include <opentelemetry/trace/trace_id.h>
#include <array>
#include <atomic>
#include <cstdint>
#include <optional>
@@ -42,6 +43,14 @@ thread_local std::optional<std::array<std::uint8_t, 16>> 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<std::uint64_t> 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