mirror of
https://github.com/XRPLF/rippled.git
synced 2026-10-05 11:18:05 +00:00
Merge branch 'pratik/otel-phase2-rpc-tracing' into pratik/otel-phase3-tx-tracing
This commit is contained in:
@@ -13,6 +13,12 @@
|
||||
|
||||
#include <xrpl/telemetry/Telemetry.h>
|
||||
|
||||
#ifndef XRPL_ENABLE_TELEMETRY
|
||||
// beast::Journal is named only by the compiled-out makeTelemetry() below, so
|
||||
// this include belongs to that configuration and would be unused in the other.
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#endif
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
|
||||
@@ -602,7 +602,14 @@ PathRequest::findPaths(
|
||||
span.setAttribute(
|
||||
pathfind_span::attr::numSourceAssets, static_cast<int64_t>(sourceAssets.size()));
|
||||
|
||||
#ifdef XRPL_ENABLE_TELEMETRY
|
||||
// Only the numPaths attribute at the end of this function reads this, so it
|
||||
// is not maintained at all when telemetry is compiled out. An #ifdef rather
|
||||
// than `if (span)`, because the attribute cannot read a variable that does
|
||||
// not exist, and a counter kept up to date but never read is an unused
|
||||
// variable, which fails the build.
|
||||
std::int64_t totalPaths = 0;
|
||||
#endif
|
||||
for (auto const& asset : sourceAssets)
|
||||
{
|
||||
if (continueCallback && !continueCallback())
|
||||
@@ -622,7 +629,9 @@ PathRequest::findPaths(
|
||||
auto ps = pathfinder->getBestPaths(
|
||||
kMaxPaths, fullLiquidityPath, context_[asset], asset.getIssuer(), continueCallback);
|
||||
context_[asset] = ps;
|
||||
#ifdef XRPL_ENABLE_TELEMETRY
|
||||
totalPaths += static_cast<std::int64_t>(ps.size());
|
||||
#endif
|
||||
|
||||
auto const& sourceAccount = [&] {
|
||||
if (!isXRP(asset.getIssuer()))
|
||||
@@ -725,7 +734,9 @@ PathRequest::findPaths(
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef XRPL_ENABLE_TELEMETRY
|
||||
span.setAttribute(pathfind_span::attr::numPaths, totalPaths);
|
||||
#endif
|
||||
|
||||
/* The resource fee is based on the number of source currencies used.
|
||||
The minimum cost is 50 and the maximum is 400. The cost increases
|
||||
@@ -748,21 +759,34 @@ PathRequest::doUpdate(
|
||||
// nests under it. doUpdate does not yield, so scoping is safe.
|
||||
auto span = ScopedSpanGuard(
|
||||
TraceCategory::Rpc, pathfind_span::prefix::pathfind, pathfind_span::op::compute);
|
||||
span.setAttribute(pathfind_span::attr::fast, fast);
|
||||
// to_string(Issue) renders a non-XRP asset as "<issuer>/<currency>" with the
|
||||
// issuer as a plaintext Base58 address, so it cannot be emitted as-is: every
|
||||
// account reaching a span is hashed first. Redact just the issuer and keep
|
||||
// the currency, which is what this attribute is for. An MPT asset renders as
|
||||
// its issuance ID and carries no address, so it needs no redaction.
|
||||
span.setAttribute(
|
||||
pathfind_span::attr::destCurrency,
|
||||
saDstAmount_.asset().visit(
|
||||
[](Issue const& issue) {
|
||||
return isXRP(issue.account)
|
||||
? to_string(issue.currency)
|
||||
: redactAccount(toBase58(issue.account)) + "/" + to_string(issue.currency);
|
||||
},
|
||||
[](MPTIssue const& mpt) { return to_string(mpt.getMptID()); }));
|
||||
// Guarded on the span being live because setAttribute's arguments are
|
||||
// evaluated whatever the build, and doUpdate is hot: PathRequestManager
|
||||
// calls it once per active path_find subscription on every ledger close, so
|
||||
// a node with N subscriptions pays this N times a close. The destCurrency
|
||||
// value costs a base58check encode of the issuer (two SHA-256 rounds), a
|
||||
// SHA-512Half over the result and three string allocations. The compiled-out
|
||||
// guard's operator bool() is a literal false, so the block disappears
|
||||
// entirely in that build; with telemetry compiled in it is skipped when
|
||||
// telemetry is disabled at runtime or the pathfind category is off.
|
||||
if (span)
|
||||
{
|
||||
span.setAttribute(pathfind_span::attr::fast, fast);
|
||||
// to_string(Issue) renders a non-XRP asset as "<issuer>/<currency>" with
|
||||
// the issuer as a plaintext Base58 address, so it cannot be emitted
|
||||
// as-is: every account reaching a span is hashed first. Redact just the
|
||||
// issuer and keep the currency, which is what this attribute is for. An
|
||||
// MPT asset renders as its issuance ID and carries no address, so it
|
||||
// needs no redaction.
|
||||
span.setAttribute(
|
||||
pathfind_span::attr::destCurrency,
|
||||
saDstAmount_.asset().visit(
|
||||
[](Issue const& issue) {
|
||||
return isXRP(issue.account)
|
||||
? to_string(issue.currency)
|
||||
: redactAccount(toBase58(issue.account)) + "/" + to_string(issue.currency);
|
||||
},
|
||||
[](MPTIssue const& mpt) { return to_string(mpt.getMptID()); }));
|
||||
}
|
||||
|
||||
JLOG(journal_.debug()) << iIdentifier_ << " update " << (fast ? "fast" : "normal");
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include <xrpld/app/ledger/LedgerMaster.h>
|
||||
#include <xrpld/app/main/Application.h>
|
||||
#include <xrpld/rpc/detail/AssetCache.h>
|
||||
#include <xrpld/rpc/detail/PathFindSpanNames.h>
|
||||
#include <xrpld/rpc/detail/PathRequest.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
@@ -16,17 +15,26 @@
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <xrpl/resource/Consumer.h>
|
||||
#include <xrpl/server/InfoSub.h>
|
||||
#include <xrpl/telemetry/SpanGuard.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
// Needed only by the update_all span in updateAll(), which is compiled out when
|
||||
// telemetry is off. Without the same guard here they would be unused includes in
|
||||
// that build, which clang-tidy's misc-include-cleaner rejects.
|
||||
#ifdef XRPL_ENABLE_TELEMETRY
|
||||
#include <xrpld/rpc/detail/PathFindSpanNames.h>
|
||||
|
||||
#include <xrpl/telemetry/SpanGuard.h>
|
||||
|
||||
#include <optional>
|
||||
#endif // XRPL_ENABLE_TELEMETRY
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
@@ -75,12 +83,16 @@ PathRequestManager::updateAll(std::shared_ptr<ReadView const> const& inLedger)
|
||||
cache = getAssetCache(inLedger, true);
|
||||
}
|
||||
|
||||
#ifdef XRPL_ENABLE_TELEMETRY
|
||||
using namespace telemetry;
|
||||
// updateAll runs on every ledger close. Skip span emission when there are
|
||||
// no active path subscriptions, to avoid a steady stream of empty spans at
|
||||
// mainnet close cadence. All other work still runs unchanged (notably the
|
||||
// isNewPathRequest() flag reset below), so behaviour matches the pre-span
|
||||
// code path.
|
||||
// Nothing outside telemetry reads this block, and updateAll runs on every
|
||||
// ledger close, so it is compiled out entirely when telemetry is off rather
|
||||
// than left to construct a stub guard and discard two attributes per close.
|
||||
// No span object exists to test here, so the guard has to be an #ifdef.
|
||||
//
|
||||
// Skip span emission when there are no active path subscriptions, to avoid
|
||||
// a steady stream of empty spans at mainnet close cadence. All other work
|
||||
// still runs unchanged (notably the isNewPathRequest() flag reset below).
|
||||
//
|
||||
// Scoped, so the pathfind.compute spans that doUpdate() creates below on
|
||||
// this thread nest under it. std::optional because ScopedSpanGuard is
|
||||
@@ -93,6 +105,7 @@ PathRequestManager::updateAll(std::shared_ptr<ReadView const> const& inLedger)
|
||||
span->setAttribute(pathfind_span::attr::ledgerIndex, static_cast<int64_t>(inLedger->seq()));
|
||||
span->setAttribute(pathfind_span::attr::numRequests, static_cast<int64_t>(requests.size()));
|
||||
}
|
||||
#endif // XRPL_ENABLE_TELEMETRY
|
||||
|
||||
bool newRequests = app_.getLedgerMaster().isNewPathRequest();
|
||||
bool mustBreak = false;
|
||||
|
||||
@@ -223,6 +223,14 @@ callMethod(JsonContext& context, Method method, std::string const& name, Object&
|
||||
}
|
||||
}
|
||||
|
||||
// Telemetry-only helper, so it is compiled out with telemetry off. Left
|
||||
// running it would cost several json lookups, up to two string copies and a
|
||||
// handler-table lookup on every failed request, for a name nobody records.
|
||||
// Its result IS the span name, so `if (span)` cannot guard it: at that point
|
||||
// no span exists to test. The single call site is gated the same way, which
|
||||
// also keeps this file-static function referenced in both configurations.
|
||||
#ifdef XRPL_ENABLE_TELEMETRY
|
||||
|
||||
// Resolve the span suffix / command attribute for a request that failed in
|
||||
// fillHandler. Returns the canonical handler name for a recognized command
|
||||
// (a finite, bounded set) or the literal "unknown" for a request that omits
|
||||
@@ -256,6 +264,8 @@ resolveCommandSpanName(JsonContext const& context)
|
||||
: std::string_view{rpc_span::val::unknownCommand};
|
||||
}
|
||||
|
||||
#endif // XRPL_ENABLE_TELEMETRY
|
||||
|
||||
} // namespace
|
||||
|
||||
Status
|
||||
@@ -264,6 +274,11 @@ doCommand(rpc::JsonContext& context, json::Value& result)
|
||||
Handler const* handler = nullptr;
|
||||
if (auto error = fillHandler(context, handler))
|
||||
{
|
||||
// Every statement below only feeds the error span, and the span name
|
||||
// itself comes from resolveCommandSpanName(), so there is no span
|
||||
// object to test with `if (span)`. With telemetry off the whole block
|
||||
// is compiled out and a storm of malformed requests pays nothing.
|
||||
#ifdef XRPL_ENABLE_TELEMETRY
|
||||
// Bound the span name and command attribute to the finite set of
|
||||
// registered handler names (plus "unknown") — see the helper for why
|
||||
// raw request input must not reach the telemetry pipeline.
|
||||
@@ -279,6 +294,7 @@ doCommand(rpc::JsonContext& context, json::Value& result)
|
||||
: std::string_view(rpc_span::val::user));
|
||||
span.setAttribute(rpc_span::attr::rpcStatus, rpc_span::val::error);
|
||||
span.setError(getErrorInfo(error).token.cStr());
|
||||
#endif // XRPL_ENABLE_TELEMETRY
|
||||
|
||||
injectError(error, result);
|
||||
return error;
|
||||
|
||||
@@ -478,7 +478,15 @@ ServerHandler::processSession(
|
||||
// else collapses to "unknown". Emitting the raw string would let request
|
||||
// input drive unbounded label cardinality. Mirrors the HTTP path's
|
||||
// resolveCommandSpanName().
|
||||
span.setAttribute(rpc_span::attr::command, resolveWsCommandSpanName(jv, app_.config()));
|
||||
//
|
||||
// The guard is required because the resolver is a call argument: it runs
|
||||
// even when setAttribute itself is an empty no-op. Without it, every
|
||||
// WebSocket message pays for the JSON member lookups, a string copy and a
|
||||
// handler-registry lookup that nothing reads.
|
||||
if (span)
|
||||
{
|
||||
span.setAttribute(rpc_span::attr::command, resolveWsCommandSpanName(jv, app_.config()));
|
||||
}
|
||||
|
||||
auto is = std::static_pointer_cast<WSInfoSub>(session->appDefined);
|
||||
if (is->getConsumer().disconnect(journal_))
|
||||
|
||||
@@ -25,16 +25,27 @@ doPathFind(rpc::JsonContext& context)
|
||||
// thread) nest under it. doPathFind does not yield, so scoping is safe.
|
||||
auto span = ScopedSpanGuard(
|
||||
TraceCategory::Rpc, pathfind_span::prefix::pathfind, pathfind_span::op::request);
|
||||
// Addresses are hashed before emission for privacy. Read through a const
|
||||
// reference: the non-const json::Value::operator[] inserts a null for a
|
||||
// missing key, which would make PathRequest::parseJson's isMember() checks
|
||||
// see an absent field as present and return Malformed instead of Missing.
|
||||
// Reading for telemetry must not alter what the request looks like.
|
||||
auto const& params = std::as_const(context.params);
|
||||
if (auto const& src = params[jss::source_account]; src.isString())
|
||||
span.setAttribute(pathfind_span::attr::sourceAccount, redactAccount(src.asString()));
|
||||
if (auto const& dst = params[jss::destination_account]; dst.isString())
|
||||
span.setAttribute(pathfind_span::attr::destAccount, redactAccount(dst.asString()));
|
||||
// Guarded on the span being live because setAttribute's arguments are
|
||||
// evaluated whatever the build, and neither is free: asString() copies the
|
||||
// address out of the JSON and redactAccount() takes a SHA-512Half over it.
|
||||
// That is two copies and two hashes on every path_find call. The
|
||||
// compiled-out guard's operator bool() is a literal false, so the block
|
||||
// disappears entirely in that build; with telemetry compiled in it is
|
||||
// skipped when telemetry is disabled at runtime or the category is off.
|
||||
if (span)
|
||||
{
|
||||
// Addresses are hashed before emission for privacy. Read through a
|
||||
// const reference: the non-const json::Value::operator[] inserts a null
|
||||
// for a missing key, which would make PathRequest::parseJson's
|
||||
// isMember() checks see an absent field as present and return Malformed
|
||||
// instead of Missing. Reading for telemetry must not alter what the
|
||||
// request looks like.
|
||||
auto const& params = std::as_const(context.params);
|
||||
if (auto const& src = params[jss::source_account]; src.isString())
|
||||
span.setAttribute(pathfind_span::attr::sourceAccount, redactAccount(src.asString()));
|
||||
if (auto const& dst = params[jss::destination_account]; dst.isString())
|
||||
span.setAttribute(pathfind_span::attr::destAccount, redactAccount(dst.asString()));
|
||||
}
|
||||
|
||||
if (context.app.config().pathSearchMax == 0)
|
||||
return rpcError(RpcNotSupported);
|
||||
|
||||
@@ -34,16 +34,27 @@ doRipplePathFind(rpc::JsonContext& context)
|
||||
// span's log lines stay trace-correlated.
|
||||
auto span = ScopedSpanGuard(
|
||||
TraceCategory::Rpc, pathfind_span::prefix::pathfind, pathfind_span::op::request);
|
||||
// Addresses are hashed before emission for privacy. Read through a const
|
||||
// reference: the non-const json::Value::operator[] inserts a null for a
|
||||
// missing key, which would make PathRequest::parseJson's isMember() checks
|
||||
// see an absent field as present and return Malformed instead of Missing.
|
||||
// Reading for telemetry must not alter what the request looks like.
|
||||
auto const& params = std::as_const(context.params);
|
||||
if (auto const& src = params[jss::source_account]; src.isString())
|
||||
span.setAttribute(pathfind_span::attr::sourceAccount, redactAccount(src.asString()));
|
||||
if (auto const& dst = params[jss::destination_account]; dst.isString())
|
||||
span.setAttribute(pathfind_span::attr::destAccount, redactAccount(dst.asString()));
|
||||
// Guarded on the span being live because setAttribute's arguments are
|
||||
// evaluated whatever the build, and neither is free: asString() copies the
|
||||
// address out of the JSON and redactAccount() takes a SHA-512Half over it.
|
||||
// That is two copies and two hashes on every ripple_path_find call. The
|
||||
// compiled-out guard's operator bool() is a literal false, so the block
|
||||
// disappears entirely in that build; with telemetry compiled in it is
|
||||
// skipped when telemetry is disabled at runtime or the category is off.
|
||||
if (span)
|
||||
{
|
||||
// Addresses are hashed before emission for privacy. Read through a
|
||||
// const reference: the non-const json::Value::operator[] inserts a null
|
||||
// for a missing key, which would make PathRequest::parseJson's
|
||||
// isMember() checks see an absent field as present and return Malformed
|
||||
// instead of Missing. Reading for telemetry must not alter what the
|
||||
// request looks like.
|
||||
auto const& params = std::as_const(context.params);
|
||||
if (auto const& src = params[jss::source_account]; src.isString())
|
||||
span.setAttribute(pathfind_span::attr::sourceAccount, redactAccount(src.asString()));
|
||||
if (auto const& dst = params[jss::destination_account]; dst.isString())
|
||||
span.setAttribute(pathfind_span::attr::destAccount, redactAccount(dst.asString()));
|
||||
}
|
||||
|
||||
if (context.app.config().pathSearchMax == 0)
|
||||
return rpcError(RpcNotSupported);
|
||||
|
||||
Reference in New Issue
Block a user