48 static constexpr auto kBACKEND_COUNTERS_KEY =
"backend_counters";
50 std::shared_ptr<BackendInterface> backend_;
51 std::shared_ptr<feed::SubscriptionManagerInterface> subscriptions_;
52 std::shared_ptr<etl::LoadBalancerInterface> balancer_;
53 std::shared_ptr<etl::ETLServiceInterface const> etl_;
54 std::reference_wrapper<CountersType const> counters_;
61 bool backendCounters =
false;
68 boost::json::object counters;
69 std::optional<boost::json::object> backendCounters;
70 boost::json::object subscriptions;
71 boost::json::object etl;
80 ripple::LedgerIndex seq = {};
81 std::optional<ripple::Fees> fees = std::nullopt;
89 bool isEnabled =
false;
91 ripple::LedgerIndex latestLedgerSeq = {};
92 float objectHitRate = 1.0;
93 float successorHitRate = 1.0;
100 std::optional<AdminSection> adminSection = std::nullopt;
101 std::string completeLedgers;
102 uint32_t loadFactor = 1u;
103 std::chrono::time_point<std::chrono::system_clock> time = std::chrono::system_clock::now();
104 std::chrono::seconds uptime = {};
105 std::string clioVersion = util::build::getClioVersionString();
106 std::string xrplVersion = ripple::BuildInfo::getVersionString();
107 std::optional<boost::json::object> rippledInfo = std::nullopt;
110 bool isAmendmentBlocked =
false;
111 bool isCorruptionDetected =
false;
121 bool validated =
true;
136 std::shared_ptr<BackendInterface> backend,
137 std::shared_ptr<feed::SubscriptionManagerInterface> subscriptions,
138 std::shared_ptr<etl::LoadBalancerInterface> balancer,
139 std::shared_ptr<etl::ETLServiceInterface const> etl,
140 CountersType
const& counters
142 : backend_(std::move(backend))
143 , subscriptions_(std::move(subscriptions))
144 , balancer_(std::move(balancer))
145 , etl_(std::move(etl))
146 , counters_(std::cref(counters))
157 spec([[maybe_unused]] uint32_t apiVersion)
159 static RpcSpec const kRPC_SPEC = {};
174 using namespace std::chrono;
176 auto const range = backend_->fetchLedgerRange();
177 ASSERT(range.has_value(),
"ServerInfo's ledger range must be available");
179 auto const lgrInfo = backend_->fetchLedgerBySequence(range->maxSequence, ctx.yield);
180 if (not lgrInfo.has_value())
183 auto const fees = backend_->fetchFees(lgrInfo->seq, ctx.yield);
184 if (not fees.has_value())
187 auto output = Output{};
188 auto const sinceEpoch =
189 duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
190 auto const age =
static_cast<int32_t
>(sinceEpoch) -
191 static_cast<int32_t
>(lgrInfo->closeTime.time_since_epoch().count()) -
194 output.info.completeLedgers = fmt::format(
"{}-{}", range->minSequence, range->maxSequence);
197 output.info.adminSection = {
198 .counters = counters_.get().report(),
200 input.backendCounters ? std::make_optional(backend_->stats()) : std::nullopt,
201 .subscriptions = subscriptions_->report(),
202 .etl = etl_->getInfo()
206 auto const serverInfoRippled = balancer_->forwardToRippled(
207 {{
"command",
"server_info"}}, ctx.clientIp, ctx.isAdmin, ctx.yield
210 if (serverInfoRippled && !serverInfoRippled->contains(JS(error))) {
211 if (serverInfoRippled->contains(JS(result)) &&
212 serverInfoRippled->at(JS(result)).as_object().contains(JS(info))) {
213 output.info.rippledInfo =
214 serverInfoRippled->at(JS(result)).as_object().at(JS(info)).as_object();
218 output.info.validatedLedger.age = age < 0 ? 0 : age;
219 output.info.validatedLedger.hash = ripple::strHex(lgrInfo->hash);
220 output.info.validatedLedger.seq = lgrInfo->seq;
221 output.info.validatedLedger.fees = fees;
222 output.info.cache.size = backend_->cache().size();
223 output.info.cache.isFull = backend_->cache().isFull();
224 output.info.cache.latestLedgerSeq = backend_->cache().latestLedgerSequence();
225 output.info.cache.objectHitRate = backend_->cache().getObjectHitRate();
226 output.info.cache.successorHitRate = backend_->cache().getSuccessorHitRate();
227 output.info.cache.isEnabled = not backend_->cache().isDisabled();
228 output.info.uptime = counters_.get().uptime();
229 output.info.isAmendmentBlocked = etl_->isAmendmentBlocked();
230 output.info.isCorruptionDetected = etl_->isCorruptionDetected();
237 tag_invoke(boost::json::value_from_tag, boost::json::value& jv, Output
const& output)
239 using boost::json::value_from;
242 {JS(info), value_from(output.info)},
243 {JS(validated), output.validated},
248 tag_invoke(boost::json::value_from_tag, boost::json::value& jv,
InfoSection const& info)
250 using boost::json::value_from;
251 using ripple::to_string;
254 {JS(complete_ledgers), info.completeLedgers},
255 {JS(load_factor), info.loadFactor},
256 {JS(time), to_string(std::chrono::floor<std::chrono::microseconds>(info.time))},
257 {JS(uptime), info.uptime.count()},
258 {
"clio_version", info.clioVersion},
259 {
"libxrpl_version", info.xrplVersion},
260 {JS(validated_ledger), value_from(info.validatedLedger)},
261 {
"cache", value_from(info.cache)},
264 if (info.isAmendmentBlocked)
265 jv.as_object()[JS(amendment_blocked)] =
true;
267 if (info.isCorruptionDetected)
268 jv.as_object()[
"corruption_detected"] =
true;
270 if (info.rippledInfo) {
271 auto const& rippledInfo = info.rippledInfo.value();
273 if (rippledInfo.contains(JS(load_factor)))
274 jv.as_object()[JS(load_factor)] = rippledInfo.at(JS(load_factor));
275 if (rippledInfo.contains(JS(validation_quorum)))
276 jv.as_object()[JS(validation_quorum)] = rippledInfo.at(JS(validation_quorum));
277 if (rippledInfo.contains(JS(build_version)))
278 jv.as_object()[
"rippled_version"] = rippledInfo.at(JS(build_version));
279 if (rippledInfo.contains(JS(network_id)))
280 jv.as_object()[JS(network_id)] = rippledInfo.at(JS(network_id));
283 if (info.adminSection) {
284 jv.as_object()[
"etl"] = info.adminSection->etl;
285 jv.as_object()[JS(counters)] = info.adminSection->counters;
286 jv.as_object()[JS(counters)].as_object()[
"subscriptions"] =
287 info.adminSection->subscriptions;
288 if (info.adminSection->backendCounters.has_value()) {
289 jv.as_object()[kBACKEND_COUNTERS_KEY] = *info.adminSection->backendCounters;
296 boost::json::value_from_tag,
297 boost::json::value& jv,
302 {JS(age), validated.age},
303 {JS(hash), validated.hash},
304 {JS(seq), validated.seq},
305 {JS(base_fee_xrp), validated.fees->base.decimalXRP()},
306 {JS(reserve_base_xrp), validated.fees->reserve.decimalXRP()},
307 {JS(reserve_inc_xrp), validated.fees->increment.decimalXRP()},
312 tag_invoke(boost::json::value_from_tag, boost::json::value& jv,
CacheSection const& cache)
315 {
"size", cache.size},
316 {
"is_enabled", cache.isEnabled},
317 {
"is_full", cache.isFull},
318 {
"latest_ledger_seq", cache.latestLedgerSeq},
319 {
"object_hit_rate", cache.objectHitRate},
320 {
"successor_hit_rate", cache.successorHitRate},
325 tag_invoke(boost::json::value_to_tag<Input>, boost::json::value
const& jv)
327 auto input = BaseServerInfoHandler::Input{};
328 auto const jsonObject = jv.as_object();
329 if (jsonObject.contains(kBACKEND_COUNTERS_KEY) &&
330 jsonObject.at(kBACKEND_COUNTERS_KEY).is_bool())
331 input.backendCounters = jv.at(kBACKEND_COUNTERS_KEY).as_bool();