docs(telemetry): align runbook and plan docs with the shipped phase-9/10 code

The reference docs had drifted from the code in ways that break the reader
rather than merely misinform: PromQL examples that return no data, a rollback
flag that is a no-op, a sampling knob that does not exist, and two span parents
that moved. Code is treated as the truth throughout; where the code is the
defective side, the doc now records it as a known issue instead of describing
the bug as intent.

Renames the docs missed: histogram names gain the exporter's unit suffix
(ios_latency_milliseconds_bucket and four siblings), ledger_history_mismatch
gains _total, the StatsD-era quantile label gives way to le buckets,
rpc.request becomes rpc.http_request, traces_spanmetrics_calls_total becomes
span_calls_total, and the nine dotted xrpl.* span attributes are recorded as
renamed rather than left as live keys.

Re-parenting: consensus.update_positions and consensus.check are children of
consensus.establish, not of consensus.round.

Units and labels: state_accounting_*_duration is microseconds, not seconds;
cache_metrics label values are case-sensitive; object_count carries demangled
C++ type names. Nodestore read and write latency stays microseconds -- the
nanosecond accumulator change did not move the exported unit.

Adds what shipped but was undocumented: the ledger.acquire span, seven
consensus.round events, twelve span attributes, node_writes_duration_us, the
7-day validation-agreement window, the TxQ admission and reduce-relay metric
families, metrics_endpoint, and the phase-10 validation workflow.

Corrects claims that never held: 10% head sampling (it is fixed at 100%),
configurable redaction (it is unconditional), -DXRPL_ENABLE_TELEMETRY=OFF
(the flag is -Dtelemetry=OFF, default ON), FindOpenTelemetry.cmake and the
xrpl_telemetry target (neither exists), Promtail and a StatsD exporter in the
pipeline (neither exists), and Loki stream selection on job= (only
service_name is a stream label).

Phase 9 is marked complete, its provisioned alerting is attributed to the
branch that shipped it, and Phase 11 stays at zero except the one prerequisite
its code closes. Counts are reconciled repo-wide: 41 emitted span families,
15 dashboards on disk with 14 asserted, 13 alert rules in 5 groups.

Hardens the gate that let this drift through: Rule E of the naming check now
covers the reference docs, its allow-dotted marker is key-scoped and warns on
stale or empty use, a missing checked file is reported instead of silently
skipped, the test suite runs in CI, and doc paths trigger the check.

C++ and CMake changes are comment-only: three MetricsRegistry instrument names,
eight OTelCollector claims of a metric-name prefix that formatName never adds,
and the telemetry option's inverted default.
This commit is contained in:
Pratik Mankawde
2026-08-13 16:18:47 +01:00
parent 733af97ce3
commit 3153f3ef56
35 changed files with 4598 additions and 1390 deletions

View File

