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

Nine conflicts, resolved as follows.

src/xrpld/app/ledger/detail/InboundLedger.cpp -- kept this branch's version.
phase10 sets the span's outcome/timeouts/peer_count attributes inline at each
exit; this branch replaced that with the idempotent finalizeAcquireSpan(), called
on all four exits (init, done, give-up, destructor). Taking phase10's blocks
would have set the outcome twice against a helper documented as not overwriting
what the real exit recorded. phase10's comment explains why peer_count must not
be read in a destructor; the helper solves that structurally by taking
std::optional<std::size_t> and being passed std::nullopt from there.

src/xrpld/telemetry/MetricsRegistry.cpp -- kept metric::ledgerEconomy over
phase10's "ledger_economy" literal. This branch added the naming check that
requires constants for converted families, so the literal would regress it. Took
phase10's comment cleanup.

src/xrpld/telemetry/MetricsRegistry.h -- kept registerRotationStateGauge(), which
only exists here, and took phase10's removal of the stale task-number comment.

validate_telemetry.py -- combined both. phase10 replaced serial metric polling
with a concurrent fan-out on one shared deadline, because 58 metrics x 45 s of
additive timeout overran the CI budget; that is kept. Its target list filters on
SKIPPED_METRIC_GROUPS rather than the two literals it hardcoded, so the
sync_diagnostics group stays owned by assert_sync_diagnostics_metrics() instead
of being polled and reported twice. Both SYNC_DIAGNOSTICS_GROUP and
METRIC_POLL_CONCURRENCY are needed and both are kept.

check_otel_naming.py -- both sides extend the rule docstring. Took phase10's
fuller Rule E text (doc discovery, allow-dotted markers) and re-appended rules
I/J/K/L, which exist only here.

expected_metrics.json -- the two sides add disjoint sibling groups, so both are
kept: sync_diagnostics alongside node_health_gauges, overlay_reduce_relay,
overlay_overflow, validation_lifetime_counters and not_asserted. Both dashboard
uids are kept, giving 16 asserted uids against 16 dashboards on disk.

expected_spans.json -- kept this branch's span set, a superset that adds the
acquire phase spans, ledger.serve, txset.acquire and peer.dial, and expands
ledger.acquire's required attributes. Took phase10's description, which documents
what the totals mean, and its note on how the RPC wildcard span is created.
total_span_types and total_unique_attributes are recomputed for the union: 48 and
74, since each side's figure counted only its own spans.

Docs: took phase10's more accurate wording on what the dashboard check actually
covers, and corrected the dashboard count from 15 to 16 where the merge made it
stale.

Verified: no conflict markers remain, both JSON contracts parse, both Python
files compile, asserted dashboard uids match the dashboards on disk exactly, and
the OTel naming check reports all layers consistent.
This commit is contained in:
Pratik Mankawde
2026-08-17 19:24:12 +01:00
89 changed files with 8770 additions and 2642 deletions

View File

