mirror of
https://github.com/XRPLF/rippled.git
synced 2026-04-29 15:37:57 +00:00
Phase 2: Complete RPC tracing — interface, macros, attributes, tests
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -38,3 +38,11 @@ if(NOT WIN32)
|
||||
target_link_libraries(xrpl.test.net PRIVATE xrpl.imports.test)
|
||||
add_dependencies(xrpl.tests xrpl.test.net)
|
||||
endif()
|
||||
|
||||
xrpl_add_test(telemetry)
|
||||
target_link_libraries(xrpl.test.telemetry PRIVATE xrpl.imports.test)
|
||||
target_include_directories(xrpl.test.telemetry PRIVATE ${CMAKE_SOURCE_DIR}/src)
|
||||
if (telemetry)
|
||||
target_link_libraries(xrpl.test.telemetry PRIVATE opentelemetry-cpp::opentelemetry-cpp)
|
||||
endif()
|
||||
add_dependencies(xrpl.tests xrpl.test.telemetry)
|
||||
|
||||
@@ -1,209 +0,0 @@
|
||||
#include <xrpl/basics/MallocTrim.h>
|
||||
|
||||
#include <boost/predef.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
using namespace xrpl;
|
||||
|
||||
// cSpell:ignore statm
|
||||
|
||||
#if defined(__GLIBC__) && BOOST_OS_LINUX
|
||||
namespace xrpl::detail {
|
||||
long
|
||||
parseStatmRSSkB(std::string const& statm);
|
||||
} // namespace xrpl::detail
|
||||
#endif
|
||||
|
||||
TEST(MallocTrimReport, structure)
|
||||
{
|
||||
// Test default construction
|
||||
MallocTrimReport report;
|
||||
EXPECT_EQ(report.supported, false);
|
||||
EXPECT_EQ(report.trimResult, -1);
|
||||
EXPECT_EQ(report.rssBeforeKB, -1);
|
||||
EXPECT_EQ(report.rssAfterKB, -1);
|
||||
EXPECT_EQ(report.durationUs, std::chrono::microseconds{-1});
|
||||
EXPECT_EQ(report.minfltDelta, -1);
|
||||
EXPECT_EQ(report.majfltDelta, -1);
|
||||
EXPECT_EQ(report.deltaKB(), 0);
|
||||
|
||||
// Test deltaKB calculation - memory freed
|
||||
report.rssBeforeKB = 1000;
|
||||
report.rssAfterKB = 800;
|
||||
EXPECT_EQ(report.deltaKB(), -200);
|
||||
|
||||
// Test deltaKB calculation - memory increased
|
||||
report.rssBeforeKB = 500;
|
||||
report.rssAfterKB = 600;
|
||||
EXPECT_EQ(report.deltaKB(), 100);
|
||||
|
||||
// Test deltaKB calculation - no change
|
||||
report.rssBeforeKB = 1234;
|
||||
report.rssAfterKB = 1234;
|
||||
EXPECT_EQ(report.deltaKB(), 0);
|
||||
}
|
||||
|
||||
#if defined(__GLIBC__) && BOOST_OS_LINUX
|
||||
TEST(parseStatmRSSkB, standard_format)
|
||||
{
|
||||
using xrpl::detail::parseStatmRSSkB;
|
||||
|
||||
// Test standard format: size resident shared text lib data dt
|
||||
// Assuming 4KB page size: resident=1000 pages = 4000 KB
|
||||
{
|
||||
std::string statm = "25365 1000 2377 0 0 5623 0";
|
||||
long result = parseStatmRSSkB(statm);
|
||||
// Note: actual result depends on system page size
|
||||
// On most systems it's 4KB, so 1000 pages = 4000 KB
|
||||
EXPECT_GT(result, 0);
|
||||
}
|
||||
|
||||
// Test with newline
|
||||
{
|
||||
std::string statm = "12345 2000 1234 0 0 3456 0\n";
|
||||
long result = parseStatmRSSkB(statm);
|
||||
EXPECT_GT(result, 0);
|
||||
}
|
||||
|
||||
// Test with tabs
|
||||
{
|
||||
std::string statm = "12345\t2000\t1234\t0\t0\t3456\t0";
|
||||
long result = parseStatmRSSkB(statm);
|
||||
EXPECT_GT(result, 0);
|
||||
}
|
||||
|
||||
// Test zero resident pages
|
||||
{
|
||||
std::string statm = "25365 0 2377 0 0 5623 0";
|
||||
long result = parseStatmRSSkB(statm);
|
||||
EXPECT_EQ(result, 0);
|
||||
}
|
||||
|
||||
// Test with extra whitespace
|
||||
{
|
||||
std::string statm = " 25365 1000 2377 ";
|
||||
long result = parseStatmRSSkB(statm);
|
||||
EXPECT_GT(result, 0);
|
||||
}
|
||||
|
||||
// Test empty string
|
||||
{
|
||||
std::string statm = "";
|
||||
long result = parseStatmRSSkB(statm);
|
||||
EXPECT_EQ(result, -1);
|
||||
}
|
||||
|
||||
// Test malformed data (only one field)
|
||||
{
|
||||
std::string statm = "25365";
|
||||
long result = parseStatmRSSkB(statm);
|
||||
EXPECT_EQ(result, -1);
|
||||
}
|
||||
|
||||
// Test malformed data (non-numeric)
|
||||
{
|
||||
std::string statm = "abc def ghi";
|
||||
long result = parseStatmRSSkB(statm);
|
||||
EXPECT_EQ(result, -1);
|
||||
}
|
||||
|
||||
// Test malformed data (second field non-numeric)
|
||||
{
|
||||
std::string statm = "25365 abc 2377";
|
||||
long result = parseStatmRSSkB(statm);
|
||||
EXPECT_EQ(result, -1);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(mallocTrim, without_debug_logging)
|
||||
{
|
||||
beast::Journal journal{beast::Journal::getNullSink()};
|
||||
|
||||
MallocTrimReport report = mallocTrim("without_debug", journal);
|
||||
|
||||
#if defined(__GLIBC__) && BOOST_OS_LINUX
|
||||
EXPECT_EQ(report.supported, true);
|
||||
EXPECT_GE(report.trimResult, 0);
|
||||
EXPECT_EQ(report.durationUs, std::chrono::microseconds{-1});
|
||||
EXPECT_EQ(report.minfltDelta, -1);
|
||||
EXPECT_EQ(report.majfltDelta, -1);
|
||||
#else
|
||||
EXPECT_EQ(report.supported, false);
|
||||
EXPECT_EQ(report.trimResult, -1);
|
||||
EXPECT_EQ(report.rssBeforeKB, -1);
|
||||
EXPECT_EQ(report.rssAfterKB, -1);
|
||||
EXPECT_EQ(report.durationUs, std::chrono::microseconds{-1});
|
||||
EXPECT_EQ(report.minfltDelta, -1);
|
||||
EXPECT_EQ(report.majfltDelta, -1);
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(mallocTrim, empty_tag)
|
||||
{
|
||||
beast::Journal journal{beast::Journal::getNullSink()};
|
||||
MallocTrimReport report = mallocTrim("", journal);
|
||||
|
||||
#if defined(__GLIBC__) && BOOST_OS_LINUX
|
||||
EXPECT_EQ(report.supported, true);
|
||||
EXPECT_GE(report.trimResult, 0);
|
||||
#else
|
||||
EXPECT_EQ(report.supported, false);
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(mallocTrim, with_debug_logging)
|
||||
{
|
||||
struct DebugSink : public beast::Journal::Sink
|
||||
{
|
||||
DebugSink() : Sink(beast::severities::kDebug, false)
|
||||
{
|
||||
}
|
||||
void
|
||||
write(beast::severities::Severity, std::string const&) override
|
||||
{
|
||||
}
|
||||
void
|
||||
writeAlways(beast::severities::Severity, std::string const&) override
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
DebugSink sink;
|
||||
beast::Journal journal{sink};
|
||||
|
||||
MallocTrimReport report = mallocTrim("debug_test", journal);
|
||||
|
||||
#if defined(__GLIBC__) && BOOST_OS_LINUX
|
||||
EXPECT_EQ(report.supported, true);
|
||||
EXPECT_GE(report.trimResult, 0);
|
||||
EXPECT_GE(report.durationUs.count(), 0);
|
||||
EXPECT_GE(report.minfltDelta, 0);
|
||||
EXPECT_GE(report.majfltDelta, 0);
|
||||
#else
|
||||
EXPECT_EQ(report.supported, false);
|
||||
EXPECT_EQ(report.trimResult, -1);
|
||||
EXPECT_EQ(report.durationUs, std::chrono::microseconds{-1});
|
||||
EXPECT_EQ(report.minfltDelta, -1);
|
||||
EXPECT_EQ(report.majfltDelta, -1);
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(mallocTrim, repeated_calls)
|
||||
{
|
||||
beast::Journal journal{beast::Journal::getNullSink()};
|
||||
|
||||
// Call malloc_trim multiple times to ensure it's safe
|
||||
for (int i = 0; i < 5; ++i)
|
||||
{
|
||||
MallocTrimReport report = mallocTrim("iteration_" + std::to_string(i), journal);
|
||||
|
||||
#if defined(__GLIBC__) && BOOST_OS_LINUX
|
||||
EXPECT_EQ(report.supported, true);
|
||||
EXPECT_GE(report.trimResult, 0);
|
||||
#else
|
||||
EXPECT_EQ(report.supported, false);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
111
src/tests/libxrpl/telemetry/TelemetryConfig.cpp
Normal file
111
src/tests/libxrpl/telemetry/TelemetryConfig.cpp
Normal file
@@ -0,0 +1,111 @@
|
||||
#include <xrpl/basics/BasicConfig.h>
|
||||
#include <xrpl/telemetry/Telemetry.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <chrono>
|
||||
|
||||
using namespace xrpl;
|
||||
|
||||
TEST(TelemetryConfig, setup_defaults)
|
||||
{
|
||||
telemetry::Telemetry::Setup s;
|
||||
EXPECT_FALSE(s.enabled);
|
||||
EXPECT_EQ(s.serviceName, "rippled");
|
||||
EXPECT_TRUE(s.serviceVersion.empty());
|
||||
EXPECT_TRUE(s.serviceInstanceId.empty());
|
||||
EXPECT_EQ(s.exporterType, "otlp_http");
|
||||
EXPECT_EQ(s.exporterEndpoint, "http://localhost:4318/v1/traces");
|
||||
EXPECT_FALSE(s.useTls);
|
||||
EXPECT_TRUE(s.tlsCertPath.empty());
|
||||
EXPECT_DOUBLE_EQ(s.samplingRatio, 1.0);
|
||||
EXPECT_EQ(s.batchSize, 512u);
|
||||
EXPECT_EQ(s.batchDelay, std::chrono::milliseconds{5000});
|
||||
EXPECT_EQ(s.maxQueueSize, 2048u);
|
||||
EXPECT_EQ(s.networkId, 0u);
|
||||
EXPECT_EQ(s.networkType, "mainnet");
|
||||
EXPECT_TRUE(s.traceTransactions);
|
||||
EXPECT_TRUE(s.traceConsensus);
|
||||
EXPECT_TRUE(s.traceRpc);
|
||||
EXPECT_FALSE(s.tracePeer);
|
||||
EXPECT_TRUE(s.traceLedger);
|
||||
}
|
||||
|
||||
TEST(TelemetryConfig, parse_empty_section)
|
||||
{
|
||||
Section section;
|
||||
auto setup = telemetry::setup_Telemetry(section, "nHUtest123", "2.0.0");
|
||||
|
||||
EXPECT_FALSE(setup.enabled);
|
||||
EXPECT_EQ(setup.serviceName, "rippled");
|
||||
EXPECT_EQ(setup.serviceVersion, "2.0.0");
|
||||
EXPECT_EQ(setup.serviceInstanceId, "nHUtest123");
|
||||
EXPECT_EQ(setup.exporterType, "otlp_http");
|
||||
EXPECT_DOUBLE_EQ(setup.samplingRatio, 1.0);
|
||||
EXPECT_TRUE(setup.traceRpc);
|
||||
EXPECT_TRUE(setup.traceTransactions);
|
||||
EXPECT_TRUE(setup.traceConsensus);
|
||||
EXPECT_FALSE(setup.tracePeer);
|
||||
EXPECT_TRUE(setup.traceLedger);
|
||||
}
|
||||
|
||||
TEST(TelemetryConfig, parse_full_section)
|
||||
{
|
||||
Section section;
|
||||
section.set("enabled", "1");
|
||||
section.set("service_name", "my-rippled");
|
||||
section.set("service_instance_id", "custom-id");
|
||||
section.set("exporter", "otlp_http");
|
||||
section.set("endpoint", "http://collector:4318/v1/traces");
|
||||
section.set("use_tls", "1");
|
||||
section.set("tls_ca_cert", "/etc/ssl/ca.pem");
|
||||
section.set("sampling_ratio", "0.5");
|
||||
section.set("batch_size", "256");
|
||||
section.set("batch_delay_ms", "3000");
|
||||
section.set("max_queue_size", "4096");
|
||||
section.set("trace_transactions", "0");
|
||||
section.set("trace_consensus", "0");
|
||||
section.set("trace_rpc", "1");
|
||||
section.set("trace_peer", "1");
|
||||
section.set("trace_ledger", "0");
|
||||
|
||||
auto setup = telemetry::setup_Telemetry(section, "nHUtest123", "2.0.0");
|
||||
|
||||
EXPECT_TRUE(setup.enabled);
|
||||
EXPECT_EQ(setup.serviceName, "my-rippled");
|
||||
EXPECT_EQ(setup.serviceInstanceId, "custom-id");
|
||||
EXPECT_EQ(setup.exporterType, "otlp_http");
|
||||
EXPECT_EQ(setup.exporterEndpoint, "http://collector:4318/v1/traces");
|
||||
EXPECT_TRUE(setup.useTls);
|
||||
EXPECT_EQ(setup.tlsCertPath, "/etc/ssl/ca.pem");
|
||||
EXPECT_DOUBLE_EQ(setup.samplingRatio, 0.5);
|
||||
EXPECT_EQ(setup.batchSize, 256u);
|
||||
EXPECT_EQ(setup.batchDelay, std::chrono::milliseconds{3000});
|
||||
EXPECT_EQ(setup.maxQueueSize, 4096u);
|
||||
EXPECT_FALSE(setup.traceTransactions);
|
||||
EXPECT_FALSE(setup.traceConsensus);
|
||||
EXPECT_TRUE(setup.traceRpc);
|
||||
EXPECT_TRUE(setup.tracePeer);
|
||||
EXPECT_FALSE(setup.traceLedger);
|
||||
}
|
||||
|
||||
TEST(TelemetryConfig, null_telemetry_factory)
|
||||
{
|
||||
telemetry::Telemetry::Setup setup;
|
||||
setup.enabled = false;
|
||||
|
||||
beast::Journal::Sink& sink = beast::Journal::getNullSink();
|
||||
beast::Journal j(sink);
|
||||
auto tel = telemetry::make_Telemetry(setup, j);
|
||||
EXPECT_TRUE(tel != nullptr);
|
||||
EXPECT_FALSE(tel->isEnabled());
|
||||
EXPECT_FALSE(tel->shouldTraceRpc());
|
||||
EXPECT_FALSE(tel->shouldTraceTransactions());
|
||||
EXPECT_FALSE(tel->shouldTraceConsensus());
|
||||
EXPECT_FALSE(tel->shouldTracePeer());
|
||||
EXPECT_FALSE(tel->shouldTraceLedger());
|
||||
|
||||
// start/stop should be no-ops without crashing
|
||||
tel->start();
|
||||
tel->stop();
|
||||
}
|
||||
141
src/tests/libxrpl/telemetry/TracingMacros.cpp
Normal file
141
src/tests/libxrpl/telemetry/TracingMacros.cpp
Normal file
@@ -0,0 +1,141 @@
|
||||
#include <xrpld/telemetry/TracingInstrumentation.h>
|
||||
|
||||
#include <xrpl/telemetry/Telemetry.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
using namespace xrpl;
|
||||
|
||||
TEST(TracingMacros, macros_with_null_telemetry)
|
||||
{
|
||||
telemetry::Telemetry::Setup setup;
|
||||
setup.enabled = false;
|
||||
beast::Journal::Sink& sink = beast::Journal::getNullSink();
|
||||
beast::Journal j(sink);
|
||||
auto tel = telemetry::make_Telemetry(setup, j);
|
||||
tel->start();
|
||||
|
||||
// Each macro should compile and execute without crashing.
|
||||
{
|
||||
XRPL_TRACE_RPC(*tel, "rpc.test.command");
|
||||
XRPL_TRACE_SET_ATTR("xrpl.rpc.command", "test");
|
||||
XRPL_TRACE_SET_ATTR("xrpl.rpc.status", "success");
|
||||
}
|
||||
{
|
||||
XRPL_TRACE_TX(*tel, "tx.test.process");
|
||||
XRPL_TRACE_SET_ATTR("xrpl.tx.hash", "abc123");
|
||||
}
|
||||
{
|
||||
XRPL_TRACE_CONSENSUS(*tel, "consensus.test");
|
||||
XRPL_TRACE_SET_ATTR("xrpl.consensus.mode", "proposing");
|
||||
}
|
||||
{
|
||||
XRPL_TRACE_PEER(*tel, "peer.test");
|
||||
}
|
||||
{
|
||||
XRPL_TRACE_LEDGER(*tel, "ledger.test");
|
||||
}
|
||||
|
||||
tel->stop();
|
||||
}
|
||||
|
||||
TEST(TracingMacros, separate_scopes)
|
||||
{
|
||||
// Multiple macros in separate scopes should not collide on
|
||||
// the _xrpl_guard_ variable name.
|
||||
telemetry::Telemetry::Setup setup;
|
||||
setup.enabled = false;
|
||||
beast::Journal::Sink& sink = beast::Journal::getNullSink();
|
||||
beast::Journal j(sink);
|
||||
auto tel = telemetry::make_Telemetry(setup, j);
|
||||
|
||||
{
|
||||
XRPL_TRACE_RPC(*tel, "rpc.outer");
|
||||
}
|
||||
{
|
||||
XRPL_TRACE_TX(*tel, "tx.inner");
|
||||
}
|
||||
{
|
||||
XRPL_TRACE_CONSENSUS(*tel, "consensus.other");
|
||||
}
|
||||
}
|
||||
|
||||
TEST(TracingMacros, conditional_guards)
|
||||
{
|
||||
// NullTelemetry returns false for all shouldTrace* methods.
|
||||
// XRPL_TRACE_SET_ATTR on an empty guard must be safe.
|
||||
telemetry::Telemetry::Setup setup;
|
||||
setup.enabled = false;
|
||||
beast::Journal::Sink& sink = beast::Journal::getNullSink();
|
||||
beast::Journal j(sink);
|
||||
auto tel = telemetry::make_Telemetry(setup, j);
|
||||
|
||||
EXPECT_FALSE(tel->shouldTraceRpc());
|
||||
EXPECT_FALSE(tel->shouldTraceTransactions());
|
||||
EXPECT_FALSE(tel->shouldTraceConsensus());
|
||||
EXPECT_FALSE(tel->shouldTracePeer());
|
||||
EXPECT_FALSE(tel->shouldTraceLedger());
|
||||
|
||||
{
|
||||
XRPL_TRACE_RPC(*tel, "should.not.create");
|
||||
XRPL_TRACE_SET_ATTR("key", "value");
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef XRPL_ENABLE_TELEMETRY
|
||||
|
||||
TEST(TracingMacros, span_guard_raii)
|
||||
{
|
||||
telemetry::Telemetry::Setup setup;
|
||||
setup.enabled = false;
|
||||
beast::Journal::Sink& sink = beast::Journal::getNullSink();
|
||||
beast::Journal j(sink);
|
||||
auto tel = telemetry::make_Telemetry(setup, j);
|
||||
|
||||
auto span = tel->startSpan("test.guard");
|
||||
{
|
||||
telemetry::SpanGuard guard(span);
|
||||
guard.setAttribute("key", "value");
|
||||
guard.addEvent("test_event");
|
||||
guard.setOk();
|
||||
}
|
||||
}
|
||||
|
||||
TEST(TracingMacros, span_guard_move)
|
||||
{
|
||||
telemetry::Telemetry::Setup setup;
|
||||
setup.enabled = false;
|
||||
beast::Journal::Sink& sink = beast::Journal::getNullSink();
|
||||
beast::Journal j(sink);
|
||||
auto tel = telemetry::make_Telemetry(setup, j);
|
||||
|
||||
auto span = tel->startSpan("test.move");
|
||||
std::optional<telemetry::SpanGuard> opt;
|
||||
opt.emplace(span);
|
||||
EXPECT_TRUE(opt.has_value());
|
||||
opt.reset();
|
||||
}
|
||||
|
||||
TEST(TracingMacros, span_guard_exception)
|
||||
{
|
||||
telemetry::Telemetry::Setup setup;
|
||||
setup.enabled = false;
|
||||
beast::Journal::Sink& sink = beast::Journal::getNullSink();
|
||||
beast::Journal j(sink);
|
||||
auto tel = telemetry::make_Telemetry(setup, j);
|
||||
|
||||
auto span = tel->startSpan("test.exception");
|
||||
{
|
||||
telemetry::SpanGuard guard(span);
|
||||
try
|
||||
{
|
||||
throw std::runtime_error("test error");
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
guard.recordException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // XRPL_ENABLE_TELEMETRY
|
||||
8
src/tests/libxrpl/telemetry/main.cpp
Normal file
8
src/tests/libxrpl/telemetry/main.cpp
Normal file
@@ -0,0 +1,8 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
int
|
||||
main(int argc, char** argv)
|
||||
{
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
Reference in New Issue
Block a user