Commit Graph

466 Commits

Author SHA1 Message Date
Pratik Mankawde
a5f967088f height update
Signed-off-by: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com>
2026-08-05 12:31:28 +01:00
Pratik Mankawde
5197ac34b5 fix(telemetry): close empty grid gap in log-derived dashboard layout
The stat block was relaid two-per-row in the previous commit, but the reflow
shifted subsequent panels by a delta computed against the block's old height.
The block shrank, so the delta left a 12-grid-line dead band between the stat
row and "Node Operating State Transitions".

Recompute every panel's y by walking the list in order and pairing half-width
panels, rather than patching offsets incrementally. The layout is now derived
from panel order and heights, so this class of drift cannot recur.

Verified: 192 grid lines, fully covered, no empty band, no overlaps, and no grid
line occupied by more than two panels.
2026-08-04 19:27:11 +01:00
Pratik Mankawde
f7855932c8 fix(telemetry): repair log-derived dashboard panels and dead TraceQL link
Fixes found while validating the Log-Derived Insights dashboard against live
Loki and Tempo. Every change was verified by executing the query, not by
inspection.

loki.yaml — the ConsensusLedgerHash derived field never matched anything:

  - The TraceQL query named `.xrpl.consensus.ledger_id`. The attribute is
    `consensus_ledger_id` (ConsensusSpanNames.h); live Tempo lists no dotted or
    xrpl-prefixed variant among its span attributes.
  - The bare `.` intrinsic scope cannot match a span attribute regardless of
    name; TraceQL requires `span.`.

  Corrected to `{span.consensus_ledger_id="..."}`, which returns the expected
  consensus.round trace for a real ledger hash.

log-derived-insights.json:

  - Stat units `suffix: <noun>` rendered raw integers, so large values printed
    in full. `si:<noun>` is unsafe because Grafana parses the leading letter as
    an SI prefix (`si:fee` rendered "29.0 Mee"). Switched to `short`, the
    convention already used by 42 panels; the noun is in each panel title.
  - Stat panels showed a single fleet-wide tile labelled "Value #A". Grafana's
    Loki backend does not name binary-operation frames from legendFormat, so the
    ratio panel could not resolve per-node labels. Replaced it with a plain sum
    of duplicate fetches; the ratio remains available as a timeseries. Added an
    explicit legendFormat to all eight stat panels.
  - The ledger-fetch regex required `good:N dupe:N` together, but
    SHAMapAddNode::get() omits any zero counter. That dropped 32% of lines --
    disproportionately the efficient fetches -- biasing the ratio upward. Each
    field now matches independently.
  - state-timeline used `spanNulls: true`, which spans gaps indefinitely and
    would render an outage as continuous. Set to the 30-minute threshold.
  - Stat panels relaid two-per-row and given a tooltip, per dashboard guidelines.

telemetry-runbook.md:

  - LogQL examples used line filters where structured metadata exists.
    `|= "ERR"` also matches the literal in a message body: measured 423 ERR
    lines plus 4 DBG lines per 6h. Replaced with severity, partition, and
    trace_id field filters.
  - Documented the new dashboard, its debug-log requirement, and the LogQL traps
    these fixes exposed.
  - Corrected the dashboard count, which was already stale at eleven against
    fifteen on disk, and named the six with no reference section.
2026-08-04 17:12:10 +01:00
Pratik Mankawde
3860c93db2 refactor(telemetry): route dashboards, runbook and collector work to phase-9
These changes were developed on the phase-10 branch but belong to content this
branch and its upstreams introduced. Carrying them on phase-10 made its PR diff
report churn in files phase-10 does not own, and left each PR claiming a scope
that did not match its contents.

Moved here from phase-10 (identical content, no functional change):

- Dashboards: all 14 existing boards plus the new log-derived-insights board.
- Docs: telemetry-runbook.md (minus the workload/benchmark sections, which
  describe phase-10 tooling) and the new telemetry-glossary.md.
- Grafana Cloud + Alloy export path: collector config, compose override, the
  two .env examples and alloy/config.alloy.
- Local stack: otel-collector-config.yaml gains sub-millisecond and
  second-scale spanmetrics buckets, pins unit=ms, and promotes
  close_time_correct; integration-test.sh and TESTING.md follow.
