From b1f1e30450e8d088ba0957feab5a82f8728097bf Mon Sep 17 00:00:00 2001 From: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com> Date: Wed, 22 Jul 2026 13:10:21 +0100 Subject: [PATCH 1/3] fix(telemetry): scope pathfind.request/compute so children nest pathfind.request (PathFind.cpp) and pathfind.compute (PathRequest.cpp doUpdate) were left as unscoped SpanGuard after the type split, so pathfind.compute and pathfind.discover no longer nested under them. Both handlers run synchronously with no coroutine yield, so ScopedSpanGuard is safe and restores the request -> compute -> discover sub-tree. rpc.command.* stays unscoped (callMethod wraps doRipplePathFind which holds across a yield), so pathfind.request parents to rpc.process. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/xrpld/rpc/detail/PathRequest.cpp | 4 +++- src/xrpld/rpc/handlers/orderbook/PathFind.cpp | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/xrpld/rpc/detail/PathRequest.cpp b/src/xrpld/rpc/detail/PathRequest.cpp index 4fb11ab10c..28a6ad3c5c 100644 --- a/src/xrpld/rpc/detail/PathRequest.cpp +++ b/src/xrpld/rpc/detail/PathRequest.cpp @@ -739,7 +739,9 @@ PathRequest::doUpdate( { using namespace std::chrono; using namespace telemetry; - auto span = SpanGuard::span( + // Scoped so pathfind.discover (created synchronously below via findPaths) + // nests under it. doUpdate does not yield, so scoping is safe. + auto span = ScopedSpanGuard( TraceCategory::Rpc, pathfind_span::prefix::pathfind, pathfind_span::op::compute); span.setAttribute(pathfind_span::attr::fast, fast); span.setAttribute(pathfind_span::attr::destCurrency, to_string(saDstAmount_.asset()).c_str()); diff --git a/src/xrpld/rpc/handlers/orderbook/PathFind.cpp b/src/xrpld/rpc/handlers/orderbook/PathFind.cpp index f04756be15..bf7fdfe264 100644 --- a/src/xrpld/rpc/handlers/orderbook/PathFind.cpp +++ b/src/xrpld/rpc/handlers/orderbook/PathFind.cpp @@ -19,7 +19,9 @@ json::Value doPathFind(RPC::JsonContext& context) { using namespace telemetry; - auto span = SpanGuard::span( + // Scoped so pathfind.compute/discover (created synchronously below on this + // thread) nest under it. doPathFind does not yield, so scoping is safe. + auto span = ScopedSpanGuard( TraceCategory::Rpc, pathfind_span::prefix::pathfind, pathfind_span::op::request); // Addresses are hashed before emission for privacy. if (auto const& src = context.params[jss::source_account]; src.isString()) From beb5b2dddc1429669f9bfe91e13ecaf5cc2105dc Mon Sep 17 00:00:00 2001 From: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com> Date: Wed, 22 Jul 2026 13:11:53 +0100 Subject: [PATCH 2/3] fix(telemetry): scope ledger.build so tx.apply nests under it ledger.build was left as unscoped SpanGuard after the type split, so tx.apply (created synchronously during applyTxs on the same JtAccept worker) no longer nested under it. buildLedgerImpl runs synchronously with no yield, so ScopedSpanGuard is safe and restores the ledger.build -> tx.apply edge. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/xrpld/app/ledger/detail/BuildLedger.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/xrpld/app/ledger/detail/BuildLedger.cpp b/src/xrpld/app/ledger/detail/BuildLedger.cpp index d0e8967c09..482721bf6a 100644 --- a/src/xrpld/app/ledger/detail/BuildLedger.cpp +++ b/src/xrpld/app/ledger/detail/BuildLedger.cpp @@ -50,7 +50,9 @@ buildLedgerImpl( ApplyTxs&& applyTxs) { using namespace telemetry; - auto buildSpan = SpanGuard::span(TraceCategory::Ledger, seg::ledger, ledger_span::op::build); + // Scoped so tx.apply (created synchronously below during applyTxs on this + // thread) nests under it. buildLedgerImpl runs synchronously with no yield. + auto buildSpan = ScopedSpanGuard(TraceCategory::Ledger, seg::ledger, ledger_span::op::build); auto built = std::make_shared(*parent, closeTime); From 2f627e8e8e0b64e9b4dedd906b87df8eb5a7702f Mon Sep 17 00:00:00 2001 From: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com> Date: Wed, 22 Jul 2026 13:12:55 +0100 Subject: [PATCH 3/3] =?UTF-8?q?docs(telemetry):=2009-doc=20=E2=80=94=20pat?= =?UTF-8?q?hfind.request=20parents=20to=20rpc.process,=20not=20rpc.command?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reflects the C1 fix: rpc.command.* stays unscoped (its dispatch wraps doRipplePathFind which yields), so pathfind.request nests under rpc.process. The request -> compute -> discover sub-tree nests correctly via ScopedSpanGuard. Co-Authored-By: Claude Opus 4.8 (1M context) --- OpenTelemetryPlan/09-data-collection-reference.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/OpenTelemetryPlan/09-data-collection-reference.md b/OpenTelemetryPlan/09-data-collection-reference.md index 710a5a4d17..87f6e97666 100644 --- a/OpenTelemetryPlan/09-data-collection-reference.md +++ b/OpenTelemetryPlan/09-data-collection-reference.md @@ -136,12 +136,19 @@ Controlled by `trace_rpc=1` in `[telemetry]` config (pathfinding spans fire with | Span Name | Parent | Source File | Description | | --------------------- | ------------------ | ---------------- | -------------------------------------------------------- | -| `pathfind.request` | `rpc.command.*` | PathRequests.cpp | RPC entry for path_find / ripple_path_find | +| `pathfind.request` | `rpc.process` | PathFind.cpp | RPC entry for path_find (doPathFind) | | `pathfind.compute` | `pathfind.request` | PathRequest.cpp | Single path computation (doUpdate) | | `pathfind.update_all` | — | PathRequests.cpp | Async recomputation of all active path requests on close | | `pathfind.discover` | `pathfind.compute` | Pathfinder.cpp | Graph exploration phase (Pathfinder::find) | | `pathfind.rank` | `pathfind.compute` | Pathfinder.cpp | Path ranking and selection phase | +> **Note**: `pathfind.request` parents to `rpc.process`, not `rpc.command.*`. +> `rpc.command.*` is intentionally unscoped: its generic dispatch (`callMethod`) +> also wraps `doRipplePathFind`, whose span is held across a coroutine +> `yield()` — scoping it would risk a wrong-thread scope pop on resume. The +> `pathfind.request → compute → discover` sub-tree nests correctly because those +> handlers run synchronously (no yield) and use `ScopedSpanGuard`. + **Where to find**: Tempo → TraceQL: `{resource.service.name="xrpld" && name=~"pathfind.*"}` **Grafana dashboard**: _RPC & Pathfinding (StatsD)_ (`xrpld-statsd-rpc`) for StatsD timers; span-derived metrics via _RPC Performance_ (`rpc-performance`)