Merge branch 'pratik/otel-phase7-native-metrics' into pratik/otel-phase8-log-correlation

This commit is contained in:
Pratik Mankawde
2026-07-07 17:38:01 +01:00
8 changed files with 380 additions and 129 deletions

View File

@@ -4,7 +4,10 @@
*
* Compiled only when XRPL_ENABLE_TELEMETRY is defined (via CMake
* telemetry=ON). Maps beast::insight instruments to OTel SDK instruments
* and exports them via OTLP/HTTP using a PeriodicMetricReader.
* 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,
* PeriodicExportingMetricReader, OTLP exporter and histogram view all live in
* xrpl::telemetry::Telemetry.
*
* When XRPL_ENABLE_TELEMETRY is not defined, OTelCollector::New() returns
* a NullCollector so the build succeeds without OTel dependencies.
@@ -22,10 +25,10 @@
* +--------------------+----------------+--------------+
* |
* v
* PeriodicMetricReader (1s interval)
* GLOBAL Meter (from metrics::Provider::GetMeterProvider())
* |
* v
* OtlpHttpMetricExporter -> OTel Collector -> Prometheus
* telemetry-owned PeriodicMetricReader (1s) -> OTLP exporter -> Prometheus
*/
#ifdef XRPL_ENABLE_TELEMETRY
@@ -41,28 +44,15 @@
#include <xrpl/beast/insight/MeterImpl.h>
#include <xrpl/beast/utility/Journal.h>
#include <opentelemetry/exporters/otlp/otlp_http_metric_exporter_factory.h>
#include <opentelemetry/exporters/otlp/otlp_http_metric_exporter_options.h>
#include <opentelemetry/metrics/async_instruments.h>
#include <opentelemetry/metrics/meter.h>
#include <opentelemetry/metrics/meter_provider.h>
#include <opentelemetry/metrics/observer_result.h>
#include <opentelemetry/metrics/provider.h>
#include <opentelemetry/metrics/sync_instruments.h>
#include <opentelemetry/nostd/shared_ptr.h>
#include <opentelemetry/nostd/unique_ptr.h>
#include <opentelemetry/nostd/variant.h>
#include <opentelemetry/sdk/metrics/aggregation/aggregation_config.h>
#include <opentelemetry/sdk/metrics/export/periodic_exporting_metric_reader_factory.h>
#include <opentelemetry/sdk/metrics/export/periodic_exporting_metric_reader_options.h>
#include <opentelemetry/sdk/metrics/instruments.h>
#include <opentelemetry/sdk/metrics/meter_provider.h>
#include <opentelemetry/sdk/metrics/meter_provider_factory.h>
#include <opentelemetry/sdk/metrics/view/instrument_selector_factory.h>
#include <opentelemetry/sdk/metrics/view/meter_selector_factory.h>
#include <opentelemetry/sdk/metrics/view/view_factory.h>
#include <opentelemetry/sdk/metrics/view/view_registry.h>
#include <opentelemetry/sdk/resource/resource.h>
#include <opentelemetry/semconv/incubating/service_attributes.h>
#include <algorithm>
#include <atomic>
@@ -79,9 +69,6 @@ namespace beast::insight {
namespace detail {
namespace metrics_api = opentelemetry::metrics;
namespace metrics_sdk = opentelemetry::sdk::metrics;
namespace otlp_http = opentelemetry::exporter::otlp;
namespace resource = opentelemetry::sdk::resource;
class OTelCollectorImp;
@@ -142,7 +129,9 @@ class OTelCounterImpl : public CounterImpl
{
public:
/**
* @param name Fully-qualified metric name (prefix.group.name).
* @param name Export-ready metric name, already run through
* formatName() by the collector: prefix prepended and
* dots replaced with underscores (e.g. "xrpld_rpc_size").
* @param meter OTel Meter used to create the counter instrument.
*/
OTelCounterImpl(
@@ -182,7 +171,9 @@ class OTelEventImpl : public EventImpl
{
public:
/**
* @param name Fully-qualified metric name (prefix.group.name).
* @param name Export-ready metric name, already run through
* formatName() by the collector: prefix prepended and
* dots replaced with underscores (e.g. "xrpld_rpc_size").
* @param meter OTel Meter used to create the histogram instrument.
*/
OTelEventImpl(
@@ -227,7 +218,9 @@ class OTelGaugeImpl : public GaugeImpl
{
public:
/**
* @param name Fully-qualified metric name (prefix.group.name).
* @param name Export-ready metric name, already run through
* formatName() by the collector: prefix prepended
* and dots replaced with underscores.
* @param meter OTel Meter used to create the observable gauge.
* @param collector Owning collector, used to invoke hooks before reads.
*/
@@ -300,7 +293,9 @@ class OTelMeterImpl : public MeterImpl
{
public:
/**
* @param name Fully-qualified metric name (prefix.group.name).
* @param name Export-ready metric name, already run through
* formatName() by the collector: prefix prepended and
* dots replaced with underscores (e.g. "xrpld_rpc_size").
* @param meter OTel Meter used to create the counter instrument.
*/
OTelMeterImpl(
@@ -327,12 +322,16 @@ private:
//------------------------------------------------------------------------------
/**
* @brief Main OTel Collector implementation.
* @brief Main OTel Collector implementation (legacy shim).
*
* Creates an OTel MeterProvider with a PeriodicMetricReader that
* exports metrics via OTLP/HTTP at 1-second intervals. Implements
* all Collector::make_*() factory methods to create OTel-backed
* instrument wrappers.
* Obtains its Meter from the GLOBAL MeterProvider owned and published by the
* telemetry module (xrpl::telemetry::Telemetry), rather than building its own
* export pipeline. Implements all Collector::make_*() factory methods to
* create OTel-backed instrument wrappers on that shared Meter.
*
* The metrics pipeline (MeterProvider + PeriodicExportingMetricReader + OTLP
* HTTP exporter + histogram view) lives in the telemetry module. This class is
* a thin adapter kept for beast::insight callers during deprecation.
*
* Class diagram:
*
@@ -345,19 +344,20 @@ private:
* | OTelCollectorImp |-------------+
* +------------------+
* | - journal_ |
* | - prefix_ |
* | - provider_ | +---------------------+
* | - otelMeter_ |---->| OTel MeterProvider |
* | - hooks_[] | | + PeriodicReader |
* | - gauges_[] | | + OtlpHttpExporter |
* +------------------+ +---------------------+
* | - prefix_ | +--------------------------+
* | - otelMeter_ |---->| GLOBAL MeterProvider |
* | - hooks_[] | | (owned by Telemetry) |
* | - gauges_[] | | + PeriodicReader |
* +------------------+ | + OtlpHttpExporter |
* +--------------------------+
*
* Lifecycle:
* 1. Constructor creates MeterProvider + exporter pipeline.
* 2. make_*() methods create instruments registered with the provider.
* 3. PeriodicMetricReader collects every 1s, calling observable callbacks.
* 1. Constructor fetches the Meter from the global MeterProvider.
* 2. make_*() methods create instruments on that shared Meter.
* 3. The telemetry-owned PeriodicMetricReader collects every 1s, calling
* observable callbacks.
* 4. Observable callbacks invoke hooks, read gauge atomics.
* 5. Destructor shuts down MeterProvider (flushes pending exports).
* 5. Destructor only logs; the telemetry module owns pipeline teardown.
*
* Caveats:
* - Observable gauge callbacks run on the SDK's internal thread. Hook
@@ -382,9 +382,12 @@ class OTelCollectorImp : public OTelCollector, public std::enable_shared_from_th
{
public:
/**
* @brief Construct the OTel collector and initialize the export pipeline.
* @brief Construct the OTel collector over the global MeterProvider.
*
* @param endpoint OTLP/HTTP metrics endpoint URL.
* @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 instanceId Value for the service.instance.id resource attribute.
* When empty, the attribute is omitted.
@@ -497,9 +500,6 @@ private:
/** Prefix for all metric names (e.g., "xrpld"). */
std::string prefix_;
/** OTel SDK MeterProvider owning the export pipeline. RAII lifecycle. */
std::shared_ptr<metrics_sdk::MeterProvider> provider_;
/** OTel Meter used to create all instruments. */
opentelemetry::nostd::shared_ptr<metrics_api::Meter> otelMeter_;
@@ -674,69 +674,33 @@ 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.
(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.
journal_.info() << "OTelCollector starting: endpoint=" << endpoint << " prefix=" << prefix_;
}
// Configure OTLP HTTP metric exporter.
otlp_http::OtlpHttpMetricExporterOptions exporterOpts;
exporterOpts.url = endpoint;
auto exporter = otlp_http::OtlpHttpMetricExporterFactory::Create(exporterOpts);
// Configure periodic metric reader (1-second export interval).
metrics_sdk::PeriodicExportingMetricReaderOptions readerOpts;
readerOpts.export_interval_millis = std::chrono::milliseconds(1000);
readerOpts.export_timeout_millis = std::chrono::milliseconds(500);
auto reader =
metrics_sdk::PeriodicExportingMetricReaderFactory::Create(std::move(exporter), readerOpts);
// Configure resource attributes matching the trace exporter so metrics
// and traces share one identity. service.name defaults to "xrpld" when
// unset. service.instance.id and xrpl.network.type are added when
// provided so Prometheus labels distinguish node and network.
// "xrpl.network.type" is a string literal (not the telemetry SpanNames
// const) because beast/insight sits below the telemetry module and
// cannot include it; the value must match the trace exporter's key.
resource::ResourceAttributes attrs;
attrs[opentelemetry::semconv::service::kServiceName] =
serviceName.empty() ? "xrpld" : serviceName;
if (!instanceId.empty())
{
attrs[opentelemetry::semconv::service::kServiceInstanceId] = instanceId;
}
if (!networkType.empty())
{
attrs["xrpl.network.type"] = networkType;
}
auto resourceAttrs = resource::Resource::Create(attrs);
// Create MeterProvider with resource, then attach the metric reader.
provider_ = metrics_sdk::MeterProviderFactory::Create(
std::make_unique<metrics_sdk::ViewRegistry>(), resourceAttrs);
provider_->AddMetricReader(std::move(reader));
// Configure histogram bucket boundaries for Event instruments.
// These match the SpanMetrics connector buckets for consistency.
auto histogramSelector = metrics_sdk::InstrumentSelectorFactory::Create(
metrics_sdk::InstrumentType::kHistogram, "*", "ms");
auto meterSelector = metrics_sdk::MeterSelectorFactory::Create("xrpld_metrics", "", "");
auto histogramConfig = std::make_shared<metrics_sdk::HistogramAggregationConfig>();
histogramConfig->boundaries_ =
std::vector<double>{1.0, 5.0, 10.0, 25.0, 50.0, 100.0, 250.0, 500.0, 1000.0, 5000.0};
auto histogramView = metrics_sdk::ViewFactory::Create(
"default_histogram",
"Default histogram view with SpanMetrics-compatible buckets",
metrics_sdk::AggregationType::kHistogram,
std::move(histogramConfig));
provider_->AddView(
std::move(histogramSelector), std::move(meterSelector), std::move(histogramView));
// Create the OTel Meter for creating instruments.
otelMeter_ = provider_->GetMeter("xrpld_metrics", "1.0.0");
// Fetch the Meter from the GLOBAL MeterProvider. The telemetry module
// (xrpl::telemetry::Telemetry) builds the metrics pipeline (exporter,
// periodic reader, histogram view, resource attributes) and registers it
// via metrics::Provider::SetMeterProvider() during start(). beast metrics
// ride that shared pipeline, so both direct-API and beast-sourced metrics
// export under one resource identity.
//
// The name/version literals MUST match the telemetry module's kMeterName
// ("xrpld") and kMeterVersion ("1.0.0"). They are written as literals (not
// referenced from Telemetry.h) because beast/insight sits below the
// telemetry module in the layering and cannot include its header.
otelMeter_ = metrics_api::Provider::GetMeterProvider()->GetMeter(
std::string{"xrpld"}, std::string{"1.0.0"});
if (journal_.info())
{
@@ -750,11 +714,8 @@ OTelCollectorImp::~OTelCollectorImp()
{
journal_.info() << "OTelCollector shutting down";
}
if (provider_)
{
provider_->ForceFlush(std::chrono::milliseconds(2000));
provider_->Shutdown();
}
// No pipeline teardown here: the telemetry module owns the global
// MeterProvider lifecycle (ForceFlush/Shutdown happen in Telemetry::stop()).
if (journal_.info())
{
journal_.info() << "OTelCollector stopped";

View File

@@ -25,7 +25,23 @@
#include <opentelemetry/context/context.h>
#include <opentelemetry/exporters/otlp/otlp_http_exporter_factory.h>
#include <opentelemetry/exporters/otlp/otlp_http_exporter_options.h>
#include <opentelemetry/exporters/otlp/otlp_http_metric_exporter_factory.h>
#include <opentelemetry/exporters/otlp/otlp_http_metric_exporter_options.h>
#include <opentelemetry/metrics/meter.h>
#include <opentelemetry/metrics/meter_provider.h>
#include <opentelemetry/metrics/noop.h>
#include <opentelemetry/metrics/provider.h>
#include <opentelemetry/nostd/shared_ptr.h>
#include <opentelemetry/sdk/metrics/aggregation/aggregation_config.h>
#include <opentelemetry/sdk/metrics/export/periodic_exporting_metric_reader_factory.h>
#include <opentelemetry/sdk/metrics/export/periodic_exporting_metric_reader_options.h>
#include <opentelemetry/sdk/metrics/instruments.h>
#include <opentelemetry/sdk/metrics/meter_provider.h>
#include <opentelemetry/sdk/metrics/meter_provider_factory.h>
#include <opentelemetry/sdk/metrics/view/instrument_selector_factory.h>
#include <opentelemetry/sdk/metrics/view/meter_selector_factory.h>
#include <opentelemetry/sdk/metrics/view/view_factory.h>
#include <opentelemetry/sdk/metrics/view/view_registry.h>
#include <opentelemetry/sdk/resource/resource.h>
#include <opentelemetry/sdk/trace/batch_span_processor_factory.h>
#include <opentelemetry/sdk/trace/batch_span_processor_options.h>
@@ -50,6 +66,7 @@
#include <string>
#include <string_view>
#include <utility>
#include <vector>
namespace xrpl::telemetry {
@@ -57,6 +74,8 @@ namespace {
namespace trace_api = opentelemetry::trace;
namespace trace_sdk = opentelemetry::sdk::trace;
namespace metrics_api = opentelemetry::metrics;
namespace metrics_sdk = opentelemetry::sdk::metrics;
namespace otlp_http = opentelemetry::exporter::otlp;
namespace resource = opentelemetry::sdk::resource;
@@ -222,6 +241,16 @@ public:
return noopTracer;
}
opentelemetry::nostd::shared_ptr<metrics_api::Meter>
getMeter(std::string_view name) override
{
// Serve a meter from a process-wide noop provider, mirroring the
// noop tracer above. Instruments created from it are inert.
static auto noopProvider = opentelemetry::nostd::shared_ptr<metrics_api::MeterProvider>(
new metrics_api::NoopMeterProvider());
return noopProvider->GetMeter(std::string(name), std::string(kMeterVersion));
}
opentelemetry::nostd::shared_ptr<trace_api::Span>
startSpan(std::string_view, trace_api::SpanKind) override
{
@@ -259,6 +288,14 @@ class TelemetryImpl : public Telemetry
*/
std::shared_ptr<trace_sdk::TracerProvider> sdkProvider_;
/** The SDK MeterProvider that owns the metric export pipeline.
Symmetric with sdkProvider_ on the tracing side. Held as
std::shared_ptr so we can ForceFlush() on shutdown; wrapped in a
nostd::shared_ptr when registered as the global meter provider.
*/
std::shared_ptr<metrics_sdk::MeterProvider> meterProvider_;
public:
TelemetryImpl(Setup setup, beast::Journal journal) : setup_(std::move(setup)), journal_(journal)
{
@@ -331,6 +368,70 @@ public:
trace_api::Provider::SetTracerProvider(
opentelemetry::nostd::shared_ptr<trace_api::TracerProvider>(sdkProvider_));
// Build the metrics pipeline, parallel to the tracer above and
// reusing the same resourceAttrs so metrics and traces share one
// resource identity.
// Derive the metrics endpoint from the trace endpoint by swapping
// the trailing "/v1/traces" path for "/v1/metrics". Any other URL
// shape is used as-is.
std::string metricsEndpoint = setup_.exporterEndpoint;
constexpr std::string_view tracesPath{"/v1/traces"};
if (metricsEndpoint.ends_with(tracesPath))
{
metricsEndpoint.replace(
metricsEndpoint.size() - tracesPath.size(), tracesPath.size(), "/v1/metrics");
}
// Configure OTLP HTTP metric exporter, honoring the same TLS
// options as the trace exporter.
otlp_http::OtlpHttpMetricExporterOptions metricExporterOpts;
metricExporterOpts.url = metricsEndpoint;
if (setup_.useTls)
{
metricExporterOpts.ssl_ca_cert_path = setup_.tlsCertPath;
metricExporterOpts.ssl_client_cert_path = setup_.tlsClientCertPath;
metricExporterOpts.ssl_client_key_path = setup_.tlsClientKeyPath;
}
auto metricExporter = otlp_http::OtlpHttpMetricExporterFactory::Create(metricExporterOpts);
// Configure periodic metric reader (1-second export interval,
// matching the beast OTelCollector path).
metrics_sdk::PeriodicExportingMetricReaderOptions readerOpts;
readerOpts.export_interval_millis = std::chrono::milliseconds(1000);
readerOpts.export_timeout_millis = std::chrono::milliseconds(500);
auto reader = metrics_sdk::PeriodicExportingMetricReaderFactory::Create(
std::move(metricExporter), readerOpts);
// Create MeterProvider with the shared resource, then attach reader.
meterProvider_ = metrics_sdk::MeterProviderFactory::Create(
std::make_unique<metrics_sdk::ViewRegistry>(), resourceAttrs);
meterProvider_->AddMetricReader(std::move(reader));
// Histogram view: SpanMetrics-compatible bucket boundaries (ms) so
// histogram instruments align with the collector's SpanMetrics.
auto histogramSelector = metrics_sdk::InstrumentSelectorFactory::Create(
metrics_sdk::InstrumentType::kHistogram, "*", "ms");
auto meterSelector = metrics_sdk::MeterSelectorFactory::Create("xrpld_metrics", "", "");
auto histogramConfig = std::make_shared<metrics_sdk::HistogramAggregationConfig>();
histogramConfig->boundaries_ =
std::vector<double>{1.0, 5.0, 10.0, 25.0, 50.0, 100.0, 250.0, 500.0, 1000.0, 5000.0};
auto histogramView = metrics_sdk::ViewFactory::Create(
"default_histogram",
"Default histogram view with SpanMetrics-compatible buckets",
metrics_sdk::AggregationType::kHistogram,
std::move(histogramConfig));
meterProvider_->AddView(
std::move(histogramSelector), std::move(meterSelector), std::move(histogramView));
// Publish as the global meter provider so developers (and the beast
// OTelCollector shim) reach the same pipeline.
metrics_api::Provider::SetMeterProvider(
opentelemetry::nostd::shared_ptr<metrics_api::MeterProvider>(meterProvider_));
// Register as the global Telemetry instance so SpanGuard factory
// methods can access it without callers passing a reference.
Telemetry::setInstance(this);
@@ -363,6 +464,18 @@ public:
opentelemetry::nostd::shared_ptr<trace_api::TracerProvider>(
new trace_api::NoopTracerProvider()));
}
if (meterProvider_)
{
// Mirror the tracer teardown: bounded flush, drop our provider,
// then restore a noop global provider. Same shutdown-order
// caveat as sdkProvider_ above applies to getMeter() callers.
meterProvider_->ForceFlush(std::chrono::milliseconds(5000));
meterProvider_.reset();
metrics_api::Provider::SetMeterProvider(
opentelemetry::nostd::shared_ptr<metrics_api::MeterProvider>(
new metrics_api::NoopMeterProvider()));
}
JLOG(journal_.info()) << "Telemetry stopped";
}
@@ -416,6 +529,15 @@ public:
return sdkProvider_->GetTracer(std::string(name));
}
opentelemetry::nostd::shared_ptr<metrics_api::Meter>
getMeter(std::string_view name = kMeterName) override
{
if (!meterProvider_)
return metrics_api::Provider::GetMeterProvider()->GetMeter(
std::string(name), std::string(kMeterVersion));
return meterProvider_->GetMeter(std::string(name), std::string(kMeterVersion));
}
opentelemetry::nostd::shared_ptr<trace_api::Span>
startSpan(std::string_view name, trace_api::SpanKind kind) override
{

View File

@@ -0,0 +1,111 @@
/** @file GetMeter.cpp
Unit tests for the direct OTel metrics API surface:
- xrpl::telemetry::Telemetry::getMeter() on the disabled (noop) path.
- The global MeterProvider contract that beast's OTelCollector shim
relies on (GetMeterProvider()->GetMeter(kMeterName, kMeterVersion)).
These tests deliberately exercise the global-provider level rather than a
fully started TelemetryImpl: TelemetryImpl::start() spins up an OTLP HTTP
exporter with background export threads and network connect attempts, which
is unsuitable for a hermetic unit test. The design spec permits testing the
metrics unlock at the provider level, which is the exact path the beast shim
and getMeter() delegate to.
Compiled only when XRPL_ENABLE_TELEMETRY is defined; the metrics API and SDK
headers exist only in that build.
*/
#ifdef XRPL_ENABLE_TELEMETRY
#include <xrpl/telemetry/Telemetry.h>
#include <gtest/gtest.h>
#include <opentelemetry/metrics/meter.h>
#include <opentelemetry/metrics/meter_provider.h>
#include <opentelemetry/metrics/provider.h>
#include <opentelemetry/metrics/sync_instruments.h>
#include <opentelemetry/nostd/shared_ptr.h>
#include <opentelemetry/sdk/metrics/meter_provider.h>
#include <opentelemetry/sdk/metrics/meter_provider_factory.h>
#include <cstdint>
#include <string>
using namespace xrpl;
using namespace xrpl::telemetry;
namespace metrics_api = opentelemetry::metrics;
namespace metrics_sdk = opentelemetry::sdk::metrics;
// ---------------------------------------------------------------
// 1. Disabled path: Telemetry::getMeter() returns a usable noop meter.
// makeTelemetry() with enabled=false yields NullTelemetryOtel, whose
// getMeter() serves a meter from a process-wide NoopMeterProvider. The
// meter and any instrument built from it must be non-null, and Add()
// (positive and negative) must be inert but safe.
// ---------------------------------------------------------------
TEST(GetMeter, disabled_path_returns_usable_noop_meter)
{
Telemetry::Setup setup;
setup.enabled = false;
beast::Journal::Sink& sink = beast::Journal::getNullSink();
beast::Journal const journal(sink);
auto telemetry = makeTelemetry(setup, journal);
ASSERT_NE(telemetry, nullptr);
// getMeter() must return a concrete (non-null) meter, never a raw nullptr.
auto meter = telemetry->getMeter();
ASSERT_TRUE(static_cast<bool>(meter));
// A synchronous UpDownCounter — one of the three instrument kinds the
// direct API unlocks — must be creatable and non-null.
auto upDown = meter->CreateInt64UpDownCounter("test_open_ledgers");
ASSERT_TRUE(static_cast<bool>(upDown));
// Both increment and decrement paths must execute without error (inert on
// the noop instrument, but exercised here to prove both directions work).
upDown->Add(static_cast<int64_t>(1));
upDown->Add(static_cast<int64_t>(-1));
}
// ---------------------------------------------------------------
// 2. Global-provider contract (the beast OTelCollector shim path).
// After a real SDK MeterProvider is registered via SetMeterProvider(),
// GetMeterProvider()->GetMeter(kMeterName, kMeterVersion) — the exact call
// the shim makes — must return a non-null meter, and an UpDownCounter built
// from it must be non-null and accept positive and negative Add().
// ---------------------------------------------------------------
TEST(GetMeter, global_provider_meter_accepts_updown_counter)
{
// Preserve and later restore the process-wide provider so this test does
// not leak state into other telemetry tests in the same binary.
auto const previous = metrics_api::Provider::GetMeterProvider();
// A views-less SDK MeterProvider with no reader is sufficient to prove the
// API contract: it hands out a real (non-noop) Meter that creates working
// instruments. No exporter/reader means no background threads or network.
std::shared_ptr<metrics_sdk::MeterProvider> sdkProvider =
metrics_sdk::MeterProviderFactory::Create();
metrics_api::Provider::SetMeterProvider(
opentelemetry::nostd::shared_ptr<metrics_api::MeterProvider>(sdkProvider));
// Fetch the meter exactly as the beast shim does: kMeterName + kMeterVersion
// from the global provider.
auto meter = metrics_api::Provider::GetMeterProvider()->GetMeter(
std::string(kMeterName), std::string(kMeterVersion));
ASSERT_TRUE(static_cast<bool>(meter));
auto upDown = meter->CreateInt64UpDownCounter("test_in_flight_requests");
ASSERT_TRUE(static_cast<bool>(upDown));
// Positive path: value goes up.
upDown->Add(static_cast<int64_t>(1));
// Negative path: UpDownCounter permits decrement (unlike a plain Counter).
upDown->Add(static_cast<int64_t>(-1));
// Restore the previous global provider.
metrics_api::Provider::SetMeterProvider(previous);
}
#endif // XRPL_ENABLE_TELEMETRY

View File

@@ -126,25 +126,11 @@ ValidationTracker::evictOldPending(TimePoint now)
}
}
// Hard trim if still over limit -- remove reconciled entries that are
// past the late-repair window first, then any reconciled entry as a
// last resort.
// Hard trim if still over limit. The loop above already removed every
// reconciled entry older than the late-repair window, so here we drop
// any remaining reconciled entry as a last resort.
if (pending_.size() > kMaxPendingEvents)
{
// Pass 1: only entries past late-repair window.
for (auto it = pending_.begin();
it != pending_.end() && pending_.size() > kMaxPendingEvents;)
{
if (it->second.reconciled && it->second.recordTime < cutoff)
{
it = pending_.erase(it);
}
else
{
++it;
}
}
// Pass 2: any reconciled entry if still over limit.
for (auto it = pending_.begin();
it != pending_.end() && pending_.size() > kMaxPendingEvents;)
{