- Node configs: exported_instance -> service_instance_id in comments; the
  mainnet sample now logs at warning to bound log volume.
- Metrics code: Telemetry.cpp builds the metrics pipeline in the constructor
  via initMetrics() so the global MeterProvider is published before any
  subsystem creates a beast::insight instrument, and the histogram view keeps
  each instrument's own name instead of collapsing them under one series.
  MetricsRegistry gains a last_close_time gauge and skips negative job-queue
  durations. OTelCollector drops an unused accessor.
- Naming CI: xrpl_work_item joins EXTERNAL_INFRA_LABELS and Rule E accepts the
  dotted perf-iac resource-attribute form. This must travel with the
  dashboards and runbook that reference those labels, or the rules fail.
- Doxygen input glob no longer recurses dot-directories.

Sections describing phase-10 tooling stay on phase-10 and keep their
"Future Enhancement" / "Planned, not yet implemented" markers here; phase-10
removes those markers when it lands the tooling.
2026-08-04 16:10:04 +01:00
Pratik Mankawde
ebe54d9db5 fix(telemetry): correct two alert thresholds found by 7-day backtest
ValidatedLedgerStale fired on every node, healthy included.
LedgerMaster::getValidatedLedgerAge() returns weeks{2} (1209600s) as a
SENTINEL when no validated ledger exists, not as a measurement. The rule
read that as "14 days stale". Measured over six days it produced sustained
firing on all nine nodes. Excluding the exact sentinel value drops that to
zero while still tracking real staleness.

ManifestFloodInbound at 50 kB/s was routine paging: ~41 sustained 5-minute
samples across six healthy nodes in six days. Healthy p99 is 1.0-1.8 kB/s
and real storms peak at 2.7 MB/s, so 512 kB/s sits ~280x above normal and
~5x below the peaks, cutting sustained samples to 2.

Both thresholds were previously justified from a 24-hour window, which was
too short to expose either problem.
2026-08-04 15:12:40 +01:00
Pratik Mankawde
97fa408633 alerting changes
Signed-off-by: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com>
2026-08-04 14:54:19 +01:00
Pratik Mankawde
dd90c558b7 fix(telemetry): repair broken dashboard grid layout
Three dashboards had layout faults that Grafana renders as visible
artifacts.

On Node Health, two row headers carried the same title as the panel
directly beneath them -- "Cache Hit Rates" and "Object Instance Counts"
each appeared twice, once as a row bar and once as a panel. Rename the
rows to "Caches" and "Object Instances"; the panels keep their names.

Worse on the same dashboard, seven bands summed to 48 columns on a
24-column grid, because the Extended Metrics block reused the y-space
already occupied by NodeStore I/O. Grafana cannot lay that out
deterministically, which is what produced the artifacts.

RPC Performance and Transaction Overview each had blank bands -- one
row and eight rows respectively -- left behind by earlier panel moves.

Repack all three grids so every band fits 24 columns, each band starts
where the previous one ends, and rows stay full-width single-height
anchors. Panel order, panel count and every panel's own width and
height are unchanged; only position moves.
2026-07-30 21:34:48 +01:00
Pratik Mankawde
b31183cf78 Merge branch 'pratik/otel-phase8-log-correlation' into pratik/otel-phase9-metric-gap-fill
# Conflicts:
#	docker/telemetry/grafana/dashboards/consensus-health.json
#	docker/telemetry/grafana/dashboards/ledger-operations.json
#	docker/telemetry/grafana/dashboards/node-health.json
#	docker/telemetry/grafana/dashboards/peer-network.json
#	docker/telemetry/grafana/dashboards/rpc-pathfinding.json
#	docker/telemetry/grafana/dashboards/rpc-performance.json
#	docker/telemetry/grafana/dashboards/transaction-overview.json
#	docs/telemetry-runbook.md
2026-07-30 19:15:15 +01:00
Pratik Mankawde
09c9dc4ec8 Merge branch 'pratik/otel-phase7-native-metrics' into pratik/otel-phase8-log-correlation 2026-07-30 18:29:48 +01:00
Pratik Mankawde
cf9ed00789 Merge branch 'pratik/otel-phase6-statsd' into pratik/otel-phase7-native-metrics
# Conflicts:
#	docker/telemetry/grafana/dashboards/peer-network.json
#	docker/telemetry/grafana/dashboards/transaction-overview.json
#	docs/telemetry-runbook.md
2026-07-30 18:29:36 +01:00
Pratik Mankawde
370130a9f9 fix(telemetry): correct job latency units and name dashboard rate nouns
The two key-job latency panels on node-health declared milliseconds
while querying `job_running_us` / `job_queued_us`, which record
microseconds. Every reading was a thousand times too large: the p95 for
acceptLedger, 241495us, rendered as "241 sec" instead of 241ms.
job-queue.json already read the same metrics as microseconds, so the
two dashboards disagreed by 1000x on identical data.

