From faaec003f46e7d9272b1a010ad92b41fec434fe5 Mon Sep 17 00:00:00 2001 From: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com> Date: Wed, 13 May 2026 16:27:55 +0100 Subject: [PATCH 1/6] docs(telemetry): update plan docs for simplified RPC/gRPC attr naming Update OpenTelemetryPlan docs and Telemetry.h doc example to reflect the renamed per-span attributes: xrpl.rpc.command -> command, xrpl.rpc.status -> rpc_status, xrpl.grpc.method -> method, etc. Co-Authored-By: Claude Opus 4.6 (1M context) --- OpenTelemetryPlan/01-architecture-analysis.md | 8 ++--- OpenTelemetryPlan/02-design-decisions.md | 8 ++--- .../03-implementation-strategy.md | 4 +-- OpenTelemetryPlan/04-code-samples.md | 8 ++--- .../05-configuration-reference.md | 8 ++--- OpenTelemetryPlan/POC_taskList.md | 34 +++++++++---------- include/xrpl/telemetry/Telemetry.h | 2 +- 7 files changed, 36 insertions(+), 36 deletions(-) diff --git a/OpenTelemetryPlan/01-architecture-analysis.md b/OpenTelemetryPlan/01-architecture-analysis.md index c62ac3454c..fde1833349 100644 --- a/OpenTelemetryPlan/01-architecture-analysis.md +++ b/OpenTelemetryPlan/01-architecture-analysis.md @@ -247,14 +247,14 @@ flowchart TB subgraph request["rpc.request (root span)"] http["HTTP Request — POST /
traceparent:
00-abc123...-def456...-01"] - attrs["Attributes:
http.method = POST
net.peer.ip = 192.168.1.100
xrpl.rpc.command = submit"] + attrs["Attributes:
http.method = POST
net.peer.ip = 192.168.1.100
command = submit"] subgraph enqueue["jobqueue.enqueue"] job_attr["xrpl.job.type = jtCLIENT_RPC"] end subgraph command["rpc.command.submit"] - cmd_attrs["xrpl.rpc.version = 2
xrpl.rpc.role = user"] + cmd_attrs["version = 2
rpc_role = user"] cmd_children["├── tx.deserialize
├── tx.validate_local
└── tx.submit_to_network"] end @@ -359,7 +359,7 @@ After implementing OpenTelemetry, operators and developers will gain visibility | **Transaction Lifecycle** | Full journey from RPC submission through validation, relay, consensus, and ledger inclusion | `{service.name="xrpld" && xrpl.tx.hash="ABC123..."}` | | **Cross-Node Propagation** | Transaction path across multiple xrpld nodes with timing | `{xrpl.tx.relay_count > 0}` | | **Consensus Rounds** | Complete round with all phases (open, establish, accept) | `{span.name=~"consensus.round.*"}` | -| **RPC Request Processing** | Individual command execution with timing breakdown | `{xrpl.rpc.command="account_info"}` | +| **RPC Request Processing** | Individual command execution with timing breakdown | `{command="account_info"}` | | **Ledger Acquisition** | Peer-to-peer ledger data requests and responses | `{span.name="ledger.acquire"}` | | **PathFinding Latency** | Path computation time and cache effectiveness for payment RPCs | `{span.name="pathfind.compute"}` | | **TxQ Behavior** | Queue depth, eviction patterns, fee escalation during congestion | `{span.name=~"txq.*"}` | @@ -458,7 +458,7 @@ xychart-beta 1. **Find Transaction**: Query by `xrpl.tx.hash` to get full trace 2. **Identify Bottleneck**: Look at span durations to find slowest component -3. **Check Attributes**: Review `xrpl.tx.validity`, `xrpl.rpc.status` for errors +3. **Check Attributes**: Review `xrpl.tx.validity`, `rpc_status` for errors 4. **Correlate Logs**: Use `trace_id` to find related PerfLog entries 5. **Compare Nodes**: Filter by `service.instance.id` to compare behavior across nodes diff --git a/OpenTelemetryPlan/02-design-decisions.md b/OpenTelemetryPlan/02-design-decisions.md index fe87fc78db..7b9f4dd140 100644 --- a/OpenTelemetryPlan/02-design-decisions.md +++ b/OpenTelemetryPlan/02-design-decisions.md @@ -244,10 +244,10 @@ resource::SemanticConventions::SERVICE_INSTANCE_ID = #### RPC Attributes ```cpp -"xrpl.rpc.command" = string // Command name -"xrpl.rpc.version" = int64 // API version -"xrpl.rpc.role" = string // "admin" or "user" -"xrpl.rpc.params" = string // Sanitized parameters (optional) +"command" = string // Command name +"version" = int64 // API version +"rpc_role" = string // "admin" or "user" +"xrpl.rpc.params" = string // Sanitized parameters (optional, planned) ``` #### Peer & Message Attributes diff --git a/OpenTelemetryPlan/03-implementation-strategy.md b/OpenTelemetryPlan/03-implementation-strategy.md index 9a4baf7131..61e522719b 100644 --- a/OpenTelemetryPlan/03-implementation-strategy.md +++ b/OpenTelemetryPlan/03-implementation-strategy.md @@ -490,11 +490,11 @@ void ServerHandler::onRequest(...) { // After (only ~4 lines added) void ServerHandler::onRequest(...) { auto span = telemetry::SpanGuard::rpcSpan("rpc.request"); // +1 line - span.setAttribute("xrpl.rpc.command", command); // +1 line + span.setAttribute("command", command); // +1 line auto result = processRequest(req); - span.setAttribute("xrpl.rpc.status", status); // +1 line + span.setAttribute("rpc_status", status); // +1 line send(result); } ``` diff --git a/OpenTelemetryPlan/04-code-samples.md b/OpenTelemetryPlan/04-code-samples.md index 9a637c0c05..d4d5c0bdc0 100644 --- a/OpenTelemetryPlan/04-code-samples.md +++ b/OpenTelemetryPlan/04-code-samples.md @@ -346,11 +346,11 @@ void ServerHandler::onRequest(...) // Factory creates a span if RPC tracing is enabled, no-op otherwise. // No Telemetry& reference needed -- accessed via global singleton. auto span = telemetry::SpanGuard::rpcSpan("rpc.request"); - span.setAttribute("xrpl.rpc.command", command); + span.setAttribute("command", command); auto result = processRequest(req); - span.setAttribute("xrpl.rpc.status", result.status()); + span.setAttribute("rpc_status", result.status()); span.setOk(); // span ended automatically when it goes out of scope } @@ -841,7 +841,7 @@ ServerHandler::onRequest( ? jv["method"].asString() : "unknown"; - span.setAttribute("xrpl.rpc.command", command); + span.setAttribute("command", command); // Create child span for command execution { @@ -854,7 +854,7 @@ ServerHandler::onRequest( // Record result attributes if (result.isMember("status")) { - cmdSpan.setAttribute("xrpl.rpc.status", + cmdSpan.setAttribute("rpc_status", result["status"].asString()); } diff --git a/OpenTelemetryPlan/05-configuration-reference.md b/OpenTelemetryPlan/05-configuration-reference.md index 1f56a7abf0..6c94e16513 100644 --- a/OpenTelemetryPlan/05-configuration-reference.md +++ b/OpenTelemetryPlan/05-configuration-reference.md @@ -480,7 +480,7 @@ processors: - name: rpc-spans type: string_attribute string_attribute: - key: xrpl.rpc.command + key: command values: [".*"] enabled_regex_matching: true - name: latency @@ -738,7 +738,7 @@ providers: "targets": [ { "queryType": "traceql", - "query": "{resource.service.name=\"xrpld\" && span.xrpl.rpc.command != \"\"} | histogram_over_time(duration) by (span.xrpl.rpc.command)" + "query": "{resource.service.name=\"xrpld\" && span.command != \"\"} | histogram_over_time(duration) by (span.command)" } ], "gridPos": { "h": 8, "w": 12, "x": 0, "y": 0 } @@ -750,7 +750,7 @@ providers: "targets": [ { "queryType": "traceql", - "query": "{resource.service.name=\"xrpld\" && status.code=error} | rate() by (span.xrpl.rpc.command)" + "query": "{resource.service.name=\"xrpld\" && status.code=error} | rate() by (span.command)" } ], "gridPos": { "h": 8, "w": 12, "x": 12, "y": 0 } @@ -762,7 +762,7 @@ providers: "targets": [ { "queryType": "traceql", - "query": "{resource.service.name=\"xrpld\" && span.xrpl.rpc.command != \"\"} | avg(duration) by (span.xrpl.rpc.command) | topk(10)" + "query": "{resource.service.name=\"xrpld\" && span.command != \"\"} | avg(duration) by (span.command) | topk(10)" } ], "gridPos": { "h": 8, "w": 24, "x": 0, "y": 8 } diff --git a/OpenTelemetryPlan/POC_taskList.md b/OpenTelemetryPlan/POC_taskList.md index 8cd390ef5b..5f93886200 100644 --- a/OpenTelemetryPlan/POC_taskList.md +++ b/OpenTelemetryPlan/POC_taskList.md @@ -302,8 +302,8 @@ // Each factory checks the global Telemetry instance internally. // No Telemetry& reference needed at the call site. auto span = telemetry::SpanGuard::rpcSpan("rpc.request"); - span.setAttribute("xrpl.rpc.command", command); - span.setAttribute("xrpl.rpc.status", status); + span.setAttribute("command", command); + span.setAttribute("rpc_status", status); ``` - Factory methods: `rpcSpan()`, `txSpan()`, `consensusSpan()`, `peerSpan()`, `ledgerSpan()`, `span()` @@ -336,12 +336,12 @@ - `#include ` - In `ServerHandler::onRequest(Session& session)`: - At the top of the method, add: `auto span = telemetry::SpanGuard::rpcSpan("rpc.request");` - - After the RPC command name is extracted, set attribute: `span.setAttribute("xrpl.rpc.command", command);` + - After the RPC command name is extracted, set attribute: `span.setAttribute("command", command);` - After the response status is known, set: `span.setAttribute("http.status_code", static_cast(statusCode));` - Wrap error paths with: `span.recordException(e);` - In `ServerHandler::processRequest(...)`: - Add a child span: `auto span = telemetry::SpanGuard::rpcSpan("rpc.process");` - - Set method attribute: `span.setAttribute("xrpl.rpc.method", request_method);` + - Set method attribute: `span.setAttribute("method", request_method);` - In `ServerHandler::onWSMessage(...)` (WebSocket path): - Add: `auto span = telemetry::SpanGuard::rpcSpan("rpc.ws.message");` @@ -362,7 +362,7 @@ - [01-architecture-analysis.md §1.5](./01-architecture-analysis.md) — RPC request flow diagram: HTTP request -> attributes -> jobqueue.enqueue -> rpc.command -> response - [01-architecture-analysis.md §1.6](./01-architecture-analysis.md) — Key trace points table: `rpc.request` in `ServerHandler.cpp::onRequest()` (Priority: High) - [02-design-decisions.md §2.3](./02-design-decisions.md) — Span naming convention: `rpc.request`, `rpc.command.*` -- [02-design-decisions.md §2.4.2](./02-design-decisions.md) — RPC span attributes: `xrpl.rpc.command`, `xrpl.rpc.version`, `xrpl.rpc.role`, `xrpl.rpc.params` +- [02-design-decisions.md §2.4.2](./02-design-decisions.md) — RPC span attributes: `command`, `version`, `rpc_role`, `xrpl.rpc.params` - [03-implementation-strategy.md §3.9.2](./03-implementation-strategy.md) — File impact: `ServerHandler.cpp` ~40 lines added, ~10 changed (Low risk) --- @@ -378,17 +378,17 @@ - In `doCommand(RPC::JsonContext& context, Json::Value& result)`: - At the top: `auto span = telemetry::SpanGuard::rpcSpan("rpc.command." + context.method);` - Set attributes: - - `span.setAttribute("xrpl.rpc.command", context.method);` - - `span.setAttribute("xrpl.rpc.version", static_cast(context.apiVersion));` - - `span.setAttribute("xrpl.rpc.role", (context.role == Role::ADMIN) ? "admin" : "user");` - - On success: `span.setAttribute("xrpl.rpc.status", "success");` - - On error: `span.setAttribute("xrpl.rpc.status", "error");` and set the error message + - `span.setAttribute("command", context.method);` + - `span.setAttribute("version", static_cast(context.apiVersion));` + - `span.setAttribute("rpc_role", (context.role == Role::ADMIN) ? "admin" : "user");` + - On success: `span.setAttribute("rpc_status", "success");` + - On error: `span.setAttribute("rpc_status", "error");` and set the error message - After this, traces in Tempo/Grafana should look like: ``` - rpc.request (xrpl.rpc.command=account_info) + rpc.request (command=account_info) └── rpc.process - └── rpc.command.account_info (xrpl.rpc.version=2, xrpl.rpc.role=user, xrpl.rpc.status=success) + └── rpc.command.account_info (version=2, rpc_role=user, rpc_status=success) ``` **Key modified file**: @@ -399,7 +399,7 @@ - [04-code-samples.md §4.5.3](./04-code-samples.md) — `ServerHandler::onRequest()` code sample (includes child span pattern for `rpc.command.*`) - [02-design-decisions.md §2.3](./02-design-decisions.md) — Span naming: `rpc.command.*` pattern with dynamic command name (e.g., `rpc.command.server_info`) -- [02-design-decisions.md §2.4.2](./02-design-decisions.md) — RPC attribute schema: `xrpl.rpc.command`, `xrpl.rpc.version`, `xrpl.rpc.role`, `xrpl.rpc.status` +- [02-design-decisions.md §2.4.2](./02-design-decisions.md) — RPC attribute schema: `command`, `version`, `rpc_role`, `rpc_status` - [01-architecture-analysis.md §1.6](./01-architecture-analysis.md) — Key trace points table: `rpc.command.*` in `RPCHandler.cpp::doCommand()` (Priority: High) - [02-design-decisions.md §2.6.5](./02-design-decisions.md) — Correlation with PerfLog: how `doCommand()` can link trace_id with existing PerfLog entries - [03-implementation-strategy.md §3.4.4](./03-implementation-strategy.md) — RPC request overhead budget: ~1.75 μs total per request @@ -472,7 +472,7 @@ - Navigate to Explore → select Tempo datasource - Search for service `xrpld` - Confirm you see traces with spans: `rpc.request` -> `rpc.process` -> `rpc.command.server_info` - - Click into a trace and verify attributes: `xrpl.rpc.command`, `xrpl.rpc.status`, `xrpl.rpc.version` + - Click into a trace and verify attributes: `command`, `rpc_status`, `version` 7. **Verify zero-overhead when disabled**: - Rebuild with `XRPL_ENABLE_TELEMETRY=OFF`, or set `enabled=0` in config @@ -486,7 +486,7 @@ - [ ] xrpld starts and connects to OTel Collector (check xrpld logs for telemetry messages) - [ ] Traces appear in Grafana/Tempo under service "xrpld" - [ ] Span hierarchy is correct (parent-child relationships) -- [ ] Span attributes are populated (`xrpl.rpc.command`, `xrpl.rpc.status`, etc.) +- [ ] Span attributes are populated (`command`, `rpc_status`, etc.) - [ ] Error spans show error status and message - [ ] Building with `XRPL_ENABLE_TELEMETRY=OFF` produces no regressions - [ ] Setting `enabled=0` at runtime produces no traces and no errors @@ -572,8 +572,8 @@ The current POC exports **traces only**. Grafana's Explore view can query Tempo explicit: buckets: [1ms, 5ms, 10ms, 25ms, 50ms, 100ms, 250ms, 500ms, 1s, 5s] dimensions: - - name: xrpl.rpc.command - - name: xrpl.rpc.status + - name: command + - name: rpc_status exporters: prometheus: diff --git a/include/xrpl/telemetry/Telemetry.h b/include/xrpl/telemetry/Telemetry.h index 1d69e01a43..090ba602ed 100644 --- a/include/xrpl/telemetry/Telemetry.h +++ b/include/xrpl/telemetry/Telemetry.h @@ -50,7 +50,7 @@ if (telemetry.isEnabled() && telemetry.shouldTraceRpc()) { SpanGuard guard(telemetry.startSpan("rpc.command.submit")); - guard.setAttribute("xrpl.rpc.command", "submit"); + guard.setAttribute("command", "submit"); // ... guard ends span automatically on scope exit } @endcode From 2430032e3acae8f7bb1d73f8490627988475d8b6 Mon Sep 17 00:00:00 2001 From: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com> Date: Wed, 13 May 2026 16:29:20 +0100 Subject: [PATCH 2/6] docs(telemetry): update Phase2 task list + design docs for attr rename - Phase2_taskList: update attr refs to bare names, note node-health attrs moved to resource level. - 02-design-decisions: strip xrpl.pathfind.* prefix from planned attrs. Co-Authored-By: Claude Opus 4.6 (1M context) --- OpenTelemetryPlan/02-design-decisions.md | 8 ++++---- OpenTelemetryPlan/Phase2_taskList.md | 7 +++---- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/OpenTelemetryPlan/02-design-decisions.md b/OpenTelemetryPlan/02-design-decisions.md index 7b9f4dd140..5df51798dd 100644 --- a/OpenTelemetryPlan/02-design-decisions.md +++ b/OpenTelemetryPlan/02-design-decisions.md @@ -277,10 +277,10 @@ resource::SemanticConventions::SERVICE_INSTANCE_ID = #### PathFinding Attributes ```cpp -"xrpl.pathfind.source_currency" = string // Source currency code -"xrpl.pathfind.dest_currency" = string // Destination currency code -"xrpl.pathfind.path_count" = int64 // Number of paths found -"xrpl.pathfind.cache_hit" = bool // RippleLineCache hit +"source_currency" = string // Source currency code (planned, not yet implemented) +"dest_currency" = string // Destination currency code (planned, not yet implemented) +"path_count" = int64 // Number of paths found (planned, not yet implemented) +"cache_hit" = bool // RippleLineCache hit (planned, not yet implemented) ``` #### TxQ Attributes diff --git a/OpenTelemetryPlan/Phase2_taskList.md b/OpenTelemetryPlan/Phase2_taskList.md index 249be880ff..6979f78869 100644 --- a/OpenTelemetryPlan/Phase2_taskList.md +++ b/OpenTelemetryPlan/Phase2_taskList.md @@ -91,7 +91,7 @@ - `http.method` is always POST for JSON-RPC - `net.peer.ip` is debug-level info available in logs -- `xrpl.rpc.duration_ms` is redundant with span duration (OTel captures start/end time natively) +- `duration_ms` is redundant with span duration (OTel captures start/end time natively) These can be added later if dashboard queries specifically need them. The node health attributes (Task 2.8) provide far more operational value and were prioritized instead. @@ -130,9 +130,8 @@ These can be added later if dashboard queries specifically need them. The node h **What to do**: - Edit `src/xrpld/rpc/detail/RPCHandler.cpp`: - - In the `rpc.command.*` span creation block (after existing `setAttribute` calls for `xrpl.rpc.command`, `xrpl.rpc.version`, etc.): - - Add `xrpl.node.amendment_blocked` (bool) — from `context.app.getOPs().isAmendmentBlocked()` - - Add `xrpl.node.server_state` (string) — from `context.app.getOPs().strOperatingMode()` + - In the `rpc.command.*` span creation block (after existing `setAttribute` calls for `command`, `version`, etc.): + - Node health attrs (`xrpl.node.amendment_blocked`, `xrpl.node.server_state`) are now resource-level attrs, not per-span. They are set at Tracer init. **New span attributes**: From 5c14b57462bbf677be8f14c042377019ce011bfe Mon Sep 17 00:00:00 2001 From: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com> Date: Wed, 13 May 2026 16:31:22 +0100 Subject: [PATCH 3/6] docs(telemetry): update Phase3 task list for simplified tx/txq attr naming Co-Authored-By: Claude Opus 4.6 (1M context) --- OpenTelemetryPlan/Phase3_taskList.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/OpenTelemetryPlan/Phase3_taskList.md b/OpenTelemetryPlan/Phase3_taskList.md index 18146dff02..9c5127120f 100644 --- a/OpenTelemetryPlan/Phase3_taskList.md +++ b/OpenTelemetryPlan/Phase3_taskList.md @@ -89,13 +89,13 @@ - In `onMessage(TMTransaction)` / `handleTransaction()`: - Extract parent trace context from incoming `TMTransaction::trace_context` field (if present) - Create `tx.receive` span as child of extracted context (or new root if none) - - Set attributes: `xrpl.tx.hash`, `xrpl.peer.id`, `xrpl.tx.status` - - On HashRouter suppression (duplicate): set `xrpl.tx.suppressed=true`, add `tx.duplicate` event + - Set attributes: `xrpl.tx.hash`, `xrpl.peer.id`, `tx_status` + - On HashRouter suppression (duplicate): set `suppressed=true`, add `tx.duplicate` event - Wrap validation call with child span `tx.validate` - Wrap relay with `tx.relay` span - When relaying to peers: - Inject current trace context into outgoing `TMTransaction::trace_context` - - Set `xrpl.tx.relay_count` attribute + - Set `relay_count` attribute - Use `SpanGuard::span(TraceCategory::Transactions, "tx", "receive")` factory (Phase 1c replaced macros with the SpanGuard factory pattern) @@ -121,7 +121,7 @@ - Edit `src/xrpld/app/misc/NetworkOPs.cpp`: - In `processTransaction()`: - Create `tx.process` span - - Set attributes: `xrpl.tx.hash`, `xrpl.tx.type`, `xrpl.tx.local` (whether from RPC or peer) + - Set attributes: `xrpl.tx.hash`, `tx_type`, `local` (whether from RPC or peer) - Record whether sync or async path is taken - In `doTransactionAsync()`: @@ -152,8 +152,8 @@ - Edit `src/xrpld/overlay/detail/PeerImp.cpp` (in handleTransaction): - After calling `HashRouter::shouldProcess()` or `addSuppressionPeer()`: - - Record `xrpl.tx.suppressed` attribute (true/false) - - Record `xrpl.tx.flags` showing current HashRouter state (SAVED, TRUSTED, etc.) + - Record `suppressed` attribute (true/false) + - Record `tx_flags` showing current HashRouter state (SAVED, TRUSTED, etc.) - Add `tx.first_seen` or `tx.duplicate` event - This is NOT a modification to HashRouter itself — just recording its decisions as span attributes in the existing PeerImp instrumentation from Task 3.3. @@ -257,14 +257,14 @@ - Edit `src/xrpld/overlay/detail/PeerImp.cpp`: - In the `tx.receive` span block (after existing `xrpl.peer.id` setAttribute call): - - Add `xrpl.peer.version` (string) — from `this->getVersion()` + - Add `peer_version` (string) — from `this->getVersion()` - Only set if `getVersion()` returns a non-empty string (avoid empty-string attributes) **New span attribute**: -| Attribute | Type | Source | Example | -| ------------------- | ------ | -------------------- | --------------- | -| `xrpl.peer.version` | string | `peer->getVersion()` | `"xrpld-2.4.0"` | +| Attribute | Type | Source | Example | +| -------------- | ------ | -------------------- | --------------- | +| `peer_version` | string | `peer->getVersion()` | `"xrpld-2.4.0"` | **Rationale**: Transaction relay is where version mismatches cause subtle serialization or validation bugs. Tracing "this tx came from a v2.3.0 peer" helps diagnose compatibility issues. The community dashboard tracks peer versions externally; this brings version awareness into the trace itself. @@ -274,7 +274,7 @@ **Exit Criteria**: -- [ ] `tx.receive` spans carry `xrpl.peer.version` attribute with a non-empty version string +- [ ] `tx.receive` spans carry `peer_version` attribute with a non-empty version string - [ ] Attribute is omitted (not set to empty string) when `getVersion()` returns empty - [ ] Attribute visible in Jaeger span detail view @@ -387,8 +387,8 @@ This gives the best of both worlds: guaranteed cross-node correlation via determ - No protobuf context to extract here (NetworkOPs is intra-node), so deterministic context alone is sufficient. -- Add `tx_trace_strategy` attribute to spans: - - Add `inline constexpr auto traceStrategy = join(xrplTx, makeStr("trace_strategy"));` +- Add `trace_strategy` attribute to spans: + - Add `inline constexpr auto traceStrategy = "trace_strategy";` to `TxSpanNames.h`. - Set on each tx span: `span.setAttribute(tx_span::attr::traceStrategy, "deterministic")`. @@ -419,7 +419,7 @@ This gives the best of both worlds: guaranteed cross-node correlation via determ - [ ] All nodes handling the same transaction produce spans under the same trace_id - [x] Protobuf `span_id` propagation still works when available (parent-child ordering) - [ ] Missing protobuf context (old peer) degrades gracefully to sibling spans, not lost traces -- [ ] `xrpl.tx.trace_strategy` attribute set to `"deterministic"` on all tx spans +- [ ] `trace_strategy` attribute set to `"deterministic"` on all tx spans - [ ] Trace queryable by tx hash (truncate hash → trace_id → direct lookup in Tempo) **Deliverables implemented (not in original plan)**: From 745102360b3a35d7fd7945ae49053e6e43e348c1 Mon Sep 17 00:00:00 2001 From: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com> Date: Wed, 13 May 2026 16:36:22 +0100 Subject: [PATCH 4/6] docs(telemetry): update Phase4 task list for simplified consensus attr naming Co-Authored-By: Claude Opus 4.6 (1M context) --- OpenTelemetryPlan/Phase4_taskList.md | 168 +++++++++++++-------------- 1 file changed, 84 insertions(+), 84 deletions(-) diff --git a/OpenTelemetryPlan/Phase4_taskList.md b/OpenTelemetryPlan/Phase4_taskList.md index 1670e9b57e..6d084c5934 100644 --- a/OpenTelemetryPlan/Phase4_taskList.md +++ b/OpenTelemetryPlan/Phase4_taskList.md @@ -27,8 +27,8 @@ - `RCLConsensus::Adaptor::startRoundTracing()` creates `consensus.round` span via `SpanGuard::hashSpan()` (deterministic) or `SpanGuard::span()` (attribute strategy) -- Attributes set: `xrpl.consensus.ledger_id`, `xrpl.consensus.ledger.seq`, - `xrpl.consensus.mode`, `xrpl.consensus.trace_strategy`, `xrpl.consensus.round_id` +- Attributes set: `xrpl.consensus.ledger_id`, `xrpl.ledger.seq`, + `xrpl.consensus.mode`, `trace_strategy`, `xrpl.consensus.round_id` - Round span stored as `roundSpan_` member in `RCLConsensus::Adaptor` - `roundSpanContext_` snapshot captured for cross-thread span linking @@ -57,9 +57,9 @@ **Design notes**: -- `xrpl.consensus.phase` attribute — phases are distinguished by span names instead +- `phase` attribute — phases are distinguished by span names instead - `phase.enter` / `phase.exit` events — not added (span start/end serves this purpose) -- `xrpl.consensus.phase_duration_ms` attribute — not set (span duration captures this) +- `phase_duration_ms` attribute — not set (span duration captures this) **Key modified files**: @@ -82,11 +82,11 @@ - In `Adaptor::propose()`: - Creates `consensus.proposal.send` span via `SpanGuard::span()` - - Sets `xrpl.consensus.round` attribute + - Sets `xrpl.consensus.round` attribute (kept — rule 5) - In `PeerImp::onMessage(TMProposeSet)`: - Creates `consensus.proposal.receive` span - - Sets `xrpl.consensus.proposal.trusted` attribute (bool) + - Sets `trusted` attribute (bool) **Not implemented** (deferred to Phase 4b — cross-node propagation): @@ -117,12 +117,12 @@ - Uses `SpanGuard::linkedSpan()` to create a follows-from link to the round span - Thread-safe: uses `roundSpanContext_` snapshot (captured on consensus thread, read on jtACCEPT thread) - - Sets `xrpl.consensus.ledger.seq` and `xrpl.consensus.proposing` attributes + - Sets `xrpl.ledger.seq` and `proposing` attributes - In `PeerImp::onMessage(TMValidation)`: - Creates `consensus.validation.receive` span - - Sets `xrpl.consensus.validation.trusted` attribute (bool) - - Sets `xrpl.consensus.validation.ledger_seq` attribute + - Sets `trusted` attribute (bool) + - Sets `xrpl.ledger.seq` attribute **Not implemented** (deferred to Phase 4b — cross-node propagation): @@ -142,18 +142,18 @@ **Implemented attributes** (across various spans): -- `xrpl.consensus.ledger.seq` — on `consensus.round`, `consensus.accept.apply` +- `xrpl.ledger.seq` — on `consensus.round`, `consensus.accept.apply` - `xrpl.consensus.round` — on `consensus.proposal.send` - `xrpl.consensus.mode` — on `consensus.round`, `consensus.ledger_close` -- `xrpl.consensus.proposers` — on `consensus.accept`, `consensus.establish`, `consensus.update_positions` -- `xrpl.consensus.converge_percent` — on `consensus.establish`, `consensus.update_positions`, `consensus.check` -- `xrpl.consensus.tx_count` — on `consensus.accept.apply` span (in `doAccept()`) -- `xrpl.consensus.disputes_count` — on `consensus.update_positions` span (in `updateOurPositions()`) +- `proposers` — on `consensus.accept`, `consensus.establish`, `consensus.update_positions` +- `converge_percent` — on `consensus.establish`, `consensus.update_positions`, `consensus.check` +- `tx_count` — on `consensus.accept.apply` span (in `doAccept()`) +- `disputes_count` — on `consensus.update_positions` span (in `updateOurPositions()`) **Design notes**: -- `xrpl.consensus.phase` — phases distinguished by span names instead -- `xrpl.consensus.phase_duration_ms` — span duration captures this +- `phase` — phases distinguished by span names instead +- `phase_duration_ms` — span duration captures this **Key modified files**: @@ -221,8 +221,8 @@ - Add `xrpl.validation.ledger_hash` (string) — the ledger hash being validated - Add `xrpl.validation.full` (bool) — whether this is a full validation (not partial) - On the `consensus.accept` span (in `onAccept()`): - - Add `xrpl.consensus.validation_quorum` (int64) — from `app_.validators().quorum()` - - Add `xrpl.consensus.proposers_validated` (int64) — from `result.proposers` + - Add `validation_quorum` (int64) — from `app_.validators().quorum()` + - Add `proposers_validated` (int64) — from `result.proposers` - Edit `src/xrpld/overlay/detail/PeerImp.cpp`: - On the `peer.validation.receive` span: @@ -231,14 +231,14 @@ **New span attributes**: -| Span | Attribute | Type | Source | -| --------------------------- | ------------------------------------ | ------ | --------------------------------- | -| `consensus.validation.send` | `xrpl.validation.ledger_hash` | string | Ledger hash from validate() args | -| `consensus.validation.send` | `xrpl.validation.full` | bool | Full vs partial validation | -| `peer.validation.receive` | `xrpl.peer.validation.ledger_hash` | string | From STValidation deserialization | -| `peer.validation.receive` | `xrpl.peer.validation.full` | bool | From STValidation flags | -| `consensus.accept` | `xrpl.consensus.validation_quorum` | int64 | `app_.validators().quorum()` | -| `consensus.accept` | `xrpl.consensus.proposers_validated` | int64 | `result.proposers` | +| Span | Attribute | Type | Source | +| --------------------------- | ---------------------------------- | ------ | --------------------------------- | +| `consensus.validation.send` | `xrpl.validation.ledger_hash` | string | Ledger hash from validate() args | +| `consensus.validation.send` | `xrpl.validation.full` | bool | Full vs partial validation | +| `peer.validation.receive` | `xrpl.peer.validation.ledger_hash` | string | From STValidation deserialization | +| `peer.validation.receive` | `xrpl.peer.validation.full` | bool | From STValidation flags | +| `consensus.accept` | `validation_quorum` | int64 | `app_.validators().quorum()` | +| `consensus.accept` | `proposers_validated` | int64 | `result.proposers` | **Rationale**: The external dashboard's most valuable feature is validation agreement tracking. By recording the ledger hash on both outgoing and incoming validation spans, we create the raw data for agreement analysis at the trace level. Example Tempo query: @@ -257,7 +257,7 @@ Phase 7's `ValidationTracker` builds metric-level aggregation (1h/24h agreement - [ ] `consensus.validation.send` spans carry `xrpl.validation.ledger_hash` and `xrpl.validation.full` - [ ] `peer.validation.receive` spans carry `xrpl.peer.validation.ledger_hash` and `xrpl.peer.validation.full` -- [ ] `consensus.accept` spans carry `xrpl.consensus.validation_quorum` and `xrpl.consensus.proposers_validated` +- [ ] `consensus.accept` spans carry `validation_quorum` and `proposers_validated` - [ ] Ledger hash attributes match between send and receive for the same ledger - [ ] No impact on consensus performance @@ -283,26 +283,26 @@ Phase 7's `ValidationTracker` builds metric-level aggregation (1h/24h agreement | Span Name | Method | Key Attributes | | --------------------------- | ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `consensus.proposal.send` | `Adaptor::propose` | `xrpl.consensus.round` | -| `consensus.ledger_close` | `Adaptor::onClose` | `xrpl.consensus.ledger.seq`, `xrpl.consensus.mode` | -| `consensus.accept` | `Adaptor::onAccept` | `xrpl.consensus.proposers`, `xrpl.consensus.round_time_ms` | -| `consensus.accept.apply` | `Adaptor::doAccept` | `xrpl.consensus.close_time`, `close_time_correct`, `close_resolution_ms`, `state`, `proposing`, `round_time_ms`, `ledger.seq`, `parent_close_time`, `close_time_self`, `close_time_vote_bins`, `resolution_direction` | -| `consensus.validation.send` | `Adaptor::onAccept` (via validate) | `xrpl.consensus.proposing` | +| `consensus.ledger_close` | `Adaptor::onClose` | `xrpl.ledger.seq`, `xrpl.consensus.mode` | +| `consensus.accept` | `Adaptor::onAccept` | `proposers`, `round_time_ms` | +| `consensus.accept.apply` | `Adaptor::doAccept` | `close_time`, `close_time_correct`, `close_resolution_ms`, `consensus_state`, `proposing`, `round_time_ms`, `xrpl.ledger.seq`, `parent_close_time`, `close_time_self`, `close_time_vote_bins`, `resolution_direction` | +| `consensus.validation.send` | `Adaptor::onAccept` (via validate) | `proposing` | #### Close Time Attributes (consensus.accept.apply) The `consensus.accept.apply` span captures ledger close time agreement details driven by `avCT_CONSENSUS_PCT` (75% validator agreement threshold): -- **`xrpl.consensus.close_time`** — Agreed-upon ledger close time (epoch seconds). When validators disagree (`consensusCloseTime == epoch`), this is synthetically set to `prevCloseTime + 1s`. -- **`xrpl.consensus.close_time_correct`** — `true` if validators reached agreement, `false` if they "agreed to disagree" (close time forced to prev+1s). -- **`xrpl.consensus.close_resolution_ms`** — Rounding granularity for close time (starts at 30s, decreases as ledger interval stabilizes). -- **`xrpl.consensus.state`** — `"finished"` (normal) or `"moved_on"` (consensus failed, adopted best available). -- **`xrpl.consensus.proposing`** — Whether this node was proposing. -- **`xrpl.consensus.round_time_ms`** — Total consensus round duration. -- **`xrpl.consensus.parent_close_time`** — Previous ledger's close time (epoch seconds). Enables computing close-time deltas across consecutive rounds without correlating separate spans. -- **`xrpl.consensus.close_time_self`** — This node's own proposed close time before consensus voting. -- **`xrpl.consensus.close_time_vote_bins`** — Number of distinct close-time vote bins from peer proposals. Higher values indicate less agreement among validators. -- **`xrpl.consensus.resolution_direction`** — Whether close-time resolution `"increased"` (coarser), `"decreased"` (finer), or stayed `"unchanged"` relative to the previous ledger. +- **`close_time`** — Agreed-upon ledger close time (epoch seconds). When validators disagree (`consensusCloseTime == epoch`), this is synthetically set to `prevCloseTime + 1s`. +- **`close_time_correct`** — `true` if validators reached agreement, `false` if they "agreed to disagree" (close time forced to prev+1s). +- **`close_resolution_ms`** — Rounding granularity for close time (starts at 30s, decreases as ledger interval stabilizes). +- **`consensus_state`** — `"finished"` (normal) or `"moved_on"` (consensus failed, adopted best available). +- **`proposing`** — Whether this node was proposing. +- **`round_time_ms`** — Total consensus round duration. +- **`parent_close_time`** — Previous ledger's close time (epoch seconds). Enables computing close-time deltas across consecutive rounds without correlating separate spans. +- **`close_time_self`** — This node's own proposed close time before consensus voting. +- **`close_time_vote_bins`** — Number of distinct close-time vote bins from peer proposals. Higher values indicate less agreement among validators. +- **`resolution_direction`** — Whether close-time resolution `"increased"` (coarser), `"decreased"` (finer), or stayed `"unchanged"` relative to the previous ledger. **Exit Criteria** (from [06-implementation-phases.md §6.11.4](./06-implementation-phases.md)): @@ -504,7 +504,7 @@ spans in `Consensus.h`. - Reads `consensus_trace_strategy` via `app_.getTelemetry().getConsensusTraceStrategy()` - **Deterministic**: uses `SpanGuard::hashSpan()` with `prevLgr.id()` data - **Attribute**: uses `SpanGuard::span(TraceCategory::Consensus, seg::consensus, "round")` - - Sets attributes: `ledger_id`, `ledger.seq`, `mode`, `trace_strategy`, `round_id` + - Sets attributes: `xrpl.consensus.ledger_id`, `xrpl.ledger.seq`, `xrpl.consensus.mode`, `trace_strategy`, `xrpl.consensus.round_id` - Captures `roundSpanContext_` snapshot for cross-thread span linking - Saves `prevRoundContext_` from previous round for follows-from links @@ -585,9 +585,9 @@ with attributes for convergence progress. `SpanGuard::span()` returns a no-op guard when telemetry is disabled. - `updateEstablishTracing()` — sets attributes on each `phaseEstablish()` call: - - `xrpl.consensus.converge_percent` — `convergePercent_` - - `xrpl.consensus.establish_count` — `establishCounter_` - - `xrpl.consensus.proposers` — `currPeerPositions_.size()` + - `converge_percent` — `convergePercent_` + - `establish_count` — `establishCounter_` + - `proposers` — `currPeerPositions_.size()` - `endEstablishTracing()` — calls `establishSpan_.reset()` on phase exit. @@ -614,11 +614,11 @@ details. ``` - Attributes set: - - `xrpl.consensus.converge_percent` — current convergence - - `xrpl.consensus.proposers` — `currPeerPositions_.size()` - - `xrpl.consensus.have_close_time_consensus` — close time consensus state - - `xrpl.consensus.close_time_threshold` — `avCT_CONSENSUS_PCT` - - `xrpl.consensus.disputes_count` — number of active disputes + - `converge_percent` — current convergence + - `proposers` — `currPeerPositions_.size()` + - `have_close_time_consensus` — close time consensus state + - `close_time_threshold` — `avCT_CONSENSUS_PCT` + - `disputes_count` — number of active disputes - Dispute events recorded via direct `span.addEvent()` call with yays/nays: ```cpp @@ -632,7 +632,7 @@ details. **Not implemented**: -- `xrpl.consensus.proposers_agreed` / `xrpl.consensus.proposers_total` attributes — not set +- `proposers_agreed` / `proposers_total` attributes — not set **Key modified files**: @@ -658,13 +658,13 @@ including the avalanche threshold. ``` - Attributes set: - - `xrpl.consensus.agree_count` — peers that agree with our position - - `xrpl.consensus.disagree_count` — peers that disagree - - `xrpl.consensus.converge_percent` — convergence percentage - - `xrpl.consensus.have_close_time_consensus` — close time consensus state - - `xrpl.consensus.threshold_percent` — set to `avCT_CONSENSUS_PCT` (75%) - - `xrpl.consensus.result` — "yes", "no", or "moved_on" - - `xrpl.consensus.avalanche_threshold` — the escalated weight from `getNeededWeight()` on the `consensus.update_positions` span + - `agree_count` — peers that agree with our position + - `disagree_count` — peers that disagree + - `converge_percent` — convergence percentage + - `have_close_time_consensus` — close time consensus state + - `threshold_percent` — set to `avCT_CONSENSUS_PCT` (75%) + - `consensus_result` — "yes", "no", or "moved_on" + - `avalanche_threshold` — the escalated weight from `getNeededWeight()` on the `consensus.update_positions` span **Key modified files**: @@ -687,8 +687,8 @@ wrongLedger, switchedLedger). ```cpp auto span = telemetry::SpanGuard::span( telemetry::TraceCategory::Consensus, telemetry::seg::consensus, "mode_change"); - span.setAttribute(cons_span::attr::modeOld, to_string(before).c_str()); - span.setAttribute(cons_span::attr::modeNew, to_string(after).c_str()); + span.setAttribute(cons_span::attr::modeOld, to_string(before).c_str()); // "mode_old" + span.setAttribute(cons_span::attr::modeNew, to_string(after).c_str()); // "mode_new" ``` - `MonitoredMode::set()` in `Consensus.h` calls `adaptor_.onModeChange(before, after)`. @@ -773,48 +773,48 @@ and OFF, and don't affect consensus timing. | Span Name | Location | Key Attributes (actually set) | | ---------------------------- | ------------------ | ----------------------------------------------------------------------------------------------------------------------------- | -| `consensus.round` | `RCLConsensus.cpp` | `round_id`, `ledger_id`, `ledger.seq`, `mode`, `trace_strategy` | +| `consensus.round` | `RCLConsensus.cpp` | `xrpl.consensus.round_id`, `xrpl.consensus.ledger_id`, `xrpl.ledger.seq`, `xrpl.consensus.mode`, `trace_strategy` | | `consensus.establish` | `Consensus.h` | `converge_percent`, `establish_count`, `proposers` | | `consensus.update_positions` | `Consensus.h` | `converge_percent`, `proposers`, `have_close_time_consensus`, `close_time_threshold`, `disputes_count`, `avalanche_threshold` | -| `consensus.check` | `Consensus.h` | `agree_count`, `disagree_count`, `converge_percent`, `have_close_time_consensus`, `threshold_percent`, `result` | -| `consensus.mode_change` | `RCLConsensus.cpp` | `mode.old`, `mode.new` | +| `consensus.check` | `Consensus.h` | `agree_count`, `disagree_count`, `converge_percent`, `have_close_time_consensus`, `threshold_percent`, `consensus_result` | +| `consensus.mode_change` | `RCLConsensus.cpp` | `mode_old`, `mode_new` | ### New Events (Phase 4a) -| Event Name | Parent Span | Attributes (actually set) | -| ----------------- | ---------------------------- | ----------------------------------- | -| `dispute.resolve` | `consensus.update_positions` | `tx_id`, `our_vote`, `yays`, `nays` | -| `tx.included` | `consensus.accept.apply` | `tx_id` | +| Event Name | Parent Span | Attributes (actually set) | +| ----------------- | ---------------------------- | ---------------------------------------------------------------- | +| `dispute.resolve` | `consensus.update_positions` | `xrpl.tx.id`, `dispute_our_vote`, `dispute_yays`, `dispute_nays` | +| `tx.included` | `consensus.accept.apply` | `xrpl.tx.id` | ### New Attributes (Phase 4a) ```cpp // Round-level (on consensus.round) — ALL IMPLEMENTED -"xrpl.consensus.round_id" = int64 // Consensus round number -"xrpl.consensus.ledger_id" = string // previousLedger.id() hash -"xrpl.consensus.trace_strategy" = string // "deterministic" or "attribute" +"xrpl.consensus.round_id" = int64 // Consensus round number (kept — rule 5) +"xrpl.consensus.ledger_id" = string // previousLedger.id() hash (kept — rule 5) +"trace_strategy" = string // "deterministic" or "attribute" // Establish-level — IMPLEMENTED -"xrpl.consensus.converge_percent" = int64 // Convergence % (0-100+) -"xrpl.consensus.establish_count" = int64 // Number of establish iterations -"xrpl.consensus.agree_count" = int64 // Peers that agree (haveConsensus) -"xrpl.consensus.disagree_count" = int64 // Peers that disagree -"xrpl.consensus.threshold_percent" = int64 // Current threshold (avCT_CONSENSUS_PCT = 75%) -"xrpl.consensus.result" = string // "yes", "no", "moved_on" -"xrpl.consensus.have_close_time_consensus" = bool // Close time consensus reached -"xrpl.consensus.close_time_threshold" = int64 // Close time voting threshold +"converge_percent" = int64 // Convergence % (0-100+) +"establish_count" = int64 // Number of establish iterations +"agree_count" = int64 // Peers that agree (haveConsensus) +"disagree_count" = int64 // Peers that disagree +"threshold_percent" = int64 // Current threshold (avCT_CONSENSUS_PCT = 75%) +"consensus_result" = string // "yes", "no", "moved_on" +"have_close_time_consensus" = bool // Close time consensus reached +"close_time_threshold" = int64 // Close time voting threshold // Establish-level — IMPLEMENTED -"xrpl.consensus.disputes_count" = int64 // Active disputes (on update_positions) -"xrpl.consensus.avalanche_threshold" = int64 // Escalated weight (on update_positions) +"disputes_count" = int64 // Active disputes (on update_positions) +"avalanche_threshold" = int64 // Escalated weight (on update_positions) // Establish-level — NOT IMPLEMENTED -// "xrpl.consensus.proposers_agreed" = int64 // Peers agreeing with us — not set -// "xrpl.consensus.proposers_total" = int64 // Total peer positions — not set (not defined) +// "proposers_agreed" = int64 // Peers agreeing with us — not set +// "proposers_total" = int64 // Total peer positions — not set (not defined) // Mode change — ALL IMPLEMENTED -"xrpl.consensus.mode.old" = string // Previous mode -"xrpl.consensus.mode.new" = string // New mode +"mode_old" = string // Previous mode +"mode_new" = string // New mode ``` ### Implementation Notes From d44a0aa3ff4d3d659bb1a91f6f132abc04fe4f30 Mon Sep 17 00:00:00 2001 From: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com> Date: Wed, 13 May 2026 16:37:27 +0100 Subject: [PATCH 5/6] docs(telemetry): update Phase5 task list for simplified attr naming Co-Authored-By: Claude Opus 4.6 (1M context) --- OpenTelemetryPlan/Phase5_taskList.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/OpenTelemetryPlan/Phase5_taskList.md b/OpenTelemetryPlan/Phase5_taskList.md index c2d0aa9c60..b573f666a7 100644 --- a/OpenTelemetryPlan/Phase5_taskList.md +++ b/OpenTelemetryPlan/Phase5_taskList.md @@ -31,10 +31,10 @@ explicit: buckets: [1ms, 5ms, 10ms, 25ms, 50ms, 100ms, 250ms, 500ms, 1s, 5s] dimensions: - - name: xrpl.rpc.command - - name: xrpl.rpc.status - - name: xrpl.consensus.phase - - name: xrpl.tx.type + - name: command + - name: rpc_status + - name: consensus_phase + - name: tx_type ``` - Add `prometheus` exporter: ```yaml From b05e650b6f4f91746f73b15f08b67f5444b245a4 Mon Sep 17 00:00:00 2001 From: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com> Date: Wed, 13 May 2026 16:42:30 +0100 Subject: [PATCH 6/6] docs(telemetry): update 09-data-collection-reference + Phase5 integration test list for simplified attr naming Co-Authored-By: Claude Opus 4.6 (1M context) --- .../09-data-collection-reference.md | 238 +++++++++--------- .../Phase5_IntegrationTest_taskList.md | 2 +- 2 files changed, 120 insertions(+), 120 deletions(-) diff --git a/OpenTelemetryPlan/09-data-collection-reference.md b/OpenTelemetryPlan/09-data-collection-reference.md index 0ce4f363eb..2c08c8f9da 100644 --- a/OpenTelemetryPlan/09-data-collection-reference.md +++ b/OpenTelemetryPlan/09-data-collection-reference.md @@ -215,29 +215,29 @@ Every span can carry key-value attributes that provide context for filtering and #### RPC Attributes -| Attribute | Type | Set On | Description | -| ----------------------- | ------ | --------------- | ------------------------------------------------ | -| `xrpl.rpc.command` | string | `rpc.command.*` | RPC command name (e.g., `server_info`, `ledger`) | -| `xrpl.rpc.version` | int64 | `rpc.command.*` | API version number | -| `xrpl.rpc.role` | string | `rpc.command.*` | Caller role: `"admin"` or `"user"` | -| `xrpl.rpc.status` | string | `rpc.command.*` | Result: `"success"` or `"error"` | -| `xrpl.rpc.payload_size` | int64 | `rpc.command.*` | Request payload size in bytes | +| Attribute | Type | Set On | Description | +| ---------------------- | ------ | --------------- | ------------------------------------------------ | +| `command` | string | `rpc.command.*` | RPC command name (e.g., `server_info`, `ledger`) | +| `version` | int64 | `rpc.command.*` | API version number | +| `rpc_role` | string | `rpc.command.*` | Caller role: `"admin"` or `"user"` | +| `rpc_status` | string | `rpc.command.*` | Result: `"success"` or `"error"` | +| `request_payload_size` | int64 | `rpc.command.*` | Request payload size in bytes | -**Tempo query**: `{span.xrpl.rpc.command="server_info"}` to find all `server_info` calls. +**Tempo query**: `{span.command="server_info"}` to find all `server_info` calls. **Prometheus label**: `xrpl_rpc_command` (dots converted to underscores by SpanMetrics). #### Transaction Attributes -| Attribute | Type | Set On | Description | -| -------------------- | ------- | -------------------------- | ---------------------------------------------------- | -| `xrpl.tx.hash` | string | `tx.process`, `tx.receive` | Transaction hash (hex-encoded) | -| `xrpl.tx.local` | boolean | `tx.process` | `true` if locally submitted, `false` if peer-relayed | -| `xrpl.tx.path` | string | `tx.process` | Submission path: `"sync"` or `"async"` | -| `xrpl.tx.suppressed` | boolean | `tx.receive` | `true` if transaction was suppressed (duplicate) | -| `xrpl.tx.status` | string | `tx.receive` | Transaction status (e.g., `"known_bad"`) | -| `xrpl.peer.id` | int64 | `tx.receive` | Peer identifier (also set on peer spans) | -| `xrpl.peer.version` | string | `tx.receive` | Peer protocol version string | +| Attribute | Type | Set On | Description | +| ------------------- | ------- | -------------------------- | ---------------------------------------------------- | +| `xrpl.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"`) | +| `xrpl.peer.id` | int64 | `tx.receive` | Peer identifier (also set on peer spans) | +| `xrpl.peer.version` | string | `tx.receive` | Peer protocol version string | **Tempo query**: `{span.xrpl.tx.hash=""}` to trace a specific transaction across nodes. @@ -245,86 +245,86 @@ Every span can carry key-value attributes that provide context for filtering and #### PathFind Attributes -| Attribute | Type | Set On | Description | -| ---------------------------------- | ------- | --------------------- | ----------------------------------------------- | -| `xrpl.pathfind.source_account` | string | `pathfind.request` | Source account address | -| `xrpl.pathfind.dest_account` | string | `pathfind.request` | Destination account address | -| `xrpl.pathfind.fast` | boolean | `pathfind.compute` | Whether this is a fast (non-full) pathfind | -| `xrpl.pathfind.search_level` | int64 | `pathfind.compute` | Search depth level | -| `xrpl.pathfind.num_complete_paths` | int64 | `pathfind.compute` | Number of complete paths found | -| `xrpl.pathfind.num_paths` | int64 | `pathfind.compute` | Total number of paths explored | -| `xrpl.pathfind.num_requests` | int64 | `pathfind.update_all` | Number of active path requests being recomputed | -| `xrpl.pathfind.ledger_index` | int64 | `pathfind.update_all` | Ledger index used for recomputation | +| Attribute | Type | Set On | Description | +| ---------------------------- | ------- | --------------------- | ----------------------------------------------- | +| `source_account` | string | `pathfind.request` | Source account address | +| `dest_account` | string | `pathfind.request` | Destination account address | +| `fast` | boolean | `pathfind.compute` | Whether this is a fast (non-full) pathfind | +| `search_level` | int64 | `pathfind.compute` | Search depth level | +| `num_complete_paths` | int64 | `pathfind.compute` | Number of complete paths found | +| `num_paths` | int64 | `pathfind.compute` | Total number of paths explored | +| `num_requests` | int64 | `pathfind.update_all` | Number of active path requests being recomputed | +| `xrpl.pathfind.ledger_index` | int64 | `pathfind.update_all` | Ledger index used for recomputation | -**Tempo query**: `{span.xrpl.pathfind.source_account="rHb9..."}` to find pathfind requests from a specific account. +**Tempo query**: `{span.source_account="rHb9..."}` to find pathfind requests from a specific account. #### TxQ Attributes -| Attribute | Type | Set On | Description | -| ----------------------------- | ------- | ------------------------------ | ---------------------------------------------------------- | -| `xrpl.txq.tx_hash` | string | `txq.enqueue`, `txq.accept.tx` | Transaction hash in the queue | -| `xrpl.txq.status` | string | `txq.enqueue` | Queue result: `"queued"`, `"applied_direct"`, `"rejected"` | -| `xrpl.txq.fee_level_paid` | int64 | `txq.enqueue` | Fee level paid by the transaction | -| `xrpl.txq.required_fee_level` | int64 | `txq.enqueue` | Minimum fee level required for queue admission | -| `xrpl.txq.queue_size` | int64 | `txq.accept` | Queue depth at start of accept | -| `xrpl.txq.ledger_changed` | boolean | `txq.accept` | Whether the open ledger changed since last accept | -| `xrpl.txq.ledger_seq` | int64 | `txq.cleanup` | Ledger sequence for cleanup | -| `xrpl.txq.expired_count` | int64 | `txq.cleanup` | Number of expired transactions removed | -| `xrpl.txq.ter_code` | string | `txq.accept.tx` | Transaction engine result code | -| `xrpl.txq.retries_remaining` | int64 | `txq.accept.tx` | Remaining retry attempts for this transaction | -| `xrpl.txq.num_cleared` | int64 | `txq.batch_clear` | Number of transactions cleared in batch | +| Attribute | Type | Set On | Description | +| -------------------- | ------- | ------------------------------ | ---------------------------------------------------------- | +| `xrpl.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 | +| `xrpl.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.xrpl.txq.status="rejected"}` to find rejected queue attempts. +**Tempo query**: `{span.txq_status="rejected"}` to find rejected queue attempts. #### gRPC Attributes -| Attribute | Type | Set On | Description | -| ------------------ | ------ | -------------- | ------------------------------------------------------------ | -| `xrpl.grpc.method` | string | `grpc.request` | gRPC method name (e.g., `GetLedger`, `GetLedgerData`) | -| `xrpl.grpc.role` | string | `grpc.request` | Caller role: `"admin"` or `"user"` | -| `xrpl.grpc.status` | string | `grpc.request` | Result: `"success"`, `"error"`, `"resource_exhausted"`, etc. | +| Attribute | Type | Set On | Description | +| ------------ | ------ | -------------- | ------------------------------------------------------------ | +| `method` | string | `grpc.request` | gRPC method name (e.g., `GetLedger`, `GetLedgerData`) | +| `rpc_role` | string | `grpc.request` | Caller role: `"admin"` or `"user"` | +| `rpc_status` | string | `grpc.request` | Result: `"success"`, `"error"`, `"resource_exhausted"`, etc. | -**Tempo query**: `{span.xrpl.grpc.method="GetLedger"}` to find gRPC ledger requests. +**Tempo query**: `{span.method="GetLedger"}` to find gRPC ledger requests. #### Consensus Attributes -| Attribute | Type | Set On | Description | -| ------------------------------------------ | ------- | ---------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- | -| `xrpl.consensus.ledger_id` | string | `consensus.round` | Previous ledger hash (used for deterministic trace ID) | -| `xrpl.consensus.ledger.seq` | int64 | `consensus.round`, `consensus.ledger_close`, `consensus.accept`, `consensus.validation.send`, `consensus.accept.apply` | Ledger sequence number | -| `xrpl.consensus.mode` | string | `consensus.round`, `consensus.proposal.send`, `consensus.ledger_close` | Node mode via `toDisplayString()`: `"Proposing"`, `"Observing"`, etc. | -| `xrpl.consensus.round` | int64 | `consensus.proposal.send` | Consensus round number | -| `xrpl.consensus.proposers` | int64 | `consensus.proposal.send`, `consensus.accept` | Number of proposers in the round | -| `xrpl.consensus.round_time_ms` | int64 | `consensus.accept`, `consensus.accept.apply` | Total consensus round duration in milliseconds | -| `xrpl.consensus.proposing` | boolean | `consensus.validation.send` | Whether this node was a proposer | -| `xrpl.consensus.state` | string | `consensus.accept.apply` | Consensus outcome: `"finished"` or `"moved_on"` | -| `xrpl.consensus.close_time` | int64 | `consensus.accept.apply` | Agreed-upon ledger close time (epoch seconds) | -| `xrpl.consensus.close_time_correct` | boolean | `consensus.accept.apply` | Whether validators reached agreement on close time | -| `xrpl.consensus.close_resolution_ms` | int64 | `consensus.accept.apply` | Close time rounding granularity in milliseconds | -| `xrpl.consensus.parent_close_time` | int64 | `consensus.accept.apply` | Parent ledger's close time (epoch seconds) | -| `xrpl.consensus.close_time_self` | int64 | `consensus.accept.apply` | This node's proposed close time | -| `xrpl.consensus.close_time_vote_bins` | string | `consensus.accept.apply` | Histogram of close time votes from validators | -| `xrpl.consensus.resolution_direction` | string | `consensus.accept.apply` | Resolution change: `"increased"`, `"decreased"`, or `"unchanged"` | -| `xrpl.consensus.converge_percent` | int64 | `consensus.establish` | Convergence percentage threshold | -| `xrpl.consensus.establish_count` | int64 | `consensus.establish` | Number of establish iterations completed | -| `xrpl.consensus.proposers_agreed` | int64 | `consensus.establish` | Number of proposers that agreed on this round | -| `xrpl.consensus.avalanche_threshold` | int64 | `consensus.update_positions` | Avalanche threshold for dispute resolution | -| `xrpl.consensus.close_time_threshold` | int64 | `consensus.update_positions` | Close time agreement threshold | -| `xrpl.consensus.have_close_time_consensus` | boolean | `consensus.update_positions` | Whether close time consensus has been reached | -| `xrpl.consensus.agree_count` | int64 | `consensus.check` | Number of proposers that agree with our position | -| `xrpl.consensus.disagree_count` | int64 | `consensus.check` | Number of proposers that disagree with our position | -| `xrpl.consensus.threshold_percent` | int64 | `consensus.check` | Required agreement threshold percentage | -| `xrpl.consensus.result` | string | `consensus.check` | Check result: `"yes"`, `"no"`, or `"expired"` | -| `xrpl.consensus.quorum` | int64 | `consensus.check` | Required quorum for validation | -| `xrpl.consensus.validation_count` | int64 | `consensus.check` | Number of validations received | -| `xrpl.consensus.trace_strategy` | string | `consensus.round` | Trace sampling strategy used for this round | -| `xrpl.consensus.round_id` | string | `consensus.round` | Deterministic round identifier | -| `xrpl.consensus.mode.old` | string | `consensus.mode_change` | Previous consensus mode | -| `xrpl.consensus.mode.new` | string | `consensus.mode_change` | New consensus mode | -| `xrpl.tx.id` | string | `consensus.update_positions` | Disputed transaction ID | -| `xrpl.dispute.our_vote` | boolean | `consensus.update_positions` | Our vote on the disputed transaction | -| `xrpl.dispute.yays` | int64 | `consensus.update_positions` | Number of proposers voting to include | -| `xrpl.dispute.nays` | int64 | `consensus.update_positions` | Number of proposers voting to exclude | +| Attribute | Type | Set On | Description | +| --------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- | +| `xrpl.consensus.ledger_id` | string | `consensus.round` | Previous ledger hash (used for deterministic trace ID) | +| `xrpl.ledger.seq` | int64 | `consensus.round`, `consensus.ledger_close`, `consensus.accept`, `consensus.validation.send`, `consensus.accept.apply` | Ledger sequence number | +| `xrpl.consensus.mode` | string | `consensus.round`, `consensus.proposal.send`, `consensus.ledger_close` | Node mode via `toDisplayString()`: `"Proposing"`, `"Observing"`, etc. | +| `xrpl.consensus.round` | int64 | `consensus.proposal.send` | Consensus round number | +| `proposers` | int64 | `consensus.proposal.send`, `consensus.accept` | Number of proposers in the round | +| `round_time_ms` | int64 | `consensus.accept`, `consensus.accept.apply` | Total consensus round duration in milliseconds | +| `proposing` | boolean | `consensus.validation.send` | Whether this node was a proposer | +| `consensus_state` | string | `consensus.accept.apply` | Consensus outcome: `"finished"` or `"moved_on"` | +| `close_time` | int64 | `consensus.accept.apply` | Agreed-upon ledger close time (epoch seconds) | +| `close_time_correct` | boolean | `consensus.accept.apply` | Whether validators reached agreement on close time | +| `close_resolution_ms` | int64 | `consensus.accept.apply` | Close time rounding granularity in milliseconds | +| `parent_close_time` | int64 | `consensus.accept.apply` | Parent ledger's close time (epoch seconds) | +| `close_time_self` | int64 | `consensus.accept.apply` | This node's proposed close time | +| `close_time_vote_bins` | string | `consensus.accept.apply` | Histogram of close time votes from validators | +| `resolution_direction` | string | `consensus.accept.apply` | Resolution change: `"increased"`, `"decreased"`, or `"unchanged"` | +| `converge_percent` | int64 | `consensus.establish` | Convergence percentage threshold | +| `establish_count` | int64 | `consensus.establish` | Number of establish iterations completed | +| `proposers_agreed` | int64 | `consensus.establish` | Number of proposers that agreed on this round | +| `avalanche_threshold` | int64 | `consensus.update_positions` | Avalanche threshold for dispute resolution | +| `close_time_threshold` | int64 | `consensus.update_positions` | Close time agreement threshold | +| `have_close_time_consensus` | boolean | `consensus.update_positions` | Whether close time consensus has been reached | +| `agree_count` | int64 | `consensus.check` | Number of proposers that agree with our position | +| `disagree_count` | int64 | `consensus.check` | Number of proposers that disagree with our position | +| `threshold_percent` | int64 | `consensus.check` | Required agreement threshold percentage | +| `consensus_result` | string | `consensus.check` | Check result: `"yes"`, `"no"`, or `"expired"` | +| `quorum` | int64 | `consensus.check` | Required quorum for validation | +| `validation_count` | int64 | `consensus.check` | Number of validations received | +| `trace_strategy` | string | `consensus.round` | Trace sampling strategy used for this round | +| `xrpl.consensus.round_id` | string | `consensus.round` | Deterministic round identifier | +| `xrpl.consensus.mode.old` | string | `consensus.mode_change` | Previous consensus mode | +| `xrpl.consensus.mode.new` | string | `consensus.mode_change` | New consensus mode | +| `xrpl.tx.id` | string | `consensus.update_positions` | Disputed transaction ID | +| `dispute_our_vote` | boolean | `consensus.update_positions` | Our vote on the disputed transaction | +| `dispute_yays` | int64 | `consensus.update_positions` | Number of proposers voting to include | +| `dispute_nays` | int64 | `consensus.update_positions` | Number of proposers voting to exclude | **Tempo query**: `{span.xrpl.consensus.mode="Proposing"}` to find rounds where node was proposing. @@ -332,27 +332,27 @@ Every span can carry key-value attributes that provide context for filtering and #### Ledger Attributes -| Attribute | Type | Set On | Description | -| --------------------------------- | ------- | ------------------------------------------------------------- | ------------------------------------------------ | -| `xrpl.ledger.seq` | int64 | `ledger.build`, `ledger.validate`, `ledger.store`, `tx.apply` | Ledger sequence number | -| `xrpl.ledger.close_time` | int64 | `ledger.build` | Ledger close time (epoch seconds) | -| `xrpl.ledger.close_time_correct` | boolean | `ledger.build` | Whether close time was agreed upon by validators | -| `xrpl.ledger.close_resolution_ms` | int64 | `ledger.build` | Close time rounding granularity in milliseconds | -| `xrpl.ledger.tx_count` | int64 | `ledger.build`, `tx.apply` | Transactions in the ledger | -| `xrpl.ledger.tx_failed` | int64 | `ledger.build`, `tx.apply` | Failed transactions in the ledger | -| `xrpl.ledger.validations` | int64 | `ledger.validate` | Number of validations received for this ledger | +| Attribute | Type | Set On | Description | +| --------------------- | ------- | ------------------------------------------------------------- | ------------------------------------------------ | +| `xrpl.ledger.seq` | int64 | `ledger.build`, `ledger.validate`, `ledger.store`, `tx.apply` | Ledger sequence number | +| `close_time` | int64 | `ledger.build` | Ledger close time (epoch seconds) | +| `close_time_correct` | boolean | `ledger.build` | Whether close time was agreed upon by validators | +| `close_resolution_ms` | int64 | `ledger.build` | Close time rounding granularity in milliseconds | +| `tx_count` | int64 | `ledger.build`, `tx.apply` | Transactions in the ledger | +| `tx_failed` | int64 | `ledger.build`, `tx.apply` | Failed transactions in the ledger | +| `validations` | int64 | `ledger.validate` | Number of validations received for this ledger | **Tempo query**: `{span.xrpl.ledger.seq=12345}` to find all spans for a specific ledger. #### Peer Attributes -| Attribute | Type | Set On | Description | -| ---------------------------------- | ------- | ---------------------------------------------------------------- | ---------------------------------------------------- | -| `xrpl.peer.id` | int64 | `tx.receive`, `peer.proposal.receive`, `peer.validation.receive` | Peer identifier | -| `xrpl.peer.proposal.trusted` | boolean | `peer.proposal.receive` | Whether the proposal came from a trusted validator | -| `xrpl.peer.validation.ledger_hash` | string | `peer.validation.receive` | Ledger hash the validation refers to | -| `xrpl.peer.validation.full` | boolean | `peer.validation.receive` | Whether this is a full (not partial) validation | -| `xrpl.peer.validation.trusted` | boolean | `peer.validation.receive` | Whether the validation came from a trusted validator | +| Attribute | Type | Set On | Description | +| -------------------- | ------- | ---------------------------------------------------------------- | ---------------------------------------------------- | +| `xrpl.peer.id` | int64 | `tx.receive`, `peer.proposal.receive`, `peer.validation.receive` | Peer identifier | +| `proposal_trusted` | boolean | `peer.proposal.receive` | Whether the proposal came from a trusted validator | +| `xrpl.ledger.hash` | string | `peer.validation.receive` | Ledger hash the validation refers to | +| `validation_full` | boolean | `peer.validation.receive` | Whether this is a full (not partial) validation | +| `validation_trusted` | boolean | `peer.validation.receive` | Whether the validation came from a trusted validator | **Prometheus labels**: `xrpl_peer_proposal_trusted`, `xrpl_peer_validation_trusted` (SpanMetrics dimensions). @@ -375,14 +375,14 @@ The OTel Collector's SpanMetrics connector automatically generates RED (Rate, Er **Additional dimension labels** (configured in `otel-collector-config.yaml`): -| Span Attribute | Prometheus Label | Applies To | -| ------------------------------ | ------------------------------ | ------------------------- | -| `xrpl.rpc.command` | `xrpl_rpc_command` | `rpc.command.*` | -| `xrpl.rpc.status` | `xrpl_rpc_status` | `rpc.command.*` | -| `xrpl.consensus.mode` | `xrpl_consensus_mode` | `consensus.ledger_close` | -| `xrpl.tx.local` | `xrpl_tx_local` | `tx.process` | -| `xrpl.peer.proposal.trusted` | `xrpl_peer_proposal_trusted` | `peer.proposal.receive` | -| `xrpl.peer.validation.trusted` | `xrpl_peer_validation_trusted` | `peer.validation.receive` | +| Span Attribute | Prometheus Label | Applies To | +| --------------------- | ------------------------------ | ------------------------- | +| `command` | `xrpl_rpc_command` | `rpc.command.*` | +| `rpc_status` | `xrpl_rpc_status` | `rpc.command.*` | +| `xrpl.consensus.mode` | `xrpl_consensus_mode` | `consensus.ledger_close` | +| `local` | `xrpl_tx_local` | `tx.process` | +| `proposal_trusted` | `xrpl_peer_proposal_trusted` | `peer.proposal.receive` | +| `validation_trusted` | `xrpl_peer_validation_trusted` | `peer.validation.receive` | **Where to query**: Prometheus → `traces_span_metrics_calls_total{span_name="rpc.command.server_info"}` @@ -545,13 +545,13 @@ Special job types (`limit=0`: `peerCommand`, `diskAccess`, `processTransaction`, The Consensus Health dashboard includes 5 close-time panels added in Phase 4: -| Panel | Metric / Attribute | Description | -| ---------------------------- | --------------------------------------------------------------- | ------------------------------------------------------------------------ | -| Close Time Correctness | `xrpl.consensus.close_time_correct` | Percentage of rounds with agreed-upon close time | -| Resolution Direction | `xrpl.consensus.resolution_direction` | Rate of resolution increases, decreases, and unchanged per time interval | -| Close Time Drift | `xrpl.consensus.close_time` vs `xrpl.consensus.close_time_self` | Difference between agreed close time and node's own proposed close time | -| Resolution Change Timeline | `xrpl.consensus.close_resolution_ms` | Close time resolution granularity over time | -| Close Time Vote Distribution | `xrpl.consensus.close_time_vote_bins` | Histogram of validator close time votes per round | +| Panel | Metric / Attribute | Description | +| ---------------------------- | --------------------------------- | ------------------------------------------------------------------------ | +| Close Time Correctness | `close_time_correct` | Percentage of rounds with agreed-upon close time | +| Resolution Direction | `resolution_direction` | Rate of resolution increases, decreases, and unchanged per time interval | +| Close Time Drift | `close_time` vs `close_time_self` | Difference between agreed close time and node's own proposed close time | +| Resolution Change Timeline | `close_resolution_ms` | Close time resolution granularity over time | +| Close Time Vote Distribution | `close_time_vote_bins` | Histogram of validator close time votes per round | **Template variables** (Consensus Health dashboard): @@ -580,13 +580,13 @@ The Consensus Health dashboard includes 5 close-time panels added in Phase 4: | All RPC calls | `{resource.service.name="xrpld" && name="rpc.http_request"}` | | Specific RPC command | `{resource.service.name="xrpld" && name="rpc.command.server_info"}` | | Slow RPC calls | `{resource.service.name="xrpld" && name=~"rpc.command.*"} \| duration > 100ms` | -| Failed RPC calls | `{span.xrpl.rpc.status="error"}` | +| Failed RPC calls | `{span.rpc_status="error"}` | | Specific transaction | `{span.xrpl.tx.hash=""}` | -| Local transactions only | `{span.xrpl.tx.local=true}` | +| Local transactions only | `{span.local=true}` | | Consensus rounds | `{resource.service.name="xrpld" && name="consensus.accept"}` | | Rounds by mode | `{span.xrpl.consensus.mode="proposing"}` | | Specific ledger | `{span.xrpl.ledger.seq=12345}` | -| Peer proposals (trusted) | `{span.xrpl.peer.proposal.trusted=true}` | +| Peer proposals (trusted) | `{span.proposal_trusted=true}` | ### Trace Structure diff --git a/OpenTelemetryPlan/Phase5_IntegrationTest_taskList.md b/OpenTelemetryPlan/Phase5_IntegrationTest_taskList.md index b8d4ea3a38..6f9df2f969 100644 --- a/OpenTelemetryPlan/Phase5_IntegrationTest_taskList.md +++ b/OpenTelemetryPlan/Phase5_IntegrationTest_taskList.md @@ -61,7 +61,7 @@ Tempo/Prometheus. - `rpc.command.server_info` spans (callMethod) - `rpc.command.server_state` spans (callMethod) - `rpc.command.ledger` spans (callMethod) -- Verify `xrpl.rpc.command` attribute present on `rpc.command.*` spans +- Verify `command` attribute present on `rpc.command.*` spans **Verification**: