Telemetry.cpp conflicted. Phase-9 rewrote the metrics pipeline into
makeTracerResource()/makeMetricsResource()/initMetrics() further down the
class, so its side of the region is empty and phase-8's private helper
block does not apply. Resolved to phase-9's structure; phase-8's own
hunks outside the region (the deleted kTracesPath/kMetricsPath, the
verbatim traces URL, the two-endpoint startup log) merged in.
Phase-9's initMetrics() still derives the metrics URL by suffix-swap.
That is fixed in the next commit, not here.
One [telemetry] key served both OTLP signals, and the metrics URL was
derived from it by suffix-swap: strip a trailing slash, strip a known
signal path if present, append the wanted one. Anything not ending
/v1/traces therefore posted metrics to the traces path, and the OTLP
version was pinned in code where an operator could not reach it.
Adds metrics_endpoint alongside traces_endpoint. Both are full URLs used
verbatim, so traces and metrics can go to different collectors, or to one
whose OTLP paths are not the defaults. signalEndpoint(), kTracesPath and
kMetricsPath are gone; nothing derives an endpoint from another.
The startup log names both URLs, since with two independent endpoints
there was otherwise no way to see where metrics were going.
Also drops exporter=otlp_http from the shipped config and the test
fixture. No branch in the chain reads an `exporter` key: it was a real
Setup member in the first phase-1b implementation, removed when only
OTLP/HTTP was wired up, and already deleted from TESTING.md once on the
same grounds.
Eighteen conflict regions across nine files. Resolved by asking, per
region, which side is the better final state rather than by taking a
branch wholesale.
Telemetry.cpp keeps phase-9's two resource builders. phase-8 offered a
single makeResource() with no node identity; phase-9 splits it into
makeTracerResource() and makeMetricsResource() because the metrics
provider is built in the constructor, before setNodeId() runs, so
xrpl.node.id can only be stamped unconditionally on the tracer side.
Collapsing them would have dropped that attribute, which is what keeps
per-node traces from folding into one identity.
Telemetry.h and the config test compose both sides: phase-9's nodeId
member and its assertion, plus the renamed endpoint.
xrpld-telemetry.cfg keeps phase-9's devnet identity and its
metrics_endpoint, renames the traces key, and drops exporter=otlp_http.
Nothing reads an `exporter` key on any branch in the chain: it was a real
Setup member in the first phase-1b implementation, removed when only
OTLP/HTTP was wired up, and already deleted from TESTING.md once on the
same grounds. The cfg line was the last carrier.
The docs keep phase-9's versions, which are both fuller and more
accurate: the incoming runbook listed the consensus strategy values as
"random" where the code compares against "attribute".
OTelCollector.cpp had five comment-only regions in a file phase-7 owns,
so those take the upstream side.
MetricsRegistry.h's usage example named a member that no longer exists
and the wrong arity; it now matches the real three-argument call and says
where the endpoint comes from.
Three conflicts, all composed rather than resolved by taking a side:
- TelemetryConfig.cpp: phase-6 kept networkTypeFromId file-local with
[[nodiscard]]; phase-7 had relocated it to public scope for
Application.cpp. Kept phase-7's relocation, so one definition remains.
The [[nodiscard]] survives on the declaration in Telemetry.h.
- Telemetry.cpp x2: phase-7 added getMeter overrides, phase-6 added
[[nodiscard]] to the startSpan below them. Kept both, and put
[[nodiscard]] on getMeter too.
- TESTING.md: phase-7 had the right metric name (span_calls_total, which
the spanmetrics namespace produces) but the wrong label. Its
xrpl.rpc.command appears nowhere else in the branch; the attribute is
bare `command`, which is what the dashboards query. Took phase-7's
metric with the correct label.
Both signalEndpoint call sites follow the renamed member. signalEndpoint
itself is left in place: removing it and adding metrics_endpoint is a
design change, not part of propagating a rename.
Two conflicts, both composed rather than taking a side.
SpanGuard.h: phase-4 added the followsFrom parameter and its @param block;
phase-3 added the @return line. Kept both.
Telemetry.cpp: phase-4 added getConsensusTraceStrategy() immediately above
getTracer()'s return type, where phase-3 added [[nodiscard]]. Kept both, in
both implementation classes.
[telemetry] endpoint carried one OTLP signal while its name implied it
covered every signal. That asymmetry is what let the metrics URL be
guessed later by rewriting this one's path suffix, so anything not
ending /v1/traces silently posted metrics to the traces path.
Renames the key to traces_endpoint and Setup::exporterEndpoint to
tracesEndpoint. The default value is unchanged and the URL is still used
verbatim, with no path derived from it. The startup log line and the
compose-file example name the new key, the latter being where an
operator copies it from.
No metrics_endpoint is added here: this branch has no metrics pipeline,
so the key would parse into a member nothing reads.
setNodeId() and Counter::add() silenced an unused parameter with a
(void) cast in their telemetry-disabled arm. The attribute is this codebase's
idiom: it appears 105 times across 40 files on develop. Parameter names stay,
because the enabled arm uses them and the @param lines name them.
totalAgreementsEver() and totalMissedEver() are pure accessors and gain
[[nodiscard]]; no caller discards either.
Nine std::count_if calls over window1h_ and window7d_ took an iterator
pair; std::ranges::count_if takes the container. One hand-rolled
erase-while-iterating loop becomes a single std::erase_if: pending_ is a
hash_map, which is a std::unordered_map alias, so the C++20 overload applies.
The hard-trim loop below it is left as a loop on purpose. It re-tests
pending_.size() every step to stop as soon as the cap is met, which erase_if
cannot express.
The accessors return values a caller must use: 13 in ValidationTracker.h, the
two Unit.h mappers, the two HistogramBuckets.h helpers, getMeter() and
networkTypeFromId(). No caller in the chain discards any of them.
The trace-context checks validate bytes received from a peer, so a
discarded result means untrusted input was accepted unchecked. hashSpan(),
txReceiveSpan() and txProcessSpan() return an RAII guard; discarding one ends
the span on the same line it began.
The telemetry-disabled twins of both hashSpan overloads already carried the
attribute, so the two #ifdef arms now agree. Both hashSpan overloads also
gain the @return line their siblings already had, now that the result cannot
be dropped.
No caller in the chain discards any of these results.
getInstance(), getTracer(), both startSpan() overloads and networkTypeFromId()
return values that a caller must use. A discarded startSpan() result destroys
the returned span immediately, so the span opens and closes with no content.
Six methods in Telemetry.h already carried the attribute, on the base and on
every override. The new attributes follow that: the overrides in Telemetry.cpp
and NullTelemetry.cpp get it too, because [[nodiscard]] is not inherited and a
call bound to the derived type would otherwise be unchecked.
No caller anywhere in the chain discards any of these results.
consensus.validation.receive is created before the drop decision, so one
span name covered three exits with unrelated cost profiles: two drop
paths that end in microseconds, and a queued path whose handle is moved
into the job and so covers job wait plus checkValidation.
No existing attribute separated them. validation_trusted=true implies
the queued path, but validation_trusted=false spans both drop paths and
the queued path, so any quantile over the span mixed the populations.
Both drop paths are live in the default config, which sets
relayUntrustedValidations.
Adds validation_status, set once on each exit rather than as a default,
following the tx_status precedent in the same file: dropped_diverged,
queued, dropped_load. On the queued path it is set before the handle is
moved into the job.
Harness and docs:
- integration-test.sh queried traces_span_metrics_* for spanmetrics, but this
branch sets the connector namespace to "span", so those two checks matched
nothing and failed. The dashboards and runbook had moved; the script had not.
- The same script queried eight native metric names with a product prefix and
capitals that formatName() cannot produce: it lowercases, maps '.' and ' ' to
'_', and prepends nothing. Corrected against the runbook tables.
- TESTING.md carried the same stale spanmetrics names and a jq example reading
a Prometheus label that does not exist.
- The runbook now records where each part of a derived metric name comes from,
since only the namespace is ours to choose.
Collector:
- OTelCounterImpl::increment silently dropped a negative amount. An OTel
counter takes unsigned deltas, so assert and let a release build under-count
rather than wrap.
- OTelGaugeImpl::increment computed current + amount in int64, which is
undefined on overflow, and the clamp ran afterwards so it could not help.
Check the headroom first. set() now clamps rather than casting a uint64 above
INT64_MAX to a negative, which is what made underflow reachable.
- The meter scope was two bare literals. They are constants now, and
Telemetry.cpp static_asserts them equal to kMeterName and kMeterVersion:
beast cannot include the telemetry header, so a build failure is the only way
to catch the copies drifting.
- formatName uses views::transform and ranges::to, as Backend.cpp already does.
- Unused constructor parameters take [[maybe_unused]] instead of (void) casts.
- The destructor logged "shutting down" and "stopped" with nothing between.
initMetrics was 79 lines doing four jobs. The exporter and the histogram views
are separate functions now, addUnitView is a member rather than a lambda
capturing this, and the export interval and timeout are named. It also derived
the metrics URL from the traces URL by suffix swap, which sent metrics to the
traces path whenever the configured URL had any other shape; both URLs now come
from one rule that handles a bare host, a trailing slash and either signal path.
The default setServiceInstanceId() body ignores its argument. Use the
attribute rather than a (void) cast: the codebase already uses it 104 times
and the build is C++23.
These comments dated themselves against this branch or against an earlier
revision of the same change, neither of which survives a squash merge.
- 'as of this branch' in the pricing-case doc and the pass-through static_assert
becomes a statement about what the constants currently produce.
- 'Extracted from processGetObjectByHash()' and 'Split from start()' describe
edits internal to this change; both now say why the method stands alone.
- Recording.h: the mock-abstract mismatch is a standing consequence of a member
set that differs between builds, not something that 'has previously' happened.
- MetricsRegistry.cpp: describe the loops.txt entry as recording two cycles
rather than as what ordering.txt 'previously had'.
- InboundLedger.h: the acquire span is the only signal for back-fill cost; it
did not 'previously emit' nothing.
- check_bucket_parity.py: replace the eleven-phase drift story with the reason
the check exists, and point the failure message at HistogramBuckets.h and the
collector config instead of OpenTelemetryPlan/, which does not reach develop.
Comments and one error message only, no behaviour change.
Both comments pointed at a state the squash merge does not publish.
- Consensus.h: 'yields a null guard, same as before' had no antecedent in the
round or the function. Say instead that a null guard makes the setAttribute
calls below no-ops, which is what SpanGuard's impl_ guard does.
- ConsensusSpanLabels.h: 'Split from ConsensusSpanNames.h' describes a split
performed entirely within this change; both headers first appear here. The
dependency rationale and the diagram are unchanged.
Comments only, no behaviour change.
The class docs for the OTel insight bridge described how the code got to its
current shape rather than what it does. Those comparisons resolve against a
revision that the squash merge does not publish, so they read as confidently
wrong once merged.
- OTelCollector: state that it is selected by [insight] server=otel as an
alternative to StatsDCollector. It replaced nothing; CollectorManager still
selects StatsDCollector for server=statsd.
- Unit: give the reason an Event needs an explicit unit, and the consequence
of omitting one, without narrating what the two backends previously assumed.
- OTelEventImpl: say that HistogramBuckets.h is the single owner of the bucket
edges and why a copy goes stale, instead of quoting a superseded edge list.
- HistogramBuckets: drop 'just as 5 s censors them today'. The millisecond
ladder tops at 120000, so nothing is censored at 5 s.
Comments only, no behaviour change.
A comment that describes an earlier state of its own branch documents something
no reader can look up, because the branch is squash-merged and the state it
contrasts against never reaches the merged history. Four passages in
HistogramBuckets.h and one in Telemetry.cpp did exactly that, and the
OTelCollector and Unit.h wording implied a transition rather than a fact.
- HistogramBuckets.h: the ladders now explain the invariant they enforce,
instead of recounting where the edges used to live and how they drifted.
- Telemetry.cpp: one view per unit means a byte count is bucketed on the byte
ladder, stated directly rather than as something it stopped inheriting.
- OTelCollector.cpp: the collector is described by what it is, a thin adapter
over the shared pipeline, rather than as a shim that gave up an exporter.
- Unit.h: the StatsD path is out of service as a present fact, and the contract
its wire format would break is an external protocol one.
Comment text only. No declaration, signature or emitted value changes.
The three close-time span attributes hold NetClock readings, which are whole
seconds since the XRP Ledger epoch of 2000-01-01 rather than the Unix epoch.
Neither the unit nor the epoch was recoverable from the key, so a consumer
rendering one as a wall-clock time without first adding the epoch offset lands
roughly thirty years early. The sibling close_resolution_ms already named its
unit, so the header disagreed with itself.
close_time -> close_time_ripple_epoch_s
parent_close_time -> parent_close_time_ripple_epoch_s
close_time_self -> close_time_self_ripple_epoch_s
Emitted values do not change; only the keys do. The public RPC response fields
of the same name are deliberately untouched, as renaming those would break the
ledger API.
The class documented a name format it does not produce. Eleven comments said
the [insight] prefix is prepended, and gave examples like "xrpld_rpc_size" and
"xrpld_LedgerMaster_Validated_Ledger_Age" that also kept the original casing.
formatName() lowercases the raw instrument name and maps dots and spaces to
underscores, and applies no prefix. All four instrument factories go through
it, so "RPC.Size" exports as "rpc_size". prefix_ is written in the constructor
and read in one place, the startup log line, so nothing it holds can reach an
exported name. The service is identified by the service.name resource
attribute.
Comments only, in both the header and the implementation. The ASCII diagram
still lists prefix_ as a member, which it is.
The overview diagram and the thread-safety note named Mirror<T>, which this
header does not declare. Drop both mentions so the file documents what it
ships.
Four conflicts. None was a take-a-side.
xrpld-telemetry.cfg: dropped the incoming [insight] block. This branch already
has one, and duplicate ini sections do not replace each other -- parseIniFile
appends onto the same section, so keys merge last-wins and the effective config
is one that appears nowhere in the file.
src/tests/libxrpl/CMakeLists.txt: kept this branch's else() branch, which
compiles MetricsRegistry.cpp for the telemetry-off build, and dropped only the
ValidationTracker target_sources inside if(telemetry). Upstream relocated that
one out of the guard, so keeping both would have compiled it twice. The
else() branch is this branch's own: the MetricsRegistry test exists only here,
and without its implementation the off build would not link.
RCLConsensus.cpp: union. The metric macros and registry come from this branch,
PropagationHelpers from upstream; all three are used.
PeerImp.cpp: MetricMacros.h stays unguarded, because all seven XRPL_METRIC_*
uses in this file are on unconditional paths and the header is what defines
them. ConsensusReceiveTracing.h takes upstream's guarded placement, and this
branch's guarded GetObjectMetricNames.h is kept beside it.
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.
The header told callers to declare members [[no_unique_address]]. That
attribute has no other use in this repo, and MSVC ignores the standard
spelling for ABI compatibility, so following the advice would have been a
portability wart for one byte per member.
Say instead that the compiled-out member collapses to padding, and that what
these types buy is work not being done.
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.
std::atomic implicitly deletes copy and move, so a class holding a Counter is
non-copyable when telemetry is compiled in. Compiled out, Counter was an empty
type with no such member, which would have made that same owner freely
copyable in one build only -- the per-configuration API difference these
utilities exist to avoid.
Declare copy and move deleted so both builds agree, and assert it
unconditionally in the tests.
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.
Sites that exist only to be reported currently spell their own gating: an
#ifdef around a member, another around the clock read, another around the
call that reports it. That puts preprocessor branches through business logic
and leaves each class with a different member set per build.
Add kEnabled, Stopwatch and Counter. Each holds real state when telemetry is
compiled in and is an empty type with no-op methods when it is not, while the
member stays declared in both builds so no class's API differs by
configuration. Tests assert both configurations from one file, including that
the compiled-out types are empty.