Also replace the generic `ops`, `cps` and `cpm` units, which Grafana
renders as the literal "ops/s", "counts/s" and "counts/min", with
custom-suffix units naming what each panel counts -- jobs, ledgers,
validations, lookups. The per-minute panels already scale correctly in
their queries; only the noun was missing.

Normalise the micro sign to U+00B5 throughout. Four axis labels used
the visually identical Greek mu, U+03BC, while every unit field used
the micro sign.

Ledger Close Rate plots ledgers closed beside ledger fetches, which one
unit cannot describe; give the fetches series its own unit on a
right-hand axis. Drop two field overrides on NodeStore Read Latency
that restated the panel unit as a custom suffix -- a suffix is appended
verbatim, so it would have suppressed magnitude scaling and left large
values unreadable, the same defect fixed above.

Queries are unchanged apart from the transaction path piechart, which
moves from rate() to increase(): a per-slice "per second" reading is
not a share of a total.

Alongside, widen the Complete Ledger Ranges table to full width, hoist
the stat panels above the fold, and bring the touched panels up to the
tooltip and null-spanning guidelines.
2026-07-30 18:06:56 +01:00
Pratik Mankawde
2095a3d2f1 fix(telemetry): correct job latency units and name dashboard rate nouns
The four job-latency panels on node-health declared milliseconds while
querying `job_running_us` / `job_queued_us`, which record microseconds
(MetricsRegistry records the raw value, and the instrument description
says microseconds). Every reading was therefore a thousand times too
large: the p95 for acceptLedger, 241495us, rendered as "241 sec"
instead of 241ms. job-queue.json already read these same metrics as
microseconds, so the two dashboards disagreed by 1000x on identical
data. Switch node-health to microseconds to match.

Also replace the generic `ops` and `cps` units, which Grafana renders
as the literal "ops/s" and "counts/s", with custom-suffix units naming
what each panel counts -- messages, fetches, calls, mismatches.

Two panels plot more than one quantity on a single axis, which no
single unit can describe. Give each series its own unit through field
overrides: reads per second beside two queue depths on NuDB Read
Pressure, and ledgers beside fetches on Ledger Close Rate, the latter
on a right-hand axis.

State Duration Rate plots a seconds-per-second time share, which can
exceed 1.0 and so is not a percentage; label it as the ratio it is.
The normalised share already exists as its own panel.

Queries are unchanged; the values were already correct.

Alongside, bring the touched panels up to the dashboard guidelines and
hoist the stat panels above the fold.
2026-07-30 18:06:23 +01:00
Pratik Mankawde
c66f9a391d fix(telemetry): state the real noun in dashboard rate units
Grafana renders `unit: "ops"` as the literal string "ops/s", so every
rate panel read as "operations per second" regardless of what it
actually counted. `Ledger Build Rate` showed "0.258 ops/s" where the
value is one ledger every 3.9s -- the number was right, the unit was
meaningless.

Replace the generic units with Grafana custom-suffix units naming the
quantity, following the existing `suffix:/hr` and `si:drops` precedent
in this repo. Nine of these are `stat` panels with no axis, so the unit
string was the only text a reader ever saw.

Also switch the two trusted/untrusted piecharts and the transaction
path piechart from rate() to increase(): a per-slice "per second"
reading is not a share of a total, counts in the window are.

Queries are unchanged apart from those three; the values were already
correct.

Alongside, bring the touched panels up to the dashboard guidelines:
tooltip mode/sort/max-height, 30-minute null spanning, and axis labels
in title case. Hoist the stat panels above the fold on
ledger-operations and rpc-performance.

