diff --git a/src/libxrpl/beast/insight/StatsDCollector.cpp b/src/libxrpl/beast/insight/StatsDCollector.cpp index d11e85830f..6116987447 100644 --- a/src/libxrpl/beast/insight/StatsDCollector.cpp +++ b/src/libxrpl/beast/insight/StatsDCollector.cpp @@ -166,7 +166,7 @@ private: std::string m_name; GaugeImpl::value_type m_last_value{0}; GaugeImpl::value_type m_value{0}; - bool m_dirty{false}; + bool m_dirty{true}; }; //------------------------------------------------------------------------------ @@ -583,6 +583,9 @@ StatsDEventImpl::do_notify(EventImpl::value_type const& value) StatsDGaugeImpl::StatsDGaugeImpl(std::string name, std::shared_ptr const& impl) : m_impl(impl), m_name(std::move(name)) { + // Start dirty so the initial value (0) is emitted on the first flush. + // Without this, gauges whose value never changes from 0 would never + // appear in downstream metric stores (e.g. Prometheus via StatsD). m_impl->add(*this); } diff --git a/src/xrpld/app/main/Application.cpp b/src/xrpld/app/main/Application.cpp index 29fb2dd0e9..bb43349eb9 100644 --- a/src/xrpld/app/main/Application.cpp +++ b/src/xrpld/app/main/Application.cpp @@ -149,6 +149,7 @@ private: beast::Journal m_journal; beast::io_latency_probe m_probe; std::atomic lastSample_; + std::atomic firstSample_; public: io_latency_sampler( @@ -156,7 +157,7 @@ private: beast::Journal journal, std::chrono::milliseconds interval, boost::asio::io_context& ios) - : m_event(std::move(ev)), m_journal(journal), m_probe(interval, ios) + : m_event(std::move(ev)), m_journal(journal), m_probe(interval, ios), firstSample_(true) { } @@ -175,7 +176,10 @@ private: lastSample_ = lastSample; - if (lastSample >= 10ms) + // Always emit the first sample so the metric is registered in + // downstream stores (Prometheus via StatsD). After that, only + // report latency >= 10 ms to avoid flooding with sub-ms values. + if (firstSample_.exchange(false) || lastSample >= 10ms) m_event.notify(lastSample); if (lastSample >= 500ms) {