mirror of
https://github.com/XRPLF/rippled.git
synced 2026-08-21 22:30:57 +00:00
Phase-10 brought in the upstream nodestore/peerfinder/consensus reorganisation along with its own write-path telemetry, which collided with the sync-diagnostic signals on this branch. Twelve files conflicted; every resolution keeps both intents rather than picking a side. The nodestore write timing was implemented twice, independently. Both sides added getStoreDurationUs()/getFetchDurationUs() to Database and both timed the backend call in each concrete store(). Keeping both would have added twice to storeDurationUs_ per store while storeStats() still counted one, so the mean write latency would have read double on every dashboard -- silently, since no test on either side asserts an exact microsecond figure. Resolved to one accumulator API: recordStoreDuration(), which takes a duration, clamps a sub-microsecond sample to zero and uses a relaxed atomic add. Phase-10's storeDurationStats() is gone and its two call sites now use the survivor, so all three store paths -- both store() overrides and importInternal() -- add exactly once. SlotCensus and its pure virtual moved from src/xrpld/peerfinder/ to include/xrpl/peerfinder/PeerfinderManager.h, following the Manager interface upstream relocated. The xrpld header is now phase-10's makeConfig shim, and Overlay.h, MetricMacros.cpp and the getSlotCensus() override chain point at the new location. ConsensusSpanNames.h and peerfinder Slot.h/Config.h include paths followed their headers into libxrpl the same way. InboundLedger gained phase-10's AcquireStats counters next to this branch's span activations in both the destructor abort path and done(); neither displaces the other. nodestore_state keeps the constant-based name this branch requires of it and phase-10's fuller description. Upstream #7292 deleted src/test/nodestore/Database_test.cpp, which held this branch's testDurationAccessors. Phase-10 restored the per-store half of that coverage in DatabaseConfig_test, but nothing covered importInternal -- it writes through storeBatch() and never through store(), so it is a third store path that has to time itself. That half is ported to a GTest in src/tests/libxrpl/nodestore/Database.cpp, keeping the exact zero-before and accumulate-after assertions and the per-instance negative check. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
408 lines
18 KiB
C++
408 lines
18 KiB
C++
#pragma once
|
|
|
|
/**
|
|
* Compile-time span name constants for consensus tracing.
|
|
*
|
|
* Used by RCLConsensus (app), Consensus.h (template), and PeerImp
|
|
* (overlay) for consensus lifecycle spans.
|
|
* Built on StaticStr/join() from SpanNames.h.
|
|
*
|
|
* ## Span Hierarchy
|
|
*
|
|
* Root span created in Adaptor::startRoundTracing(). In "deterministic"
|
|
* strategy the trace-id is derived from the previous ledger hash so all
|
|
* nodes tracing the same round share a trace.
|
|
*
|
|
* consensus.round [main thread, root]
|
|
* | Created: Adaptor::startRoundTracing()
|
|
* | Attrs: consensus_ledger_id, ledger_seq, consensus_mode,
|
|
* | trace_strategy, consensus_round_id
|
|
* |
|
|
* +-- consensus.phase.open [main thread, child]
|
|
* | Created: Consensus::startRoundInternal()
|
|
* | Ended: Consensus::closeLedger()
|
|
* |
|
|
* +-- consensus.proposal.send [main thread]
|
|
* | Created: Adaptor::propose()
|
|
* | Attrs: consensus_round (proposeSeq)
|
|
* |
|
|
* +-- consensus.ledger_close [main thread]
|
|
* | Created: Adaptor::onClose()
|
|
* | Attrs: ledger_seq, consensus_mode
|
|
* |
|
|
* +-- consensus.establish [main thread, child]
|
|
* | Created: Consensus::startEstablishTracing()
|
|
* | Ended: Consensus::phaseEstablish() on accept
|
|
* | Attrs: converge_percent, establish_count, proposers
|
|
* |
|
|
* +-- consensus.update_positions [main thread]
|
|
* | Created: Consensus::updateOurPositions()
|
|
* | Attrs: converge_percent, proposers, disputes_count
|
|
* | Events: per-dispute vote details (tx_id, our_vote, yays, nays)
|
|
* |
|
|
* +-- consensus.check [main thread]
|
|
* | Created: Consensus::haveConsensus()
|
|
* | Attrs: agree/disagree counts, threshold_percent, result
|
|
* |
|
|
* +-- consensus.accept [main thread, child of round]
|
|
* | Created: Adaptor::makeAcceptSpan(), shared_ptr kept alive
|
|
* | until doAccept() completes on jtACCEPT thread
|
|
* | Attrs: proposers, round_time_ms, quorum
|
|
* | |
|
|
* | +-- consensus.accept.apply [jtACCEPT thread, child of accept]
|
|
* | Created: Adaptor::doAccept()
|
|
* | Attrs: ledger_seq, close_time, close_time_correct,
|
|
* | close_resolution_ms, consensus_state, proposing, round_time_ms,
|
|
* | parent_close_time, close_time_self, close_time_vote_bins,
|
|
* | resolution_direction, tx_count
|
|
* | Events: tx.included (per tx, attrs: tx_id)
|
|
* |
|
|
* +~~~ consensus.validation.send [jtACCEPT thread, linked]
|
|
* | Created: Adaptor::createValidationSpan() (follows-from link)
|
|
* | Attrs: ledger_seq, proposing
|
|
* |
|
|
* +-- consensus.mode_change [main thread]
|
|
* Created: Adaptor::onModeChange()
|
|
* Attrs: mode_old, mode_new
|
|
*
|
|
* Standalone spans (no parent, created per-message in overlay):
|
|
*
|
|
* consensus.proposal.receive [PeerImp I/O thread]
|
|
* Created: PeerImp::onMessage(TMProposeSet)
|
|
*
|
|
* consensus.validation.receive [PeerImp I/O thread]
|
|
* Created: PeerImp::onMessage(TMValidation)
|
|
*
|
|
* ## Per-ledger trace (separate from the per-round trace above)
|
|
*
|
|
* consensus.validation.accept sits in a DIFFERENT trace from the spans above.
|
|
* The round trace is keyed on the PREVIOUS ledger hash (the round's seed);
|
|
* this span is keyed on the hash of the ledger the arriving validation is FOR,
|
|
* the same key LedgerMaster uses for ledger.validate and ledger.store, so a
|
|
* reader following one slow ledger sees the validation that triggered its
|
|
* acceptance beside the acceptance itself:
|
|
*
|
|
* trace_id = validatedLedgerHash[0:16]
|
|
* +-- consensus.validation.accept [JtValidationT worker / consensus thread]
|
|
* +-- ledger.validate [LedgerMaster::checkAccept]
|
|
* +-- ledger.store [LedgerMaster::storeLedger]
|
|
*
|
|
* Legend:
|
|
* +-- child-of relationship (same trace)
|
|
* +~~~ follows-from link (separate sub-tree, causal link)
|
|
*/
|
|
|
|
#include <xrpl/telemetry/SpanNames.h>
|
|
|
|
#include <string_view>
|
|
|
|
namespace xrpl::telemetry::consensus::span {
|
|
|
|
// ===== Span name segments ====================================================
|
|
|
|
namespace part {
|
|
inline constexpr auto proposal = makeStr("proposal");
|
|
inline constexpr auto validation = makeStr("validation");
|
|
inline constexpr auto accept = makeStr("accept");
|
|
inline constexpr auto phase = makeStr("phase");
|
|
} // namespace part
|
|
|
|
namespace op {
|
|
inline constexpr auto round = makeStr("round");
|
|
inline constexpr auto proposalSend = join(part::proposal, makeStr("send"));
|
|
inline constexpr auto ledgerClose = makeStr("ledger_close");
|
|
inline constexpr auto establish = makeStr("establish");
|
|
inline constexpr auto updatePositions = makeStr("update_positions");
|
|
inline constexpr auto check = makeStr("check");
|
|
inline constexpr auto accept = makeStr("accept");
|
|
inline constexpr auto acceptApply = join(part::accept, makeStr("apply"));
|
|
inline constexpr auto validationSend = join(part::validation, makeStr("send"));
|
|
inline constexpr auto modeChange = makeStr("mode_change");
|
|
inline constexpr auto proposalReceive = join(part::proposal, makeStr("receive"));
|
|
inline constexpr auto validationReceive = join(part::validation, makeStr("receive"));
|
|
inline constexpr auto phaseOpen = join(part::phase, makeStr("open"));
|
|
/**
|
|
* "validation.accept" — an arriving trusted validation being handed to the
|
|
* acceptance gate. Distinct from `validationReceive`, which is the overlay
|
|
* decoding a validation message: this is the later, per-ledger step where the
|
|
* validation is counted toward accepting a specific ledger.
|
|
*/
|
|
inline constexpr auto validationAccept = join(part::validation, makeStr("accept"));
|
|
} // namespace op
|
|
|
|
// ===== Full span names (prefix.op) ===========================================
|
|
|
|
inline constexpr auto round = join(seg::consensus, op::round);
|
|
inline constexpr auto proposalSend = join(seg::consensus, op::proposalSend);
|
|
inline constexpr auto ledgerClose = join(seg::consensus, op::ledgerClose);
|
|
inline constexpr auto establish = join(seg::consensus, op::establish);
|
|
inline constexpr auto updatePositions = join(seg::consensus, op::updatePositions);
|
|
inline constexpr auto check = join(seg::consensus, op::check);
|
|
inline constexpr auto accept = join(seg::consensus, op::accept);
|
|
inline constexpr auto acceptApply = join(seg::consensus, op::acceptApply);
|
|
inline constexpr auto validationSend = join(seg::consensus, op::validationSend);
|
|
inline constexpr auto modeChange = join(seg::consensus, op::modeChange);
|
|
inline constexpr auto proposalReceive = join(seg::consensus, op::proposalReceive);
|
|
inline constexpr auto validationReceive = join(seg::consensus, op::validationReceive);
|
|
inline constexpr auto phaseOpen = join(seg::consensus, op::phaseOpen);
|
|
/**
|
|
* "consensus.validation.accept" — full name, passed to `SpanGuard::hashSpan()`
|
|
* (which takes one complete span name) so the span adopts the trace id derived
|
|
* from the VALIDATED ledger's hash and joins that ledger's trace.
|
|
*/
|
|
inline constexpr auto validationAccept = join(seg::consensus, op::validationAccept);
|
|
|
|
// ===== Attribute keys ========================================================
|
|
|
|
namespace attr {
|
|
/**
|
|
* Canonical shared constants (defined in SpanNames.h). `ledgerHash` and
|
|
* `fullValidation` are shared with the peer.validation.receive span — same
|
|
* concept, same key, distinguished by span name (not an emitter prefix).
|
|
*/
|
|
using ::xrpl::telemetry::attr::closeResolutionMs;
|
|
using ::xrpl::telemetry::attr::closeTime;
|
|
using ::xrpl::telemetry::attr::closeTimeCorrect;
|
|
using ::xrpl::telemetry::attr::fullValidation;
|
|
using ::xrpl::telemetry::attr::ledgerHash;
|
|
using ::xrpl::telemetry::attr::ledgerSeq;
|
|
|
|
/**
|
|
* Domain-qualified attrs (rule 5 — bare name ambiguous across domains).
|
|
* Use `<domain>_<field>` underscore form for TraceQL ergonomics.
|
|
*/
|
|
inline constexpr auto ledgerId = makeStr("consensus_ledger_id");
|
|
inline constexpr auto mode = makeStr("consensus_mode");
|
|
inline constexpr auto round = makeStr("consensus_round");
|
|
inline constexpr auto roundId = makeStr("consensus_round_id");
|
|
/**
|
|
* Current phase name attached to consensus.round; updated on each
|
|
* phase transition event (open/establish/accepted).
|
|
*/
|
|
inline constexpr auto consensusPhase = makeStr("consensus_phase");
|
|
/**
|
|
* Boolean flag set on consensus.check when checkConsensus reports stalled.
|
|
*/
|
|
inline constexpr auto consensusStalled = makeStr("consensus_stalled");
|
|
|
|
/**
|
|
* Domain-owned bare attrs.
|
|
*/
|
|
inline constexpr auto proposers = makeStr("proposers");
|
|
inline constexpr auto roundTimeMs = makeStr("round_time_ms");
|
|
inline constexpr auto proposing = makeStr("proposing");
|
|
/**
|
|
* Round continuity / context attrs (set on consensus.round at round start).
|
|
*/
|
|
inline constexpr auto previousProposers = makeStr("previous_proposers");
|
|
inline constexpr auto previousRoundTimeMs = makeStr("previous_round_time_ms");
|
|
inline constexpr auto previousLedgerSeq = makeStr("previous_ledger_seq");
|
|
inline constexpr auto closeTimeResolutionMs = makeStr("close_time_resolution_ms");
|
|
/**
|
|
* Open-phase end metadata (set on consensus.phase.open before reset).
|
|
*/
|
|
inline constexpr auto openDurationMs = makeStr("open_duration_ms");
|
|
inline constexpr auto peerPositionsAtClose = makeStr("peer_positions_at_close");
|
|
/**
|
|
* Ledger-close inputs.
|
|
*/
|
|
inline constexpr auto txCountOpen = makeStr("tx_count_open");
|
|
/**
|
|
* Establish/check additional state.
|
|
*/
|
|
inline constexpr auto proposersFinished = makeStr("proposers_finished");
|
|
/**
|
|
* Accept/apply enrichment.
|
|
*/
|
|
inline constexpr auto disputesResolvedCount = makeStr("disputes_resolved_count");
|
|
/**
|
|
* Validation send/receive enrichment. (`full_validation` is shared — see the
|
|
* `using` re-export above.)
|
|
*/
|
|
inline constexpr auto validationSignTime = makeStr("validation_sign_time");
|
|
/**
|
|
* "validation_status" — what the validation store did with an arriving
|
|
* validation, set on consensus.validation.accept. One of the bounded
|
|
* `ValStatus` names (see `val::status*`), so it is safe to aggregate: it is the
|
|
* difference between "validations are arriving and counting" and "they arrive
|
|
* and are all rejected", which on a stuck node look identical from the outside.
|
|
*/
|
|
inline constexpr auto validationStatus = makeStr("validation_status");
|
|
/**
|
|
* "accept_gated" — true when this validation did NOT reach the acceptance gate
|
|
* because another thread was already accepting the same ledger (the
|
|
* bypass-accept path). Two values, so it is aggregatable; without it the
|
|
* absence of a following ledger.validate span in the trace is unexplained.
|
|
*/
|
|
inline constexpr auto acceptGated = makeStr("accept_gated");
|
|
/**
|
|
* Receive-side hash prefixes for cross-peer correlation.
|
|
*/
|
|
inline constexpr auto prevLedgerPrefix = makeStr("prev_ledger_prefix");
|
|
inline constexpr auto positionHashPrefix = makeStr("position_hash_prefix");
|
|
/**
|
|
* "consensus_state" — domain-qualified (collides with other domains' state).
|
|
*/
|
|
inline constexpr auto consensusState = makeStr("consensus_state");
|
|
inline constexpr auto parentCloseTime = makeStr("parent_close_time");
|
|
inline constexpr auto closeTimeSelf = makeStr("close_time_self");
|
|
inline constexpr auto closeTimeVoteBins = makeStr("close_time_vote_bins");
|
|
inline constexpr auto resolutionDirection = makeStr("resolution_direction");
|
|
inline constexpr auto convergePercent = makeStr("converge_percent");
|
|
inline constexpr auto establishCount = makeStr("establish_count");
|
|
inline constexpr auto avalancheThreshold = makeStr("avalanche_threshold");
|
|
inline constexpr auto closeTimeThreshold = makeStr("close_time_threshold");
|
|
inline constexpr auto haveCloseTimeConsensus = makeStr("have_close_time_consensus");
|
|
inline constexpr auto agreeCount = makeStr("agree_count");
|
|
inline constexpr auto disagreeCount = makeStr("disagree_count");
|
|
inline constexpr auto thresholdPercent = makeStr("threshold_percent");
|
|
/**
|
|
* "consensus_result" — domain-qualified (collides with generic result).
|
|
*/
|
|
inline constexpr auto consensusResult = makeStr("consensus_result");
|
|
inline constexpr auto quorum = makeStr("quorum");
|
|
inline constexpr auto traceStrategy = makeStr("trace_strategy");
|
|
inline constexpr auto modeOld = makeStr("mode_old");
|
|
inline constexpr auto modeNew = makeStr("mode_new");
|
|
|
|
/**
|
|
* "is_bow_out" — whether this proposal is a bow-out (resigning from round).
|
|
*/
|
|
inline constexpr auto isBowOut = makeStr("is_bow_out");
|
|
|
|
/**
|
|
* Transaction/dispute attrs used in consensus accept spans.
|
|
*/
|
|
inline constexpr auto txId = makeStr("tx_id");
|
|
inline constexpr auto disputeOurVote = makeStr("dispute_our_vote");
|
|
inline constexpr auto disputeYays = makeStr("dispute_yays");
|
|
inline constexpr auto disputeNays = makeStr("dispute_nays");
|
|
inline constexpr auto txCount = makeStr("tx_count");
|
|
inline constexpr auto disputesCount = makeStr("disputes_count");
|
|
/**
|
|
* Trust flag (is the message origin a trusted UNL validator). Qualified by
|
|
* message type, shared with the peer.{proposal,validation}.receive spans:
|
|
* consensus.proposal.receive uses `proposal_trusted`, consensus.validation.
|
|
* receive uses `validation_trusted`. Same concept on both emitters → same key.
|
|
*/
|
|
inline constexpr auto proposalTrusted = makeStr("proposal_trusted");
|
|
inline constexpr auto validationTrusted = makeStr("validation_trusted");
|
|
} // namespace attr
|
|
|
|
// ===== Event names ===========================================================
|
|
|
|
namespace event {
|
|
/**
|
|
* "dispute.resolve"
|
|
*/
|
|
inline constexpr auto disputeResolve = join(makeStr("dispute"), makeStr("resolve"));
|
|
/**
|
|
* "tx.included"
|
|
*/
|
|
inline constexpr auto txIncluded = join(makeStr("tx"), makeStr("included"));
|
|
|
|
/**
|
|
* Phase transition events — fired on consensus.round at each transition
|
|
* so the round-level span carries a complete timeline of phase changes,
|
|
* including the handleWrongLedger recovery edge that re-enters Open.
|
|
*/
|
|
inline constexpr auto phaseOpen = join(makeStr("phase"), makeStr("open"));
|
|
inline constexpr auto phaseEstablish = join(makeStr("phase"), makeStr("establish"));
|
|
inline constexpr auto phaseAccepted = join(makeStr("phase"), makeStr("accepted"));
|
|
inline constexpr auto phaseRecovery = join(makeStr("phase"), makeStr("recovery"));
|
|
|
|
/**
|
|
* Outcome events — fired on consensus.round at the establish→accepted
|
|
* transition so the path that drove acceptance is queryable.
|
|
*/
|
|
inline constexpr auto outcomeYes = join(makeStr("outcome"), makeStr("yes"));
|
|
inline constexpr auto outcomeMovedOn = join(makeStr("outcome"), makeStr("moved_on"));
|
|
inline constexpr auto outcomeExpired = join(makeStr("outcome"), makeStr("expired"));
|
|
} // namespace event
|
|
|
|
// ===== Attribute values ======================================================
|
|
|
|
namespace val {
|
|
inline constexpr auto finished = makeStr("finished");
|
|
inline constexpr auto movedOn = makeStr("moved_on");
|
|
inline constexpr auto yes = makeStr("yes");
|
|
inline constexpr auto no = makeStr("no");
|
|
inline constexpr auto expired = makeStr("expired");
|
|
inline constexpr auto increased = makeStr("increased");
|
|
inline constexpr auto decreased = makeStr("decreased");
|
|
inline constexpr auto unchanged = makeStr("unchanged");
|
|
// consensus_phase attribute values (the phase the round is entering).
|
|
inline constexpr auto phaseOpen = makeStr("open");
|
|
inline constexpr auto phaseEstablish = makeStr("establish");
|
|
inline constexpr auto phaseAccepted = makeStr("accepted");
|
|
|
|
/**
|
|
* validation_status values — the five `ValStatus` outcomes of adding an
|
|
* arriving validation to the validation store, plus a sentinel.
|
|
*
|
|
* Only `current` continues to the acceptance gate; every other value means the
|
|
* validation was counted for nothing, which is exactly the state a node stuck
|
|
* below quorum is in. Spelled here rather than reusing `to_string(ValStatus)`
|
|
* because that function returns camelCase ("badSeq"), and attribute values on
|
|
* an aggregated dimension must stay lower_snake_case.
|
|
*/
|
|
inline constexpr auto statusCurrent = makeStr("current");
|
|
inline constexpr auto statusStale = makeStr("stale");
|
|
inline constexpr auto statusBadSeq = makeStr("bad_seq");
|
|
inline constexpr auto statusMultiple = makeStr("multiple");
|
|
inline constexpr auto statusConflicting = makeStr("conflicting");
|
|
inline constexpr auto statusUnknown = makeStr("unknown");
|
|
} // namespace val
|
|
|
|
// ===== Value rules ===========================================================
|
|
|
|
/**
|
|
* Map a `ValStatus` to its `validation_status` attribute value.
|
|
*
|
|
* Takes a plain int rather than the enum so this header stays free of
|
|
* `Validations.h` (which pulls in the whole validation-store template) and so
|
|
* the rule can be asserted directly from the lib-only test binary, which cannot
|
|
* link xrpld. The caller passes `static_cast<int>(status)`; the enumerator order
|
|
* is fixed by `ValStatus` and asserted by the paired unit test, which is what
|
|
* keeps this mapping honest if a value is ever inserted.
|
|
*
|
|
* @param valStatus `ValStatus` as an int: 0 Current, 1 Stale, 2 BadSeq,
|
|
* 3 Multiple, 4 Conflicting.
|
|
* @return The matching `val::status*` value, or `unknown` for anything else.
|
|
*
|
|
* Example — the two readings that matter on a stuck node:
|
|
* @code
|
|
* validationStatusValue(0); // "current" -- counted, gate will be tried
|
|
* validationStatusValue(2); // "bad_seq" -- rejected, counted for nothing
|
|
* @endcode
|
|
*
|
|
* Example — edge case: an out-of-range value still yields a usable label rather
|
|
* than an empty attribute, so the dimension never gains a blank series:
|
|
* @code
|
|
* validationStatusValue(99); // "unknown"
|
|
* @endcode
|
|
*
|
|
* @note Pure and side-effect free; safe to call from any thread.
|
|
*/
|
|
[[nodiscard]] constexpr std::string_view
|
|
validationStatusValue(int valStatus) noexcept
|
|
{
|
|
switch (valStatus)
|
|
{
|
|
case 0:
|
|
return val::statusCurrent;
|
|
case 1:
|
|
return val::statusStale;
|
|
case 2:
|
|
return val::statusBadSeq;
|
|
case 3:
|
|
return val::statusMultiple;
|
|
case 4:
|
|
return val::statusConflicting;
|
|
default:
|
|
return val::statusUnknown;
|
|
}
|
|
}
|
|
|
|
} // namespace xrpl::telemetry::consensus::span
|