Panels that a later branch in this chain removes are deliberately left
alone -- fixing them would only add merge conflicts.
2026-07-30 18:05:56 +01:00
Pratik Mankawde
05f545ff5a text alignment
Signed-off-by: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com>
2026-07-30 12:05:18 +01:00
Pratik Mankawde
40566d55df fix(telemetry): declare the DS_PROMETHEUS variable job-queue references
Three template variables (xrpl_work_item, xrpl_branch, xrpl_node_role)
select their datasource via ${DS_PROMETHEUS}, but the variable itself was
absent from templating.list, so those label_values() lookups resolved to
no datasource and the filters stayed empty.

Add the datasource variable as the first templating entry, matching the
other dashboards in this folder.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 19:29:06 +01:00
Pratik Mankawde
e28b5d1914 Merge branch 'pratik/otel-phase8-log-correlation' into pratik/otel-phase9-metric-gap-fill 2026-07-28 19:28:38 +01:00
Pratik Mankawde
d9c8f31c87 Merge branch 'pratik/otel-phase7-native-metrics' into pratik/otel-phase8-log-correlation 2026-07-28 19:28:32 +01:00
Pratik Mankawde
1accc921d9 fix(telemetry): declare the DS_PROMETHEUS variable ledger-data-sync references
The dashboard's template variables and panel targets select their
datasource via ${DS_PROMETHEUS}, but the variable itself was absent from
templating.list. An unresolvable datasource variable leaves those queries
without a datasource, so the panels render empty.

Add the datasource variable as the first templating entry, matching the
other dashboards in this folder.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 19:28:23 +01:00
Pratik Mankawde
253cd06489 docs(telemetry): correct the three measurement defects in docs and dashboard
Three measurement fixes landed with no doc or dashboard change, leaving text
that is now false and one fix unusable from a dashboard.

Deferrals and timeouts are recorded in TimeoutCounter, a base shared by five
subclasses, so the all-lane pair could show the documented livelock
fingerprint while ledger acquisition was healthy. The runbook procedure and
the reference doc now name acquire_ledger_deferrals and
acquire_ledger_timeouts and say why the all-lane pair misleads; a new panel
plots the ledger-scoped pair as rates on one axis, since the divergence is
the signal. The existing panel is retitled All Lanes and points at it.

Writer mean depth is depthSum over depthSamples, not over insertCount, and
the measured 1.60 came from the biased estimator, so it and the 37% queueing
share derived from it are lower bounds rather than values. The reference
table now marks them as such, and the decision rule is shown to survive the
correction rather than depending on the exact figures.

Completions were never counted for acquisitions satisfied from the local
store, so the run that read zero across 510 seconds had in fact reached
full. Every place that treated a zero as a symptom now says it only means
something on a build that has the fix.

Also corrects the sync-diagnosis label-value count from 13 to 15 and a stale
source line range; the instrument count stays 35, because both new values
multiplex onto the existing nodestore_state gauge.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 18:46:55 +01:00
Pratik Mankawde
aea1f25fa8 Merge branch 'pratik/otel-phase8-log-correlation' into pratik/otel-phase9-metric-gap-fill 2026-07-28 16:25:00 +01:00
Pratik Mankawde
0a22a512bb Revert the nodestore read-latency histogram
Drops nodestore_read_us and everything added to reach it. read_mean_us already
carries microsecond precision and separated the two sync failure modes cleanly
in live testing -- 8.8 us on a clean store against a 223 us cold-store peak --
so the distribution added no signal that changed a diagnosis.

The cost of getting it was disproportionate. NodeStoreScheduler had no path to
the metrics registry, so its production constructor grew a ServiceRegistry
parameter: a metric addition changing a production signature. That in turn
forced an edit to a pre-existing test, src/test/app/SHAMapStore_test.cpp, whose
only stake in this is that it constructs a scheduler. Worse, the scheduler is
built in Application's member initializer list, long before metricsRegistry_
exists, so the registry could not be captured once and had to be re-resolved on
every fetch -- a lookup on a path that runs millions of times per sync.

The constructor returns to taking JobQueue& alone and SHAMapStore_test.cpp
returns to the single-argument call, leaving that file differing from its
pre-change form only by the NodeStore:: to node_store:: rename it picked up from
develop.

FetchReport::elapsed stays microseconds and onFetch keeps its explicit
duration_cast to milliseconds for addLoadEvents, which takes milliseconds. That
widening was a separate fix and is what makes read latency measurable at all.

