From 7ada57e2a84651d62b23ac86d3b13a12b3ad2e03 Mon Sep 17 00:00:00 2001 From: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com> Date: Thu, 14 May 2026 15:53:59 +0100 Subject: [PATCH] fix(telemetry): map TraceCategory to OTel SpanKind in SpanGuard::span() SpanGuard::span() hardcoded SpanKind::kInternal for every span. Tempo's service-graph and spanmetrics RED calculations rely on kServer / kConsumer / kClient / kProducer to classify inbound vs outbound vs internal operations. With kInternal everywhere, the service graph collapses to a single self-loop and RED metrics attribute all latency to internal work. Add categoryToSpanKind() mapping: - Rpc -> kServer (inbound synchronous request) - Peer -> kConsumer (inbound async peer message) - Transactions -> kInternal - Consensus -> kInternal - Ledger -> kInternal Only the single-argument overload is affected; childSpan / linkedSpan continue to default to kInternal because they represent in-process continuations of an already-kinded parent. --- src/libxrpl/telemetry/SpanGuard.cpp | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/libxrpl/telemetry/SpanGuard.cpp b/src/libxrpl/telemetry/SpanGuard.cpp index 22c210c7c5..11eda56ed6 100644 --- a/src/libxrpl/telemetry/SpanGuard.cpp +++ b/src/libxrpl/telemetry/SpanGuard.cpp @@ -132,6 +132,32 @@ isCategoryEnabled(Telemetry const& tel, TraceCategory cat) return false; // unreachable, silences compiler warning } +namespace { + +// Map a TraceCategory to an OTel SpanKind so Tempo's service-graph / +// RED metrics see the correct direction. RPC spans are emitted at the +// server entry point (handler dispatch), Peer spans at inbound-message +// receipt. Transactions / Consensus / Ledger are internal processing +// and keep the default kInternal. +otel_trace::SpanKind +categoryToSpanKind(TraceCategory cat) +{ + switch (cat) + { + case TraceCategory::Rpc: + return otel_trace::SpanKind::kServer; + case TraceCategory::Peer: + return otel_trace::SpanKind::kConsumer; + case TraceCategory::Transactions: + case TraceCategory::Consensus: + case TraceCategory::Ledger: + return otel_trace::SpanKind::kInternal; + } + return otel_trace::SpanKind::kInternal; // unreachable +} + +} // namespace + SpanGuard SpanGuard::span(TraceCategory cat, std::string_view prefix, std::string_view name) { @@ -139,7 +165,7 @@ SpanGuard::span(TraceCategory cat, std::string_view prefix, std::string_view nam if (!tel || !tel->isEnabled() || !isCategoryEnabled(*tel, cat)) return {}; auto fullName = std::string(prefix) + "." + std::string(name); - return SpanGuard(std::make_unique(tel->startSpan(fullName))); + return SpanGuard(std::make_unique(tel->startSpan(fullName, categoryToSpanKind(cat)))); } // ===== Child / linked span creation ========================================