Add time/uptime/amendment_blocked to server_info (#775)

This commit is contained in:
Alex Kremer
2023-07-14 16:46:10 +01:00
committed by GitHub
parent b83d7478ef
commit b8705ae086
10 changed files with 139 additions and 12 deletions

View File

@@ -79,8 +79,7 @@ ETLService::monitor()
auto rng = backend_->hardFetchLedgerRangeNoThrow(); auto rng = backend_->hardFetchLedgerRangeNoThrow();
if (!rng) if (!rng)
{ {
log_.info() << "Database is empty. Will download a ledger " log_.info() << "Database is empty. Will download a ledger from the network.";
"from the network.";
std::optional<ripple::LedgerInfo> ledger; std::optional<ripple::LedgerInfo> ledger;
try try
@@ -98,23 +97,22 @@ ETLService::monitor()
if (mostRecentValidated) if (mostRecentValidated)
{ {
log_.info() << "Ledger " << *mostRecentValidated << " has been validated. " log_.info() << "Ledger " << *mostRecentValidated << " has been validated. Downloading...";
<< "Downloading...";
ledger = ledgerLoader_.loadInitialLedger(*mostRecentValidated); ledger = ledgerLoader_.loadInitialLedger(*mostRecentValidated);
} }
else else
{ {
log_.info() << "The wait for the next validated " log_.info() << "The wait for the next validated ledger has been aborted. Exiting monitor loop";
<< "ledger has been aborted. "
<< "Exiting monitor loop";
return; return;
} }
} }
} }
catch (std::runtime_error const& e) catch (std::runtime_error const& e)
{ {
setAmendmentBlocked();
log_.fatal() 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."; << " Possible cause: The ETL node is not compatible with the version of the rippled lib Clio is using.";
return; return;
} }

View File

@@ -152,6 +152,17 @@ public:
return ledgerPublisher_.lastCloseAgeSeconds(); 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 * @brief Get state of ETL as a JSON object
*/ */
@@ -236,4 +247,13 @@ private:
*/ */
void void
doWork(); doWork();
/**
* @brief Sets amendment blocked flag
*/
void
setAmendmentBlocked()
{
state_.isAmendmentBlocked = true;
}
}; };

View File

@@ -47,4 +47,9 @@ struct SystemState
* @brief Whether a write conflict was detected * @brief Whether a write conflict was detected
*/ */
std::atomic_bool writeConflict = false; std::atomic_bool writeConflict = false;
/**
* @brief Whether we detected an amendment block
*/
std::atomic_bool isAmendmentBlocked = false;
}; };

View File

@@ -186,8 +186,10 @@ private:
} }
catch (std::runtime_error const& e) catch (std::runtime_error const& e)
{ {
setAmendmentBlocked();
log_.fatal() 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."; << " Possible cause: The ETL node is not compatible with the version of the rippled lib Clio is using.";
return {ripple::LedgerInfo{}, false}; return {ripple::LedgerInfo{}, false};
} }
@@ -414,6 +416,12 @@ private:
{ {
state_.get().writeConflict = conflict; state_.get().writeConflict = conflict;
} }
void
setAmendmentBlocked()
{
state_.get().isAmendmentBlocked = true;
}
}; };
} // namespace clio::detail } // namespace clio::detail

View File

@@ -97,6 +97,12 @@ Counters::onInternalError()
++internalErrorCounter_; ++internalErrorCounter_;
} }
std::chrono::seconds
Counters::uptime() const
{
return std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now() - startupTime_);
}
boost::json::object boost::json::object
Counters::report() const Counters::report() const
{ {

View File

@@ -54,9 +54,10 @@ class Counters
std::atomic_uint64_t internalErrorCounter_; std::atomic_uint64_t internalErrorCounter_;
std::reference_wrapper<const WorkQueue> workQueue_; std::reference_wrapper<const WorkQueue> workQueue_;
std::chrono::time_point<std::chrono::system_clock> startupTime_;
public: 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 static Counters
make_Counters(WorkQueue const& wq) make_Counters(WorkQueue const& wq)
@@ -94,6 +95,9 @@ public:
void void
onInternalError(); onInternalError();
std::chrono::seconds
uptime() const;
boost::json::object boost::json::object
report() const; report() const;
}; };

View File

