Commit Graph

15500 Commits

Author SHA1 Message Date
Pratik Mankawde
676c19b838 Merge branch 'pratik/otel-phase5-docs-deployment' into pratik/otel-phase6-statsd 2026-08-27 16:04:36 +01:00
Pratik Mankawde
f293f65bd0 Merge branch 'pratik/otel-phase4-consensus-tracing' into pratik/otel-phase5-docs-deployment 2026-08-27 16:01:02 +01:00
Pratik Mankawde
b060c76a76 Merge branch 'pratik/otel-phase3-tx-tracing' into pratik/otel-phase4-consensus-tracing 2026-08-27 16:01:01 +01:00
Pratik Mankawde
cc24101629 Merge branch 'pratik/otel-phase2-rpc-tracing' into pratik/otel-phase3-tx-tracing 2026-08-27 16:01:01 +01:00
Pratik Mankawde
189755bbb2 Merge branch 'pratik/otel-phase1c-rpc-integration' into pratik/otel-phase2-rpc-tracing 2026-08-27 16:00:26 +01:00
Pratik Mankawde
5dfee8564b Merge branch 'pratik/otel-phase1b-telemetry-infra' into pratik/otel-phase1c-rpc-integration 2026-08-27 16:00:26 +01:00
Pratik Mankawde
fff4124d9b Merge branch 'pratik/otel-phase1a-plan-docs' into pratik/otel-phase1b-telemetry-infra 2026-08-27 16:00:25 +01:00
Pratik Mankawde
078788af64 Merge remote-tracking branch 'origin/develop' into pratik/otel-phase1a-plan-docs 2026-08-27 15:59:56 +01:00
Pratik Mankawde
d3faaa9240 Merge branch 'pratik/otel-phase5-docs-deployment' into pratik/otel-phase6-statsd 2026-08-27 15:57:48 +01:00
Pratik Mankawde
12fe70a6d5 Merge branch 'pratik/otel-phase4-consensus-tracing' into pratik/otel-phase5-docs-deployment 2026-08-27 15:57:47 +01:00
Pratik Mankawde
0e5992af00 fix(consensus): mark the prior-round span context as possibly unused
startRoundTracing() is the only reader and writer of prevRoundSpanContext_,
and it is compiled out with telemetry, so a telemetry-off build sees an unused
private field and -Werror rejects it.

