From d426f4983a3bb52af2faf9225b362493270eba82 Mon Sep 17 00:00:00 2001 From: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com> Date: Wed, 25 Mar 2026 17:23:51 +0000 Subject: [PATCH] feat(telemetry): add push_metrics.py parity gauges to MetricsRegistry Co-Authored-By: Claude Opus 4.6 --- src/xrpld/telemetry/MetricsRegistry.cpp | 205 ++++++++++++++++++++++++ src/xrpld/telemetry/MetricsRegistry.h | 13 ++ 2 files changed, 218 insertions(+) diff --git a/src/xrpld/telemetry/MetricsRegistry.cpp b/src/xrpld/telemetry/MetricsRegistry.cpp index 99c94efc85..7e74c747ae 100644 --- a/src/xrpld/telemetry/MetricsRegistry.cpp +++ b/src/xrpld/telemetry/MetricsRegistry.cpp @@ -24,14 +24,21 @@ #ifdef XRPL_ENABLE_TELEMETRY #include +#include #include #include #include +#include #include +#include #include #include +#include +#include +#include #include +#include #include #include @@ -44,6 +51,8 @@ #include #include +#include + namespace metric_sdk = opentelemetry::sdk::metrics; namespace otlp_http = opentelemetry::exporter::otlp; namespace resource = opentelemetry::sdk::resource; @@ -318,6 +327,12 @@ MetricsRegistry::registerAsyncGauges() opentelemetry::nostd::get>>(result) ->Observe(static_cast(fbSize), {{"metric", "fullbelow_size"}}); + + // AcceptedLedger cache size (entry count). + auto alSize = app.getAcceptedLedgerCache().size(); + opentelemetry::nostd::get>>(result) + ->Observe(static_cast(alSize), {{"metric", "AL_size"}}); } catch (...) // NOLINT(bugprone-empty-catch) { @@ -498,6 +513,30 @@ MetricsRegistry::registerAsyncGauges() { observe("read_queue", static_cast(obj["read_queue"].asUInt())); } + + // Cumulative read duration (stored as JSON string, not int). + if (obj.isMember(jss::node_reads_duration_us)) + { + auto durStr = obj[jss::node_reads_duration_us].asString(); + if (!durStr.empty()) + { + observe("node_reads_duration_us", static_cast(std::stoll(durStr))); + } + } + + // Read thread pool stats (native JSON ints, no jss:: constants). + if (obj.isMember("read_request_bundle")) + observe( + "read_request_bundle", + static_cast(obj["read_request_bundle"].asInt())); + if (obj.isMember("read_threads_running")) + observe( + "read_threads_running", + static_cast(obj["read_threads_running"].asInt())); + if (obj.isMember("read_threads_total")) + observe( + "read_threads_total", + static_cast(obj["read_threads_total"].asInt())); } catch (...) // NOLINT(bugprone-empty-catch) { @@ -505,6 +544,172 @@ MetricsRegistry::registerAsyncGauges() } }, this); + + // --- Task 9.7a: Server info gauges --- + serverInfoGauge_ = + meter_->CreateInt64ObservableGauge("rippled_server_info", "Server-level health metrics"); + serverInfoGauge_->AddCallback( + [](opentelemetry::metrics::ObserverResult result, void* state) { + auto* self = static_cast(state); + auto& app = self->app_; + + try + { + auto observe = [&](char const* name, int64_t value) { + opentelemetry::nostd::get>>(result) + ->Observe(value, {{"metric", name}}); + }; + + // Server operating mode (DISCONNECTED=0 .. FULL=4). + observe("server_state", static_cast(app.getOPs().getOperatingMode())); + + // Uptime in seconds since server start. + observe( + "uptime", static_cast(UptimeClock::now().time_since_epoch().count())); + + // Total peer count (inbound + outbound). + observe("peers", static_cast(app.overlay().size())); + + // Validated ledger sequence (0 if none yet). + observe( + "validated_ledger_seq", + static_cast(app.getLedgerMaster().getValidLedgerIndex())); + + // Current open ledger sequence. + observe( + "ledger_current_index", + static_cast(app.getLedgerMaster().getCurrentLedgerIndex())); + + // Cumulative resource-related peer disconnects. + observe( + "peer_disconnects_resources", + static_cast(app.overlay().getPeerDisconnectCharges())); + + // Last consensus round data (from JSON — only public API). + auto const consensusInfo = app.getOPs().getConsensusInfo(); + if (consensusInfo.isMember("previous_proposers")) + { + observe( + "last_close_proposers", + static_cast(consensusInfo["previous_proposers"].asUInt())); + } + if (consensusInfo.isMember("previous_mseconds")) + { + observe( + "last_close_converge_time_ms", + static_cast(consensusInfo["previous_mseconds"].asUInt())); + } + } + catch (...) // NOLINT(bugprone-empty-catch) + { + // Silently skip if services are not yet ready. + } + }, + this); + + // --- Task 9.7b: Build info gauge --- + buildInfoGauge_ = + meter_->CreateInt64ObservableGauge("rippled_build_info", "Build version information"); + buildInfoGauge_->AddCallback( + [](opentelemetry::metrics::ObserverResult result, void* /* state */) { + try + { + opentelemetry::nostd::get>>(result) + ->Observe(1, {{"version", std::string(BuildInfo::getVersionString())}}); + } + catch (...) // NOLINT(bugprone-empty-catch) + { + } + }, + nullptr); + + // --- Task 9.7c: Complete ledgers range gauge --- + completeLedgersGauge_ = meter_->CreateInt64ObservableGauge( + "rippled_complete_ledgers", "Complete ledger range start/end pairs"); + completeLedgersGauge_->AddCallback( + [](opentelemetry::metrics::ObserverResult result, void* state) { + auto* self = static_cast(state); + auto& app = self->app_; + + try + { + auto const rangeStr = app.getLedgerMaster().getCompleteLedgers(); + if (rangeStr.empty() || rangeStr == "empty") + return; + + // Parse comma-separated ranges like + // "32570-50000,50005-75891421". + std::size_t rangeIndex = 0; + std::istringstream stream(rangeStr); + std::string segment; + while (std::getline(stream, segment, ',')) + { + auto const dashPos = segment.find('-'); + if (dashPos == std::string::npos || dashPos == 0 || + dashPos == segment.size() - 1) + continue; + + auto const startStr = segment.substr(0, dashPos); + auto const endStr = segment.substr(dashPos + 1); + + auto const idxStr = std::to_string(rangeIndex); + + opentelemetry::nostd::get>>(result) + ->Observe( + static_cast(std::stoll(startStr)), + {{"bound", "start"}, {"index", idxStr}}); + + opentelemetry::nostd::get>>(result) + ->Observe( + static_cast(std::stoll(endStr)), + {{"bound", "end"}, {"index", idxStr}}); + + ++rangeIndex; + } + } + catch (...) // NOLINT(bugprone-empty-catch) + { + // Silently skip on parse error or if services not ready. + } + }, + this); + + // --- Task 9.7d: Database size and fetch rate gauges --- + dbMetricsGauge_ = meter_->CreateInt64ObservableGauge( + "rippled_db_metrics", "Database storage sizes and fetch rates"); + dbMetricsGauge_->AddCallback( + [](opentelemetry::metrics::ObserverResult result, void* state) { + auto* self = static_cast(state); + auto& app = self->app_; + + try + { + auto observe = [&](char const* name, int64_t value) { + opentelemetry::nostd::get>>(result) + ->Observe(value, {{"metric", name}}); + }; + + auto& rdb = app.getRelationalDatabase(); + observe("db_kb_total", static_cast(rdb.getKBUsedAll())); + observe("db_kb_ledger", static_cast(rdb.getKBUsedLedger())); + observe("db_kb_transaction", static_cast(rdb.getKBUsedTransaction())); + + // Historical ledger fetches per minute. + observe( + "historical_perminute", + static_cast(app.getInboundLedgers().fetchRate())); + } + catch (...) // NOLINT(bugprone-empty-catch) + { + // Silently skip if services are not yet ready. + } + }, + this); } #endif // XRPL_ENABLE_TELEMETRY diff --git a/src/xrpld/telemetry/MetricsRegistry.h b/src/xrpld/telemetry/MetricsRegistry.h index e6d39892b1..fde40de170 100644 --- a/src/xrpld/telemetry/MetricsRegistry.h +++ b/src/xrpld/telemetry/MetricsRegistry.h @@ -40,6 +40,10 @@ +-- CountedObject counts +-- Load factor breakdown +-- NodeStore I/O gauges + +-- Server info (state, uptime, peers, consensus) + +-- Build info (version label) + +-- Complete ledger ranges (start/end pairs) + +-- DB metrics (storage KB, fetch rate) Control-flow for async gauges: @@ -271,6 +275,15 @@ private: opentelemetry::nostd::shared_ptr loadFactorGauge_; /// Observable gauges for NodeStore write_load and read_queue. opentelemetry::nostd::shared_ptr nodeStoreGauge_; + /// Observable gauge for server-level health metrics (state, uptime, peers, etc.). + opentelemetry::nostd::shared_ptr serverInfoGauge_; + /// Observable gauge for build version info (label-based, value=1). + opentelemetry::nostd::shared_ptr buildInfoGauge_; + /// Observable gauge for complete ledger range start/end pairs. + opentelemetry::nostd::shared_ptr + completeLedgersGauge_; + /// Observable gauge for database sizes and historical fetch rate. + opentelemetry::nostd::shared_ptr dbMetricsGauge_; /** Register all observable gauge callbacks with the OTel SDK. Called once during start().