From b8705ae0863f69aaf5a36675ce5f8ef9b57b2db0 Mon Sep 17 00:00:00 2001 From: Alex Kremer Date: Fri, 14 Jul 2023 16:46:10 +0100 Subject: [PATCH] Add time/uptime/amendment_blocked to server_info (#775) --- src/etl/ETLService.cpp | 14 ++--- src/etl/ETLService.h | 20 +++++++ src/etl/SystemState.h | 5 ++ src/etl/impl/Transformer.h | 10 +++- src/rpc/Counters.cpp | 6 ++ src/rpc/Counters.h | 6 +- src/rpc/handlers/ServerInfo.h | 15 +++++ unittests/rpc/handlers/ServerInfoTest.cpp | 73 ++++++++++++++++++++++- unittests/util/MockCounters.h | 1 + unittests/util/MockETLService.h | 1 + 10 files changed, 139 insertions(+), 12 deletions(-) diff --git a/src/etl/ETLService.cpp b/src/etl/ETLService.cpp index b1826c82..11c06d46 100644 --- a/src/etl/ETLService.cpp +++ b/src/etl/ETLService.cpp @@ -79,8 +79,7 @@ ETLService::monitor() auto rng = backend_->hardFetchLedgerRangeNoThrow(); if (!rng) { - log_.info() << "Database is empty. Will download a ledger " - "from the network."; + log_.info() << "Database is empty. Will download a ledger from the network."; std::optional ledger; try @@ -98,23 +97,22 @@ ETLService::monitor() if (mostRecentValidated) { - log_.info() << "Ledger " << *mostRecentValidated << " has been validated. " - << "Downloading..."; + log_.info() << "Ledger " << *mostRecentValidated << " has been validated. Downloading..."; ledger = ledgerLoader_.loadInitialLedger(*mostRecentValidated); } else { - log_.info() << "The wait for the next validated " - << "ledger has been aborted. " - << "Exiting monitor loop"; + log_.info() << "The wait for the next validated ledger has been aborted. Exiting monitor loop"; return; } } } catch (std::runtime_error const& e) { + setAmendmentBlocked(); + log_.fatal() - << "Failed to load initial ledger, Exiting monitor loop : " << e.what() + << "Failed to load initial ledger, Exiting monitor loop: " << e.what() << " Possible cause: The ETL node is not compatible with the version of the rippled lib Clio is using."; return; } diff --git a/src/etl/ETLService.h b/src/etl/ETLService.h index 11fc44b6..75703143 100644 --- a/src/etl/ETLService.h +++ b/src/etl/ETLService.h @@ -152,6 +152,17 @@ public: return ledgerPublisher_.lastCloseAgeSeconds(); } + /** + * @brief Check for the amendment blocked state. + * + * @return true if currently amendment blocked; false otherwise + */ + bool + isAmendmentBlocked() const + { + return state_.isAmendmentBlocked; + } + /** * @brief Get state of ETL as a JSON object */ @@ -236,4 +247,13 @@ private: */ void doWork(); + + /** + * @brief Sets amendment blocked flag + */ + void + setAmendmentBlocked() + { + state_.isAmendmentBlocked = true; + } }; diff --git a/src/etl/SystemState.h b/src/etl/SystemState.h index f9ac6b22..35fa8928 100644 --- a/src/etl/SystemState.h +++ b/src/etl/SystemState.h @@ -47,4 +47,9 @@ struct SystemState * @brief Whether a write conflict was detected */ std::atomic_bool writeConflict = false; + + /** + * @brief Whether we detected an amendment block + */ + std::atomic_bool isAmendmentBlocked = false; }; diff --git a/src/etl/impl/Transformer.h b/src/etl/impl/Transformer.h index 4c803cb1..1c04ba63 100644 --- a/src/etl/impl/Transformer.h +++ b/src/etl/impl/Transformer.h @@ -186,8 +186,10 @@ private: } catch (std::runtime_error const& e) { + setAmendmentBlocked(); + log_.fatal() - << "Failed to build next ledger : " << e.what() + << "Failed to build next ledger: " << e.what() << " Possible cause: The ETL node is not compatible with the version of the rippled lib Clio is using."; return {ripple::LedgerInfo{}, false}; } @@ -414,6 +416,12 @@ private: { state_.get().writeConflict = conflict; } + + void + setAmendmentBlocked() + { + state_.get().isAmendmentBlocked = true; + } }; } // namespace clio::detail diff --git a/src/rpc/Counters.cpp b/src/rpc/Counters.cpp index 2d65f82c..03889bb9 100644 --- a/src/rpc/Counters.cpp +++ b/src/rpc/Counters.cpp @@ -97,6 +97,12 @@ Counters::onInternalError() ++internalErrorCounter_; } +std::chrono::seconds +Counters::uptime() const +{ + return std::chrono::duration_cast(std::chrono::system_clock::now() - startupTime_); +} + boost::json::object Counters::report() const { diff --git a/src/rpc/Counters.h b/src/rpc/Counters.h index 60ddae15..c9cbf4cd 100644 --- a/src/rpc/Counters.h +++ b/src/rpc/Counters.h @@ -54,9 +54,10 @@ class Counters std::atomic_uint64_t internalErrorCounter_; std::reference_wrapper workQueue_; + std::chrono::time_point startupTime_; public: - Counters(WorkQueue const& wq) : workQueue_(std::cref(wq)){}; + Counters(WorkQueue const& wq) : workQueue_(std::cref(wq)), startupTime_{std::chrono::system_clock::now()} {}; static Counters make_Counters(WorkQueue const& wq) @@ -94,6 +95,9 @@ public: void onInternalError(); + std::chrono::seconds + uptime() const; + boost::json::object report() const; }; diff --git a/src/rpc/handlers/ServerInfo.h b/src/rpc/handlers/ServerInfo.h index f63497b6..504375a1 100644 --- a/src/rpc/handlers/ServerInfo.h +++ b/src/rpc/handlers/ServerInfo.h @@ -27,6 +27,9 @@ #include #include +#include + +#include #include class SubscriptionManager; @@ -78,10 +81,13 @@ public: std::optional adminSection = std::nullopt; std::string completeLedgers = {}; uint32_t loadFactor = 1u; + std::chrono::time_point time = std::chrono::system_clock::now(); + std::chrono::seconds uptime = {}; std::string clioVersion = Build::getClioVersionString(); std::optional rippledInfo = std::nullopt; ValidatedLedgerSection validatedLedger = {}; CacheSection cache = {}; + bool isAmendmentBlocked = false; }; struct Output @@ -155,6 +161,8 @@ public: output.info.cache.latestLedgerSeq = backend_->cache().latestLedgerSequence(); output.info.cache.objectHitRate = backend_->cache().getObjectHitRate(); output.info.cache.successorHitRate = backend_->cache().getSuccessorHitRate(); + output.info.uptime = counters_.get().uptime(); + output.info.isAmendmentBlocked = etl_->isAmendmentBlocked(); return output; } @@ -172,14 +180,21 @@ private: friend void tag_invoke(boost::json::value_from_tag, boost::json::value& jv, InfoSection const& info) { + using ripple::to_string; + jv = { {JS(complete_ledgers), info.completeLedgers}, {JS(load_factor), info.loadFactor}, + {JS(time), to_string(std::chrono::floor(info.time))}, + {JS(uptime), info.uptime.count()}, {"clio_version", info.clioVersion}, {JS(validated_ledger), info.validatedLedger}, {"cache", info.cache}, }; + if (info.isAmendmentBlocked) + jv.as_object()[JS(amendment_blocked)] = true; + if (info.rippledInfo) { try diff --git a/unittests/rpc/handlers/ServerInfoTest.cpp b/unittests/rpc/handlers/ServerInfoTest.cpp index 65e8a1eb..b055bf42 100644 --- a/unittests/rpc/handlers/ServerInfoTest.cpp +++ b/unittests/rpc/handlers/ServerInfoTest.cpp @@ -72,6 +72,8 @@ protected: EXPECT_TRUE(info.contains("load_factor")); EXPECT_TRUE(info.contains("clio_version")); EXPECT_TRUE(info.contains("validated_ledger")); + EXPECT_TRUE(info.contains("time")); + EXPECT_TRUE(info.contains("uptime")); auto const& validated = info.at("validated_ledger").as_object(); EXPECT_TRUE(validated.contains("age")); @@ -188,6 +190,8 @@ TEST_F(RPCServerInfoHandlerTest, DefaultOutputIsPresent) { MockBackend* rawBackendPtr = static_cast(mockBackendPtr.get()); MockLoadBalancer* rawBalancerPtr = static_cast(mockLoadBalancerPtr.get()); + MockCounters* rawCountersPtr = static_cast(mockCountersPtr.get()); + MockETLService* rawETLServicePtr = static_cast(mockETLServicePtr.get()); mockBackendPtr->updateRange(10); // min mockBackendPtr->updateRange(30); // max @@ -203,6 +207,12 @@ TEST_F(RPCServerInfoHandlerTest, DefaultOutputIsPresent) ON_CALL(*rawBalancerPtr, forwardToRippled).WillByDefault(Return(std::nullopt)); EXPECT_CALL(*rawBalancerPtr, forwardToRippled(testing::_, testing::Eq(CLIENTIP), testing::_)).Times(1); + ON_CALL(*rawCountersPtr, uptime).WillByDefault(Return(std::chrono::seconds{1234})); + EXPECT_CALL(*rawCountersPtr, uptime).Times(1); + + ON_CALL(*rawETLServicePtr, isAmendmentBlocked).WillByDefault(Return(false)); + EXPECT_CALL(*rawETLServicePtr, isAmendmentBlocked).Times(1); + auto const handler = AnyHandler{TestServerInfoHandler{ mockBackendPtr, mockSubscriptionManagerPtr, mockLoadBalancerPtr, mockETLServicePtr, *mockCountersPtr}}; @@ -220,6 +230,48 @@ TEST_F(RPCServerInfoHandlerTest, DefaultOutputIsPresent) }); } +TEST_F(RPCServerInfoHandlerTest, AmendmentBlockedIsPresentIfSet) +{ + MockBackend* rawBackendPtr = static_cast(mockBackendPtr.get()); + MockLoadBalancer* rawBalancerPtr = static_cast(mockLoadBalancerPtr.get()); + MockCounters* rawCountersPtr = static_cast(mockCountersPtr.get()); + MockETLService* rawETLServicePtr = static_cast(mockETLServicePtr.get()); + + mockBackendPtr->updateRange(10); // min + mockBackendPtr->updateRange(30); // max + + auto const ledgerinfo = CreateLedgerInfo(LEDGERHASH, 30, 3); // 3 seconds old + ON_CALL(*rawBackendPtr, fetchLedgerBySequence).WillByDefault(Return(ledgerinfo)); + EXPECT_CALL(*rawBackendPtr, fetchLedgerBySequence).Times(1); + + auto const feeBlob = CreateFeeSettingBlob(1, 2, 3, 4, 0); + ON_CALL(*rawBackendPtr, doFetchLedgerObject).WillByDefault(Return(feeBlob)); + EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(1); + + ON_CALL(*rawBalancerPtr, forwardToRippled).WillByDefault(Return(std::nullopt)); + EXPECT_CALL(*rawBalancerPtr, forwardToRippled(testing::_, testing::Eq(CLIENTIP), testing::_)).Times(1); + + ON_CALL(*rawCountersPtr, uptime).WillByDefault(Return(std::chrono::seconds{1234})); + EXPECT_CALL(*rawCountersPtr, uptime).Times(1); + + ON_CALL(*rawETLServicePtr, isAmendmentBlocked).WillByDefault(Return(true)); + EXPECT_CALL(*rawETLServicePtr, isAmendmentBlocked).Times(1); + + auto const handler = AnyHandler{TestServerInfoHandler{ + mockBackendPtr, mockSubscriptionManagerPtr, mockLoadBalancerPtr, mockETLServicePtr, *mockCountersPtr}}; + + runSpawn([&](auto& yield) { + auto const req = json::parse("{}"); + auto const output = handler.process(req, Context{std::ref(yield), {}, false, CLIENTIP}); + + validateNormalOutput(output); + + auto const& info = output.value().as_object().at("info").as_object(); + EXPECT_TRUE(info.contains("amendment_blocked")); + EXPECT_EQ(info.at("amendment_blocked").as_bool(), true); + }); +} + TEST_F(RPCServerInfoHandlerTest, AdminSectionPresentWhenAdminFlagIsSet) { MockBackend* rawBackendPtr = static_cast(mockBackendPtr.get()); @@ -244,6 +296,12 @@ TEST_F(RPCServerInfoHandlerTest, AdminSectionPresentWhenAdminFlagIsSet) ON_CALL(*rawBalancerPtr, forwardToRippled).WillByDefault(Return(empty)); EXPECT_CALL(*rawBalancerPtr, forwardToRippled).Times(1); + ON_CALL(*rawCountersPtr, uptime).WillByDefault(Return(std::chrono::seconds{1234})); + EXPECT_CALL(*rawCountersPtr, uptime).Times(1); + + ON_CALL(*rawETLServicePtr, isAmendmentBlocked).WillByDefault(Return(false)); + EXPECT_CALL(*rawETLServicePtr, isAmendmentBlocked).Times(1); + // admin calls ON_CALL(*rawCountersPtr, report).WillByDefault(Return(empty)); EXPECT_CALL(*rawCountersPtr, report).Times(1); @@ -287,6 +345,12 @@ TEST_F(RPCServerInfoHandlerTest, RippledForwardedValuesPresent) ON_CALL(*rawBackendPtr, doFetchLedgerObject).WillByDefault(Return(feeBlob)); EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(1); + ON_CALL(*rawCountersPtr, uptime).WillByDefault(Return(std::chrono::seconds{1234})); + EXPECT_CALL(*rawCountersPtr, uptime).Times(1); + + ON_CALL(*rawETLServicePtr, isAmendmentBlocked).WillByDefault(Return(false)); + EXPECT_CALL(*rawETLServicePtr, isAmendmentBlocked).Times(1); + auto const rippledObj = boost::json::parse(R"({ "result": { "info": { @@ -344,10 +408,15 @@ TEST_F(RPCServerInfoHandlerTest, RippledForwardedValuesMissingNoExceptionThrown) ON_CALL(*rawBackendPtr, doFetchLedgerObject).WillByDefault(Return(feeBlob)); EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(1); + ON_CALL(*rawCountersPtr, uptime).WillByDefault(Return(std::chrono::seconds{1234})); + EXPECT_CALL(*rawCountersPtr, uptime).Times(1); + + ON_CALL(*rawETLServicePtr, isAmendmentBlocked).WillByDefault(Return(false)); + EXPECT_CALL(*rawETLServicePtr, isAmendmentBlocked).Times(1); + auto const rippledObj = boost::json::parse(R"({ "result": { - "info": { - } + "info": {} } })"); ON_CALL(*rawBalancerPtr, forwardToRippled).WillByDefault(Return(rippledObj.as_object())); diff --git a/unittests/util/MockCounters.h b/unittests/util/MockCounters.h index defbc18a..33ec4696 100644 --- a/unittests/util/MockCounters.h +++ b/unittests/util/MockCounters.h @@ -37,4 +37,5 @@ struct MockCounters MOCK_METHOD(void, onUnknownCommand, (), ()); MOCK_METHOD(void, onInternalError, (), ()); MOCK_METHOD(boost::json::object, report, (), (const)); + MOCK_METHOD(std::chrono::seconds, uptime, (), (const)); }; diff --git a/unittests/util/MockETLService.h b/unittests/util/MockETLService.h index 4ddc795a..1636215c 100644 --- a/unittests/util/MockETLService.h +++ b/unittests/util/MockETLService.h @@ -30,4 +30,5 @@ struct MockETLService MOCK_METHOD(std::chrono::time_point, getLastPublish, (), (const)); MOCK_METHOD(std::uint32_t, lastPublishAgeSeconds, (), (const)); MOCK_METHOD(std::uint32_t, lastCloseAgeSeconds, (), (const)); + MOCK_METHOD(bool, isAmendmentBlocked, (), (const)); };