From fb76c43307f0c97622cde10f1f9716c8cede554a Mon Sep 17 00:00:00 2001 From: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com> Date: Fri, 24 Jul 2026 16:03:42 +0100 Subject: [PATCH 1/3] feat(telemetry): correlate tx spans to the ledger being worked on Add shared current_ledger_seq / current_ledger_hash span attributes so a transaction's work can be joined to the ledger trace that produced it, and fix discrepancy D1 (txq.enqueue was a detached trace root). - Define current_ledger_seq / current_ledger_hash once in SpanNames.h and re-export via `using` from TxQ/TxApply/Tx span-name headers. These name the ledger being worked on (open/tentative apply or in-flight consensus build), distinct from ledger_seq (the built/validated ledger on ledger.build / consensus.round). Named after the RPC field ledger_current_index. - txq.enqueue: set current_ledger_seq/hash from the view, and parent the span to the caller's tx.process span via an explicit captured SpanContext (new trailing TxQ::apply param) instead of a detached root. The parent is explicit, not ambient-inherited, and the ScopedSpanGuard scope is RAII-bound to the synchronous apply, so it cannot leak onto a reused worker (D1 fix). On the open-ledger rebuild path no tx.process context exists, so it stays a root and the attribute provides the correlation. - tx.preclaim / tx.transactor: set both attributes from their ledger view. tx.preflight is stateless (no view) and is the documented exception. - tx.process / tx.receive: set current_ledger_seq from the current open ledger index at submit/receive time (no hash: not yet applied to a ledger). - Contract test pins the two new attribute key strings. Neither key is a spanmetrics dimension, so there is no metric-cardinality impact. Dashboards/collector/docs land on the later phases per the chain split. Co-Authored-By: Claude Opus 4.8 (1M context) --- include/xrpl/telemetry/SpanNames.h | 13 ++++++++ include/xrpl/tx/detail/TxApplySpanNames.h | 9 ++++++ src/libxrpl/tx/Transactor.cpp | 7 ++++ src/libxrpl/tx/applySteps.cpp | 32 +++++++++++++++++-- .../libxrpl/telemetry/TxApplySpanNames.cpp | 9 ++++++ src/xrpld/app/misc/NetworkOPs.cpp | 19 ++++++++++- src/xrpld/app/misc/TxQ.h | 4 ++- src/xrpld/app/misc/detail/TxQ.cpp | 26 +++++++++++++-- src/xrpld/app/misc/detail/TxQSpanNames.h | 10 ++++++ src/xrpld/overlay/detail/PeerImp.cpp | 6 ++++ src/xrpld/telemetry/TxSpanNames.h | 7 ++++ 11 files changed, 136 insertions(+), 6 deletions(-) diff --git a/include/xrpl/telemetry/SpanNames.h b/include/xrpl/telemetry/SpanNames.h index 41fe1720cf..d674875f85 100644 --- a/include/xrpl/telemetry/SpanNames.h +++ b/include/xrpl/telemetry/SpanNames.h @@ -130,6 +130,19 @@ inline constexpr auto networkType = join(join(seg::xrpl, seg::network), makeStr( inline constexpr auto txHash = makeStr("tx_hash"); inline constexpr auto peerId = makeStr("peer_id"); inline constexpr auto ledgerSeq = makeStr("ledger_seq"); + +/** + * Shared "ledger being worked on" attrs — the open/tentative or in-flight + * consensus-build ledger a transaction is applied into, NOT an established or + * validated ledger (that is `ledgerSeq`, set on ledger.build / consensus.round). + * Named after the RPC field `ledger_current_index` and the `currentLedgerSeq` + * log usage. Reused by the tx lifecycle, apply-pipeline, and TxQ spans so a + * transaction's work can be correlated to the ledger it targeted. + * `currentLedgerHash` is the current view's parent-ledger hash, which equals the + * consensus.round deterministic trace-id seed on the consensus-build path. + */ +inline constexpr auto currentLedgerSeq = makeStr("current_ledger_seq"); +inline constexpr auto currentLedgerHash = makeStr("current_ledger_hash"); } // namespace attr // ===== Shared attribute values ============================================= diff --git a/include/xrpl/tx/detail/TxApplySpanNames.h b/include/xrpl/tx/detail/TxApplySpanNames.h index 1228bd57ef..b44019fc62 100644 --- a/include/xrpl/tx/detail/TxApplySpanNames.h +++ b/include/xrpl/tx/detail/TxApplySpanNames.h @@ -101,6 +101,15 @@ inline constexpr auto transactor = join(seg::tx, op::transactor); // ===== Attribute keys ====================================================== namespace attr { +/** + * Shared "ledger being worked on" attrs (defined in SpanNames.h). Set on + * tx.preclaim and tx.transactor (both run against a view whose seq() is the + * ledger being applied into). tx.preflight is stateless (no view) and is the + * documented exception — it carries neither. + */ +using ::xrpl::telemetry::attr::currentLedgerHash; +using ::xrpl::telemetry::attr::currentLedgerSeq; + /** * "stage" — which apply-pipeline stage this span represents. Drives the * collector spanmetrics `stage` dimension for per-stage RED metrics. diff --git a/src/libxrpl/tx/Transactor.cpp b/src/libxrpl/tx/Transactor.cpp index 6a4eb6f3bb..f541d788cf 100644 --- a/src/libxrpl/tx/Transactor.cpp +++ b/src/libxrpl/tx/Transactor.cpp @@ -1607,6 +1607,13 @@ Transactor::operator()() span.setAttribute(telemetry::tx_apply_span::attr::stage, telemetry::tx_apply_span::val::apply); if (auto const* fmt = TxFormats::getInstance().findByType(ctx_.tx.getTxnType())) span.setAttribute(telemetry::tx_apply_span::attr::txType, fmt->getName().c_str()); + // The ledger being worked on (seq + parent hash) — correlates this apply + // stage to the ledger/consensus trace it is building into. + span.setAttribute( + telemetry::tx_apply_span::attr::currentLedgerSeq, static_cast(view().seq())); + span.setAttribute( + telemetry::tx_apply_span::attr::currentLedgerHash, + to_string(view().header().parentHash).c_str()); JLOG(j_.trace()) << "apply: " << ctx_.tx.getTransactionID(); diff --git a/src/libxrpl/tx/applySteps.cpp b/src/libxrpl/tx/applySteps.cpp index f982f447e3..f2f99e9536 100644 --- a/src/libxrpl/tx/applySteps.cpp +++ b/src/libxrpl/tx/applySteps.cpp @@ -78,9 +78,20 @@ txTypeName(TxType txnType) * @param name Full span name (tx_apply_span::preflight / ::preclaim). * @param stage Stage attribute value (tx_apply_span::val::*). * @param tx The transaction supplying the id and type. + * @param curLedgerSeq Seq of the ledger being worked on, set as the + * current_ledger_seq attribute. Passed by the + * view-bearing stages (preclaim); nullopt for the + * stateless preflight, which then omits it. + * @param curLedgerParentHash Parent hash of that ledger, set as + * current_ledger_hash. nullptr to omit. */ [[nodiscard]] telemetry::SpanGuard -makeStageSpan(std::string_view name, std::string_view stage, STTx const& tx) +makeStageSpan( + std::string_view name, + std::string_view stage, + STTx const& tx, + std::optional curLedgerSeq = std::nullopt, + uint256 const* curLedgerParentHash = nullptr) { auto const txID = tx.getTransactionID(); auto span = telemetry::SpanGuard::hashSpan( @@ -92,6 +103,17 @@ makeStageSpan(std::string_view name, std::string_view stage, STTx const& tx) span.setAttribute(telemetry::tx_apply_span::attr::stage, stage); if (char const* typeName = txTypeName(tx.getTxnType())) span.setAttribute(telemetry::tx_apply_span::attr::txType, typeName); + // The ledger being worked on — set only by the view-bearing stages + // (preclaim/transactor). Preflight is stateless and passes nullopt, so + // it carries no ledger attribute (documented exception). + if (curLedgerSeq) + span.setAttribute( + telemetry::tx_apply_span::attr::currentLedgerSeq, + static_cast(*curLedgerSeq)); + if (curLedgerParentHash) + span.setAttribute( + telemetry::tx_apply_span::attr::currentLedgerHash, + to_string(*curLedgerParentHash).c_str()); } return span; } @@ -224,8 +246,14 @@ static TER invokePreclaim(PreclaimContext const& ctx) { // Trace the preclaim stage under the transaction's deterministic trace_id. + // Preclaim has a ledger view, so tag the span with the ledger being worked + // on (seq + parent hash) to correlate it to the ledger/consensus trace. auto span = makeStageSpan( - telemetry::tx_apply_span::preclaim, telemetry::tx_apply_span::val::preclaim, ctx.tx); + telemetry::tx_apply_span::preclaim, + telemetry::tx_apply_span::val::preclaim, + ctx.tx, + ctx.view.seq(), + &ctx.view.header().parentHash); try { // use name hiding to accomplish compile-time polymorphism of static diff --git a/src/tests/libxrpl/telemetry/TxApplySpanNames.cpp b/src/tests/libxrpl/telemetry/TxApplySpanNames.cpp index 52c4bdab0e..ad6bb4efa8 100644 --- a/src/tests/libxrpl/telemetry/TxApplySpanNames.cpp +++ b/src/tests/libxrpl/telemetry/TxApplySpanNames.cpp @@ -51,3 +51,12 @@ TEST(TxApplySpanNames, stage_values_are_the_three_pipeline_stages) EXPECT_EQ(std::string_view(tx_apply_span::val::preclaim), "preclaim"); EXPECT_EQ(std::string_view(tx_apply_span::val::apply), "apply"); } + +TEST(TxApplySpanNames, current_ledger_keys_are_shared_constants) +{ + // "ledger being worked on" keys, re-exported from the base SpanNames.h so + // every emitter (tx.*, txq.*, apply pipeline) shares one string. Set on + // preclaim/transactor (view-bearing); preflight omits them (stateless). + EXPECT_EQ(std::string_view(tx_apply_span::attr::currentLedgerSeq), "current_ledger_seq"); + EXPECT_EQ(std::string_view(tx_apply_span::attr::currentLedgerHash), "current_ledger_hash"); +} diff --git a/src/xrpld/app/misc/NetworkOPs.cpp b/src/xrpld/app/misc/NetworkOPs.cpp index 54733cf3e0..b5d5218e21 100644 --- a/src/xrpld/app/misc/NetworkOPs.cpp +++ b/src/xrpld/app/misc/NetworkOPs.cpp @@ -1390,6 +1390,12 @@ NetworkOPsImp::processTransaction( auto span = std::make_shared(txProcessSpan(transaction->getID())); span->setAttribute(tx_span::attr::txHash, to_string(transaction->getID()).c_str()); span->setAttribute(tx_span::attr::local, bLocal); + // The current (open) ledger index at submission/relay time — the ledger + // being worked on. Correlates this tx.process to the ledger trace; the tx + // has not yet been applied to a specific ledger here, so there is no hash. + span->setAttribute( + tx_span::attr::currentLedgerSeq, + static_cast(ledgerMaster_.getCurrentLedgerIndex())); if (auto const& stx = transaction->getSTransaction()) { if (auto const* fmt = TxFormats::getInstance().findByType(stx->getTxnType())) @@ -1600,8 +1606,19 @@ NetworkOPsImp::apply(std::unique_lock& batchLock) if (e.failType == FailHard::Yes) flags |= TapFailHard; + // Parent the txq.enqueue span to this tx's tx.process span + // via an explicit captured context (the parent is explicit, + // not ambient-inherited). Null on the open-ledger rebuild + // path, where no tx.process span exists. + auto const txProcessCtx = + (e.span && *e.span) ? e.span->spanContext() : telemetry::SpanContext{}; auto const result = registry_.get().getTxQ().apply( - registry_.get().getApp(), view, e.transaction->getSTransaction(), flags, j); + registry_.get().getApp(), + view, + e.transaction->getSTransaction(), + flags, + j, + txProcessCtx.isValid() ? &txProcessCtx : nullptr); e.result = result.ter; e.applied = result.applied; changed = changed || result.applied; diff --git a/src/xrpld/app/misc/TxQ.h b/src/xrpld/app/misc/TxQ.h index 65b4e9778c..bbcbffecf3 100644 --- a/src/xrpld/app/misc/TxQ.h +++ b/src/xrpld/app/misc/TxQ.h @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -334,7 +335,8 @@ public: OpenView& view, std::shared_ptr const& tx, ApplyFlags flags, - beast::Journal j); + beast::Journal j, + telemetry::SpanContext const* parentCtx = nullptr); /** * Fill the new open ledger with transactions from the queue. diff --git a/src/xrpld/app/misc/detail/TxQ.cpp b/src/xrpld/app/misc/detail/TxQ.cpp index e0c988a418..016974a024 100644 --- a/src/xrpld/app/misc/detail/TxQ.cpp +++ b/src/xrpld/app/misc/detail/TxQ.cpp @@ -749,13 +749,35 @@ TxQ::apply( OpenView& view, std::shared_ptr const& tx, ApplyFlags flags, - beast::Journal j) + beast::Journal j, + telemetry::SpanContext const* parentCtx) { using namespace telemetry; - ScopedSpanGuard span(TraceCategory::Transactions, txq_span::prefix::txq, txq_span::op::enqueue); + // Parent the enqueue span to the caller's tx.process span when a context is + // supplied (submission path), using the explicit-context factory: the parent + // comes from the captured ctx rather than being inherited from whatever span + // is ambient, and this ScopedSpanGuard's scope is RAII-bounded to this fully + // synchronous call (no coroutine yield), so no unrelated parent leaks in and + // its scope cannot leak out onto a reused worker. + // On the open-ledger rebuild path parentCtx is null and this is a root; the + // current_ledger_seq attribute below correlates it to the ledger instead. + // A lambda (not a ternary) picks the factory: ScopedSpanGuard's move ctor is + // deleted, so guaranteed copy elision on each return is the only way to + // construct it conditionally. + auto span = [&]() -> ScopedSpanGuard { + if (parentCtx && parentCtx->isValid()) + return ScopedSpanGuard::childSpan(txq_span::enqueue, *parentCtx); + return ScopedSpanGuard( + TraceCategory::Transactions, txq_span::prefix::txq, txq_span::op::enqueue); + }(); span.setAttribute(txq_span::attr::txHash, to_string(tx->getTransactionID()).c_str()); if (auto const* fmt = TxFormats::getInstance().findByType(tx->getTxnType())) span.setAttribute(txq_span::attr::txType, fmt->getName().c_str()); + // The ledger being worked on (open/tentative apply or in-flight consensus + // build) — correlates this enqueue to the ledger trace in every context. + span.setAttribute(txq_span::attr::currentLedgerSeq, static_cast(view.seq())); + span.setAttribute( + txq_span::attr::currentLedgerHash, to_string(view.header().parentHash).c_str()); // Default outcome; overridden below on the direct-apply and queued paths. // Every other early return leaves the tx rejected from the queue. span.setAttribute(txq_span::attr::txqStatus, txq_span::val::rejected); diff --git a/src/xrpld/app/misc/detail/TxQSpanNames.h b/src/xrpld/app/misc/detail/TxQSpanNames.h index e062e361d1..6b64b76a57 100644 --- a/src/xrpld/app/misc/detail/TxQSpanNames.h +++ b/src/xrpld/app/misc/detail/TxQSpanNames.h @@ -73,12 +73,22 @@ inline constexpr auto acceptTx = makeStr("accept_tx"); inline constexpr auto cleanup = makeStr("cleanup"); } // namespace op +// ===== Full span names (prefix.op) =========================================== +// +// Joined "txq." names for the explicit-context factories +// (SpanGuard::childSpan(name, ctx)) that take one full span name rather than a +// prefix/suffix pair. + +inline constexpr auto enqueue = join(prefix::txq, op::enqueue); + // ===== Attribute keys ====================================================== namespace attr { /** * Canonical shared constants (defined in SpanNames.h). */ +using ::xrpl::telemetry::attr::currentLedgerHash; +using ::xrpl::telemetry::attr::currentLedgerSeq; using ::xrpl::telemetry::attr::ledgerSeq; using ::xrpl::telemetry::attr::txHash; diff --git a/src/xrpld/overlay/detail/PeerImp.cpp b/src/xrpld/overlay/detail/PeerImp.cpp index 2c7554377c..55398a87ab 100644 --- a/src/xrpld/overlay/detail/PeerImp.cpp +++ b/src/xrpld/overlay/detail/PeerImp.cpp @@ -1316,6 +1316,12 @@ PeerImp::handleTransaction( auto span = std::make_shared(txReceiveSpan(txID, *m)); span->setAttribute(tx_span::attr::txHash, to_string(txID).c_str()); span->setAttribute(tx_span::attr::peerId, static_cast(id_)); + // The current (open) ledger index when the relayed tx was received — + // the ledger being worked on. Correlates this tx.receive to the ledger + // trace; not yet applied to a specific ledger here, so no hash. + span->setAttribute( + tx_span::attr::currentLedgerSeq, + static_cast(app_.getLedgerMaster().getCurrentLedgerIndex())); if (auto const* fmt = TxFormats::getInstance().findByType(stx->getTxnType())) span->setAttribute(tx_span::attr::txType, fmt->getName().c_str()); if (auto const version = getVersion(); !version.empty()) diff --git a/src/xrpld/telemetry/TxSpanNames.h b/src/xrpld/telemetry/TxSpanNames.h index e8d75f17af..2a6aa9824c 100644 --- a/src/xrpld/telemetry/TxSpanNames.h +++ b/src/xrpld/telemetry/TxSpanNames.h @@ -50,6 +50,13 @@ namespace attr { using ::xrpl::telemetry::attr::peerId; using ::xrpl::telemetry::attr::txHash; +/** + * "current_ledger_seq" — the open/current ledger index at the time the tx is + * received or submitted (the ledger being worked on), NOT an established ledger. + * No parent-ledger hash here: these entry points have no ledger view. + */ +using ::xrpl::telemetry::attr::currentLedgerSeq; + /** * "local" — whether tx originated locally. */ From 17a1e1b142780cc7596f89ca382aba2b204e38ef Mon Sep 17 00:00:00 2001 From: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com> Date: Fri, 24 Jul 2026 16:08:41 +0100 Subject: [PATCH 2/3] docs(telemetry): document current_ledger_seq correlation in runbook Add current_ledger_seq / current_ledger_hash to the tx.process, tx.receive, and txq.enqueue span-reference rows, correct the txq.enqueue parent note (parents to tx.process on the submission path via explicit context; a root on the open-ledger rebuild path), and add a "Correlating a transaction to the ledger it was worked on" recipe joining the txID-keyed tx/txq spans to the ledger trace via current_ledger_seq. Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/telemetry-runbook.md | 50 +++++++++++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/docs/telemetry-runbook.md b/docs/telemetry-runbook.md index 264f9b6ed6..3d5d0d9e76 100644 --- a/docs/telemetry-runbook.md +++ b/docs/telemetry-runbook.md @@ -78,21 +78,25 @@ All spans instrumented in xrpld, grouped by subsystem: ### Transaction Spans -| Span Name | Source File | Attributes | Description | -| ------------ | -------------- | --------------------------------------------------------------------------------- | ------------------------------------- | -| `tx.process` | NetworkOPs.cpp | `tx_hash`, `local`, `path`, `tx_type`, `fee`, `sequence`, `ter_result`, `applied` | Transaction submission and processing | -| `tx.receive` | PeerImp.cpp | `peer_id`, `tx_hash`, `tx_type`, `peer_version`, `suppressed`, `tx_status` | Transaction received from peer relay | +| Span Name | Source File | Attributes | Description | +| ------------ | -------------- | ------------------------------------------------------------------------------------------------------- | ------------------------------------- | +| `tx.process` | NetworkOPs.cpp | `tx_hash`, `local`, `path`, `tx_type`, `fee`, `sequence`, `ter_result`, `applied`, `current_ledger_seq` | Transaction submission and processing | +| `tx.receive` | PeerImp.cpp | `peer_id`, `tx_hash`, `tx_type`, `peer_version`, `suppressed`, `tx_status`, `current_ledger_seq` | Transaction received from peer relay | + +`current_ledger_seq` is the current (open) ledger index at submit/receive time — +the ledger being worked on, not an established one. It lets a transaction's +lifecycle spans be joined to the ledger trace it targeted (`span.current_ledger_seq`). ### Transaction Queue Spans -| Span Name | Source File | Attributes | Description | -| ------------------ | ----------- | -------------------------------------------------------- | -------------------------------------------------- | -| `txq.enqueue` | TxQ.cpp | `tx_hash`, `tx_type` | Transaction enqueue decision (child of tx.process) | -| `txq.apply_direct` | TxQ.cpp | -- | Direct apply attempt (bypassing queue) | -| `txq.batch_clear` | TxQ.cpp | -- | Batch clear of queued transactions for an account | -| `txq.accept` | TxQ.cpp | `queue_size`, `ledger_changed` | Ledger-close accept loop over queued transactions | -| `txq.accept_tx` | TxQ.cpp | `tx_hash`, `retries_remaining`, `ter_code`, `txq_status` | Per-transaction apply during accept | -| `txq.cleanup` | TxQ.cpp | `ledger_seq` | Post-close cleanup of expired queue entries | +| Span Name | Source File | Attributes | Description | +| ------------------ | ----------- | ----------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `txq.enqueue` | TxQ.cpp | `tx_hash`, `tx_type`, `current_ledger_seq`, `current_ledger_hash` | Enqueue decision; parents to `tx.process` on the submission path (explicit context), a root on the open-ledger rebuild path — `current_ledger_seq` correlates it to the ledger in both cases | +| `txq.apply_direct` | TxQ.cpp | -- | Direct apply attempt (bypassing queue) | +| `txq.batch_clear` | TxQ.cpp | -- | Batch clear of queued transactions for an account | +| `txq.accept` | TxQ.cpp | `queue_size`, `ledger_changed` | Ledger-close accept loop over queued transactions | +| `txq.accept_tx` | TxQ.cpp | `tx_hash`, `retries_remaining`, `ter_code`, `txq_status` | Per-transaction apply during accept | +| `txq.cleanup` | TxQ.cpp | `ledger_seq` | Post-close cleanup of expired queue entries | ### PathFinding Spans @@ -275,6 +279,28 @@ This section shows what questions you can now answer using the enriched span att | "Was consensus outcome normal?" | `consensus.accept` | `consensus_state` | | "Did a validator bow out?" | `consensus.proposal.send` | `is_bow_out` | | "Which ledger was validated?" | `consensus.validation.send` | `ledger_hash` | +| "What tx work fed a given ledger?" | `tx.*` / `txq.*` | `current_ledger_seq` | + +### Correlating a transaction to the ledger it was worked on + +The `tx.process`, `tx.receive`, `txq.enqueue`, `tx.preclaim`, and `tx.transactor` +spans carry `current_ledger_seq` — the open/in-flight ledger they acted on (not +an established ledger). Because these spans are keyed on the transaction id (their +own trace) while the ledger/consensus spans are keyed on the ledger, use the +attribute to bridge the two id-spaces: + +``` +# All transaction-side work recorded against ledger N +{span.current_ledger_seq = N} + +# Join to the ledger build/consensus trace for the same ledger +{name="ledger.build" && span.ledger_seq = N} +``` + +`txq.enqueue` and the view-bearing apply stages also carry `current_ledger_hash` +(the current ledger's parent hash), which equals the `consensus.round` +deterministic trace-id seed on the consensus-build path. `tx.preflight` is +stateless and omits both attributes. --- From 21d9b2f8f98aa5d236839fb093f0a952e7555936 Mon Sep 17 00:00:00 2001 From: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com> Date: Fri, 24 Jul 2026 16:11:18 +0100 Subject: [PATCH 3/3] docs(telemetry): add current_ledger_seq/hash to 09 attribute inventory Document the two new ledger-correlation attributes in the data-collection reference: add them to the transaction and TxQ attribute tables (with per-span coverage), update the attribute count, and correct the txq.enqueue parent note (parents to tx.process on the submission path via explicit context; a root on the open-ledger rebuild path, correlated via current_ledger_seq). Co-Authored-By: Claude Opus 4.8 (1M context) --- .../09-data-collection-reference.md | 75 ++++++++++--------- 1 file changed, 40 insertions(+), 35 deletions(-) diff --git a/OpenTelemetryPlan/09-data-collection-reference.md b/OpenTelemetryPlan/09-data-collection-reference.md index 7f4b9e64c8..96002dff32 100644 --- a/OpenTelemetryPlan/09-data-collection-reference.md +++ b/OpenTelemetryPlan/09-data-collection-reference.md @@ -168,14 +168,14 @@ Controlled by `trace_rpc=1` in `[telemetry]` config (pathfinding spans fire with Controlled by `trace_transactions=1` in `[telemetry]` config. -| Span Name | Parent | Source File | Description | -| ------------------ | ------------- | ----------- | ---------------------------------------------------- | -| `txq.enqueue` | `tx.process` | TxQ.cpp | Queue admission decision (apply/queue/reject) | -| `txq.apply_direct` | `txq.enqueue` | TxQ.cpp | Direct application attempt (bypassing queue) | -| `txq.batch_clear` | `txq.enqueue` | TxQ.cpp | Batch clear of account's queued transactions | -| `txq.accept` | — | TxQ.cpp | Ledger-close accept loop (drain queued transactions) | -| `txq.accept.tx` | `txq.accept` | TxQ.cpp | Per-transaction apply within accept loop | -| `txq.cleanup` | — | TxQ.cpp | Post-close cleanup (expire old transactions) | +| Span Name | Parent | Source File | Description | +| ------------------ | ----------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `txq.enqueue` | `tx.process` (submission path; root on open-ledger rebuild) | TxQ.cpp | Queue admission decision (apply/queue/reject). Parents to `tx.process` via explicit context on submit; correlates via `current_ledger_seq` on all paths | +| `txq.apply_direct` | `txq.enqueue` | TxQ.cpp | Direct application attempt (bypassing queue) | +| `txq.batch_clear` | `txq.enqueue` | TxQ.cpp | Batch clear of account's queued transactions | +| `txq.accept` | — | TxQ.cpp | Ledger-close accept loop (drain queued transactions) | +| `txq.accept.tx` | `txq.accept` | TxQ.cpp | Per-transaction apply within accept loop | +| `txq.cleanup` | — | TxQ.cpp | Post-close cleanup (expire old transactions) | **Where to find**: Tempo → TraceQL: `{resource.service.name="xrpld" && name=~"txq.*"}` @@ -248,7 +248,7 @@ under an unrelated transaction's trace. --- -### 1.2 Complete Attribute Inventory (81 attributes) +### 1.2 Complete Attribute Inventory (83 attributes) > **See also**: [02-design-decisions.md §2.4.2](./02-design-decisions.md#242-span-attributes-by-category) for attribute design rationale and privacy considerations. @@ -270,21 +270,24 @@ Every span can carry key-value attributes that provide context for filtering and #### Transaction Attributes -| Attribute | Type | Set On | Description | -| -------------- | ------- | ---------------------------------------------- | --------------------------------------------------------------------- | -| `tx_hash` | string | `tx.process`, `tx.receive` | Transaction hash (hex-encoded) | -| `local` | boolean | `tx.process` | `true` if locally submitted, `false` if peer-relayed | -| `path` | string | `tx.process` | Submission path: `"sync"` or `"async"` | -| `suppressed` | boolean | `tx.receive` | `true` if transaction was suppressed (duplicate) | -| `tx_status` | string | `tx.receive` | Transaction status (e.g., `"known_bad"`) | -| `peer_id` | int64 | `tx.receive` | Peer identifier (also set on peer spans) | -| `peer_version` | string | `tx.receive` | Peer protocol version string | -| `stage` | string | `tx.preflight`, `tx.preclaim`, `tx.transactor` | Apply-pipeline stage: `preflight`, `preclaim`, or `apply` | -| `tx_type` | string | `tx.preflight`, `tx.preclaim`, `tx.transactor` | Transaction type name (e.g., `Payment`) | -| `ter_result` | string | `tx.preflight`, `tx.preclaim`, `tx.transactor` | Engine result token for that stage (e.g., `tesSUCCESS`, `terPRE_SEQ`) | -| `applied` | boolean | `tx.transactor` | `true` if the transaction was applied to the ledger | +| Attribute | Type | Set On | Description | +| --------------------- | ------- | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | +| `tx_hash` | string | `tx.process`, `tx.receive` | Transaction hash (hex-encoded) | +| `local` | boolean | `tx.process` | `true` if locally submitted, `false` if peer-relayed | +| `path` | string | `tx.process` | Submission path: `"sync"` or `"async"` | +| `suppressed` | boolean | `tx.receive` | `true` if transaction was suppressed (duplicate) | +| `tx_status` | string | `tx.receive` | Transaction status (e.g., `"known_bad"`) | +| `peer_id` | int64 | `tx.receive` | Peer identifier (also set on peer spans) | +| `peer_version` | string | `tx.receive` | Peer protocol version string | +| `stage` | string | `tx.preflight`, `tx.preclaim`, `tx.transactor` | Apply-pipeline stage: `preflight`, `preclaim`, or `apply` | +| `tx_type` | string | `tx.preflight`, `tx.preclaim`, `tx.transactor` | Transaction type name (e.g., `Payment`) | +| `ter_result` | string | `tx.preflight`, `tx.preclaim`, `tx.transactor` | Engine result token for that stage (e.g., `tesSUCCESS`, `terPRE_SEQ`) | +| `applied` | boolean | `tx.transactor` | `true` if the transaction was applied to the ledger | +| `current_ledger_seq` | int64 | `tx.process`, `tx.receive`, `tx.preclaim`, `tx.transactor` | Seq of the ledger being worked on (open/in-flight, not established) — joins the txID-keyed spans to the ledger trace | +| `current_ledger_hash` | string | `tx.preclaim`, `tx.transactor` | Parent hash of that ledger (= `consensus.round` trace-id seed on the build path). View-bearing stages only; `tx.preflight` omits both | **Tempo query**: `{span.tx_hash=""}` to trace a specific transaction across nodes. +Join a transaction's work to its ledger with `{span.current_ledger_seq=}`. **Prometheus label**: `xrpl_tx_local` (used as SpanMetrics dimension). @@ -305,19 +308,21 @@ Every span can carry key-value attributes that provide context for filtering and #### TxQ Attributes -| Attribute | Type | Set On | Description | -| -------------------- | ------- | ------------------------------ | ---------------------------------------------------------- | -| `tx_hash` | string | `txq.enqueue`, `txq.accept.tx` | Transaction hash in the queue | -| `txq_status` | string | `txq.enqueue` | Queue result: `"queued"`, `"applied_direct"`, `"rejected"` | -| `fee_level_paid` | int64 | `txq.enqueue` | Fee level paid by the transaction | -| `required_fee_level` | int64 | `txq.enqueue` | Minimum fee level required for queue admission | -| `queue_size` | int64 | `txq.accept` | Queue depth at start of accept | -| `ledger_changed` | boolean | `txq.accept` | Whether the open ledger changed since last accept | -| `ledger_seq` | int64 | `txq.cleanup` | Ledger sequence for cleanup | -| `expired_count` | int64 | `txq.cleanup` | Number of expired transactions removed | -| `ter_code` | string | `txq.accept.tx` | Transaction engine result code | -| `retries_remaining` | int64 | `txq.accept.tx` | Remaining retry attempts for this transaction | -| `num_cleared` | int64 | `txq.batch_clear` | Number of transactions cleared in batch | +| Attribute | Type | Set On | Description | +| --------------------- | ------- | ------------------------------ | -------------------------------------------------------------------------------- | +| `tx_hash` | string | `txq.enqueue`, `txq.accept.tx` | Transaction hash in the queue | +| `current_ledger_seq` | int64 | `txq.enqueue` | Seq of the ledger being worked on — correlates the enqueue to the ledger trace | +| `current_ledger_hash` | string | `txq.enqueue` | Parent hash of that ledger (= `consensus.round` trace-id seed on the build path) | +| `txq_status` | string | `txq.enqueue` | Queue result: `"queued"`, `"applied_direct"`, `"rejected"` | +| `fee_level_paid` | int64 | `txq.enqueue` | Fee level paid by the transaction | +| `required_fee_level` | int64 | `txq.enqueue` | Minimum fee level required for queue admission | +| `queue_size` | int64 | `txq.accept` | Queue depth at start of accept | +| `ledger_changed` | boolean | `txq.accept` | Whether the open ledger changed since last accept | +| `ledger_seq` | int64 | `txq.cleanup` | Ledger sequence for cleanup | +| `expired_count` | int64 | `txq.cleanup` | Number of expired transactions removed | +| `ter_code` | string | `txq.accept.tx` | Transaction engine result code | +| `retries_remaining` | int64 | `txq.accept.tx` | Remaining retry attempts for this transaction | +| `num_cleared` | int64 | `txq.batch_clear` | Number of transactions cleared in batch | **Tempo query**: `{span.txq_status="rejected"}` to find rejected queue attempts.