mirror of
https://github.com/XRPLF/clio.git
synced 2025-12-06 17:27:58 +00:00
@@ -37,11 +37,11 @@ class SimpleCache
|
||||
};
|
||||
|
||||
// counters for fetchLedgerObject(s) hit rate
|
||||
mutable std::atomic_uint32_t objectReqCounter_;
|
||||
mutable std::atomic_uint32_t objectHitCounter_;
|
||||
mutable std::atomic_uint32_t objectReqCounter_ = 0;
|
||||
mutable std::atomic_uint32_t objectHitCounter_ = 0;
|
||||
// counters for fetchSuccessorKey hit rate
|
||||
mutable std::atomic_uint32_t successorReqCounter_;
|
||||
mutable std::atomic_uint32_t successorHitCounter_;
|
||||
mutable std::atomic_uint32_t successorReqCounter_ = 0;
|
||||
mutable std::atomic_uint32_t successorHitCounter_ = 0;
|
||||
|
||||
std::map<ripple::uint256, CacheEntry> map_;
|
||||
mutable std::shared_mutex mtx_;
|
||||
|
||||
@@ -80,7 +80,7 @@ Counters::rpcForwarded(std::string const& method)
|
||||
}
|
||||
|
||||
boost::json::object
|
||||
Counters::report()
|
||||
Counters::report() const
|
||||
{
|
||||
std::shared_lock lk(mutex_);
|
||||
boost::json::object obj = {};
|
||||
|
||||
@@ -47,7 +47,7 @@ private:
|
||||
void
|
||||
initializeCounter(std::string const& method);
|
||||
|
||||
std::shared_mutex mutex_;
|
||||
mutable std::shared_mutex mutex_;
|
||||
std::unordered_map<std::string, MethodInfo> methodInfo_;
|
||||
|
||||
std::reference_wrapper<const WorkQueue> workQueue_;
|
||||
@@ -65,7 +65,7 @@ public:
|
||||
rpcForwarded(std::string const& method);
|
||||
|
||||
boost::json::object
|
||||
report();
|
||||
report() const;
|
||||
};
|
||||
|
||||
} // namespace RPC
|
||||
|
||||
@@ -49,29 +49,39 @@ concept Requirement = requires(T a) {
|
||||
*/
|
||||
// clang-format off
|
||||
template <typename T>
|
||||
concept ContextProcess = requires(T a, typename T::Input in, typename T::Output out, Context const& y) {
|
||||
{ a.process(in, y) } -> std::same_as<HandlerReturnType<decltype(out)>>; };
|
||||
concept ContextProcessWithInput = requires(T a, typename T::Input in, typename T::Output out, Context const& ctx) {
|
||||
{ a.process(in, ctx) } -> std::same_as<HandlerReturnType<decltype(out)>>;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept ContextProcessWithoutInput = requires(T a, typename T::Output out, Context const& ctx) {
|
||||
{ a.process(ctx) } -> std::same_as<HandlerReturnType<decltype(out)>>;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept NonContextProcess = requires(T a, typename T::Input in, typename T::Output out) {
|
||||
{ a.process(in) } -> std::same_as<HandlerReturnType<decltype(out)>>; };
|
||||
{ a.process(in) } -> std::same_as<HandlerReturnType<decltype(out)>>;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept HandlerWithInput = requires(T a, typename T::Input in, typename T::Output out) {
|
||||
{ a.spec() } -> std::same_as<RpcSpecConstRef>; }
|
||||
and (ContextProcess<T> or NonContextProcess<T>)
|
||||
and boost::json::has_value_to<typename T::Input>::value;
|
||||
concept HandlerWithInput = requires(T a) {
|
||||
{ a.spec() } -> std::same_as<RpcSpecConstRef>;
|
||||
}
|
||||
and (ContextProcessWithInput<T> or NonContextProcess<T>)
|
||||
and boost::json::has_value_to<typename T::Input>::value;
|
||||
|
||||
template <typename T>
|
||||
concept HandlerWithoutInput = requires(T a, typename T::Output out) {
|
||||
{ a.process() } -> std::same_as<HandlerReturnType<decltype(out)>>; };
|
||||
{ a.process() } -> std::same_as<HandlerReturnType<decltype(out)>>;
|
||||
}
|
||||
or ContextProcessWithoutInput<T>;
|
||||
|
||||
template <typename T>
|
||||
concept Handler =
|
||||
(HandlerWithInput<T>
|
||||
or
|
||||
HandlerWithoutInput<T>)
|
||||
(
|
||||
HandlerWithInput<T> or
|
||||
HandlerWithoutInput<T>
|
||||
)
|
||||
and boost::json::has_value_from<typename T::Output>::value;
|
||||
// clang-format on
|
||||
|
||||
} // namespace RPCng
|
||||
|
||||
@@ -63,8 +63,10 @@ struct Context
|
||||
{
|
||||
// TODO: we shall change yield_context to const yield_context after we
|
||||
// update backend interfaces to use const& yield
|
||||
const std::reference_wrapper<boost::asio::yield_context> yield;
|
||||
const std::shared_ptr<WsBase> session;
|
||||
std::reference_wrapper<boost::asio::yield_context> yield;
|
||||
std::shared_ptr<WsBase const> session;
|
||||
bool isAdmin = false;
|
||||
std::string clientIp;
|
||||
};
|
||||
|
||||
inline void
|
||||
|
||||
@@ -64,11 +64,23 @@ struct DefaultProcessor final
|
||||
}
|
||||
else if constexpr (HandlerWithoutInput<HandlerType>)
|
||||
{
|
||||
using OutType = HandlerReturnType<typename HandlerType::Output>;
|
||||
|
||||
// no input to pass, ignore the value
|
||||
if (auto const ret = handler.process(); not ret)
|
||||
return Error{ret.error()}; // forward Status
|
||||
if constexpr (ContextProcessWithoutInput<HandlerType>)
|
||||
{
|
||||
if (auto const ret = handler.process(*ctx); not ret)
|
||||
return Error{ret.error()}; // forward Status
|
||||
else
|
||||
return value_from(ret.value());
|
||||
}
|
||||
else
|
||||
return value_from(ret.value());
|
||||
{
|
||||
if (auto const ret = handler.process(); not ret)
|
||||
return Error{ret.error()}; // forward Status
|
||||
else
|
||||
return value_from(ret.value());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
243
src/rpc/ngHandlers/ServerInfo.h
Normal file
243
src/rpc/ngHandlers/ServerInfo.h
Normal file
@@ -0,0 +1,243 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of clio: https://github.com/XRPLF/clio
|
||||
Copyright (c) 2023, the clio developers.
|
||||
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <backend/BackendInterface.h>
|
||||
#include <backend/DBHelpers.h>
|
||||
#include <main/Build.h>
|
||||
#include <rpc/Errors.h>
|
||||
#include <rpc/RPCHelpers.h>
|
||||
#include <rpc/common/Types.h>
|
||||
#include <rpc/common/Validators.h>
|
||||
|
||||
#include <fmt/core.h>
|
||||
|
||||
class SubscriptionManager;
|
||||
class ReportingETL;
|
||||
class ETLLoadBalancer;
|
||||
|
||||
namespace RPC {
|
||||
class Counters;
|
||||
}
|
||||
|
||||
namespace RPCng {
|
||||
|
||||
template <
|
||||
typename SubscriptionManagerType,
|
||||
typename ETLLoadBalancerType,
|
||||
typename ReportingETLType,
|
||||
typename CountersType>
|
||||
class BaseServerInfoHandler
|
||||
{
|
||||
std::shared_ptr<BackendInterface> backend_;
|
||||
std::shared_ptr<SubscriptionManagerType> subscriptions_;
|
||||
std::shared_ptr<ETLLoadBalancerType> balancer_;
|
||||
std::shared_ptr<ReportingETLType const> etl_;
|
||||
std::reference_wrapper<CountersType const> counters_;
|
||||
|
||||
public:
|
||||
struct AdminSection
|
||||
{
|
||||
boost::json::object counters = {};
|
||||
boost::json::object subscriptions = {};
|
||||
boost::json::object etl = {};
|
||||
};
|
||||
|
||||
struct ValidatedLedgerSection
|
||||
{
|
||||
uint32_t age = 0;
|
||||
std::string hash = {};
|
||||
ripple::LedgerIndex seq = {};
|
||||
std::optional<ripple::Fees> fees = std::nullopt;
|
||||
};
|
||||
|
||||
struct CacheSection
|
||||
{
|
||||
std::size_t size = 0;
|
||||
bool isFull = false;
|
||||
ripple::LedgerIndex latestLedgerSeq = {};
|
||||
float objectHitRate = 1.0;
|
||||
float successorHitRate = 1.0;
|
||||
};
|
||||
|
||||
struct InfoSection
|
||||
{
|
||||
std::optional<AdminSection> adminSection = std::nullopt;
|
||||
std::string completeLedgers = {};
|
||||
uint32_t loadFactor = 1u;
|
||||
std::string clioVersion = Build::getClioVersionString();
|
||||
std::optional<boost::json::object> rippledInfo = std::nullopt;
|
||||
ValidatedLedgerSection validatedLedger = {};
|
||||
CacheSection cache = {};
|
||||
};
|
||||
|
||||
struct Output
|
||||
{
|
||||
InfoSection info = {};
|
||||
|
||||
// validated should be sent via framework
|
||||
bool validated = true;
|
||||
};
|
||||
|
||||
using Result = HandlerReturnType<Output>;
|
||||
|
||||
BaseServerInfoHandler(
|
||||
std::shared_ptr<BackendInterface> const& backend,
|
||||
std::shared_ptr<SubscriptionManagerType> const& subscriptions,
|
||||
std::shared_ptr<ETLLoadBalancerType> const& balancer,
|
||||
std::shared_ptr<ReportingETLType const> const& etl,
|
||||
CountersType const& counters)
|
||||
: backend_(backend)
|
||||
, subscriptions_(subscriptions)
|
||||
, balancer_(balancer)
|
||||
, etl_(etl)
|
||||
, counters_(std::cref(counters))
|
||||
{
|
||||
}
|
||||
|
||||
Result
|
||||
process(Context const& ctx) const
|
||||
{
|
||||
using namespace RPC;
|
||||
using namespace std::chrono;
|
||||
|
||||
auto const range = backend_->fetchLedgerRange();
|
||||
|
||||
// TODO: remove this check in https://github.com/XRPLF/clio/issues/592
|
||||
// note: this should happen on framework level.
|
||||
if (not range.has_value())
|
||||
return Error{Status{RippledError::rpcNOT_READY, "emptyDatabase", "The server has no data in the database"}};
|
||||
|
||||
auto const lgrInfo = backend_->fetchLedgerBySequence(range->maxSequence, ctx.yield);
|
||||
if (not lgrInfo.has_value())
|
||||
return Error{Status{RippledError::rpcINTERNAL}};
|
||||
|
||||
auto const fees = backend_->fetchFees(lgrInfo->seq, ctx.yield);
|
||||
if (not fees.has_value())
|
||||
return Error{Status{RippledError::rpcINTERNAL}};
|
||||
|
||||
auto const sinceEpoch = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
|
||||
auto const age = static_cast<int32_t>(sinceEpoch) -
|
||||
static_cast<int32_t>(lgrInfo->closeTime.time_since_epoch().count()) -
|
||||
static_cast<int32_t>(rippleEpochStart);
|
||||
|
||||
Output output;
|
||||
|
||||
output.info.completeLedgers = fmt::format("{}-{}", range->minSequence, range->maxSequence);
|
||||
if (ctx.isAdmin)
|
||||
output.info.adminSection = {counters_.get().report(), subscriptions_->report(), etl_->getInfo()};
|
||||
|
||||
auto const serverInfoRippled =
|
||||
balancer_->forwardToRippled({{"command", "server_info"}}, ctx.clientIp, ctx.yield);
|
||||
if (serverInfoRippled && !serverInfoRippled->contains(JS(error)))
|
||||
{
|
||||
if (serverInfoRippled->contains(JS(result)) &&
|
||||
serverInfoRippled->at(JS(result)).as_object().contains(JS(info)))
|
||||
{
|
||||
output.info.rippledInfo = serverInfoRippled->at(JS(result)).as_object().at(JS(info)).as_object();
|
||||
}
|
||||
}
|
||||
|
||||
output.info.validatedLedger.age = age < 0 ? 0 : age;
|
||||
output.info.validatedLedger.hash = ripple::strHex(lgrInfo->hash);
|
||||
output.info.validatedLedger.seq = lgrInfo->seq;
|
||||
output.info.validatedLedger.fees = fees;
|
||||
|
||||
output.info.cache.size = backend_->cache().size();
|
||||
output.info.cache.isFull = backend_->cache().isFull();
|
||||
output.info.cache.latestLedgerSeq = backend_->cache().latestLedgerSequence();
|
||||
output.info.cache.objectHitRate = backend_->cache().getObjectHitRate();
|
||||
output.info.cache.successorHitRate = backend_->cache().getSuccessorHitRate();
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
private:
|
||||
friend void
|
||||
tag_invoke(boost::json::value_from_tag, boost::json::value& jv, Output const& output)
|
||||
{
|
||||
jv = {
|
||||
{JS(info), output.info},
|
||||
{JS(validated), output.validated},
|
||||
};
|
||||
}
|
||||
|
||||
friend void
|
||||
tag_invoke(boost::json::value_from_tag, boost::json::value& jv, InfoSection const& info)
|
||||
{
|
||||
jv = {
|
||||
{JS(complete_ledgers), info.completeLedgers},
|
||||
{JS(load_factor), info.loadFactor},
|
||||
{"clio_version", info.clioVersion},
|
||||
{JS(validated_ledger), info.validatedLedger},
|
||||
{"cache", info.cache},
|
||||
};
|
||||
|
||||
if (info.rippledInfo)
|
||||
{
|
||||
try
|
||||
{
|
||||
auto const& rippledInfo = info.rippledInfo.value();
|
||||
jv.as_object()[JS(load_factor)] = rippledInfo.at(JS(load_factor));
|
||||
jv.as_object()[JS(validation_quorum)] = rippledInfo.at(JS(validation_quorum));
|
||||
jv.as_object()["rippled_version"] = rippledInfo.at(JS(build_version));
|
||||
}
|
||||
catch (std::exception const&)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
if (info.adminSection)
|
||||
{
|
||||
jv.as_object()["etl"] = info.adminSection->etl;
|
||||
jv.as_object()[JS(counters)] = info.adminSection->counters;
|
||||
jv.as_object()[JS(counters)].as_object()["subscriptions"] = info.adminSection->subscriptions;
|
||||
}
|
||||
}
|
||||
|
||||
friend void
|
||||
tag_invoke(boost::json::value_from_tag, boost::json::value& jv, ValidatedLedgerSection const& validated)
|
||||
{
|
||||
jv = {
|
||||
{JS(age), validated.age},
|
||||
{JS(hash), validated.hash},
|
||||
{JS(seq), validated.seq},
|
||||
{JS(base_fee_xrp), validated.fees->base.decimalXRP()},
|
||||
{JS(reserve_base_xrp), validated.fees->reserve.decimalXRP()},
|
||||
{JS(reserve_inc_xrp), validated.fees->increment.decimalXRP()},
|
||||
};
|
||||
}
|
||||
|
||||
friend void
|
||||
tag_invoke(boost::json::value_from_tag, boost::json::value& jv, CacheSection const& cache)
|
||||
{
|
||||
jv = {
|
||||
{"size", cache.size},
|
||||
{"is_full", cache.isFull},
|
||||
{"latest_ledger_seq", cache.latestLedgerSeq},
|
||||
{"object_hit_rate", cache.objectHitRate},
|
||||
{"successor_hit_rate", cache.successorHitRate},
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
using ServerInfoHandler = BaseServerInfoHandler<SubscriptionManager, ETLLoadBalancer, ReportingETL, RPC::Counters>;
|
||||
|
||||
} // namespace RPCng
|
||||
@@ -98,7 +98,7 @@ public:
|
||||
publish(std::shared_ptr<Message> const& message, Key const& key);
|
||||
|
||||
std::uint64_t
|
||||
count()
|
||||
count() const
|
||||
{
|
||||
return subCount_.load();
|
||||
}
|
||||
@@ -330,7 +330,7 @@ public:
|
||||
cleanup(session_ptr session);
|
||||
|
||||
boost::json::object
|
||||
report()
|
||||
report() const
|
||||
{
|
||||
boost::json::object counts = {};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user