@@ -5,7 +5,7 @@
* Compiled only when XRPL_ENABLE_TELEMETRY is defined (via CMake
* telemetry=ON). Maps beast::insight instruments to OTel SDK instruments
* created on the GLOBAL Meter published by the telemetry module. This class
* is a legacy shim: it no longer owns an export pipeline. The MeterProvider,
* is an adapter only: it owns no export pipeline. The MeterProvider,
* PeriodicExportingMetricReader, OTLP exporter and histogram view all live in
* xrpl::telemetry::Telemetry.
*
@@ -134,8 +134,8 @@ class OTelCounterImpl : public CounterImpl
public:
/**
* @param name Export-ready metric name, already run through
* formatName() by the collector: prefix prepended and
* dots replaced with underscores (e.g. "rpc_size").
* formatName() by the collector: lowercase, with `.` and
* ` ` mapped to `_` (e.g. "rpc_size").
* @param meter OTel Meter used to create the counter instrument.
*/
OTelCounterImpl(
@@ -178,8 +178,8 @@ class OTelEventImpl : public EventImpl
public:
/**
* @param name Export-ready metric name, already run through
* formatName() by the collector: prefix prepended and
* dots replaced with underscores (e.g. "rpc_size").
* formatName() by the collector: lowercase, with `.` and
* ` ` mapped to `_` (e.g. "rpc_size").
* @param meter OTel Meter used to create the histogram instrument.
*/
OTelEventImpl(
@@ -227,8 +227,8 @@ class OTelGaugeImpl : public GaugeImpl
public:
/**
* @param name Export-ready metric name, already run through
* formatName() by the collector: prefix prepended
* and dots replaced with underscores.
* formatName() by the collector: lowercase, with `.`
* and ` ` mapped to `_`.
* @param meter OTel Meter used to create the observable gauge.
* @param collector Owning collector, used to invoke hooks before reads.
*/
@@ -310,8 +310,8 @@ class OTelMeterImpl : public MeterImpl
public:
/**
* @param name Export-ready metric name, already run through
* formatName() by the collector: prefix prepended and
* dots replaced with underscores (e.g. "rpc_size").
* formatName() by the collector: lowercase, with `.` and
* ` ` mapped to `_` (e.g. "rpc_size").
* @param meter OTel Meter used to create the counter instrument.
*/
OTelMeterImpl(
@@ -340,7 +340,7 @@ private:
//------------------------------------------------------------------------------
/**
* @brief Main OTel Collector implementation (legacy shim).
* @brief Main OTel Collector implementation (adapter over the global Meter).
*
* Obtains its Meter from the GLOBAL MeterProvider owned and published by the
* telemetry module (xrpl::telemetry::Telemetry), rather than building its own
@@ -380,8 +380,11 @@ private:
* Caveats:
* - Observable gauge callbacks run on the SDK's internal thread. Hook
* handlers must be thread-safe.
* - Metric names are formed as "prefix_name" with dots replaced by
* underscores to match StatsD->Prometheus naming conventions.
* - Metric names carry NO prefix. formatName() only lowercases the raw
* name and turns dots and spaces into underscores, to match
* StatsD->Prometheus naming conventions. The service is identified by
* the OTel resource (service.name), so prefix_ is kept for logging
* only and never affects an exported name.
* - The OTel Prometheus exporter appends "_total" to counters. The
* metric names we register do NOT include this suffix — Prometheus
* adds it automatically.
@@ -402,11 +405,14 @@ public:
/**
* @brief Construct the OTel collector over the global MeterProvider.
*
* @param endpoint OTLP/HTTP metrics endpoint URL. Informational only:
* the global telemetry pipeline is authoritative for
* the actual export endpoint. Retained for logging and
* back-compat with the New() signature.
* @param prefix Prefix for all metric names.
* @param endpoint OTLP/HTTP metrics endpoint URL, recorded in the
* collector's startup log line. Export uses the
* endpoint configured on the global telemetry
* pipeline.
* @param prefix Label for the collector's startup log line
* (e.g. "xrpld"). Exported metric names come from
* formatName(); the service is identified by the
* service.name resource attribute.
* @param instanceId Value for the service.instance.id resource attribute.
* When empty, the attribute is omitted.
* @param serviceName Value for the service.name resource attribute.
@@ -498,10 +504,12 @@ public:
/** @} */
/**
* @brief Format a metric name with the configured prefix.
* @brief Format a raw metric name for export.
*
* Replaces dots with underscores to match StatsD->Prometheus naming.
* Example: prefix="xrpld", name="LedgerMaster.Validated_Ledger_Age"
* Lowercases the name and replaces dots and spaces with underscores to
* match StatsD->Prometheus naming. Adds NO prefix: the service is
* identified by the OTel resource (service.name).
* Example: name="LedgerMaster.Validated_Ledger_Age"
* -> "ledgermaster_validated_ledger_age"
*
* @param name Raw metric name from beast::insight callers.
@@ -517,7 +525,8 @@ private:
Journal journal_;
/**
* Prefix for all metric names (e.g., "xrpld").
* Configured metric-name prefix (e.g., "xrpld"). Log-only: it is
* echoed in the startup log line and never applied to a metric name.
*/
std::string prefix_;
@@ -708,17 +717,17 @@ OTelCollectorImp::OTelCollectorImp(
Journal journal)
: journal_(journal), prefix_(std::move(prefix))
{
// instanceId/serviceName/networkType are retained on the New() signature
// for back-compat but no longer used here: the telemetry module owns the
// resource attributes for the shared metrics pipeline.
// instanceId/serviceName/networkType are accepted but unused here: the
// telemetry module owns the resource attributes for the shared metrics
// pipeline, so setting them from this collector would have no effect.
(void)instanceId;
(void)serviceName;
(void)networkType;
if (journal_.info())
{
// endpoint is informational: the global telemetry pipeline owns the
// real exporter. It is logged here for back-compat and diagnostics.
// endpoint is logged for diagnostics only: the global telemetry
// pipeline owns the exporter that actually sends the metrics.
journal_.info() << "OTelCollector starting: endpoint=" << endpoint << " prefix=" << prefix_;
}
@@ -846,9 +855,9 @@ OTelCollectorImp::removeGauge(OTelGaugeImpl* gauge)
std::string
OTelCollectorImp::formatName(std::string const& name)
{
// Produce a clean, lowercase, Prometheus-compatible metric name.
// No prefix — the OTel resource (service.name) identifies the service.
// Dots and spaces become underscores; everything lowercased.
// Produce a lowercase, Prometheus-compatible metric name: dots and
// spaces become underscores. Service identity travels in the
// service.name resource attribute, not in the metric name.
std::string result;
result.reserve(name.size());
for (char const c : name)

View File

@@ -786,7 +786,7 @@ private:
*/
opentelemetry::nostd::unique_ptr<opentelemetry::metrics::Counter<uint64_t>> rpcErroredCounter_;
/**
* Histogram: rpc_method_duration_us{method="<name>"}
* Histogram: rpc_method_us{method="<name>"}
*/
opentelemetry::nostd::unique_ptr<opentelemetry::metrics::Histogram<double>>
rpcDurationHistogram_;
@@ -807,12 +807,12 @@ private:
*/
opentelemetry::nostd::unique_ptr<opentelemetry::metrics::Counter<uint64_t>> jobFinishedCounter_;
/**
* Histogram: job_queued_duration_us{job_type="<name>",handler="<name>"}
* Histogram: job_queued_us{job_type="<name>",handler="<name>"}
*/
opentelemetry::nostd::unique_ptr<opentelemetry::metrics::Histogram<double>>
jobQueuedDurationHistogram_;
/**
* Histogram: job_running_duration_us{job_type="<name>",handler="<name>"}
* Histogram: job_running_us{job_type="<name>",handler="<name>"}
*/
opentelemetry::nostd::unique_ptr<opentelemetry::metrics::Histogram<double>>
jobRunningDurationHistogram_;