Merge branch 'pratik/otel-phase10-workload-validation' into pratik/otel-sync-diagnostics

This commit is contained in:
Pratik Mankawde
2026-08-11 16:29:00 +01:00
6 changed files with 375 additions and 50 deletions

View File

@@ -123,44 +123,56 @@ path in Phase 1b through Phase 5.
### 2.3.2 Complete Span Catalog
| Span name | Description |
| ------------------------------ | --------------------------------------- |
| `tx.receive` | Transaction received from network |
| `tx.validate` | Transaction signature/format validation |
| `tx.process` | Full transaction processing |
| `tx.relay` | Transaction relay to peers |
| `tx.apply` | Apply transaction to ledger |
| `consensus.round` | Complete consensus round |
| `consensus.phase.open` | Open phase - collecting transactions |
| `consensus.phase.establish` | Establish phase - reaching agreement |
| `consensus.phase.accept` | Accept phase - applying consensus |
| `consensus.proposal.receive` | Receive peer proposal |
| `consensus.proposal.send` | Send our proposal |
| `consensus.validation.receive` | Receive peer validation |
| `consensus.validation.send` | Send our validation |
| `rpc.request` | HTTP/WebSocket request handling |
| `rpc.command.*` | Specific RPC command (dynamic) |
| `peer.connect` | Peer connection establishment |
| `peer.disconnect` | Peer disconnection |
| `peer.message.send` | Send protocol message |
| `peer.message.receive` | Receive protocol message |
| `ledger.acquire` | Ledger acquisition from network |
| `ledger.build` | Build new ledger |
| `ledger.validate` | Ledger validation |
| `ledger.close` | Close ledger |
| `ledger.replay` | Ledger replay executed |
| `ledger.delta` | Delta-based ledger acquired |
| `pathfind.request` | Path request initiated |
| `pathfind.compute` | Path computation executed |
| `txq.enqueue` | Transaction queued |
| `txq.apply` | Queued transaction applied |
| `fee.escalate` | Fee escalation triggered |
| `validator.list.fetch` | UNL list fetched |
| `validator.manifest` | Manifest update processed |
| `amendment.vote` | Amendment voting executed |
| `shamap.sync` | State tree synchronization |
| `job.enqueue` | Job added to queue |
| `job.execute` | Job execution |
> **Status column.** This catalog is the design inventory; it is not a
> statement of what currently emits. `Live` means the span is present in the
> implemented inventory ([09-data-collection-reference.md §1.1](./09-data-collection-reference.md#11-complete-span-inventory-37-spans)),
> which is the authoritative list. `Renamed`/`Split` means the concept shipped
> under a different name than planned here. **Not built** means no span is
> emitted for it today.
>
> The four `peer.*` entries are the peer-span coverage gap: only
> `peer.proposal.receive` and `peer.validation.receive` exist, so protocol
> message send/receive and connection lifecycle are untraced. See
> [09 §6.4](./09-data-collection-reference.md#64-peer-span-coverage-gap-not-implemented).
| Span name | Description | Status |
| ------------------------------ | --------------------------------------- | ------------------------------------------------ |
| `tx.receive` | Transaction received from network | Live |
| `tx.validate` | Transaction signature/format validation | **Not built** |
| `tx.process` | Full transaction processing | Live |
| `tx.relay` | Transaction relay to peers | **Not built** |
| `tx.apply` | Apply transaction to ledger | Live |
| `consensus.round` | Complete consensus round | Live |
| `consensus.phase.open` | Open phase - collecting transactions | Live |
| `consensus.phase.establish` | Establish phase - reaching agreement | Renamed `consensus.establish` |
| `consensus.phase.accept` | Accept phase - applying consensus | Renamed `consensus.accept` |
| `consensus.proposal.receive` | Receive peer proposal | Live |
| `consensus.proposal.send` | Send our proposal | Live |
| `consensus.validation.receive` | Receive peer validation | Live |
| `consensus.validation.send` | Send our validation | Live |
| `rpc.request` | HTTP/WebSocket request handling | Split into `rpc.http_request` / `rpc.ws_message` |
| `rpc.command.*` | Specific RPC command (dynamic) | Live |
| `peer.connect` | Peer connection establishment | **Not built** |
| `peer.disconnect` | Peer disconnection | **Not built** |
| `peer.message.send` | Send protocol message | **Not built** |
| `peer.message.receive` | Receive protocol message | **Not built** |
| `ledger.acquire` | Ledger acquisition from network | Live |
| `ledger.build` | Build new ledger | Live |
| `ledger.validate` | Ledger validation | Live |
| `ledger.close` | Close ledger | Renamed `consensus.ledger_close` |
| `ledger.replay` | Ledger replay executed | **Not built** |
| `ledger.delta` | Delta-based ledger acquired | **Not built** |
| `pathfind.request` | Path request initiated | Live |
| `pathfind.compute` | Path computation executed | Live |
| `txq.enqueue` | Transaction queued | Live |
| `txq.apply` | Queued transaction applied | Renamed `txq.apply_direct` / `txq.accept_tx` |
| `fee.escalate` | Fee escalation triggered | **Not built** |
| `validator.list.fetch` | UNL list fetched | **Not built** |
| `validator.manifest` | Manifest update processed | **Not built** |
| `amendment.vote` | Amendment voting executed | **Not built** |
| `shamap.sync` | State tree synchronization | **Not built** |
| `job.enqueue` | Job added to queue | **Not built** |
| `job.execute` | Job execution | **Not built** |
### 2.3.3 Attribute Naming Conventions

View File

@@ -1707,13 +1707,158 @@ counters), observed from an existing cumulative source each collection cycle:
## 6. Known Issues
| Issue | Impact | Status |
| ------------------------------------------------------------------ | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------- |
| `warn` and `drop` metrics use non-standard StatsD `\|m` meter type | Metrics silently dropped by OTel StatsD receiver | Phase 6 Task 6.1 — needs `\|m``\|c` change in StatsDCollector.cpp |
| `jobq_job_count` may not emit in standalone mode | Missing from Prometheus in some test configs | Requires active job queue activity |
| `rpc_requests` depends on `[insight]` config | Zero series if StatsD not configured | Requires `[insight] server=statsd` in xrpld.cfg |
| Peer tracing enabled by default | `peer.*` spans emit unless `trace_peer=0` | High volume — set `trace_peer=0` to opt out on busy mainnet nodes |
| `handler="other"` mixes several producers | Cannot separate `GetConsL1` from `GetConsL2` | By design — the cardinality bound; see [§Per-Job-Type Metrics](#per-job-type-metrics-synchronous-countershistogram) |
| Issue | Impact | Status |
| --------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| `warn` and `drop` metrics use non-standard StatsD `\|m` meter type | Metrics silently dropped by OTel StatsD receiver | Phase 6 Task 6.1 — needs `\|m``\|c` change in StatsDCollector.cpp |
| `jobq_job_count` may not emit in standalone mode | Missing from Prometheus in some test configs | Requires active job queue activity |
| `rpc_requests` depends on `[insight]` config | Zero series if StatsD not configured | Requires `[insight] server=statsd` in xrpld.cfg |
| Peer tracing enabled by default | `peer.*` spans emit unless `trace_peer=0` | High volume — set `trace_peer=0` to opt out on busy mainnet nodes |
| `handler="other"` mixes several producers | Cannot separate `GetConsL1` from `GetConsL2` | By design — the cardinality bound; see [§Per-Job-Type Metrics](#per-job-type-metrics-synchronous-countershistogram) |
| `overhead_cluster_*` is always zero | 8 dashboard panel references are flatlines by construction; cluster traffic is counted as `unknown` | **NOT IMPLEMENTED** — see [§6.0](#60-mtcluster-is-counted-as-unknown-not-implemented) |
| `squelch_ignored_bytes_in/out` always read zero | Only the `_messages_*` pair carries signal for this category | **NOT IMPLEMENTED** — see [§6.1](#61-squelch_ignored-byte-counts-not-implemented) |
| `total_bytes_in` and `total_bytes_out` use different size bases | In/out byte totals are not directly comparable when compression is on | **NOT IMPLEMENTED** — see [§6.2](#62-inboundoutbound-byte-basis-asymmetry-not-implemented) |
| `overhead` conflates `mtPING` with `mtSTATUS_CHANGE` | Keepalive traffic cannot be isolated from status-change traffic | **NOT IMPLEMENTED** — needs a new category; see [§6.3](#63-peer-keepalive-and-discovery-traffic-gaps-not-implemented) |
| No metrics for ping RTT distribution, ping timeouts, or `mtENDPOINTS` | Peer keepalive and discovery health are not observable | **NOT IMPLEMENTED** — see [§6.3](#63-peer-keepalive-and-discovery-traffic-gaps-not-implemented) |
| 11 of 13 peer message families have no spans | `02` §2.3.2 catalogs `peer.message.*`, `peer.connect`, `peer.disconnect` that were never built | **NOT IMPLEMENTED** — see [§6.4](#64-peer-span-coverage-gap-not-implemented) |
| PeerFinder exports 2 of ~17 available slot/cache readings | Slot pressure, connection churn and discovery-cache health are not observable | **NOT IMPLEMENTED** — see [§6.5](#65-peerfinder-slot-and-cache-metrics-not-implemented) |
### 6.0 `mtCLUSTER` is counted as `unknown`: NOT IMPLEMENTED
`mtCLUSTER` is absent from `kTypeLookup`
(`src/xrpld/overlay/detail/TrafficCount.cpp:11-27`), and `categorize()`'s
fallback chain only inspects `TMLedgerData`, `TMGetLedger` and
`TMGetObjectByHash` before returning `Category::Unknown` (`:135`). No call site
ever passes `Category::Cluster`. Cluster traffic is therefore counted as
`unknown`, and `overhead_cluster_bytes_in/out` and
`overhead_cluster_messages_in/out` are always zero — including the 8 panel
references across `network-traffic` and `overlay-traffic-detail` (both the local
and grafanacloud copies).
This also degrades `unknown_*` as an anomaly signal: on a clustered node it mixes
genuinely unrecognized wire types with routine `mtCLUSTER` traffic.
**Status**: Planned, not yet implemented. The fix is a one-line addition to
`kTypeLookup`, but `TrafficCount.cpp` is shared overlay code rather than a
telemetry-owned file, so it is scoped as a separate overlay change. Note that
landing it moves volume out of `unknown_bytes_in`, so any threshold measured
against that series needs re-baselining. Until then, treat `overhead_cluster_*`
as "no data" rather than "no cluster traffic", and read the
[Cluster](../docs/telemetry-glossary.md#cluster) glossary entry's guidance on
sustained cluster overhead as not yet observable.
### 6.1 `squelch_ignored` byte counts: NOT IMPLEMENTED
`OverlayImpl::updateSlotAndSquelch` reports the `SquelchIgnored` category with a
hardcoded size of `0` (`src/xrpld/overlay/detail/OverlayImpl.cpp:1460` and
`:1489`), so `squelch_ignored_bytes_in` and `squelch_ignored_bytes_out` are
always zero. Only `squelch_ignored_messages_in/out` carry signal. This is
inconsistent with `SquelchSuppressed`, which passes the real wire size
(`src/xrpld/overlay/detail/PeerImp.cpp:302`) — so the two squelch categories are
not comparable on bytes.
The message size is available at all four call sites (each holds the protobuf
message and could call `Message::messageSize()`), but plumbing it through would
require widening the two `OverlayImpl::updateSlotAndSquelch` overloads.
**Status**: Deferred as a separate change — a public signature change on
`OverlayImpl` is out of scope for the telemetry chain, since `OverlayImpl.h` is
shared overlay code rather than a telemetry-owned file. Until it lands, read
`squelch_ignored` on the `_messages_*` series only and do not build a
bytes-per-message ratio from this category.
### 6.2 Inbound/outbound byte-basis asymmetry: NOT IMPLEMENTED
Inbound traffic is counted with the raw wire size as received
(`src/xrpld/overlay/detail/PeerImp.cpp:1079`), while outbound traffic is counted
from the possibly-compressed send buffer
(`getBuffer(compressionEnabled_).size()`, `PeerImp.cpp:313`). When compression is
enabled the two directions measure different things, so `total_bytes_in` versus
`total_bytes_out` is not a like-for-like comparison, and neither is any
`{category}_bytes_in` / `_bytes_out` pair.
A related documentation defect sits in the same class: the `TrafficCount` header
comment states that "messages whose category is not in `TrafficCount::categorize`
are not included in the total" (`src/xrpld/overlay/detail/TrafficCount.h:28-31`),
but `Category::Total` is incremented unconditionally at
`src/xrpld/overlay/detail/PeerImp.cpp:1079`, _before_ the per-category split. The
total does include uncategorized traffic; the comment is stale.
**Status**: Planned, not yet implemented — neither the metric change nor the
header-comment correction has landed, because `TrafficCount.h` is shared overlay
code rather than a telemetry-owned file. Normalizing one direction would in any
case silently redefine an existing series, so the likely resolution is to document
the asymmetry at the class and leave both readings intact. Until then, compare
`_bytes_in` against `_bytes_out` only when compression is known to be off.
### 6.3 Peer keepalive and discovery traffic gaps: NOT IMPLEMENTED
Three related gaps on the peer keepalive and discovery paths. All are byte/message
counters only — none has a dedicated instrument, and none is traced.
| Gap | Current state | What is missing |
| --------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `mtPING` / `mtSTATUS_CHANGE` share `Category::Base` (`TrafficCount.cpp:12-13`) → `overhead_*` | Both message types land in one bucket | A distinct category for one of them, plus name-map, `counts_` and dashboard entries |
| Ping RTT | An 8-sample EWMA per peer (`PeerImp.cpp:1150-1163`), exported only as the single `peer_quality{metric="peer_latency_p90_ms"}` gauge | A histogram; the lone p90 hides a bimodal peer set |
| Ping failures | `fail("Ping Timeout")` (`PeerImp.cpp:762`) logs only; a wrong-cookie PONG (`PeerImp.cpp:1146`) is silently ignored | A counter for each |
| `mtENDPOINTS` | `overhead_overlay_*` bytes only | Counters for endpoints received / handed out / malformed (`PeerImp.cpp:1265-1270` charges a fee but records no metric) |
**Status**: Planned, not yet implemented. Adding these means a new metric family
plus matching rows in this document, in
[docs/telemetry-runbook.md](../docs/telemetry-runbook.md) § Metric Reference, and
in `docker/telemetry/workload/expected_metrics.json` (Phase 10 branch — see the
Cross-Phase Dependency Chain in
[06-implementation-phases.md](./06-implementation-phases.md)), and dashboard
panels following the conventions in `06` § Branch-to-Change Mapping.
### 6.4 Peer span coverage gap: NOT IMPLEMENTED
[02-design-decisions.md §2.3.2](./02-design-decisions.md#232-complete-span-catalog)
catalogs `peer.connect`, `peer.disconnect`, `peer.message.send` and
`peer.message.receive`. None was ever built: the implemented peer surface is the
two spans in [§Peer Spans](#peer-spans) above (`peer.proposal.receive`,
`peer.validation.receive`). Of the 13 protocol message families, only
`mtGET_OBJECTS` has native instrumentation, and only transactions and consensus
messages are traced.
**Status**: NOT IMPLEMENTED. The span catalog in `02` §2.3.2 is a design
inventory, not a statement of what emits; §2.3.2 now marks which entries are
live. Instrumenting the remaining families would change the "~37 spans" count
asserted in [§1.1](#11-complete-span-inventory-37-spans) and in
`docker/telemetry/workload/expected_spans.json`, so it is scoped as its own
change rather than folded into a metric task.
### 6.5 PeerFinder slot and cache metrics: NOT IMPLEMENTED
`peer_finder::Manager` registers exactly two instruments —
`peer_finder_active_inbound_peers` and `peer_finder_active_outbound_peers`
(`src/libxrpl/peerfinder/PeerfinderManager.cpp:229-230`), listed in
[§2.1](#21-gauges). The `Counts` class exposes roughly fifteen further readings
that are never exported (`include/xrpl/peerfinder/detail/Counts.h`), and neither
discovery cache has any instrument at all.
| Reading | Source | Why it matters |
| --------------------------------------------------- | ---------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `attempts()`, `attemptsNeeded()` | `Counts.h:68,79` | Outbound connection churn; distinguishes "not trying" from "trying and failing" |
| `outMax()`, `outActive()`, `outboundSlotsFree()` | `Counts.h:88,98,205` | Outbound slot saturation |
| `inMax()`, `inboundActive()`, `inboundSlotsFree()` | `Counts.h:165,174,193` | Inbound slot saturation — the two exported gauges give the actives but not the caps, so utilization cannot be computed |
| `acceptCount()`, `connectCount()`, `closingCount()` | `Counts.h:138,147,156` | Handshake pipeline depth; `closingCount()` rising is teardown backpressure |
| `fixed()`, `fixedActive()` | `Counts.h:107,116` | Whether configured fixed peers are actually connected |
| `isConnectedToNetwork()` | `Counts.h:218` | Binary reachability |
| `Livecache::size()` | `Livecache.h:365` | Size of the live endpoint pool used to answer `mtENDPOINTS` |
| `Bootcache::size()` | `Bootcache.h:121` | Bootstrap-address pool; an empty bootcache is why a fresh node cannot find peers |
The two exported actives are also the only inputs to the "Inbound vs Outbound"
panel specified for the Peer Quality dashboard
([06 § Branch-to-Change Mapping, Task 9.12](./06-implementation-phases.md)), so
that panel cannot show slot utilization as a percentage.
**Status**: Planned, not yet implemented. These would extend the existing
`beast::insight` registration in `PeerfinderManager.cpp` (arrow **B** in the
[Data Flow Overview](#data-flow-overview)) rather than use the `XRPL_METRIC_*`
macros, because `libxrpl` code cannot use those macros — see the pipeline note in
[§2.5](#25-per-job-type-queue-gauges). `Livecache`/`Bootcache` currently receive
no collector reference, so exporting their sizes needs one plumbed in or the
values read via the existing `Manager` hook.
---

View File

@@ -439,6 +439,137 @@ These metrics serve multiple external consumer categories identified during rese
---
## Task 9.14: Overlay Traffic Accounting Defects (Documentation Only)
> **Status**: DOCUMENTED, NOT FIXED. Reference: [09 §6.0-§6.2](./09-data-collection-reference.md#6-known-issues)
**Objective**: Record four pre-existing overlay traffic-accounting defects so
dashboard readers are not misled. All four originate in `develop`-owned overlay
files, so **no code fix lands on this branch**.
| # | Defect | Effect | Fix location (NOT this branch) |
| --- | -------------------------------------- | ----------------------------------------------------------------------------------------- | ------------------------------------------------ |
| 1 | `mtCLUSTER` missing from `kTypeLookup` | `overhead_cluster_*` always zero; 8 panels flatline; cluster traffic counted as `unknown` | `TrafficCount.cpp:11-27` |
| 2 | Stale `Total` header comment | Claims uncategorized traffic is excluded; it is included | `TrafficCount.h:28-31` |
| 3 | `SquelchIgnored` reported with size 0 | `squelch_ignored_bytes_*` always zero, inconsistent with `SquelchSuppressed` | `OverlayImpl.cpp:1460,1489` (+ signature change) |
| 4 | In/out byte-basis asymmetry | `_bytes_in` vs `_bytes_out` not comparable under compression | `PeerImp.cpp:1079` vs `:313` |
**Why deferred**: Defect 3 requires widening the two
`OverlayImpl::updateSlotAndSquelch` overloads — a public signature change on
shared overlay code. Defects 1, 2 and 4 sit in `TrafficCount.{h,cpp}`, likewise
not telemetry-owned. Routing them through the telemetry chain would hide overlay
changes from overlay reviewers and couple them to a 12-PR merge timeline.
**Key modified files**: `OpenTelemetryPlan/09-data-collection-reference.md` only.
**Exit Criteria**:
- [x] Each defect documented with file:line evidence in `09` §6
- [x] `overhead_cluster_*` documented as "no data", not "no cluster traffic"
- [ ] Follow-up overlay-owned branch raised for the four code fixes
- [ ] Re-baseline any threshold keyed on `unknown_bytes_in` when defect 1 lands
---
## Task 9.15: Peer Keepalive and Discovery Instrumentation
> **Status**: NOT IMPLEMENTED — awaiting a decision on whether `XRPL_METRIC_*`
> call sites may be added to `src/xrpld/overlay/detail/PeerImp.cpp` from this
> branch. Reference: [09 §6.3](./09-data-collection-reference.md#63-peer-keepalive-and-discovery-traffic-gaps-not-implemented)
**Objective**: Make peer keepalive and peer-discovery health observable. Today
`mtPING`, `mtSTATUS_CHANGE` and `mtENDPOINTS` are byte counters only.
| Proposed metric | Type | Labels | Record site |
| ------------------------------- | --------- | -------------------------------- | --------------------------------------------------- |
| `peer_ping_rtt_ms` | Histogram | none (see note) | `PeerImp.cpp:1150-1163`, where the EWMA is computed |
| `peer_ping_timeouts_total` | Counter | `reason="timeout"\|"bad_cookie"` | `PeerImp.cpp:762` and `:1146` |
| `peer_endpoints_received_total` | Counter | `result="accepted"\|"malformed"` | `PeerImp.cpp:1265-1270` |
**Design notes / open questions**:
- A histogram needs an explicit bucket view: the SDK default tops out at 10000,
and these are milliseconds. Follow the µs-ladder precedent in
`MetricsRegistry.cpp` (see [09 § GetObject Request Path](./09-data-collection-reference.md#getobject-request-path-synchronous-countershistograms)).
- `peer_id` as a label is unbounded cardinality — rejected. A bounded
`peer_role`-style label is the alternative if per-peer attribution is needed.
- Splitting `mtPING` out of `Category::Base` is a `TrafficCount.cpp` change and
therefore blocked with Task 9.14.
- Per the runbook's "Adding a New Metric" contract, `_total` is reserved for
monotonic counters; a histogram takes no suffix.
**Key files (if approved)**: `src/xrpld/overlay/detail/PeerImp.cpp`,
`09-data-collection-reference.md`, `docs/telemetry-runbook.md` § Metric Reference,
`docker/telemetry/grafana/dashboards/peer-quality.json`, and
`docker/telemetry/workload/expected_metrics.json` (**Phase 10 branch**).
**Exit Criteria**:
- [ ] Decision recorded on editing `PeerImp.cpp` from the telemetry chain
- [ ] Three instruments emitting, with an explicit histogram bucket view
- [ ] Rows added to `09` §5b, runbook § Metric Reference, and `expected_metrics.json`
- [ ] Peer Quality dashboard panels follow the Task 9.12 conventions (`$node`, Title Case, legend dimensions)
- [ ] `check_otel_naming.py` passes (Rules D and E cover the new labels)
---
## Task 9.16: PeerFinder Slot and Cache Metrics
> **Status**: NOT IMPLEMENTED. Reference: [09 §6.5](./09-data-collection-reference.md#65-peerfinder-slot-and-cache-metrics-not-implemented)
**Objective**: Export the PeerFinder slot counts and discovery-cache sizes.
Only 2 of ~17 available readings are exported today.
**What to do**: Extend the existing `Stats` struct in
`src/libxrpl/peerfinder/PeerfinderManager.cpp:227-236` with gauges for the
`Counts` accessors listed in [09 §6.5](./09-data-collection-reference.md#65-peerfinder-slot-and-cache-metrics-not-implemented)
(slot caps and frees, attempt counts, handshake pipeline depth, fixed-peer state,
network reachability), plus `Livecache::size()` and `Bootcache::size()`.
**Pipeline constraint**: `PeerfinderManager.cpp` is in `libxrpl`, which **cannot**
use the `XRPL_METRIC_*` macros. These must go through `beast::insight`
arrow **B**, not **C**. Naming follows `GroupImp::makeName()` +
`OTelCollectorImp::formatName()`, so the `"Peer_Finder"` group yields
`peer_finder_<name>` lowercased.
**Known obstacle**: `Livecache` and `Bootcache` hold no collector reference, so
their sizes must either be read through the existing `Manager` hook or have a
collector plumbed in.
**Exit Criteria**:
- [ ] Slot caps exported so utilization (`active / max`) is computable
- [ ] Both cache sizes exported
- [ ] "Inbound vs Outbound" panel on `peer-quality` extended to show utilization %
- [ ] Rows added to `09` §2.1, runbook § Metric Reference, `expected_metrics.json` (Phase 10)
---
## Task 9.17: Peer Span Coverage (Deferred to Phase 11)
> **Status**: NOT IMPLEMENTED — design only, pending approval. Reference:
> [09 §6.4](./09-data-collection-reference.md#64-peer-span-coverage-gap-not-implemented)
> and [02 §2.3.2](./02-design-decisions.md#232-complete-span-catalog)
**Objective**: Close the gap between the `02` §2.3.2 span catalog and what
actually emits. `peer.connect`, `peer.disconnect`, `peer.message.send` and
`peer.message.receive` were catalogued from the start and never built; 11 of 13
protocol message families have no spans.
**Scope warning**: This is larger than Tasks 9.14-9.16 combined and changes the
"~37 spans" figure asserted in `09` §1.1 and in
`docker/telemetry/workload/expected_spans.json`. `trace_peer` is also **on by
default** and already flagged as high-volume, so adding per-message spans has a
volume cost that needs measuring before commitment.
**Exit Criteria**:
- [x] `02` §2.3.2 marked Live / Not built / Renamed against the real inventory
- [ ] User approval to proceed with span implementation
- [ ] Volume impact measured under `trace_peer=1` before any span is added
---
## Exit Criteria
- [ ] All ~50 new metrics visible in Prometheus via OTLP pipeline

View File

@@ -1858,7 +1858,7 @@
{
"type": "timeseries",
"title": "Peer Disconnect Rate By Reason",
"description": "###### What this is:\n*Rate of peer connection endings, split by the reason recorded in the log.*\n\n###### How it's computed:\n*Per-second count of Peer log lines matching Timeout, Closed, or a refused connection attempt.*\n\n###### Reading it:\n*Distinguishes clean teardown from failure. Closed is a normal ending; Timeout and Connection refused are not.*\n\n###### Healthy range:\n*Closed dominant with a low, steady background of the others.*\n\n###### Watch for:\n*A Timeout rate approaching the Closed rate, which points at network trouble or unresponsive peers.*\n\n###### Keywords:\n- **Closed** *(per peer)* — a clean connection teardown; the normal ending.\n- **Timeout** *(per peer)* — the peer stopped responding.\n- **Connection refused** *(per attempt)* — an outbound attempt the remote rejected.\n\n###### Computation boundary:\n*Result: Per node per outcome — a count of peer lifecycle events.*\n*Derived in the Grafana query. Note `overlay_peer_disconnects` exists as a metric but carries no reason breakdown, which is what this panel adds.*\n\n###### Source:\n[PeerImp.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/PeerImp.cpp)\n\n###### Function:\n`PeerImp::close / onTimer`\n\n###### References:\n[Peer protocol](https://xrpl.org/docs/concepts/networks-and-servers/peer-protocol)",
"description": "###### What this is:\n*Rate of peer connection endings, split by the reason recorded in the log.*\n\n###### How it's computed:\n*Per-second count of Peer log lines matching a clean close, a ping-keepalive timeout, a connect timeout, or a refused connection attempt.*\n\n###### Reading it:\n*Distinguishes clean teardown from failure. Closed is a normal ending; Timeout and Connection refused are not.*\n\n###### Healthy range:\n*Closed dominant with a low, steady background of the others.*\n\n###### Watch for:\n*A Ping Timeout rate approaching the Closed rate, which points at network trouble or unresponsive peers.*\n\n###### Keywords:\n- **Closed** *(per peer)* — a clean connection teardown; the normal ending.\n- **Connect Timeout** *(per attempt)* — an outbound connect attempt timed out.\n- **Ping Timeout** *(per peer)* — an established peer missed its keepalive PONG.\n- **Connection refused** *(per attempt)* — an outbound attempt the remote rejected.\n\n###### Computation boundary:\n*Result: Per node per outcome — a count of peer lifecycle events.*\n*Derived in the Grafana query. Note `overlay_peer_disconnects` exists as a metric but carries no reason breakdown, which is what this panel adds.*\n\n###### Source:\n[PeerImp.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/PeerImp.cpp)\n\n###### Function:\n`PeerImp::close / onTimer`\n\n###### References:\n[Peer protocol](https://xrpl.org/docs/concepts/networks-and-servers/peer-protocol)",
"gridPos": {
"h": 10,
"w": 12,
@@ -1906,7 +1906,7 @@
"uid": "${DS_LOKI}"
},
"refId": "A",
"expr": "sum by (outcome, service_instance_id) (rate({service_name=~\"$service_name\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\"} | xrpl_network_type =~ \"$xrpl_network_type\" | partition = `Peer` | severity =~ \"$severity\" | regexp `\\] (?P<outcome>Timeout|Closed|onConnect: Connection refused)` | outcome != `` | label_format outcome=`{{if eq .outcome \"Closed\"}}Closed{{else if eq .outcome \"Timeout\"}}Timeout{{else if eq .outcome \"onConnect: Connection refused\"}}Connection Refused{{else}}{{.outcome}}{{end}}` [$__auto]))"
"expr": "sum by (outcome, service_instance_id) (rate({service_name=~\"$service_name\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\"} | xrpl_network_type =~ \"$xrpl_network_type\" | partition = `Peer` | severity =~ \"$severity\" | regexp `(?:\\] |failed: |close: )(?P<outcome>Ping Timeout|Timeout|Closed|onConnect: Connection refused)` | outcome != `` | label_format outcome=`{{if eq .outcome \"Closed\"}}Closed{{else if eq .outcome \"Ping Timeout\"}}Ping Timeout{{else if eq .outcome \"Timeout\"}}Connect Timeout{{else if eq .outcome \"onConnect: Connection refused\"}}Connection Refused{{else}}{{.outcome}}{{end}}` [$__auto]))"
}
],
"id": 29,

View File

@@ -9,7 +9,12 @@ documentation.
> **Related docs**:
> [docs/telemetry-runbook.md](./telemetry-runbook.md) (operator runbook).
<!-- This file is generated from tasks/telemetry_terms.py. Edit the terms there. -->
<!-- This file was originally generated from tasks/telemetry_terms.py. That
generator is NOT in the repository (`tasks/` is gitignored) and no copy
survives, so this file is now maintained by hand. Follow the existing entry
shape: an `<a id="...">` anchor, a `###` term heading, one plain-language
paragraph, then `**Scope:**` and optionally `**What is observable:**` and
`**See also:**`. Terms are alphabetical within each category. -->
## Contents
@@ -1016,7 +1021,9 @@ A cluster is a set of servers run by the same operator that trust each other, ex
**Scope:** cluster-wide — shared across a co-operated cluster of nodes run by one operator.
**See also:** [Cluster on xrpl.org](https://xrpl.org/docs/concepts/networks-and-servers/clustering)
**What is observable:** cluster overhead is **not** measurable today. Cluster messages are counted under `unknown` rather than `overhead_cluster`, so the `overhead_cluster_*` series read zero on a clustered node — treat them as "no data", not "no cluster traffic". The churn guidance above cannot yet be acted on.
**See also:** [Cluster on xrpl.org](https://xrpl.org/docs/concepts/networks-and-servers/clustering) · [Data collection reference §6.0](../OpenTelemetryPlan/09-data-collection-reference.md#60-mtcluster-is-counted-as-unknown-not-implemented)
<a id="disconnect-reason"></a>
@@ -1086,6 +1093,18 @@ The overlay is xrpld's peer-to-peer messaging layer connecting nodes. All inter-
**See also:** [Overlay on xrpl.org](https://xrpl.org/docs/concepts/networks-and-servers/peer-protocol)
<a id="ping-pong-keepalive"></a>
### Ping / pong keepalive
Each peer connection is probed on a timer: the node sends a ping carrying a random cookie and expects a pong echoing it back. The round-trip is smoothed into a per-peer latency estimate that feeds peer scoring, and a peer that leaves a ping unanswered before the next probe is dropped as a ping timeout. A pong bearing the wrong cookie is ignored, so a peer answering incorrectly eventually times out too. Ping timeouts are distinct from connect timeouts, which happen while an outbound connection is still being established and so involve no established peer.
**Scope:** per node — measured on and specific to this individual server.
**What is observable:** only the p90 of the smoothed per-peer latency (`peer_quality{metric="peer_latency_p90_ms"}`) — there is no distribution, ping timeouts and wrong-cookie pongs have no counter, and ping bytes are not separable from status-change bytes because both share the `overhead` traffic category.
**See also:** [Data collection reference §6.3](../OpenTelemetryPlan/09-data-collection-reference.md#63-peer-keepalive-and-discovery-traffic-gaps-not-implemented)
<a id="proof-path"></a>
### Proof path
@@ -1162,6 +1181,10 @@ Squelching is a relay-control mechanism: a node tells peers to stop sending it a
**Scope:** per node — measured on and specific to this individual server.
**What is observable:** read ignored directives on `squelch_ignored_messages_in/out` only. The paired `squelch_ignored_bytes_*` series are always zero because the ignored-squelch callback records no size, so bandwidth wasted by peers ignoring squelch cannot be quantified — and `squelch_ignored` is therefore not comparable on bytes against `squelch_suppressed`, which does record real sizes.
**See also:** [Data collection reference §6.1](../OpenTelemetryPlan/09-data-collection-reference.md#61-squelch_ignored-byte-counts-not-implemented)
<a id="trusted-untrusted-duplicate"></a>
### Trusted / untrusted / duplicate

View File

@@ -2507,7 +2507,7 @@ timeseries, 2 table, 1 state-timeline, 1 logs, 1 text, across 35 queries.
| Manifests — Disposition & Producers | `[DBG]` | Disposition rate; accept-vs-reject; top-N master keys |
| Resource Fee Charges — Load Attribution | `[DBG]` | Charge rate by reason; fee-weighted load; top-N peers by IP and public key |
| Ledger Acquisition Efficiency | `[DBG]` | Duplicate ratio; good vs duplicate vs timeout |
| Peer Lifecycle & Disconnects | `[DBG]` | Disconnect reason breakdown; handshake and accept rate |
| Peer Lifecycle & Disconnects | `[DBG]` | Disconnect reason breakdown (Closed / Ping Timeout / Connect Timeout / Connection Refused); handshake and accept rate |
| Consensus Phase & Mode | `[DEFAULT OK]` | Phase transitions; operating-mode proxy; quorum and trusted-set size |
| Slow Job Latency Breaches | `[DEFAULT OK]` | Run p99, wait p99, breach rate by job (`LoadMonitor`, >500ms only) |
| Error & Warning Stream | `[DEFAULT OK]` | WRN/ERR/FTL rate by partition; live log tail |
@@ -2577,6 +2577,20 @@ Stream labels are only `service_name`, `service_instance_id`,
target as a range query even with `instant: true`, so `lastNotNull` reads only
the final bucket — a window total shows as a single-bucket count. Aggregate
over `$__range` and reduce with `max`.
11. **A `regexp` anchored on the log prefix silently drops most matches.** The
_Peer Disconnect Rate By Reason_ panel anchored its capture on `\] `, which
only matches a reason emitted immediately after the `[NNN] ` peer-id prefix.
`PeerImp` does not log that way: `PeerImp::fail` emits
`[NNN] <name> failed: <reason>` (`src/xrpld/overlay/detail/PeerImp.cpp:645`)
and the clean teardown emits `close: Closed` (`:635`). Only
`ConnectAttempt::fail`, which logs the bare reason
(`src/xrpld/overlay/detail/ConnectAttempt.cpp:136`), ever matched — so the
panel's `Timeout` series was connect-attempt timeouts only, `Ping Timeout`
(`PeerImp.cpp:762`) was invisible, and `PeerImp`'s own `Closed` was
uncounted. The panel now matches all three prefixes
(`(?:\] |failed: |close: )`) and distinguishes `Ping Timeout` from
`Connect Timeout`. When adding a log-derived panel, enumerate every producer
of the string being captured rather than sampling one.
Also worth knowing: the **Grafana Cloud image renderer cannot query Loki** in this
stack. A minimal probe dashboard with a hardcoded datasource uid, a literal