kSubMillisecondBoundaries loses its only consumer and regains [[maybe_unused]],
which is the state the commit that introduced it left it in; without the
attribute an unused constant is an error under wextra with werr.

Also removes the ledger-data-sync panel that charted the histogram and the
fetch_type and found template variables, which filtered on labels no metric
emits any more, plus the runbook and reference-doc sections and the two
instrument and view counts that named it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 16:24:35 +01:00
Pratik Mankawde
7e2cd62477 Merge branch 'pratik/otel-phase7-native-metrics' into pratik/otel-phase8-log-correlation 2026-07-28 16:24:23 +01:00
Pratik Mankawde
7aa41e3dfd fix(telemetry): show intermediate sync states on Ledger Data & Sync
The Sync State panel only ever showed Connected and Full. Two causes:

1. state_tracking{metric="state_value"} is emitted with values 0-6, where
   5 is FULL+validating and 6 is FULL+proposing, but the panel declared
   max: 4 with value mappings for 0-4 only. Values above 4 were pinned to
   the axis ceiling and rendered unmapped. Most nodes sit at 6, so the
   majority of series were clipped.

2. The gauge samples the instantaneous mode on a 10s export tick, so
   states shorter than one tick fall between samples. A real sync showed
   SYNCING for a single scrape and skipped TRACKING entirely. This is the
   sampling hazard already noted on StateAccounting in NetworkOPs.h.

Extend the panel domain to 0-6 with Validating and Proposing mappings and
matching threshold steps, and add a colour-coded state-timeline panel where
each band's width is the time spent in that state, so brief states appear
as thin slivers rather than disappearing. The timeline reads
server_info{metric="server_state"} (raw OperatingMode 0-4) rather than
state_value, which folds 5 and 6 onto FULL and would split one Full band
into three colours.

Panel layout below the insertion point shifts down by 6 rows.

Note this makes short states legible, not lossless: exporting
StateAccounting's per-state duration accumulators is the sampling-immune
fix and is left as follow-up.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 16:24:16 +01:00
Pratik Mankawde
7ef8c07cf9 Rename nudb_bytes metric to stored_object_bytes
The nudb_bytes label value on the storage_detail gauge named something the
code never measured. It observes Database::getStoreSize(), which returns the
storeSz_ accumulator: the cumulative payload bytes of objects this process
handed to the NodeStore. That is not a NuDB file size. It excludes NuDB's
keys, bucket padding and log, and it resets with the process while the files
on disk do not.

The name caused two concrete errors. It invited sizing the store on disk from
a number that cannot do it, and it invited a write-amplification ratio against
node_written_bytes -- which reads the same accessor at MetricsRegistry.cpp:836,
so that ratio is a constant 1.0 and measures nothing.

The nudb_ prefix was wrong too. storeSz_ is written only by
Database::storeStats(), called from DatabaseNodeImp, DatabaseRotatingImp and
Database itself. No backend code touches it, so the value reads the same on
RocksDB. That distinguishes it from the real nudb_* family
(nudb_writers_in_flight and friends), which come from getWriteStats() and are
absent entirely on a non-NuDB backend.

stored_object_bytes says what the value is and claims nothing about the
filesystem. Docs already described the value correctly; they keep that
explanation and now also record the old name, so a query pinned to it can be
traced. Neither Backend nor Database exposes an on-disk size accessor and none
was added -- no metric reports the store's on-disk size today.

Updates the node-health panel title, description and PromQL, and the four docs
that name the label value.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 15:46:24 +01:00
Pratik Mankawde
8ade2b43d9 docs(telemetry): rename node-health found-ratio and stored-bytes panels
node_reads_hit is a found count, not a cache hit. fetchHitCount_ increments
whenever a fetch returned an object, whatever served it, and a node with
online_delete uses DatabaseRotatingImp which has no NodeObject cache at all. The
ratio therefore reads near 100% while every fetch goes to disk, which made the
cold-read failure mode look impossible on the board.

- Phase9_taskList: node_reads_hit is a found count, not cache-served reads.
- node-health: "NodeStore Cache Hit Ratio" -> "NodeStore Read Found Ratio",
  legends "Cache Hit Ratio" -> "Found Ratio" and "Reads Hit (cache)" ->
  "Reads Found"; corrected the ratio panel's axis label, which read
  "Operations / Sec" on a percentunit panel.