@@ -302,9 +302,9 @@ Logs::format(
}
#ifdef XRPL_ENABLE_TELEMETRY
// Inject OTel trace context when an active span exists on this thread.
// Checks the thread-local context value directly to avoid the heap
// allocation that GetSpan() performs on the no-span path.
// Inject OTel trace context when an active, sampled span exists on this
// thread. Checks the thread-local context value directly to avoid the
// heap allocation that GetSpan() performs on the no-span path.
{
auto context = opentelemetry::context::RuntimeContext::GetCurrent();
auto spanValue = context.GetValue(opentelemetry::trace::kSpanKey);
@@ -314,7 +314,18 @@ Logs::format(
auto span = opentelemetry::nostd::get<
opentelemetry::nostd::shared_ptr<opentelemetry::trace::Span>>(spanValue);
auto spanCtx = span->GetContext();
if (spanCtx.IsValid())
// Require the sampled flag as well as a valid context. A dropped
// span still carries its parent's ids, so a valid context does
// not imply the span reaches the backend. An unsampled remote
// parent arrives either because an upstream node propagated
// sampled=0, or because a peer omitted trace_flags entirely and
// it defaults to 0 (TraceContextPropagator, TxTracing,
// ConsensusReceiveTracing). Either way the ParentBasedSampler
// drops the local span, while the tracer still returns a no-op
// span with a valid context.
// Logging those ids would advertise a trace that was never
// exported, leaving the log-to-trace link resolving to nothing.
if (spanCtx.IsValid() && spanCtx.IsSampled())
{
// Hex widths of a W3C trace context: 16-byte trace_id and
// 8-byte span_id render to 32 and 16 lowercase hex chars.

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

@@ -589,9 +589,9 @@ TEST(MetricMacros, observable_gauge_register_reports_current_value)
FakeApp app;
wire(app, /*enabled=*/true);
// Own the state exactly as a real caller would (Use Case 5 in the
// design doc) -- the macro's callback reads through this atomic on
// every collection tick, it does not own the value itself.
// Own the state exactly as a real caller would -- the macro's callback
// reads through this atomic on every collection tick, it does not own
// the value itself.
std::atomic<std::int64_t> queueDepth{0};
XRPL_METRIC_OBSERVABLE_GAUGE_REGISTER(
app,
@@ -599,11 +599,11 @@ TEST(MetricMacros, observable_gauge_register_reports_current_value)
"Test observable gauge for macro unit test",
[&queueDepth] { return queueDepth.load(); });
// There is no application-level read-back API (Use Case 4) -- this
// test can only prove registration doesn't crash and that meter() was
// consulted to create the observable instrument. It does NOT assert the
// observed value reaches Prometheus; that is Task 3b's docker-harness
// job, not this hermetic unit test.
// There is no application-level read-back API -- this test can only
// prove registration doesn't crash and that meter() was consulted to
// create the observable instrument. It does NOT assert the observed
// value reaches Prometheus; that is the docker-harness integration
// test's job, not this hermetic unit test.
queueDepth.store(42);
EXPECT_EQ(app.registry().meterCalls(), 1);
}

View File

@@ -17,8 +17,6 @@
#include <opentelemetry/trace/trace_flags.h>
#include <opentelemetry/trace/trace_id.h>
#include <xrpl.pb.h>
#include <cstdint>
#include <cstring>

View File

@@ -277,9 +277,9 @@ RCLConsensus::Adaptor::propose(RCLCxPeerPos::Proposal const& proposal)
app_.getHashRouter().addSuppression(suppression);
// Inject the current thread's active span context (e.g. the
// consensus round span from Phase 4) so receiving peers can link
// their proposal.receive span as a child of this trace.
// Inject the current thread's active span context (e.g. the consensus
// round span) so receiving peers can link their proposal.receive span
// as a child of this trace.
telemetry::SpanGuard::injectCurrentContextToProtobuf(*prop.mutable_trace_context());
app_.getOverlay().broadcast(prop);
@@ -765,7 +765,7 @@ RCLConsensus::Adaptor::doAccept(
// Record ledger close for OTel dashboard parity counter. Uses the
// call-site macro (see MetricMacros.h) rather than a MetricsRegistry
// member -- proof-of-concept for tasks/metric-macro-plan.md.
// member.
XRPL_METRIC_COUNTER_INC(app_, "ledgers_closed_total", "Total ledgers closed by consensus");
//-------------------------------------------------------------------------

View File

@@ -127,9 +127,9 @@ class RCLConsensus
*
* Captured in makeAcceptSpan() and consumed by createValidationSpan()
* on the jtACCEPT worker thread so the validation.send span can be
* follows-from linked to consensus.accept (matching the design doc
* and span hierarchy diagram). Reset on each startRoundTracing()
* to prevent a stale prior-round context from being linked.
* follows-from linked to consensus.accept. Reset on each
* startRoundTracing() to prevent a stale prior-round context from
* being linked.
*
* Thread safety: same model as roundSpanContext_. The write in
* makeAcceptSpan happens on the main consensus thread under

View File

@@ -177,6 +177,12 @@ inline constexpr auto abandoned = makeStr("abandoned");
*/
inline constexpr auto timeout = makeStr("timeout");
/**
* Set when the acquisition is abandoned before it finishes, i.e. the
* InboundLedger is destroyed while !isDone(). Distinct from `failed`, which
* means the fetch ran to its retry limit and gave up.
*/
inline constexpr auto aborted = makeStr("aborted");
/**
* ledger.acquire reason values (mirror InboundLedger::Reason).
*/

View File

@@ -331,17 +331,21 @@ PerfLogImp::rpcStart(std::string const& method, std::uint64_t const requestId)
std::scoped_lock const lock(counter->second.mutex);
++counter->second.value.started;
}
std::scoped_lock const lock(counters_.methodsMutex);
counters_.methods[requestId] = {counter->first.c_str(), steady_clock::now()};
{
std::scoped_lock const lock(counters_.methodsMutex);
counters_.methods[requestId] = {counter->first.c_str(), steady_clock::now()};
}
// Task 9.4: Record RPC start in OTel metrics pipeline.
// Record RPC start in OTel metrics pipeline. Recorded after the locks
// above are released: the OTel call path allocates and takes locks
// inside the SDK, so holding methodsMutex across it would widen a
// process-wide critical section for no reason. Mirrors rpcEnd().
if (auto* mr = app_.getMetricsRegistry())
mr->recordRpcStarted(method);
// Proof-of-concept for tasks/metric-macro-plan.md Use Case 2: a value
// that must be able to decrease (UpDownCounter), added at its call
// site with no MetricsRegistry member/init-line/method. Paired with the
// matching -1 in rpcEnd(). Runs on the same path as recordRpcStarted
// A value that must be able to decrease (UpDownCounter), added at its
// call site with no MetricsRegistry member/init-line/method. Paired with
// the matching -1 in rpcEnd(). Runs on the same path as recordRpcStarted
// above, i.e. only after a methods-map entry exists for this request.
XRPL_METRIC_UPDOWN_ADD(app_, "rpc_in_flight_requests", "RPC requests currently executing", 1);
}
@@ -392,9 +396,9 @@ PerfLogImp::rpcEnd(std::string const& method, std::uint64_t const requestId, boo
counter->second.value.duration += durationUs;
}
// Task 9.4: Record RPC completion in OTel metrics pipeline.
// Mirrors the rpcStart() instrumentation so the finished/errored
// counters and duration histogram advance with every call.
// Record RPC completion in OTel metrics pipeline. Mirrors the
// rpcStart() instrumentation so the finished/errored counters and
// duration histogram advance with every call.
if (auto* mr = app_.getMetricsRegistry())
{
if (finish)
@@ -424,10 +428,13 @@ PerfLogImp::jobQueue(JobType const type, std::string const& name)
return;
// LCOV_EXCL_STOP
}
std::scoped_lock const lock(counter->second.mutex);
++counter->second.value.queued;
{
std::scoped_lock const lock(counter->second.mutex);
++counter->second.value.queued;
}
// Task 9.5: Record job enqueue in OTel metrics pipeline.
// Record job enqueue in OTel metrics pipeline, after the lock above is
// released so the SDK's work stays outside the critical section.
if (auto* mr = app_.getMetricsRegistry())
mr->recordJobQueued(JobTypes::name(type), name);
}
@@ -454,11 +461,16 @@ PerfLogImp::jobStart(
++counter->second.value.started;
counter->second.value.queuedDuration += dur;
}
std::scoped_lock const lock(counters_.jobsMutex);
if (instance >= 0 && instance < counters_.jobs.size())
counters_.jobs[instance] = {type, startTime};
{
std::scoped_lock const lock(counters_.jobsMutex);
if (instance >= 0 && instance < counters_.jobs.size())
counters_.jobs[instance] = {type, startTime};
}
// Task 9.5: Record job start in OTel metrics pipeline.
// Record job start in OTel metrics pipeline, after the locks above are
// released. jobsMutex is process-wide and taken by every worker thread
// on every job, so the SDK's allocation and internal locking must not
// run inside it.
if (auto* mr = app_.getMetricsRegistry())
mr->recordJobStarted(JobTypes::name(type), name, dur.count());
}
@@ -480,11 +492,14 @@ PerfLogImp::jobFinish(JobType const type, std::string const& name, microseconds
++counter->second.value.finished;
counter->second.value.runningDuration += dur;
}
std::scoped_lock const lock(counters_.jobsMutex);
if (instance >= 0 && instance < counters_.jobs.size())
counters_.jobs[instance] = {JtInvalid, steady_time_point()};
{
std::scoped_lock const lock(counters_.jobsMutex);
if (instance >= 0 && instance < counters_.jobs.size())
counters_.jobs[instance] = {JtInvalid, steady_time_point()};
}
// Task 9.5: Record job finish in OTel metrics pipeline.
// Record job finish in OTel metrics pipeline, after the locks above
// are released, for the same reason as jobStart().
if (auto* mr = app_.getMetricsRegistry())
mr->recordJobFinished(JobTypes::name(type), name, dur.count());
}