Marked rather than guarded: the two sibling span contexts are declared in both
configurations -- one of them escapes the warning only because an unguarded
accessor returns it -- and guarding this one alone would make the member set
depend on the build.
2026-08-27 15:54:13 +01:00
Jingchen
71f5555873 feat: Remove pseudo account field filter (#8042)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-08-27 13:52:23 +00:00
Ayaz Salikhov
3967ed6d54 build: Update release-info to get better pkg_release (#8131) 2026-08-27 13:34:38 +00:00
Pratik Mankawde
388dca05ed fix(overlay): name the proposal receive handle apart from the root span
onMessage(TMProposeSet) already owns a ScopedSpanGuard called span, the root
for the inbound peer message. The thread-free handle for the proposal receive
span was declared with the same name in the same scope, so the second
declaration conflicted with the first and every use of it -- the assignment,
the liveness test, the attribute writes and the job capture -- resolved
against the wrong type.

Call it proposalSpan. The validation handler already keeps its two apart the
same way, with valSpan for the root.
2026-08-27 14:21:35 +01:00
Pratik Mankawde
6a01f96877 Merge branch 'pratik/otel-phase4-consensus-tracing' into pratik/otel-phase5-docs-deployment 2026-08-27 14:04:50 +01:00
Pratik Mankawde
e8635f3452 Merge branch 'pratik/otel-phase3-tx-tracing' into pratik/otel-phase4-consensus-tracing 2026-08-27 14:04:50 +01:00
Pratik Mankawde
e85957da95 Merge branch 'pratik/otel-phase5-docs-deployment' into pratik/otel-phase6-statsd 2026-08-27 14:04:50 +01:00
Pratik Mankawde
295e147214 docs(telemetry): list the injection subsection in the contents
The table of contents in this file indexes third- and fourth-level headings,
so a new subsection that is absent from it is a gap rather than a style
choice.
2026-08-27 14:04:45 +01:00
Pratik Mankawde
8db0ded57b docs(telemetry): document the four states of current-context injection
Compiled out, compiled in and tracing, compiled in with no active span, and
compiled in but disabled by config all have to produce the right wire bytes,
and only two of them are obvious. Tabulate them, and record why the predicate
reads the context directly instead of calling GetSpan(), which allocates a
DefaultSpan in the no-span case.
2026-08-27 14:02:14 +01:00
Pratik Mankawde
9d86d2df71 Merge branch 'pratik/otel-phase3-tx-tracing' into pratik/otel-phase4-consensus-tracing 2026-08-27 13:59:46 +01:00
Pratik Mankawde
50848ac935 docs(telemetry): pass the whole message to the injection helpers
mutable_ on a protobuf optional submessage allocates it and sets its has-bit
at the call site, before the helper can decide there is nothing to write. A
caller that dereferences it ships an empty TraceContext whenever nothing is
recorded, and its peers each take a branch to extract nothing.

Document the rule with the right and wrong forms side by side.
2026-08-27 13:58:30 +01:00
Pratik Mankawde
457fea650e Merge branch 'pratik/otel-phase1c-rpc-integration' into pratik/otel-phase2-rpc-tracing 2026-08-27 13:56:22 +01:00
Pratik Mankawde
5a1816adde Merge branch 'pratik/otel-phase1b-telemetry-infra' into pratik/otel-phase1c-rpc-integration 2026-08-27 13:56:22 +01:00
Pratik Mankawde
2d9691c2db Merge branch 'pratik/otel-phase2-rpc-tracing' into pratik/otel-phase3-tx-tracing 2026-08-27 13:56:22 +01:00
Pratik Mankawde
27ef26d231 docs(telemetry): say what the compiled-out path actually costs
The conditional-compilation section promised zero overhead when telemetry is
not wanted. The span disappears, but the arguments passed to it do not: the
compiled-out guards are ordinary inline functions, so a to_string() or a hash
in an argument list still runs and its result is then discarded.

State that, show the guard that does remove the work, and name the opposite
case -- the metric macros, which discard their arguments and need no guard.
2026-08-27 13:55:20 +01:00
Pratik Mankawde
2e3e25d056 Merge branch 'pratik/otel-phase5-docs-deployment' into pratik/otel-phase6-statsd
Two conflicts, both in PeerImp's proposal and validation receive paths, and
both resolved by taking the incoming side: it holds the span in a handle that
stays empty when telemetry is compiled out and moves every attribute behind
if (span && *span), which supersedes the unguarded form on this side.

Taking the incoming text renamed consSpan to span in both blocks, while the
two job-lambda captures further down had merged cleanly and still named
consSpan. Renamed those captures so each names the handle its own function
declares.

This branch's own guard on the inbound-validation ledger_hash attribute is a
different span in a different function; it merged cleanly and is preserved.
2026-08-27 12:32:27 +01:00
Pratik Mankawde
bba40f0bb8 Merge branch 'pratik/otel-phase4-consensus-tracing' into pratik/otel-phase5-docs-deployment 2026-08-27 12:30:33 +01:00
Pratik Mankawde
85b42d8bf0 fix(consensus): send no trace context when nothing is being traced
Both broadcast paths passed *msg.mutable_trace_context() to the injector,
which allocates the submessage and sets its has-bit before the injector can
decide there is nothing to write. A compile-time guard covered the
telemetry-off build, but a node with telemetry compiled in and no active
span -- a disabled category, telemetry disabled by config, or a round that
is not being traced -- still broadcast an empty TraceContext to every peer,
and every peer took its has_trace_context() branch to extract nothing.

Add SpanGuard::hasCurrentContext(), a predicate that tests the same two
conditions the injector bails out on without allocating, and an
injectCurrentContext(message) helper that uses it to decide whether to
create the submessage at all. Both consensus call sites now call the helper
unguarded.
2026-08-27 12:25:09 +01:00
Pratik Mankawde
c93de72401 Merge branch 'pratik/otel-phase3-tx-tracing' into pratik/otel-phase4-consensus-tracing
Two conflicts, both in the telemetry include blocks.

SpanGuard.h: kept the union. The incoming side moves <memory> inside the
telemetry guard and adds <type_traits>; this branch adds <initializer_list>,
<utility> and the protocol::TraceContext forward declaration. Guarding
<memory> is correct here: the only std::shared_ptr uses are SpanContext's
member and constructor, both inside the guard, and SpanGuardHandle is a
template parameter name rather than a smart-pointer typedef.

NullTelemetry.cpp: took only the incoming guarded Journal.h block. The
incoming hunk also carried <memory> and <utility>, which this branch already
includes below the guarded OpenTelemetry block; taking them as well would
have tripped readability-duplicate-include.
2026-08-27 12:16:36 +01:00
Pratik Mankawde
0259604a35 Merge branch 'pratik/otel-phase2-rpc-tracing' into pratik/otel-phase3-tx-tracing 2026-08-27 12:14:00 +01:00
Pratik Mankawde
0d4d624622 refactor(telemetry): inject trace context from the whole message
The existing helper takes the TraceContext submessage, so every caller
writes *msg.mutable_trace_context(). On a protobuf optional field that
allocates the submessage and sets its has-bit before the helper runs, so a
message ships an empty TraceContext whenever nothing is recorded and its
peers take their has_trace_context() branch to extract nothing.

Add an overload taking the parent message, which decides whether to create
the submessage at all, and correct the header note that claimed the old
helper was already free.
2026-08-27 12:11:10 +01:00
Pratik Mankawde
2ea9005c1b Merge branch 'pratik/otel-phase1c-rpc-integration' into pratik/otel-phase2-rpc-tracing 2026-08-27 12:08:59 +01:00
Pratik Mankawde
69c413d332 Merge branch 'pratik/otel-phase1b-telemetry-infra' into pratik/otel-phase1c-rpc-integration 2026-08-27 12:08:26 +01:00
Pratik Mankawde
58d0c30e08 style(consensus): keep the gated span helpers non-static for clang-tidy
The three helpers whose bodies are compiled out with telemetry read members
only inside the guard, so in a telemetry-off build they touch no member and
readability-convert-member-functions-to-static fires. WarningsAsErrors makes
that fatal.

Suppress it where the body is gated, matching OverlayImpl::reportDnsResolve.
Making them static instead would give the two configurations different
signatures, which is the hazard the gating pattern avoids.
2026-08-27 11:13:12 +01:00
Pratik Mankawde
626c4e7ae3 Skip telemetry-only work on the consensus round path
Five sites on the consensus round path did telemetry-only work whether or
not anything could record it.

onClose set four attributes on the ledger-close span. Two cost real work
once per round: OpenLedger::current() takes currentMutex_ and copies a
shared_ptr just to read txCount, and the mode attribute builds a string.
The block now sits behind if (span).

doAccept read the previous close-time resolution and ran a lambda
returning a std::string, feeding the resolution_direction attribute and
nothing else, once per accepted ledger. Now behind if (doAcceptSpan).

makeAcceptSpan, startRoundTracing and createValidationSpan have wholly
telemetry bodies, so each body sits inside XRPL_ENABLE_TELEMETRY.
makeAcceptSpan then allocates no control block per accepted ledger; an
empty handle is safe because doAccept only passes it to activateIfLive(),
which tests it. Its attributes are additionally guarded on the span being
live. startRoundTracing's early return sits after two virtual Telemetry
calls and a strategy string compare, so the whole body is compiled out
rather than reached each round. createValidationSpan yields std::nullopt,
and its two call sites in validate() test the guard as well as the
optional -- an engaged optional holding a dead guard still turned a
32-byte ledger hash into a 64-character string.

Telemetry.h and SpanNames.h are named only by startRoundTracing, so their
includes are guarded the same way to keep misc-include-cleaner satisfied
when telemetry is off.

onPhaseEvent and onOutcomeEvent are left as they are: the generic
Consensus template calls both and the csf simulator implements both, so
they are part of the adaptor surface.
2026-08-27 10:10:26 +01:00
Pratik Mankawde
455bc9d3a5 Skip proposal and validation receive-span work when inactive
Both inbound peer-message handlers built a receive span and set its
attributes unconditionally. The proposal path turned two 32-byte hashes
into full hex strings and then took a 16-character substring of each --
four heap allocations per message -- and it ran for every inbound
proposal, trusted or untrusted. The validation path did a field lookup,
two flag reads and a sign-time conversion for every inbound validation,
including the ones dropped just below it for peer divergence or local
load.

The handles are declared empty and only the make_shared sits inside
XRPL_ENABLE_TELEMETRY, so with telemetry compiled out neither path
allocates. The attribute blocks sit behind if (span && *span), which
also skips them when telemetry is compiled in but disabled in config,
and when the consensus trace category is off. It does not skip a span
that exists but was sampled out; that span still pays.

Both job bodies only carry the handle to hold the span alive and never
dereference it, so an empty handle is safe there. The validation span is
still built before the drop decision, so a dropped validation is still
traced; only its cost is removed.

ConsensusReceiveTracing.h has no other user in the file, so its include
is guarded the same way to keep misc-include-cleaner satisfied when
telemetry is off.
2026-08-27 09:41:05 +01:00
Pratik Mankawde
98f0e99df8 Guard the inbound-validation ledger-hash attribute
PeerImp::onMessage(TMValidation) runs once per inbound validation message,
and it reaches these attribute calls before the HashRouter duplicate
check, so every peer's copy of every validation paid for them. Span
setAttribute is a real inline function whose arguments are evaluated even
when telemetry is compiled out, and to_string(val->getLedgerHash())
heap-allocates a 64-character hex string on each call.

Wrap the ledger_hash and full_validation attributes in if (valSpan), so
neither the string build nor the flags lookup behind isFull() runs when
telemetry is compiled out, when it is switched off in the config, or when
the Peer trace category is disabled. A span that exists but was sampled
out still pays; there is no isRecording() to test.

peer_id and validation_trusted stay unguarded: their arguments are an
integer cast and a bool the surrounding logic already computes.
2026-08-27 09:40:16 +01:00
Vito Tumas
e0151229b6 fix: Waive unrealized-loss discount on sole-holder VaultClawback (#8119) 2026-08-27 08:15:38 +00:00
Ayaz Salikhov
b6a899583b build: Make packaging reusable (#8126) 2026-08-26 19:44:31 +00:00
Pratik Mankawde
40824c4d46 docs(telemetry): correct what the span-liveness guard actually skips
The comments claimed the guard skips work for a span that is "not being
recorded", which reads as sampling awareness. It has none: operator bool() is
impl_ != nullptr, and the span factories return an empty guard only when
telemetry is absent, disabled at runtime, or the trace category is off. A span
that exists but was sampled out still pays.

There is no isRecording() in the telemetry API, so the guard is still the
strongest available; only the justification was overstated.
2026-08-26 19:53:53 +01:00
Pratik Mankawde
a2a5ba778e docs(telemetry): correct what the span-liveness guard actually skips
The comments claimed the guard skips work for a span that is "not being
recorded", which reads as sampling awareness. It has none: operator bool() is
impl_ != nullptr, and the span factories return an empty guard only when
telemetry is absent, disabled at runtime, or the trace category is off. A span
that exists but was sampled out still pays.

There is no isRecording() in the telemetry API, so the guard is still the
strongest available; only the justification was overstated.
2026-08-26 19:53:50 +01:00
Pratik Mankawde
e8bbc36265 perf(rpc): compile out the pathfind update_all span with telemetry off
updateAll's update_all span is wholly telemetry: the optional guard, the
empty-requests test that decides whether to emit at all, and the two
attributes have no reader outside the span. It runs on every ledger close, so
with telemetry compiled out the function still constructed a stub guard and
discarded pathfind_ledger_index and pathfind_num_requests once a close for
nothing. Everything the rest of updateAll depends on, including the
isNewPathRequest() flag reset, is outside the block and unchanged.

No span object exists to test before it is created, so the guard is an #ifdef
over the whole block. The three includes it was the sole user of --
PathFindSpanNames.h, SpanGuard.h and <optional> -- are gated the same way,
because otherwise they would be unused includes in that build and
clang-tidy's misc-include-cleaner would reject them.
2026-08-26 19:49:09 +01:00
Pratik Mankawde
ef22e20a1e perf(rpc): build pathfind update attributes only when recorded
Two pieces of pathfinding telemetry ran regardless of the build.

doUpdate fills pathfind_dest_currency by rendering the destination asset for
the pathfind.compute span. For a non-XRP issue that is a base58 check encode
of the issuer, two SHA-256 rounds, then a SHA-512Half over the result and
three string allocations. It is a call argument, so it ran even where
setAttribute's body is empty. doUpdate is not a cold path: besides once per
pathfinding RPC, PathRequestManager::updateAll calls it once per active
path_find subscription on every ledger close, so a node with N subscriptions
paid N times a close. It now sits inside "if (span)" with the cheap
pathfind_fast flag, so it is skipped with telemetry compiled out and also for
any span that is not being recorded.

findPaths keeps a totalPaths counter across its per-source-asset loop. Its
only reader is the pathfind_num_paths attribute at the end of the same
function, so the counter is maintained only when telemetry is compiled in.
That needs an #ifdef rather than "if (span)": 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.
2026-08-26 19:48:59 +01:00
Pratik Mankawde
fcdbbabedf perf(rpc): hash pathfind accounts only when the span records them
doPathFind and doRipplePathFind fill two span attributes from the request's
source and destination accounts. Both values are call arguments, so they are
built whatever the build: asString() copies the address out of the JSON and
redactAccount() takes a SHA-512Half over it and formats 16 hex characters.
That is two copies and two hashes on every pathfinding RPC, for
pathfind_source_account and pathfind_dest_account, which nothing outside the
span reads.

Wrapping the block in "if (span)" drops that work when telemetry is compiled
out, where the guard's operator bool() is a literal false, and also when
telemetry is on but this span is not being recorded. The const-reference read
of context.params moves inside the guard with the code that needs it, so a
telemetry read still never inserts a null into the request.
2026-08-26 19:48:38 +01:00
Pratik Mankawde
da68beaef4 Compile out the RPC error-span name resolver with telemetry off
resolveCommandSpanName() and the error span in doCommand() exist only to
name and label a telemetry span. With telemetry compiled out the
resolver still ran on every RPC that fillHandler() rejects: up to five
json isMember lookups, up to three string copies, a virtual config()
call and a handler-table lookup, all to build a name nobody records. A
storm of malformed requests paid that cost once per request.

A runtime `if (span)` guard cannot work here. The resolver's result is
the span name itself, passed as the third argument of the
ScopedSpanGuard constructor, so no span object exists yet to test. That
leaves `#ifdef XRPL_ENABLE_TELEMETRY`, matching the house style used
elsewhere.

The guard covers the whole telemetry block at the call site and the
helper definition too, so the file-static helper does not become an
unreferenced function, which the build rejects because warnings are
errors. injectError() and the error return stay outside the guard, so
behaviour on the failure path is unchanged. No include is orphaned:
ErrorCodes.h, SpanGuard.h, RpcSpanNames.h and <string_view> all keep
uses outside the guards.

With telemetry on nothing changes: only comments and the four directives
were added.
2026-08-26 19:44:28 +01:00
Pratik Mankawde
f40c17a7ed perf(telemetry): only build accept-span attributes when recorded
TxQ::accept set two of its per-transaction span attributes unconditionally, so
every queued transaction the loop tried to apply to the open ledger paid for a
64-char string built from the transaction id and a transToken() lookup that
builds a string, whether or not anything recorded them. That is once per
candidate clearing the required fee level, on every ledger close.

Guard both on the span being active, as the enqueue path in TxQ::apply and the
apply-pipeline spans already do. The transaction apply itself stays outside the
guard; only the attribute that reads its result is telemetry.
2026-08-26 19:34:28 +01:00
Pratik Mankawde
12e0fddeae perf(rpc): skip WS command resolution when no span records it
The rpc.ws_message span's command attribute was resolved for every
inbound WebSocket message, whether or not anything was recording it.
The resolver does two JSON member tests plus two subscripts, copies the
command into a std::string, calls getAPIVersionNumber and then looks the
name up in the handler multimap. Because it is a call argument to
setAttribute, it ran even with telemetry compiled out, where
setAttribute's body is empty.

Wrapping the call in "if (span)" drops that work entirely when telemetry
is off, and also when telemetry is on but this span is not being
recorded. Nothing outside the attribute reads the resolved value, so no
other behaviour changes; the real request validation further down
computes its own api version, command string and handler role.
2026-08-26 19:32:19 +01:00
Pratik Mankawde
ceadaad841 Skip dispute-resolve event work when update span is inactive
updateOurPositions built a 64-character hex string from the transaction
id plus two std::to_string number conversions, then attached them to a
span event. That ran for every dispute that flipped position, on every
establish tick of every consensus round, whether or not anything was
recording.

The three strings and the event have no other consumer; the vote change
itself (mutableSet insert/erase) is untouched.

The block now sits behind if (span). With telemetry compiled out the
stub's operator bool is a literal false, so it is eliminated; with
telemetry on it is also skipped when the span is null because the
establish context was never captured. The guard is inside the function
body, so Consensus<Adaptor>'s adaptor interface is unchanged and the
csf::Peer simulator is unaffected.
2026-08-26 19:26:18 +01:00
Pratik Mankawde
1cf81f6edb Skip per-transaction span work when accept span is inactive
doAccept's canonical-tx-set loop built a 64-character hex string from
every transaction hash and added a span event for it, then reported a
txCount that only the span reads. That ran once per transaction in every
accepted ledger, whether or not anything was recording.

txCount and txHash have no other consumer: txCount is only read by the
tx_count attribute, and txHash only by the tx_included event. buildLCL
takes retriableTxs, not the count.

Both now sit behind if (doAcceptSpan). With telemetry compiled out the
stub's operator bool is a literal false, so the blocks are eliminated;
with telemetry on they are also skipped whenever the span is null
because the consensus trace category is off.
2026-08-26 19:25:59 +01:00
Pratik Mankawde
922f1a653a perf(telemetry): only build txq enqueue attributes when the span is recorded
TxQ::apply set its enqueue-span attributes unconditionally, so every transaction
paid for two 64-char hash strings and a TxFormats lookup whether or not anything
recorded them. The open-ledger rebuild replays transactions through this same
path, so it was paid more than once each.

Guard the block on the span being active, as the tx apply-pipeline spans do.
2026-08-26 19:13:38 +01:00