Merge branch 'pratik/otel-phase10-workload-validation' into pratik/otel-sync-diagnostics

Nine conflicts, resolved as follows.

src/xrpld/app/ledger/detail/InboundLedger.cpp -- kept this branch's version.
phase10 sets the span's outcome/timeouts/peer_count attributes inline at each
exit; this branch replaced that with the idempotent finalizeAcquireSpan(), called
on all four exits (init, done, give-up, destructor). Taking phase10's blocks
would have set the outcome twice against a helper documented as not overwriting
what the real exit recorded. phase10's comment explains why peer_count must not
be read in a destructor; the helper solves that structurally by taking
std::optional<std::size_t> and being passed std::nullopt from there.

src/xrpld/telemetry/MetricsRegistry.cpp -- kept metric::ledgerEconomy over
phase10's "ledger_economy" literal. This branch added the naming check that
requires constants for converted families, so the literal would regress it. Took
phase10's comment cleanup.

src/xrpld/telemetry/MetricsRegistry.h -- kept registerRotationStateGauge(), which
only exists here, and took phase10's removal of the stale task-number comment.

validate_telemetry.py -- combined both. phase10 replaced serial metric polling
with a concurrent fan-out on one shared deadline, because 58 metrics x 45 s of
additive timeout overran the CI budget; that is kept. Its target list filters on
SKIPPED_METRIC_GROUPS rather than the two literals it hardcoded, so the
sync_diagnostics group stays owned by assert_sync_diagnostics_metrics() instead
of being polled and reported twice. Both SYNC_DIAGNOSTICS_GROUP and
METRIC_POLL_CONCURRENCY are needed and both are kept.

check_otel_naming.py -- both sides extend the rule docstring. Took phase10's
fuller Rule E text (doc discovery, allow-dotted markers) and re-appended rules
I/J/K/L, which exist only here.

expected_metrics.json -- the two sides add disjoint sibling groups, so both are
kept: sync_diagnostics alongside node_health_gauges, overlay_reduce_relay,
overlay_overflow, validation_lifetime_counters and not_asserted. Both dashboard
uids are kept, giving 16 asserted uids against 16 dashboards on disk.

expected_spans.json -- kept this branch's span set, a superset that adds the
acquire phase spans, ledger.serve, txset.acquire and peer.dial, and expands
ledger.acquire's required attributes. Took phase10's description, which documents
what the totals mean, and its note on how the RPC wildcard span is created.
total_span_types and total_unique_attributes are recomputed for the union: 48 and
74, since each side's figure counted only its own spans.

Docs: took phase10's more accurate wording on what the dashboard check actually
covers, and corrected the dashboard count from 15 to 16 where the merge made it
stale.

Verified: no conflict markers remain, both JSON contracts parse, both Python
files compile, asserted dashboard uids match the dashboards on disk exactly, and
the OTel naming check reports all layers consistent.
This commit is contained in:
Pratik Mankawde
2026-08-17 19:24:12 +01:00
89 changed files with 8770 additions and 2642 deletions

View File

@@ -58,7 +58,7 @@ namespace beast::insight {
* @code
* auto collector = beast::insight::OTelCollector::New(
* "http://localhost:4318/v1/metrics", // OTLP/HTTP endpoint
* "xrpld", // metric name prefix
* "xrpld", // logging label only
* "node-1", // service.instance.id
* "xrpld", // service.name
* "mainnet", // xrpl.network.type
@@ -105,8 +105,12 @@ public:
*
* @param endpoint OTLP/HTTP metrics endpoint URL
* (e.g. "http://localhost:4318/v1/metrics").
* @param prefix Prefix prepended to all metric names
* (e.g. "xrpld").
* @param prefix Label for the collector's startup log line
* (e.g. "xrpld"). Exported metric names are produced
* by formatName(), which lowercases the raw name and
* maps dots and spaces to underscores. The service is
* identified by the `service.name` OTel resource
* attribute.
* @param instanceId Unique identifier for this node instance,
* emitted as the `service.instance.id` OTel
* resource attribute. Defaults to empty string

View File

@@ -95,9 +95,16 @@ message TMPublicKey {
// Older peers that do not understand field 1001 will simply ignore it
// per protobuf wire-format rules, preserving backwards compatibility.
//
// trace_state is reserved for future use (secure tracing pipeline,
// OpenTelemetryPlan/secure-OTel.md). It is currently neither populated
// on inject nor read on extract; consumers must not rely on it.
// trace_state (field 4) is reserved and inert: it is neither populated on
// inject nor read on extract, so consumers must not rely on it. Beyond the
// W3C tracestate use noted on the field below, it is the intended home for
// an authenticated token a receiver could verify before adopting a peer's
// trace context as its parent. Today this message is unauthenticated peer
// input: the receiver only checks that the ids are well formed (16-byte
// trace_id, 8-byte span_id, neither all-zero) and otherwise starts a fresh
// trace, so the ids are a hint, not trusted provenance. An authenticated
// scheme would need a shared verification key, a canonical form to sign,
// and a defined policy for peers that send no token.
message TraceContext {
optional bytes trace_id = 1; // 16-byte trace identifier
optional bytes span_id = 2; // 8-byte parent span identifier

View File

@@ -101,13 +101,23 @@ injectToProtobuf(opentelemetry::context::Context const& ctx, protocol::TraceCont
// Serialize flags
proto.set_trace_flags(spanCtx.trace_flags().flags());
// TODO(observability/secure-OTel): the protobuf TraceContext message
// also carries `trace_state` (field 4), which is currently neither
// populated here nor read by extractFromProtobuf above. The field is
// reserved for the secure tracing pipeline outlined in
// OpenTelemetryPlan/secure-OTel.md, where an authenticated token in
// tracestate will let receivers reject spoofed/poisoned trace context.
// Wire trace_state through inject/extract once the consumer lands.
/**
* TODO: wire `trace_state` (protobuf TraceContext field 4) through
* inject and extract. It is neither written here nor read by
* extractFromProtobuf above, so the field is inert on the wire.
*
* Two uses are intended. One is W3C tracestate vendor-specific
* key-value pairs, for cross-vendor propagation. The other is an
* authenticated token. Today a peer's trace context is
* unauthenticated input: extractFromProtobuf only checks that the ids
* are well formed (16-byte trace_id, 8-byte span_id, neither
* all-zero) before using them as a parent, so the ids are a hint
* rather than trusted provenance. A token the receiver could verify
* would let it decide whether to adopt a peer's context at all. That
* needs a shared verification key, a canonical form to sign, and a
* defined policy for peers that send no token. None of that exists
* yet, which is why the field stays unpopulated.
*/
}
} // namespace xrpl::telemetry