mirror of
https://github.com/XRPLF/rippled.git
synced 2026-08-21 14:20:56 +00:00
A slow fresh-sync ledger produced spans scattered across threads with no way to relate them. They now share a trace id derived from the ledger's own hash, the one value every participating site already holds, so nothing new is plumbed across threads. This is the pattern the transaction pipeline already uses for its tx id. Joined: ledger.validate, ledger.store, and a new consensus.validation.accept recorded when a trusted validation arrives. In Tempo, searching one ledger hash returns them together, so an operator can tell whether the ledger was slow to arrive, slow to be accepted, or slow to be stored. They are siblings rather than a chain because the accept gate is entered from three different threads, so no fixed parent order exists. consensus.validation.accept also records why an arriving validation did or did not advance the gate, which makes "validations arrive but are all rejected" visible for the first time. consensus_round_duration_ms turns the existing round-time span attribute into a histogram, so a fleet trend needs a metric query rather than raw trace inspection. An explicit bucket view is required, not optional: the SDK default tops out at ten seconds while consensus abandons a round at two minutes, so slow rounds would all fall in one bucket and every quantile would read exactly ten seconds. Cost is one record per round. Record layer: the histogram is native and needs no collector change. The two new bounded attributes are added as span-metric dimensions to both collector configs. The ledger hash stays out of them, since a per-ledger dimension mints a series per ledger; it is indexed in Tempo as the join key. The ledger.acquire span is not joined yet, because that file was being changed concurrently. It is registered as an optional member of the join group so nothing fails, and switching it is a one-line follow-up. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
143 lines
5.7 KiB
YAML
143 lines
5.7 KiB
YAML
# Grafana Tempo configuration for xrpld telemetry stack.
|
|
#
|
|
# Runs in single-binary mode for local development.
|
|
# Receives traces via OTLP/gRPC from the OTel Collector and stores
|
|
# them locally. Queryable via Grafana Explore using the Tempo datasource.
|
|
#
|
|
# Search filters are configured on the Grafana datasource side
|
|
# (grafana/provisioning/datasources/tempo.yaml). Tempo auto-indexes
|
|
# all span attributes for search in single-binary mode.
|
|
#
|
|
# For production, replace local storage with S3/GCS backend and adjust
|
|
# retention via the compactor settings. See:
|
|
# https://grafana.com/docs/tempo/latest/configuration/
|
|
|
|
stream_over_http_enabled: true
|
|
|
|
server:
|
|
http_listen_port: 3200
|
|
|
|
# Raise the TraceQL metrics query range limit. The default
|
|
# query_frontend.metrics.max_duration is 3h, so a dashboard set to a longer
|
|
# window (e.g. 6h/12h) fails with "range exceeds 3h0m0s". 168h matches the
|
|
# search max_duration and gives dashboards generous headroom.
|
|
query_frontend:
|
|
metrics:
|
|
max_duration: 168h
|
|
|
|
distributor:
|
|
receivers:
|
|
otlp:
|
|
protocols:
|
|
grpc:
|
|
endpoint: 0.0.0.0:4317
|
|
|
|
ingester:
|
|
max_block_duration: 5m
|
|
|
|
compactor:
|
|
compaction:
|
|
block_retention: 1h
|
|
|
|
# Enable metrics generator for service graph, span metrics, and the
|
|
# local-blocks processor. Produces RED metrics (rate, errors, duration) per
|
|
# service/span for the service map, and keeps recent trace blocks queryable so
|
|
# TraceQL metrics queries (quantile_over_time, count_over_time, etc. via
|
|
# /api/metrics/query_range) work.
|
|
metrics_generator:
|
|
registry:
|
|
external_labels:
|
|
source: tempo
|
|
storage:
|
|
path: /var/tempo/generator/wal
|
|
# Uncomment and add a Prometheus service to docker-compose.yml
|
|
# to enable remote_write for service graph metrics:
|
|
# remote_write:
|
|
# - url: http://prometheus:9090/api/v1/write
|
|
# Separate WAL the local-blocks processor flushes traces to for metrics
|
|
# queries. Required when flush_to_storage is true.
|
|
traces_storage:
|
|
path: /var/tempo/generator/traces
|
|
processor:
|
|
local_blocks:
|
|
# xrpld consensus/transaction spans are SPAN_KIND_INTERNAL. By default
|
|
# local-blocks keeps only server spans for TraceQL metrics, so attribute
|
|
# aggregations over internal spans return nothing. Keep all spans.
|
|
filter_server_spans: false
|
|
# Flush recent blocks to traces_storage so query_range can read them.
|
|
flush_to_storage: true
|
|
|
|
overrides:
|
|
defaults:
|
|
metrics_generator:
|
|
processors:
|
|
- service-graphs
|
|
- span-metrics
|
|
- local-blocks
|
|
|
|
storage:
|
|
trace:
|
|
backend: local
|
|
wal:
|
|
path: /var/tempo/wal
|
|
local:
|
|
path: /var/tempo/blocks
|
|
block:
|
|
# Give the highest-value span attributes their own Parquet columns so
|
|
# TraceQL scopes a search to one ledger without scanning the generic
|
|
# attribute list. Tempo indexes every attribute for search regardless;
|
|
# a dedicated column only changes the cost of filtering on it.
|
|
#
|
|
# ledger_hash is here rather than in the collector's spanmetrics
|
|
# `dimensions:` on purpose: it is a per-ledger value, so as a metric
|
|
# dimension it would mint one series per ledger acquired (unbounded
|
|
# cardinality). As a trace column it gives the same per-ledger lookup at
|
|
# zero metric cost -- which is how a single stuck or abandoned
|
|
# ledger.acquire is found: {name="ledger.acquire" && span.outcome=
|
|
# "abandoned" && span.ledger_hash="<hash>"}.
|
|
#
|
|
# txset_hash and remote_endpoint are here for the same reason and by the
|
|
# same rule: a tx-set root hash is one value per consensus round and a
|
|
# peer endpoint is one value per address dialled, so either as a
|
|
# spanmetrics dimension would mint an unbounded number of metric series.
|
|
# As trace columns they answer the per-object questions the metrics
|
|
# cannot -- which proposed set stalled a round:
|
|
# {name="txset.acquire" && span.outcome="timeout" &&
|
|
# span.txset_hash="<hash>"}
|
|
# and which peer a dial keeps failing against:
|
|
# {name="peer.dial" && span.outcome="tcp_fail" &&
|
|
# span.remote_endpoint="10.0.0.5:51235"}
|
|
#
|
|
# missing_nodes, served_nodes and duration_ms are deliberately NOT given
|
|
# columns. They are unbounded integers, so they are useless as equality
|
|
# filters; Tempo indexes every attribute for search regardless, so they
|
|
# remain queryable by range (e.g. span.missing_nodes > 0) at no
|
|
# storage cost, which is how they are actually read.
|
|
#
|
|
# The ledger_hash column below carries a second job as of the per-ledger
|
|
# trace join (WP-B3), which is why it stays the highest-value column here.
|
|
# Every span for one ledger -- ledger.acquire, consensus.validation.accept,
|
|
# ledger.validate, ledger.store -- now derives its TRACE ID from that same
|
|
# ledger hash, so the whole story of one slow ledger is one trace:
|
|
# {span.ledger_hash="<hash>"}
|
|
# returns all of them across the three threads that produced them. The
|
|
# column is what makes that lookup cheap; it also stays the way a reader
|
|
# confirms two spans really are the same ledger rather than a trace-id
|
|
# coincidence, since the attribute holds the full 32 bytes while the trace
|
|
# id is only the leading 16.
|
|
#
|
|
# validation_status and accept_gated get no column: both are small closed
|
|
# value sets promoted to spanmetrics dimensions instead (see the collector
|
|
# configs), so the aggregate question is answered by a metric query and no
|
|
# per-value trace lookup is needed.
|
|
parquet_dedicated_columns:
|
|
- name: ledger_hash
|
|
type: string
|
|
scope: span
|
|
- name: txset_hash
|
|
type: string
|
|
scope: span
|
|
- name: remote_endpoint
|
|
type: string
|
|
scope: span
|