- node-health: "NuDB Storage Size" -> "NuDB Stored Bytes". nudb_bytes observes
  getStoreSize(), the accumulator node_written_bytes also reads, so it is
  cumulative object-payload bytes and not a filesystem measurement.
- ledger-data-sync: point the read-latency discriminator at the renamed panel.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 15:23:41 +01:00
Pratik Mankawde
8be97bca62 Merge branch 'pratik/otel-phase8-log-correlation' into pratik/otel-phase9-metric-gap-fill 2026-07-28 15:20:16 +01:00
Pratik Mankawde
f78db4ba0a Merge branch 'pratik/otel-phase7-native-metrics' into pratik/otel-phase8-log-correlation 2026-07-28 15:20:11 +01:00
Pratik Mankawde
7da5ac5992 docs(telemetry): correct nudb_bytes and NuDB found-ratio descriptions
nudb_bytes was documented as a NuDB file size, one place even claiming a
filesystem stat. It observes Database::getStoreSize(), which sums the object
payloads this process has written. It excludes NuDB's keys, bucket padding and
log, and resets with the process. node_written_bytes calls the same accessor, so
the two series are equal by construction and a write-amplification ratio built
from them is a constant 1.0. Neither Backend nor Database exposes a file-size
accessor, so nothing reports on-disk size today.

The Ledger Data & Sync panel plotting node_reads_hit / node_reads_total was
titled "NuDB Cache Hit Ratio" and described as reads served from cache.
fetchHitCount_ increments whenever a fetch returned an object, whatever served
it, so the ratio is a found rate. It reads near 100% while every fetch goes to
disk, which made the cold-read failure mode look impossible. Renamed to
"NuDB Read Found Ratio" and rewrote the guidance to pair it with read latency.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 15:20:02 +01:00
Pratik Mankawde
8b14176609 feat(telemetry): add sync bottleneck discrimination dashboard row
A stalled ledger sync has two distinct causes that look identical from the
job queue: in both, the ledgerData lane sits at its concurrency limit of 3
with work waiting. Lane occupancy therefore discriminates nothing, and the
dashboard had no panel that separated the two.

Adds a Sync Bottleneck Discrimination row to Ledger Data & Sync with seven
panels built on the nodestore_state write-path and acquire counters:

- NodeStore Read Latency, lifetime and windowed, on a log axis with
  threshold lines at 10 and 100 us. This is the discriminator: fast reads
  mean the cost is on the write side, slow reads with a high hit ratio
  mean every access is paying disk latency.
- NuDB Writer Queue Depth, the confirming half. NuDB takes one global
  mutex per insert, so depth above 1 is queueing at that mutex.
- NuDB Insert Time, mean against true max, which quantifies how much of
  each insert is wait rather than work.
- Acquire Deferrals vs Timeouts on one panel, because the livelock
  fingerprint is deferrals rising while timeouts stay flat, and neither
  counter shows that alone.
- Acquisition Progress and Discarded Acquire Work, the outcome side: no
  completions while the lane is busy means acquisitions resolve neither
  way.
- NodeStore Read Latency Distribution split by fetch_type and found, from
  the nodestore_read_us histogram, since a mean cannot tell a uniformly
  warm store from a mostly-warm one with a cold tail.

Every expression carries the existing service_instance_id and tier filters.
The histogram introduces two new label dimensions, so fetch_type and found
template variables are added to match.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 12:20:47 +01:00
Pratik Mankawde
15596f5b8d feat(telemetry): pinpoint root cause of slow TMGetObjectByHash service
Slowness on the peer object-fetch path could be observed but not
attributed. Job duration metrics carry only `job_type`, and both
`RcvGetLedger` and `RcvGetObjByHash` report as `ledgerRequest`, so a
queue-wait spike could not be traced to a handler. Nothing measured
NodeStore cost, request size, or the differential charge.

Latency now decomposes into three additive parts, each separately
measurable:

    end-to-end = queue wait + NodeStore lookup + everything else

- `handler` label on job_queued_total/_started_total/_finished_total and
  job_queued_us/job_running_us. The value is sanitised: a name passes
  through only if non-empty and all ASCII letters, else "other". Two job
  names embed a ledger sequence, so a raw label would mint one series
  per ledger; the rule bounds the domain at 43 names plus "other".