View File

@@ -34,7 +34,8 @@
*
* @note Span names come from the canonical constants in
* ConsensusSpanNames.h (consensus::span::proposalReceive /
* validationReceive) so they stay in sync with the rest of Phase 4.
* validationReceive) so they stay in sync with the rest of the
* consensus tracing surface.
*/
#include <xrpl/consensus/ConsensusSpanNames.h>

View File

@@ -83,27 +83,26 @@
* @note A histogram whose values can exceed ~10,000 units (e.g. a
* microsecond duration beyond 10ms) needs an explicit-bucket View, which
* OTel can only register at MeterProvider construction time -- this
* cannot be done from a call site. See Limitation 2. Register such a
* view in MetricsRegistry::initExporterAndProvider() as today; the
* cannot be done from a call site. Register such a view in
* MetricsRegistry::initExporterAndProvider() as today; the
* histogram-record call itself can still use the macro.
*
* @note Only call the SYNCHRONOUS macros (Counter/UpDownCounter/
* Histogram/Gauge) from code that runs AFTER MetricsRegistry::start() has
* completed (RPC handlers, job callbacks, consensus rounds, tx apply, peer
* message handlers). See Limitation 1.
* message handlers).
*
* @note The OBSERVABLE registration macros are the opposite: call them
* EAGERLY, exactly once, from constructor/init code -- never from a hot
* path. Repeated calls at the same call site register a NEW callback
* each time (no create-once caching, unlike the synchronous macros),
* which leaks callbacks. See Limitation 3.
* which leaks callbacks.
*
* @note There is no way to read back a synchronous instrument's current
* accumulated value from application code -- the OTel API is
* write-only/push-based by design. If your logic needs both to record a
* metric AND read its running value, keep your own state (std::atomic or
* similar) and separately feed OTel via these macros. See "Use Case 4" in
* tasks/metric-macro-plan.md.
* similar) and separately feed OTel via these macros.
*/
// On Windows, OTel's spin_lock_mutex.h (transitively included from
@@ -204,11 +203,11 @@
} while (false)
// UpDownCounter: like COUNTER_ADD, but the underlying instrument permits a
// negative amount (Use Case 2 -- e.g. in-flight request count, +1 on start
// / -1 on finish from two different points in the same or different call
// sites). A plain Counter's Add() must never see a negative value per the
// OTel API contract; use this macro, not COUNTER_ADD, whenever the value
// can decrease.
// negative amount (e.g. in-flight request count, +1 on start / -1 on
// finish from two different points in the same or different call sites).
// A plain Counter's Add() must never see a negative value per the OTel
// API contract; use this macro, not COUNTER_ADD, whenever the value can
// decrease.
#define XRPL_METRIC_UPDOWN_ADD(app, name, description, amount) \
do \
{ \
@@ -350,15 +349,15 @@
#endif // OPENTELEMETRY_ABI_VERSION_NO >= 2
// -----------------------------------------------------------------
// Observable/async instrument registration (Use Case 5). Unlike the
// synchronous macros above, these do NOT lazily create-on-first-call --
// they register a callback with the SDK immediately, at the call site,
// the moment the macro executes. Callers MUST invoke this during
// Observable/async instrument registration. Unlike the synchronous
// macros above, these do NOT lazily create-on-first-call -- they
// register a callback with the SDK immediately, at the call site, the
// moment the macro executes. Callers MUST invoke this during
// construction/init, before the server is fully live (same timing rule
// MetricsRegistry::registerAsyncGauges() already follows for its own
// gauges -- see Limitation 3). Calling it from a hot-path function
// instead of an init path re-registers a new callback on every call,
// which leaks callbacks and is NOT what this macro is for.
// gauges). Calling it from a hot-path function instead of an init path
// re-registers a new callback on every call, which leaks callbacks and
// is NOT what this macro is for.
//
// The callable is captured in a heap-allocated std::function, and its
// address is passed as the `void* state` to AddCallback (whose signature,

View File

@@ -24,6 +24,23 @@
#ifdef XRPL_ENABLE_TELEMETRY
// The app and overlay includes below are why
// .github/scripts/levelization/results/loops.txt records
// `xrpld.app <-> xrpld.telemetry` and `xrpld.overlay <-> xrpld.telemetry`, where
// ordering.txt previously had telemetry strictly below both. The observable
// gauges are pull-model: their callbacks sample live state when the reader
// thread fires, so they need the concrete types to call getJqTransOverflow(),
// size(), getPeerDisconnectCharges(), foreach() and txMetrics().
//
// The cycle is confined to this translation unit. No telemetry header includes
// app or overlay (MetricsRegistry.h forward-declares what it needs and takes a
// ServiceRegistry&), and all of src/xrpld builds into a single CMake target, so
// there is no header cycle and no link cycle to break.
//
// Inverting it properly means declaring a metrics-source interface below overlay
// and implementing it there, which is deliberately left as follow-up rather than
// widening this change. Note loops.txt is generated: it can only change as a
// consequence of changing these includes, never by editing the baseline.
#include <xrpld/app/ledger/AcquireStats.h>
#include <xrpld/app/ledger/InboundLedgers.h>
#include <xrpld/app/ledger/LedgerMaster.h>
@@ -451,7 +468,7 @@ MetricsRegistry::initSyncInstruments()
jobRunningDurationHistogram_ =
meter_->CreateDoubleHistogram(kJobRunningDurationUs, "Job execution time in microseconds");
// --- External dashboard parity counters (Task 7.14) ---
// --- External dashboard parity counters ---
ledgersClosedCounter_ =
meter_->CreateUInt64Counter("ledgers_closed_total", "Total ledgers closed by consensus");
validationsSentCounter_ = meter_->CreateUInt64Counter(
@@ -512,7 +529,7 @@ MetricsRegistry::stop()
}
// -----------------------------------------------------------------
// Synchronous instrument recording — RPC metrics (Task 9.4)
// Synchronous instrument recording — RPC metrics
// -----------------------------------------------------------------
void
@@ -571,7 +588,7 @@ MetricsRegistry::recordRpcErrored(std::string_view method, std::int64_t duration
}
// -----------------------------------------------------------------
// Synchronous instrument recording — Job Queue metrics (Task 9.5)
// Synchronous instrument recording — Job Queue metrics
// -----------------------------------------------------------------
void
@@ -651,7 +668,7 @@ MetricsRegistry::recordJobFinished(
}
// -----------------------------------------------------------------
// Observable gauge callbacks (Tasks 9.1, 9.2, 9.3, 9.6, 9.7)
// Observable gauge callbacks
// -----------------------------------------------------------------
#ifdef XRPL_ENABLE_TELEMETRY
@@ -731,7 +748,7 @@ MetricsRegistry::registerJqTransOverflowCounter()
void
MetricsRegistry::registerCacheHitRateGauge()
{
// --- Task 9.2: Cache hit rate and size gauges ---
// --- Cache hit rate and size gauges ---
cacheHitRateGauge_ =
meter_->CreateDoubleObservableGauge("cache_metrics", "Cache hit rates and sizes");
cacheHitRateGauge_->AddCallback(
@@ -802,7 +819,7 @@ MetricsRegistry::registerCacheHitRateGauge()
void
MetricsRegistry::registerTxqGauge()
{
// --- Task 9.3: TxQ metrics gauges ---
// --- TxQ metrics gauges ---
txqGauge_ = meter_->CreateDoubleObservableGauge("txq_metrics", "Transaction queue metrics");
txqGauge_->AddCallback(
[](opentelemetry::metrics::ObserverResult result, void* state) {
@@ -849,7 +866,7 @@ MetricsRegistry::registerTxqGauge()
void
MetricsRegistry::registerObjectCountGauge()
{
// --- Task 9.6: Counted object instance gauges ---
// --- Counted object instance gauges ---
objectCountGauge_ = meter_->CreateInt64ObservableGauge(
"object_count", "Live instance counts for key internal object types");
objectCountGauge_->AddCallback(
@@ -881,7 +898,7 @@ MetricsRegistry::registerObjectCountGauge()
void
MetricsRegistry::registerLoadFactorGauge()
{
// --- Task 9.7: Load factor breakdown gauges ---
// --- Load factor breakdown gauges ---
loadFactorGauge_ =
meter_->CreateDoubleObservableGauge("load_factor_metrics", "Fee load factor breakdown");
loadFactorGauge_->AddCallback(
@@ -1045,7 +1062,7 @@ MetricsRegistry::observeReadQueue(node_store::Database& db, ObserveFn const& obs
void
MetricsRegistry::registerNodeStoreGauge()
{
// --- Task 9.1: NodeStore I/O gauges ---
// --- NodeStore I/O gauges ---
// The cumulative counters (reads, writes, bytes) are also exposed here
// as observable gauges. This avoids adding an xrpld dependency into the
// libxrpl nodestore code — the MetricsRegistry reads the existing atomic
@@ -1157,7 +1174,7 @@ MetricsRegistry::registerRotationStateGauge()
void
MetricsRegistry::registerServerInfoGauge()
{
// --- Task 9.7a: Server info gauges ---
// --- Server info gauges ---
serverInfoGauge_ =
meter_->CreateInt64ObservableGauge(metric::serverInfo, "Server-level health metrics");
serverInfoGauge_->AddCallback(
@@ -1242,7 +1259,7 @@ MetricsRegistry::registerServerInfoGauge()
void
MetricsRegistry::registerBuildInfoGauge()
{
// --- Task 9.7b: Build info gauge ---
// --- Build info gauge ---
buildInfoGauge_ = meter_->CreateInt64ObservableGauge("build_info", "Build version information");
buildInfoGauge_->AddCallback(
[](opentelemetry::metrics::ObserverResult result, void* /* state */) {
@@ -1262,7 +1279,7 @@ MetricsRegistry::registerBuildInfoGauge()
void
MetricsRegistry::registerCompleteLedgersGauge()
{
// --- Task 9.7c: Complete ledgers range gauge ---
// --- Complete ledgers range gauge ---
completeLedgersGauge_ = meter_->CreateInt64ObservableGauge(
"complete_ledgers", "Complete ledger range start/end pairs");
completeLedgersGauge_->AddCallback(
@@ -1321,7 +1338,7 @@ MetricsRegistry::registerCompleteLedgersGauge()
void
MetricsRegistry::registerDbMetricsGauge()
{
// --- Task 9.7d: Database size and fetch rate gauges ---
// --- Database size and fetch rate gauges ---
dbMetricsGauge_ =
meter_->CreateInt64ObservableGauge("db_metrics", "Database storage sizes and fetch rates");
dbMetricsGauge_->AddCallback(
@@ -1360,7 +1377,7 @@ MetricsRegistry::registerDbMetricsGauge()
void
MetricsRegistry::registerValidatorHealthGauge()
{
// --- Task 7.9: Validator health gauges ---
// --- Validator health gauges ---
validatorHealthGauge_ =
meter_->CreateDoubleObservableGauge("validator_health", "Validator health indicators");
validatorHealthGauge_->AddCallback(
@@ -1407,7 +1424,7 @@ MetricsRegistry::registerValidatorHealthGauge()
void
MetricsRegistry::registerPeerQualityGauge()
{
// --- Task 7.10: Peer quality gauges ---
// --- Peer quality gauges ---
// Uses Peer::json() to read latency and version since those accessors
// are not on the abstract Peer interface (they live on PeerImp).
peerQualityGauge_ =
@@ -1561,7 +1578,7 @@ MetricsRegistry::registerReduceRelayGauge()
void
MetricsRegistry::registerLedgerEconomyGauge()
{
// --- Task 7.11: Ledger economy gauges ---
// --- Ledger economy gauges ---
ledgerEconomyGauge_ = meter_->CreateDoubleObservableGauge(
metric::ledgerEconomy, "Ledger fee and economy metrics");
ledgerEconomyGauge_->AddCallback(
@@ -1626,7 +1643,7 @@ MetricsRegistry::registerLedgerEconomyGauge()
void
MetricsRegistry::registerStateTrackingGauge()
{
// --- Task 7.12: State tracking gauges ---
// --- State tracking gauges ---
stateTrackingGauge_ =
meter_->CreateDoubleObservableGauge(metric::stateTracking, "Node state and mode tracking");
stateTrackingGauge_->AddCallback(
@@ -1680,7 +1697,7 @@ MetricsRegistry::registerStateTrackingGauge()
void
MetricsRegistry::registerStorageDetailGauge()
{
// --- Task 7.13: Storage detail gauges ---
// --- Storage detail gauges ---
// Reports the cumulative payload bytes handed to the NodeStore. See the
// note at the observe() call below: this is logical bytes stored, not
// on-disk file size, because no accessor for the latter exists. The label
@@ -1732,7 +1749,7 @@ MetricsRegistry::registerStorageDetailGauge()
void
MetricsRegistry::registerValidationAgreementGauge()
{
// --- Task 7.15: Validation agreement gauges ---
// --- Validation agreement gauges ---
// Reports rolling-window agreement percentages and counts from
// ValidationTracker. reconcile() is called at the start of the
// callback so that pending ledger events are resolved before the
@@ -2383,7 +2400,7 @@ MetricsRegistry::registerLedgerQuorumPublishGauge()
#endif // XRPL_ENABLE_TELEMETRY
// -----------------------------------------------------------------
// External dashboard parity counter increments (Task 7.14)
// External dashboard parity counter increments
// -----------------------------------------------------------------
void

View File

@@ -241,8 +241,8 @@ namespace telemetry {
* edit needed. Fall back to a dedicated member + init line + record
* method (the pattern below) only when the metric needs to be read
* back by other code (e.g. ValidationTracker-style accumulation) or
* needs a custom histogram bucket View (see MetricMacros.h Limitation
* 2 in tasks/metric-macro-plan.md).
* needs a custom histogram bucket View (see the histogram note in
* MetricMacros.h).
* - Adding a new OBSERVABLE gauge still requires eager central
* registration -- pull-model instruments cannot be lazily created.
*/
@@ -585,7 +585,7 @@ public:
std::int64_t runningDurUs);
// -----------------------------------------------------------------
// External dashboard parity counters (Tasks 7.9-7.14)
// External dashboard parity counters
// -----------------------------------------------------------------
/**
@@ -813,7 +813,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_;
@@ -834,12 +834,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_;
@@ -959,7 +959,7 @@ private:
*/
opentelemetry::nostd::shared_ptr<opentelemetry::metrics::ObservableInstrument> dbMetricsGauge_;
// --- External dashboard parity gauges (Tasks 7.9-7.13) ---
// --- External dashboard parity gauges ---
/**
* Observable gauge for validator health indicators (amendment blocked,
* UNL blocked, quorum, UNL expiry).
@@ -1002,7 +1002,7 @@ private:
opentelemetry::nostd::shared_ptr<opentelemetry::metrics::ObservableInstrument>
validationAgreementGauge_;
// --- External dashboard parity counters (Task 7.14) ---
// --- External dashboard parity counters ---
/**
* Counter: ledgers_closed_total — incremented each consensus round.
*/
@@ -1095,15 +1095,15 @@ private:
void
registerJqTransOverflowCounter(); // gap-fill: overlay overflow total
void
registerCacheHitRateGauge(); // Task 9.2
registerCacheHitRateGauge();
void
registerTxqGauge(); // Task 9.3
registerTxqGauge();
void
registerObjectCountGauge(); // Task 9.6
registerObjectCountGauge();
void
registerLoadFactorGauge(); // Task 9.7
registerLoadFactorGauge();
void
registerNodeStoreGauge(); // Task 9.1
registerNodeStoreGauge();
// The four nodestore_state helpers and their ObserveFn sink are public
// (above), so a test can drive each one with a recording sink and assert
@@ -1113,27 +1113,27 @@ private:
void
registerRotationStateGauge(); // Sync diagnostics: online_delete rotation
void
registerServerInfoGauge(); // Task 9.7a
registerServerInfoGauge();
void
registerBuildInfoGauge(); // Task 9.7b
registerBuildInfoGauge();
void
registerCompleteLedgersGauge(); // Task 9.7c
registerCompleteLedgersGauge();
void
registerDbMetricsGauge(); // Task 9.7d
registerDbMetricsGauge();
void
registerValidatorHealthGauge(); // Task 7.9
registerValidatorHealthGauge();
void
registerPeerQualityGauge(); // Task 7.10
registerPeerQualityGauge();
void
registerReduceRelayGauge(); // Reduce-relay efficiency
void
registerLedgerEconomyGauge(); // Task 7.11
registerLedgerEconomyGauge();
void
registerStateTrackingGauge(); // Task 7.12
registerStateTrackingGauge();
void
registerStorageDetailGauge(); // Task 7.13
registerStorageDetailGauge();
void
registerValidationAgreementGauge(); // Task 7.15
registerValidationAgreementGauge();
void
registerValidationTotalsCounters(); // gap-fill: lifetime agree/miss _total