@@ -27,6 +27,9 @@
#include <rpc/common/Types.h> #include <rpc/common/Types.h>
#include <rpc/common/Validators.h> #include <rpc/common/Validators.h>
#include <ripple/basics/chrono.h>
#include <chrono>
#include <fmt/core.h> #include <fmt/core.h>
class SubscriptionManager; class SubscriptionManager;
@@ -78,10 +81,13 @@ public:
std::optional<AdminSection> adminSection = std::nullopt; std::optional<AdminSection> adminSection = std::nullopt;
std::string completeLedgers = {}; std::string completeLedgers = {};
uint32_t loadFactor = 1u; uint32_t loadFactor = 1u;
std::chrono::time_point<std::chrono::system_clock> time = std::chrono::system_clock::now();
std::chrono::seconds uptime = {};
std::string clioVersion = Build::getClioVersionString(); std::string clioVersion = Build::getClioVersionString();
std::optional<boost::json::object> rippledInfo = std::nullopt; std::optional<boost::json::object> rippledInfo = std::nullopt;
ValidatedLedgerSection validatedLedger = {}; ValidatedLedgerSection validatedLedger = {};
CacheSection cache = {}; CacheSection cache = {};
bool isAmendmentBlocked = false;
}; };
struct Output struct Output
@@ -155,6 +161,8 @@ public:
output.info.cache.latestLedgerSeq = backend_->cache().latestLedgerSequence(); output.info.cache.latestLedgerSeq = backend_->cache().latestLedgerSequence();
output.info.cache.objectHitRate = backend_->cache().getObjectHitRate(); output.info.cache.objectHitRate = backend_->cache().getObjectHitRate();
output.info.cache.successorHitRate = backend_->cache().getSuccessorHitRate(); output.info.cache.successorHitRate = backend_->cache().getSuccessorHitRate();
output.info.uptime = counters_.get().uptime();
output.info.isAmendmentBlocked = etl_->isAmendmentBlocked();
return output; return output;
} }
@@ -172,14 +180,21 @@ private:
friend void friend void
tag_invoke(boost::json::value_from_tag, boost::json::value& jv, InfoSection const& info) tag_invoke(boost::json::value_from_tag, boost::json::value& jv, InfoSection const& info)
{ {
using ripple::to_string;
jv = { jv = {
{JS(complete_ledgers), info.completeLedgers}, {JS(complete_ledgers), info.completeLedgers},
{JS(load_factor), info.loadFactor}, {JS(load_factor), info.loadFactor},
{JS(time), to_string(std::chrono::floor<std::chrono::microseconds>(info.time))},
{JS(uptime), info.uptime.count()},
{"clio_version", info.clioVersion}, {"clio_version", info.clioVersion},
{JS(validated_ledger), info.validatedLedger}, {JS(validated_ledger), info.validatedLedger},
{"cache", info.cache}, {"cache", info.cache},
}; };
if (info.isAmendmentBlocked)
jv.as_object()[JS(amendment_blocked)] = true;
if (info.rippledInfo) if (info.rippledInfo)
{ {
try try

View File

@@ -72,6 +72,8 @@ protected:
EXPECT_TRUE(info.contains("load_factor")); EXPECT_TRUE(info.contains("load_factor"));
EXPECT_TRUE(info.contains("clio_version")); EXPECT_TRUE(info.contains("clio_version"));
EXPECT_TRUE(info.contains("validated_ledger")); 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(); auto const& validated = info.at("validated_ledger").as_object();
EXPECT_TRUE(validated.contains("age")); EXPECT_TRUE(validated.contains("age"));
@@ -188,6 +190,8 @@ TEST_F(RPCServerInfoHandlerTest, DefaultOutputIsPresent)
{ {
MockBackend* rawBackendPtr = static_cast<MockBackend*>(mockBackendPtr.get()); MockBackend* rawBackendPtr = static_cast<MockBackend*>(mockBackendPtr.get());
MockLoadBalancer* rawBalancerPtr = static_cast<MockLoadBalancer*>(mockLoadBalancerPtr.get()); MockLoadBalancer* rawBalancerPtr = static_cast<MockLoadBalancer*>(mockLoadBalancerPtr.get());
MockCounters* rawCountersPtr = static_cast<MockCounters*>(mockCountersPtr.get());
MockETLService* rawETLServicePtr = static_cast<MockETLService*>(mockETLServicePtr.get());
mockBackendPtr->updateRange(10); // min mockBackendPtr->updateRange(10); // min
mockBackendPtr->updateRange(30); // max mockBackendPtr->updateRange(30); // max
@@ -203,6 +207,12 @@ TEST_F(RPCServerInfoHandlerTest, DefaultOutputIsPresent)
ON_CALL(*rawBalancerPtr, forwardToRippled).WillByDefault(Return(std::nullopt)); ON_CALL(*rawBalancerPtr, forwardToRippled).WillByDefault(Return(std::nullopt));
EXPECT_CALL(*rawBalancerPtr, forwardToRippled(testing::_, testing::Eq(CLIENTIP), testing::_)).Times(1); 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{ auto const handler = AnyHandler{TestServerInfoHandler{
mockBackendPtr, mockSubscriptionManagerPtr, mockLoadBalancerPtr, mockETLServicePtr, *mockCountersPtr}}; mockBackendPtr, mockSubscriptionManagerPtr, mockLoadBalancerPtr, mockETLServicePtr, *mockCountersPtr}};
@@ -220,6 +230,48 @@ TEST_F(RPCServerInfoHandlerTest, DefaultOutputIsPresent)
}); });
} }
TEST_F(RPCServerInfoHandlerTest, AmendmentBlockedIsPresentIfSet)
{
MockBackend* rawBackendPtr = static_cast<MockBackend*>(mockBackendPtr.get());
MockLoadBalancer* rawBalancerPtr = static_cast<MockLoadBalancer*>(mockLoadBalancerPtr.get());
MockCounters* rawCountersPtr = static_cast<MockCounters*>(mockCountersPtr.get());
MockETLService* rawETLServicePtr = static_cast<MockETLService*>(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) TEST_F(RPCServerInfoHandlerTest, AdminSectionPresentWhenAdminFlagIsSet)
{ {
MockBackend* rawBackendPtr = static_cast<MockBackend*>(mockBackendPtr.get()); MockBackend* rawBackendPtr = static_cast<MockBackend*>(mockBackendPtr.get());
@@ -244,6 +296,12 @@ TEST_F(RPCServerInfoHandlerTest, AdminSectionPresentWhenAdminFlagIsSet)
ON_CALL(*rawBalancerPtr, forwardToRippled).WillByDefault(Return(empty)); ON_CALL(*rawBalancerPtr, forwardToRippled).WillByDefault(Return(empty));
EXPECT_CALL(*rawBalancerPtr, forwardToRippled).Times(1); 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 // admin calls
ON_CALL(*rawCountersPtr, report).WillByDefault(Return(empty)); ON_CALL(*rawCountersPtr, report).WillByDefault(Return(empty));
EXPECT_CALL(*rawCountersPtr, report).Times(1); EXPECT_CALL(*rawCountersPtr, report).Times(1);
@@ -287,6 +345,12 @@ TEST_F(RPCServerInfoHandlerTest, RippledForwardedValuesPresent)
ON_CALL(*rawBackendPtr, doFetchLedgerObject).WillByDefault(Return(feeBlob)); ON_CALL(*rawBackendPtr, doFetchLedgerObject).WillByDefault(Return(feeBlob));
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(1); 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"({ auto const rippledObj = boost::json::parse(R"({
"result": { "result": {
"info": { "info": {
@@ -344,10 +408,15 @@ TEST_F(RPCServerInfoHandlerTest, RippledForwardedValuesMissingNoExceptionThrown)
ON_CALL(*rawBackendPtr, doFetchLedgerObject).WillByDefault(Return(feeBlob)); ON_CALL(*rawBackendPtr, doFetchLedgerObject).WillByDefault(Return(feeBlob));
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(1); 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"({ auto const rippledObj = boost::json::parse(R"({
"result": { "result": {
"info": { "info": {}
}
} }
})"); })");
ON_CALL(*rawBalancerPtr, forwardToRippled).WillByDefault(Return(rippledObj.as_object())); ON_CALL(*rawBalancerPtr, forwardToRippled).WillByDefault(Return(rippledObj.as_object()));

View File

@@ -37,4 +37,5 @@ struct MockCounters
MOCK_METHOD(void, onUnknownCommand, (), ()); MOCK_METHOD(void, onUnknownCommand, (), ());
MOCK_METHOD(void, onInternalError, (), ()); MOCK_METHOD(void, onInternalError, (), ());
MOCK_METHOD(boost::json::object, report, (), (const)); MOCK_METHOD(boost::json::object, report, (), (const));
MOCK_METHOD(std::chrono::seconds, uptime, (), (const));
}; };

View File

@@ -30,4 +30,5 @@ struct MockETLService
MOCK_METHOD(std::chrono::time_point<std::chrono::system_clock>, getLastPublish, (), (const)); MOCK_METHOD(std::chrono::time_point<std::chrono::system_clock>, getLastPublish, (), (const));
MOCK_METHOD(std::uint32_t, lastPublishAgeSeconds, (), (const)); MOCK_METHOD(std::uint32_t, lastPublishAgeSeconds, (), (const));
MOCK_METHOD(std::uint32_t, lastCloseAgeSeconds, (), (const)); MOCK_METHOD(std::uint32_t, lastCloseAgeSeconds, (), (const));
MOCK_METHOD(bool, isAmendmentBlocked, (), (const));
}; };