- getobject_lookup_us, _request_objects, _lookups_total{result},
  _rejected_total{reason} and _charge, recorded at their call sites.
  All three histograms get explicit bucket views: the SDK default stops
  at 10,000, which every one of them exceeds.
- Per-job-type waiting/running/deferred gauges for the 35 non-special
  job types. `deferred` is the leading indicator, since addJob never
  rejects -- it defers, so backpressure otherwise shows up only as
  latency after the fact.

`JobQueue::collect()` snapshots the counters under the queue lock and
publishes gauges after releasing it. Writing them while holding the lock
would invert a lock order against the collector's own lock, which the
collector's flush thread already holds when it calls this hook.

Tests assert exact values, including that the charge is priced on the
requested count rather than the capped iteration count.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 11:59:16 +01:00
Pratik Mankawde
3cf24e1888 Merge branch 'pratik/otel-phase7-native-metrics' into pratik/otel-phase8-log-correlation 2026-07-24 21:06:30 +01:00
Pratik Mankawde
92fc2daff6 Merge branch 'pratik/otel-phase8-log-correlation' into pratik/otel-phase9-metric-gap-fill 2026-07-24 21:06:30 +01:00
Pratik Mankawde
8fe00536e7 fix(telemetry): Size the overlay traffic bar gauge for 20 bars
Match the panel height the operator settled on in Grafana (18 rows) so
all 20 ranked bars render without an inner scrollbar, and reflow the
Sync Diagnostics row and the panels below it accordingly.
2026-07-24 21:06:20 +01:00
Pratik Mankawde
8f09dc2906 Merge branch 'pratik/otel-phase8-log-correlation' into pratik/otel-phase9-metric-gap-fill 2026-07-24 21:02:43 +01:00
Pratik Mankawde
fe1c4db29c Merge branch 'pratik/otel-phase7-native-metrics' into pratik/otel-phase8-log-correlation 2026-07-24 21:02:39 +01:00
Pratik Mankawde
97ed918e16 fix(telemetry): Make overlay traffic bar-gauge labels readable
The bar labels on 'Overlay Traffic Heatmap (All Categories, Bytes In)'
were raw metric names carrying a redundant '_bytes_in' suffix, and the
half-width 8-row panel truncated both the category and the node
identity.

- Strip the '_bytes_in' suffix from the derived series label; the panel
  title already states the metric is inbound bytes.
- Widen the panel to full width and grow it to 12 rows so 20 bars render
  with their full category and node labels.
- Shift the Sync Diagnostics row and the panels below it down by 4 to
  keep the layout gap-free.
- Note the label derivation in the panel description.
2026-07-24 21:02:31 +01:00
Pratik Mankawde
9c54020322 fix(telemetry): plot all five states in State Duration Rate panel
The panel plotted only Full and Tracking, so the states a node actually
passes through while catching up were invisible. On the dev box's cold
sync the node spent ~19 minutes in Connected and ~3 seconds in Syncing,
while Tracking totalled 2 microseconds -- the one non-Full line the
panel did draw was the least informative of the five.

Add Syncing, Connected and Disconnected series (state-ladder order,
matching the Operating Mode Transitions panel) and rename the panel to
"State Duration Rate (All States)". Because the node is always in
exactly one state, the five rates sum to ~1.0, so the panel now reads as
"which state is time going into right now", and a handover between two
lines marks a state change with its width showing the dwell time.

Description expanded to the full section set (keywords, computation
boundary, references) used by the other panels on this dashboard.

Verified live: rate(state_accounting_connected_duration)/1e6 reaches
1.0 then 0.387 across the catch-up window and syncing reaches 0.0103 --
both previously undrawable.
2026-07-24 20:50:39 +01:00
Pratik Mankawde
0d9533270a Merge branch 'pratik/otel-phase8-log-correlation' into pratik/otel-phase9-metric-gap-fill 2026-07-24 20:44:27 +01:00
Pratik Mankawde
b0168f92a7 Merge branch 'pratik/otel-phase7-native-metrics' into pratik/otel-phase8-log-correlation 2026-07-24 20:44:23 +01:00
Pratik Mankawde
a80cccb05a feat(telemetry): add Node Health verdict panel to Node Health dashboard
The dashboard showed 54 individual signals but no single answer to
"is this node healthy and doing its job?". A reader had to correlate
server state, ledger age and peer counts by eye.

Add a stat panel at the top that reduces the two conditions that define
a healthy observer node to one green/red verdict:

  server_state == 4 (Full)  AND  validated_ledger_age < 30s

Both terms use `== bool` so each yields 0/1, joined with
`* on(service_instance_id)` so the verdict is per node. Value mappings
render 1 as "Healthy" (green) and 0 as "Not Healthy" (red) with
background colouring, so the state is readable at a glance.

Placed at y=0 per the dashboard convention that gauges/stats lead;
existing panels shift down by 4 rows with no other change.

Verified live against Grafana Cloud: returns 1 for aws-dev-xrpl-1
(Full, validated seq 105,824,596).
2026-07-24 20:44:23 +01:00
Pratik Mankawde
a30c08240b fix(telemetry): drop xrpl_work_item from Sync Diagnostics sum-by clauses
The four aggregated Sync Diagnostics panels grouped by xrpl_work_item,
which no layer in this repo emits (it is injected by the perf-iac
harness). That tripped Rule D of the telemetry naming check:

  D  ledger-data-sync.json  xrpl_work_item
     must exist in L1, a metric label, or be a builtin

Align with the convention used by every other aggregated panel in the
dashboard set: group by (service_instance_id, xrpl_branch,
xrpl_node_role) and leave the xrpl_ident label_join untouched. The
legend is unaffected -- label_join over a label dropped by the
aggregation contributes an empty segment, which the trailing
label_replace already strips.

Verified live: both the NuDB ratio and the job-queue p95 queries still
return per-node series rendering as "[aws-dev-xrpl-1]".
2026-07-24 20:44:13 +01:00
Pratik Mankawde
75cc3785ad Merge branch 'pratik/otel-phase7-native-metrics' into pratik/otel-phase8-log-correlation 2026-07-24 19:57:13 +01:00
Pratik Mankawde
63474c510d Merge branch 'pratik/otel-phase8-log-correlation' into pratik/otel-phase9-metric-gap-fill 2026-07-24 19:57:13 +01:00
Pratik Mankawde
5533dde43d fix(telemetry): Sync State axis label "State (0-4)" -> "Server State"
The 0-4 values now render as named states via value mappings, so the numeric
axis label is misleading. Rename to "Server State".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 19:57:11 +01:00
Pratik Mankawde
5718ebbdd6 Merge branch 'pratik/otel-phase8-log-correlation' into pratik/otel-phase9-metric-gap-fill 2026-07-24 19:51:37 +01:00
Pratik Mankawde
8f8ce40a21 Merge branch 'pratik/otel-phase7-native-metrics' into pratik/otel-phase8-log-correlation 2026-07-24 19:51:36 +01:00
Pratik Mankawde
ecfaab9f85 feat(telemetry): map Sync State 0-4 to named states with threshold colors
Add value mappings (0 Disconnected, 1 Connected, 2 Syncing, 3 Tracking,
4 Full) and matching red->green thresholds to the Sync State panel, so the
line, tooltip, and legend render state names and colors instead of bare 0-4.
Keeps the minimal custom-key convention; only the Sync State panel changes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 19:51:25 +01:00
Pratik Mankawde
3c9935c0df Merge branch 'pratik/otel-phase8-log-correlation' into pratik/otel-phase9-metric-gap-fill 2026-07-24 19:26:14 +01:00
Pratik Mankawde
e8aac409be Merge branch 'pratik/otel-phase7-native-metrics' into pratik/otel-phase8-log-correlation 2026-07-24 19:26:06 +01:00
Pratik Mankawde
794b5f43eb style(telemetry): align Sync Diagnostics legends with dashboard xrpl_ident format
Rework the 10 Sync Diagnostics panels' legends to match the format used by the
existing panels: wrap each query in label_join + label_replace to build the
xrpl_ident label ([service_instance_id, xrpl_branch, xrpl_work_item] with empty
values stripped) and set displayName to "${series} ${xrpl_ident}". Verified
against a live node: renders as "Series [aws-dev-xrpl-1]" with no empty-label
gaps, identical mechanism to the original 15 panels. Only the new panels change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 19:25:54 +01:00